#!/usr/bin/env sh set -eu REPO_URL="${REPO_URL:-http://100.64.0.1:30087/section0/rta-handbook.git}" TARGET_DIR="${TARGET_DIR:-$HOME/Developer/Section0/rta-handbook}" COMMAND_DIR="${COMMAND_DIR:-$HOME/.local/bin}" COMMAND_NAME="${COMMAND_NAME:-section0-docs}" COMMAND_PATH="$COMMAND_DIR/$COMMAND_NAME" SERVER_URL="${SECTION0_SERVER_URL:-https://ops.virgil.info/md-to-section0-api}" STATUS_URL="${SECTION0_STATUS_URL:-https://ops.virgil.info/section0/projection-status.json}" step() { printf "\n==> %s\n" "$*" } if ! command -v git >/dev/null 2>&1; then echo "git is required but was not found on PATH" >&2 exit 1 fi step "Preparing local Markdown workspace" if [ -d "$TARGET_DIR/.git" ]; then echo "Repo already exists: $TARGET_DIR" git -C "$TARGET_DIR" remote set-url origin "$REPO_URL" git -C "$TARGET_DIR" fetch origin git -C "$TARGET_DIR" checkout main git -C "$TARGET_DIR" pull --ff-only else mkdir -p "$(dirname "$TARGET_DIR")" git clone "$REPO_URL" "$TARGET_DIR" fi git -C "$TARGET_DIR" config pull.ff only step "Installing helper command" mkdir -p "$COMMAND_DIR" cat > "$COMMAND_PATH" </dev/null 2>&1 || { echo "python3 is required for Authentik login JSON handling" >&2 exit 1 } } json_get() { need_python python3 -c 'import json,sys; data=json.load(sys.stdin); cur=data for part in sys.argv[1].split("."): cur = cur.get(part, "") if isinstance(cur, dict) else "" print(cur if cur is not None else "")' "\$1" } now_utc() { date -u +%Y-%m-%dT%H:%M:%SZ } run_id() { if command -v uuidgen >/dev/null 2>&1; then uuidgen | tr '[:upper:]' '[:lower:]' else date -u +%Y%m%d%H%M%S fi } json_log() { need_python mkdir -p "\$SESSION_DIR" event="\$1" detail="\${2:-}" current_head="\$(git -C "\$REPO_DIR" rev-parse --short HEAD 2>/dev/null || true)" current_branch="\$(git -C "\$REPO_DIR" branch --show-current 2>/dev/null || true)" remote="\$(git -C "\$REPO_DIR" remote get-url origin 2>/dev/null || true)" python3 -c 'import json,sys print(json.dumps({ "ts": sys.argv[1], "event": sys.argv[2], "detail": sys.argv[3], "repo": sys.argv[4], "branch": sys.argv[5], "head": sys.argv[6], "remote": sys.argv[7], }, separators=(",", ":")))' "\$(now_utc)" "\$event" "\$detail" "\$REPO_DIR" "\$current_branch" "\$current_head" "\$remote" >> "\$EVENTS_PATH" chmod 600 "\$EVENTS_PATH" } open_url() { if command -v open >/dev/null 2>&1; then open "\$1" >/dev/null 2>&1 || true elif command -v xdg-open >/dev/null 2>&1; then xdg-open "\$1" >/dev/null 2>&1 || true fi } credential_host() { need_python python3 -c 'from urllib.parse import urlparse; import sys url=urlparse(sys.argv[1]) print(url.netloc)' "\$1" } http_json() { curl --connect-timeout 5 --max-time 15 -fsS "\$@" } projection_status() { http_json "\$STATUS_URL" } wait_for_projection() { expected="\${1:-}" [ -n "\$expected" ] || expected="\$(git -C "\$REPO_DIR" rev-parse --short HEAD)" timeout_seconds="\${SECTION0_WAIT_TIMEOUT_SECONDS:-180}" started_at="\$(date +%s)" echo "Waiting for AFFiNE projection:" echo " commit: \$expected" echo " status: \$STATUS_URL" while :; do status_json="\$(projection_status 2>/dev/null || true)" if [ -n "\$status_json" ]; then state="\$(printf "%s" "\$status_json" | json_get state 2>/dev/null || true)" origin_commit="\$(printf "%s" "\$status_json" | json_get source.originCommit 2>/dev/null || true)" projected_commit="\$(printf "%s" "\$status_json" | json_get projected.commit 2>/dev/null || true)" completed_at="\$(printf "%s" "\$status_json" | json_get projected.completedAt 2>/dev/null || true)" backend_ok="\$(printf "%s" "\$status_json" | json_get verification.backend.ok 2>/dev/null || true)" native_ok="\$(printf "%s" "\$status_json" | json_get verification.nativeRead.ok 2>/dev/null || true)" browser_status="\$(printf "%s" "\$status_json" | json_get verification.browserFreshness.status 2>/dev/null || true)" writer_status="\$(printf "%s" "\$status_json" | json_get writer.status 2>/dev/null || true)" writer_note="\$(printf "%s" "\$status_json" | json_get writer.note 2>/dev/null || true)" readme_url="\$(printf "%s" "\$status_json" | python3 -c 'import json,sys data=json.load(sys.stdin) for doc in (data.get("projected") or {}).get("docs") or []: if doc.get("sourcePath") == "README.md": print(doc.get("url","")) break' 2>/dev/null || true)" printf " origin=%s projected=%s state=%s%s\n" "\${origin_commit:-unknown}" "\${projected_commit:-none}" "\${state:-unknown}" "\${completed_at:+ completed=\$completed_at}" printf " writer: %s\n" "\${writer_status:-unknown}" printf " verification: backend=%s native-read=%s browser=%s\n" "\${backend_ok:-unknown}" "\${native_ok:-unknown}" "\${browser_status:-not_verified}" if [ "\$state" = "writer-frozen" ] || [ "\$writer_status" = "frozen" ]; then echo "AFFiNE projection is paused. Your Git push is still the source of truth." [ -z "\$writer_note" ] || echo "Reason: \$writer_note" json_log "wait.writer_frozen" "\$expected" return 2 fi if [ "\$projected_commit" = "\$expected" ] && { [ "\$backend_ok" = "True" ] || [ "\$backend_ok" = "true" ]; } && { [ "\$native_ok" = "True" ] || [ "\$native_ok" = "true" ]; }; then echo "Projected and verified through AFFiNE backend and native read." [ -z "\$readme_url" ] || echo "Doc: \$readme_url" echo "If a browser tab still shows old content, reload the AFFiNE tab or open the doc URL in a fresh tab." json_log "wait.complete" "\$expected" return 0 elif [ "\$projected_commit" = "\$expected" ]; then echo " commit is projected, but verification is not complete yet" fi else echo " projection status not reachable yet" fi now="\$(date +%s)" elapsed=\$((now - started_at)) if [ "\$elapsed" -ge "\$timeout_seconds" ]; then echo "Timed out after \${timeout_seconds}s waiting for projection of \$expected." >&2 echo "Run again later: section0-docs wait" >&2 json_log "wait.timeout" "\$expected" return 1 fi sleep "\${SECTION0_WAIT_INTERVAL_SECONDS:-5}" done } install_git_credential() { remote="\$1" username="\$2" password="\$3" host="\$(credential_host "\$remote")" protocol="\$(printf "%s" "\$remote" | sed -n "s#^\\([^:/]*\\)://.*#\\1#p")" [ -n "\$protocol" ] || protocol="http" mkdir -p "\$SESSION_DIR" credential_path="\$SESSION_DIR/git-credentials" touch "\$credential_path" chmod 600 "\$credential_path" git -C "\$REPO_DIR" config --local --replace-all credential.helper "" git -C "\$REPO_DIR" config --local --add credential.helper "store --file=\$credential_path" printf "protocol=%s\nhost=%s\nusername=%s\npassword=%s\n\n" "\$protocol" "\$host" "\$username" "\$password" \ | git -C "\$REPO_DIR" credential approve chmod 600 "\$credential_path" } auth_login() { need_python mkdir -p "\$SESSION_DIR" login_run_id="\$(run_id)" json_log "auth.login.start" "\$login_run_id" device_json="\$(curl -fsS -X POST "\$SERVER_URL/device")" code="\$(printf "%s" "\$device_json" | json_get code)" auth_url="\$(printf "%s" "\$device_json" | json_get authUrl)" [ -n "\$code" ] && [ -n "\$auth_url" ] || { echo "login server did not return a device code" >&2 exit 1 } echo "Opening Authentik login:" echo " \$auth_url" echo "Device code:" echo " \$code" echo "Run id:" echo " \$login_run_id" echo "Token check:" echo " \$SERVER_URL/device/\$code/token" open_url "\$auth_url" if [ "\${SECTION0_AUTH_POLL:-}" != "1" ]; then echo "When the browser says login succeeded, return here and press Enter." printf "Press Enter to finish login: " IFS= read -r _section0_continue auth_finish "\$code" return fi echo "Waiting for login..." token_json="" i=0 while [ "\$i" -lt 90 ]; do token_json="\$(http_json "\$SERVER_URL/device/\$code/token" 2>/dev/null || true)" access_token="\$(printf "%s" "\${token_json:-{}}" | json_get accessToken 2>/dev/null || true)" [ -n "\$access_token" ] && break status="\$(printf "%s" "\${token_json:-{}}" | json_get status 2>/dev/null || true)" if [ \$((i % 5)) -eq 0 ]; then printf "\n still waiting%s\n" "\${status:+ (\$status)}" else printf "." fi i=\$((i + 1)) sleep 2 done printf "\n" [ -n "\${access_token:-}" ] || { echo "timed out waiting for Authentik login" >&2 echo "If the browser says login succeeded, run:" >&2 echo " section0-docs auth finish \$code" >&2 exit 1 } complete_login "\$token_json" "\$access_token" } auth_finish() { need_python mkdir -p "\$SESSION_DIR" code="\${1:-}" [ -n "\$code" ] || { echo "usage: section0-docs auth finish DEVICE_CODE" >&2 exit 1 } token_json="\$(http_json "\$SERVER_URL/device/\$code/token")" access_token="\$(printf "%s" "\$token_json" | json_get accessToken 2>/dev/null || true)" [ -n "\$access_token" ] || { echo "No completed login token for code: \$code" >&2 printf "%s\n" "\$token_json" >&2 exit 1 } complete_login "\$token_json" "\$access_token" } complete_login() { token_json="\$1" access_token="\$2" echo "Login accepted; requesting Section 0 Git access..." access_json="\$(http_json -X POST -H "Authorization: Bearer \$access_token" "\$SERVER_URL/section0/git/access")" ok="\$(printf "%s" "\$access_json" | json_get ok)" [ "\$ok" = "True" ] || [ "\$ok" = "true" ] || { echo "Git access broker did not return credentials:" >&2 printf "%s\n" "\$access_json" >&2 exit 1 } remote="\$(printf "%s" "\$access_json" | json_get remote)" git_username="\$(printf "%s" "\$access_json" | json_get git.username)" git_password="\$(printf "%s" "\$access_json" | json_get git.password)" author_name="\$(printf "%s" "\$access_json" | json_get author.name)" author_email="\$(printf "%s" "\$access_json" | json_get author.email)" [ -n "\$remote" ] && [ -n "\$git_username" ] && [ -n "\$git_password" ] || { echo "Git access broker response was incomplete" >&2 exit 1 } git -C "\$REPO_DIR" remote set-url origin "\$remote" git -C "\$REPO_DIR" config user.name "\$author_name" git -C "\$REPO_DIR" config user.email "\$author_email" echo "Storing Git credential..." install_git_credential "\$remote" "\$git_username" "\$git_password" python3 -c 'import json,sys token=json.loads(sys.argv[1]) access=json.loads(sys.argv[2]) print(json.dumps({ "authenticated": True, "serverUrl": sys.argv[3], "accessToken": token.get("accessToken", ""), "authentik": token.get("authentik", {}), "affine": token.get("affine", {}), "author": access.get("author", {}), "remote": access.get("remote", ""), "credentialUser": access.get("git", {}).get("username", "") }, indent=2))' "\$token_json" "\$access_json" "\$SERVER_URL" > "\$SESSION_PATH" chmod 600 "\$SESSION_PATH" echo "Authenticated: \$author_email" echo "Git remote: \$remote" echo "Git user: \$git_username" echo "Session: \$SESSION_PATH" json_log "auth.login.complete" "\$author_email" } require_session() { if [ ! -f "\$SESSION_PATH" ]; then echo "not authenticated; run: section0-docs auth login" >&2 exit 1 fi } session_value() { require_session json_get "\$1" < "\$SESSION_PATH" } mounts_init() { mkdir -p "\$SESSION_DIR" if [ ! -f "\$MOUNTS_PATH" ]; then printf '{\n "mounts": {}\n}\n' > "\$MOUNTS_PATH" chmod 600 "\$MOUNTS_PATH" fi } mount_add() { need_python require_session mounts_init name="\${1:-}" source="\${2:-}" [ -n "\$name" ] && [ -n "\$source" ] || { echo "usage: section0-docs mount add NAME PATH" >&2 exit 1 } if [ ! -d "\$source" ]; then echo "mount source is not a directory: \$source" >&2 exit 1 fi abs_source="\$(cd "\$source" && pwd)" username="\$(session_value authentik.username)" [ -n "\$username" ] || username="\$(session_value authentik.email | sed 's/@.*//')" workspace_id="\$(session_value affine.workspaceId)" namespace="mount/\$username/\$name" python3 - "\$MOUNTS_PATH" "\$name" "\$abs_source" "\$namespace" "\$workspace_id" <<'PY' import datetime, json, sys path, name, source, namespace, workspace_id = sys.argv[1:6] now = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") with open(path) as f: data = json.load(f) mounts = data.setdefault("mounts", {}) mounts[name] = { "name": name, "source": source, "namespace": namespace, "workspaceId": workspace_id, "include": ["**/*.md"], "exclude": [".git/**", ".obsidian/**", ".trash/**", "node_modules/**"], "createdAt": mounts.get(name, {}).get("createdAt") or now, "updatedAt": now, } with open(path, "w") as f: json.dump(data, f, indent=2) f.write("\n") PY chmod 600 "\$MOUNTS_PATH" echo "Mount saved: \$name" echo "Source: \$abs_source" echo "AFFiNE namespace: \$namespace" json_log "mount.add" "\$name" } mount_list() { need_python mounts_init python3 - "\$MOUNTS_PATH" <<'PY' import json, sys with open(sys.argv[1]) as f: mounts = json.load(f).get("mounts", {}) if not mounts: print("no personal Markdown mounts; add one with: section0-docs mount add NAME PATH") raise SystemExit for name, mount in sorted(mounts.items()): print(f"{name}\t{mount.get('source','')}\t{mount.get('namespace','')}") PY } mount_status() { need_python mounts_init name="\${1:-}" [ -n "\$name" ] || { echo "usage: section0-docs mount status NAME" >&2 exit 1 } python3 - "\$MOUNTS_PATH" "\$name" <<'PY' import json, sys with open(sys.argv[1]) as f: mount = json.load(f).get("mounts", {}).get(sys.argv[2]) if not mount: print(f"mount not found: {sys.argv[2]}", file=sys.stderr) raise SystemExit(1) print(json.dumps(mount, indent=2)) PY } mount_remove() { need_python mounts_init name="\${1:-}" [ -n "\$name" ] || { echo "usage: section0-docs mount remove NAME" >&2 exit 1 } python3 - "\$MOUNTS_PATH" "\$name" <<'PY' import json, sys path, name = sys.argv[1:3] with open(path) as f: data = json.load(f) if name not in data.get("mounts", {}): print(f"mount not found: {name}", file=sys.stderr) raise SystemExit(1) del data["mounts"][name] with open(path, "w") as f: json.dump(data, f, indent=2) f.write("\n") PY echo "Removed local mount declaration: \$name" json_log "mount.remove" "\$name" } mount_plan() { need_python require_session mounts_init name="\${1:-}" [ -n "\$name" ] || { echo "usage: section0-docs mount plan NAME" >&2 exit 1 } python3 - "\$MOUNTS_PATH" "\$SESSION_PATH" "\$name" <<'PY' import fnmatch, hashlib, json, os, pathlib, sys mounts_path, session_path, name = sys.argv[1:4] with open(mounts_path) as f: mount = json.load(f).get("mounts", {}).get(name) if not mount: print(f"mount not found: {name}", file=sys.stderr) raise SystemExit(1) with open(session_path) as f: session = json.load(f) source = pathlib.Path(mount["source"]) if not source.is_dir(): print(f"mount source is not a directory: {source}", file=sys.stderr) raise SystemExit(1) include = mount.get("include") or ["**/*.md"] exclude = mount.get("exclude") or [] docs = [] def matches(patterns, rel): for pattern in patterns: if fnmatch.fnmatch(rel, pattern): return True if pattern.startswith("**/") and fnmatch.fnmatch(rel, pattern[3:]): return True return False for path in sorted(source.rglob("*.md")): if not path.is_file(): continue rel = path.relative_to(source).as_posix() if not matches(include, rel): continue if any(fnmatch.fnmatch(rel, pattern) or rel.startswith(pattern.removesuffix("/**") + "/") for pattern in exclude): continue markdown = path.read_text(encoding="utf-8") digest = hashlib.sha256(markdown.encode("utf-8")).hexdigest() stable = hashlib.sha256(f"{session.get('authentik', {}).get('subject','')}:{mount['namespace']}:{rel}".encode("utf-8")).hexdigest()[:24] title = path.stem.replace("_", " ").replace("-", " ").strip() or rel docs.append({ "stableSourceId": stable, "operation": "update", "sourcePath": rel, "sourceSha256": digest, "title": title, "markdown": markdown, "affine": { "docId": f"obs-{stable}", "namespace": mount["namespace"], }, }) plan = { "metadata": { "name": name, "sourceModel": "personal-local-markdown", "custody": "local-user", "readOnlyIntent": True, }, "owner": { "authentik": session.get("authentik", {}), "affine": session.get("affine", {}), }, "target": { "workspaceId": mount.get("workspaceId") or session.get("affine", {}).get("workspaceId"), "namespace": mount["namespace"], }, "summary": { "documents": len(docs), "source": str(source), }, "projectedDocuments": docs, } print(json.dumps({"plan": plan}, indent=2)) PY } mount_sync() { need_python require_session name="\${1:-}" [ -n "\$name" ] || { echo "usage: section0-docs mount sync NAME" >&2 exit 1 } access_token="\$(session_value accessToken)" [ -n "\$access_token" ] || { echo "saved session has no access token; run: section0-docs auth login" >&2 exit 1 } tmp_plan="\$(mktemp)" mount_plan "\$name" > "\$tmp_plan" doc_count="\$(python3 -c 'import json,sys; print(len(json.load(open(sys.argv[1]))["plan"].get("projectedDocuments", [])))' "\$tmp_plan")" echo "Submitting personal Markdown projection:" echo " mount: \$name" echo " docs: \$doc_count" response="\$(curl -fsS -X POST -H "Authorization: Bearer \$access_token" -H "Content-Type: application/json" --data-binary "@\$tmp_plan" "\$SERVER_URL/sync")" rm -f "\$tmp_plan" printf "%s\n" "\$response" python3 - "\$MOUNTS_PATH" "\$name" "\$response" <<'PY' import datetime, json, sys path, name, response = sys.argv[1:4] now = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") with open(path) as f: data = json.load(f) mount = data.setdefault("mounts", {}).setdefault(name, {"name": name}) mount["lastSyncAt"] = now try: mount["lastSync"] = json.loads(response) except Exception: mount["lastSync"] = {"raw": response} with open(path, "w") as f: json.dump(data, f, indent=2) f.write("\n") PY json_log "mount.sync" "\$name" } mount_unmount() { need_python require_session mounts_init name="\${1:-}" [ -n "\$name" ] || { echo "usage: section0-docs mount unmount NAME" >&2 exit 1 } access_token="\$(session_value accessToken)" [ -n "\$access_token" ] || { echo "saved session has no access token; run: section0-docs auth login" >&2 exit 1 } payload="\$(python3 - "\$MOUNTS_PATH" "\$SESSION_PATH" "\$name" <<'PY' import json, sys with open(sys.argv[1]) as f: mount = json.load(f).get("mounts", {}).get(sys.argv[3]) if not mount: print(f"mount not found: {sys.argv[3]}", file=sys.stderr) raise SystemExit(1) with open(sys.argv[2]) as f: session = json.load(f) print(json.dumps({ "workspaceId": mount.get("workspaceId") or session.get("affine", {}).get("workspaceId"), "namespace": mount["namespace"], })) PY )" response="\$(printf "%s" "\$payload" | curl -fsS -X POST -H "Authorization: Bearer \$access_token" -H "Content-Type: application/json" --data-binary @- "\$SERVER_URL/unmount")" printf "%s\n" "\$response" json_log "mount.unmount" "\$name" } case "\${1:-help}" in auth) case "\${2:-}" in login) auth_login ;; finish) auth_finish "\${3:-}" ;; status) if [ -f "\$SESSION_PATH" ]; then cat "\$SESSION_PATH" else echo "not authenticated; run: section0-docs auth login" >&2 exit 1 fi ;; *) echo "usage: section0-docs auth " >&2 exit 1 ;; esac ;; configure) current_name="\$(git -C "\$REPO_DIR" config user.name || true)" current_email="\$(git -C "\$REPO_DIR" config user.email || true)" printf "Git author name [%s]: " "\$current_name" IFS= read -r author_name printf "Git author email [%s]: " "\$current_email" IFS= read -r author_email if [ -n "\$author_name" ]; then git -C "\$REPO_DIR" config user.name "\$author_name" elif [ -z "\$current_name" ]; then echo "Git author name is required" >&2 exit 1 fi if [ -n "\$author_email" ]; then git -C "\$REPO_DIR" config user.email "\$author_email" elif [ -z "\$current_email" ]; then echo "Git author email is required" >&2 exit 1 fi ;; doctor) remote="\$(git -C "\$REPO_DIR" remote get-url origin)" branch="\$(git -C "\$REPO_DIR" branch --show-current)" author_name="\$(git -C "\$REPO_DIR" config user.name || true)" author_email="\$(git -C "\$REPO_DIR" config user.email || true)" echo "Repo: \$REPO_DIR" echo "Remote: \$remote" echo "Branch: \$branch" if [ -n "\$author_name" ] && [ -n "\$author_email" ]; then echo "Git author: \$author_name <\$author_email>" else echo "Git author: missing; run section0-docs configure" fi git -C "\$REPO_DIR" ls-remote --heads origin main >/dev/null echo "Read access: ok" ;; open) printf "%s\n" "\$REPO_DIR" ;; pull) git -C "\$REPO_DIR" pull --ff-only ;; status) git -C "\$REPO_DIR" status --short --branch ;; commit) : "\${MESSAGE:?Set MESSAGE before running commit}" json_log "commit.start" "\$MESSAGE" git -C "\$REPO_DIR" add . git -C "\$REPO_DIR" commit -m "\$MESSAGE" json_log "commit.complete" "\$(git -C "\$REPO_DIR" rev-parse --short HEAD)" ;; push) before="\$(git -C "\$REPO_DIR" rev-parse --short HEAD)" json_log "push.start" "\$before" git -C "\$REPO_DIR" push git -C "\$REPO_DIR" fetch origin >/dev/null 2>&1 || true origin_head="\$(git -C "\$REPO_DIR" rev-parse --short origin/main 2>/dev/null || true)" json_log "push.complete" "\${origin_head:-\$before}" echo "Pushed commit: \${origin_head:-\$before}" if [ "\${SECTION0_WAIT_AFTER_PUSH:-1}" = "1" ]; then wait_for_projection "\${origin_head:-\$before}" else echo "AFFiNE updates after the operator refresh runs." echo "To watch it: section0-docs wait" fi ;; wait) wait_for_projection "\${2:-}" ;; push-test) branch="section0-smoke-\${USER:-user}-\$(date +%Y%m%d%H%M%S)" git -C "\$REPO_DIR" push origin HEAD:refs/heads/"\$branch" git -C "\$REPO_DIR" push origin :refs/heads/"\$branch" echo "Write access: ok" json_log "push-test.complete" "\$branch" ;; trace) echo "Repo: \$REPO_DIR" echo "Remote: \$(git -C "\$REPO_DIR" remote get-url origin)" echo "Projection status: \$STATUS_URL" echo "Branch: \$(git -C "\$REPO_DIR" branch --show-current)" echo "Local HEAD: \$(git -C "\$REPO_DIR" rev-parse --short HEAD)" git -C "\$REPO_DIR" fetch origin >/dev/null 2>&1 || true echo "Origin HEAD: \$(git -C "\$REPO_DIR" rev-parse --short origin/main 2>/dev/null || true)" echo "Status:" git -C "\$REPO_DIR" status --short --branch echo echo "Recent helper events: \$EVENTS_PATH" if [ -f "\$EVENTS_PATH" ]; then tail -20 "\$EVENTS_PATH" else echo "no events yet" fi ;; mount) case "\${2:-}" in list) mount_list ;; add) mount_add "\${3:-}" "\${4:-}" ;; plan) mount_plan "\${3:-}" ;; sync) mount_sync "\${3:-}" ;; status) mount_status "\${3:-}" ;; unmount) mount_unmount "\${3:-}" ;; remove) mount_remove "\${3:-}" ;; *) echo "usage: section0-docs mount " >&2 exit 1 ;; esac ;; help|--help|-h) usage ;; *) usage >&2 exit 1 ;; esac EOF chmod +x "$COMMAND_PATH" cat <