transcript-viewer 0.7.0__tar.gz → 0.7.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/PKG-INFO +1 -1
  2. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/pyproject.toml +1 -1
  3. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/src/transcript_viewer/page.html +30 -3
  4. transcript_viewer-0.7.2/tests/fixtures/big-command.json +1 -0
  5. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/tests/page.test.js +51 -0
  6. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/tests/test_style.py +31 -0
  7. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/uv.lock +1 -1
  8. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/.coverage +0 -0
  9. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/.github/workflows/publish.yml +0 -0
  10. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/.github/workflows/test.yml +0 -0
  11. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/.gitignore +0 -0
  12. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/README.md +0 -0
  13. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/src/transcript_viewer/__init__.py +0 -0
  14. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/src/transcript_viewer/ai.py +0 -0
  15. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/src/transcript_viewer/cli.py +0 -0
  16. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/src/transcript_viewer/config.py +0 -0
  17. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/src/transcript_viewer/corpus.py +0 -0
  18. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/src/transcript_viewer/fetch.py +0 -0
  19. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/src/transcript_viewer/library.py +0 -0
  20. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/src/transcript_viewer/store.py +0 -0
  21. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/src/transcript_viewer/viewer.py +0 -0
  22. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/tests/test_ai.py +0 -0
  23. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/tests/test_cli.py +0 -0
  24. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/tests/test_config.py +0 -0
  25. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/tests/test_corpus.py +0 -0
  26. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/tests/test_fetch.py +0 -0
  27. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/tests/test_library.py +0 -0
  28. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/tests/test_page.py +0 -0
  29. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/tests/test_readme.py +0 -0
  30. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/tests/test_store.py +0 -0
  31. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/tests/test_stream.py +0 -0
  32. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/tests/test_tidiness.py +0 -0
  33. {transcript_viewer-0.7.0 → transcript_viewer-0.7.2}/tests/test_viewer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: transcript-viewer
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: Browse agent transcripts in a local, dependency-free web viewer.
5
5
  License: MIT
6
6
  Requires-Python: >=3.12
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "transcript-viewer"
3
- version = "0.7.0"
3
+ version = "0.7.2"
4
4
  description = "Browse agent transcripts in a local, dependency-free web viewer."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -412,9 +412,17 @@ pre{background:var(--sunk);border-radius:7px;padding:10px;overflow:auto;font-siz
412
412
  /* JSON syntax colours */
413
413
  .j-key{color:var(--j-key)}
414
414
  /* A command reads as a command: one gutter mark, then the script as written. */
415
+ /* Wrapped rather than scrolled: a command is read, and a line that runs off the
416
+ side is a line nobody reads.
417
+
418
+ No hanging indent. A negative text-indent pulls the *first* line out of the
419
+ content box to the left, and `pre` carries overflow:auto, so the box scrolled
420
+ itself sideways to chase it — which hid the first word of every command and
421
+ left the prompt mark stranded mid-block. Wrapped lines simply align with the
422
+ rest; which lines the shell sees as separate is legible from the text. */
415
423
  .sh{background:var(--bg);border-radius:7px;padding:10px 12px 10px 26px;margin:0;
416
- position:relative;overflow-x:auto;white-space:pre;font:12px/1.6 var(--font-mono);
417
- color:var(--ink)}
424
+ position:relative;font:12px/1.6 var(--font-mono);color:var(--ink);
425
+ white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:hidden}
418
426
  .sh::before{content:"$";position:absolute;left:11px;top:10px;color:var(--faint);
419
427
  user-select:none}
420
428
  .sh-cmd{color:var(--sh-cmd)}
@@ -834,8 +842,22 @@ function sh(src) {
834
842
  }
835
843
 
836
844
  if (ch === "$") {
837
- const m = /^\$(?:\{[^}]*\}|[A-Za-z_]\w*|[@*?#$!0-9])/.exec(s.slice(i));
845
+ // $(…) is a command substitution: mark the opening and let what is inside
846
+ // be scanned as the command it is.
847
+ if (s[i + 1] === "(") {
848
+ put("sh-var", "$(");
849
+ i += 2;
850
+ fresh = true;
851
+ continue;
852
+ }
853
+ const m = /^\$(?:\{[^}]*\}|[A-Za-z_]\w*|[@*?#$!0-9])/.exec(s.slice(i, i + 200));
838
854
  if (m) { put("sh-var", m[0]); i += m[0].length; fresh = false; continue; }
855
+ // A lone $ is just a character. Said explicitly, because falling through
856
+ // to the word reader below leaves it unable to move.
857
+ put("", "$");
858
+ i++;
859
+ fresh = false;
860
+ continue;
839
861
  }
840
862
 
841
863
  if (SH_OPS.includes(ch)) {
@@ -852,6 +874,11 @@ function sh(src) {
852
874
 
853
875
  let j = i;
854
876
  while (j < s.length && !" \t\n'\"$".includes(s[j]) && !SH_OPS.includes(s[j])) j++;
877
+ // Every character this reaches must be consumed by something. If a branch
878
+ // above ever stops matching a character it used to, this keeps the loop
879
+ // moving instead of hanging the page — which is what it did, on any command
880
+ // containing $( .
881
+ if (j === i) j = i + 1;
855
882
  const word = s.slice(i, j);
856
883
  put(fresh ? "sh-cmd" : word[0] === "-" ? "sh-flag" : "", word);
857
884
  i = j;
@@ -0,0 +1 @@
1
+ "cat > /Users/girish/Documents/personalwebsite/website/public/check-my-mac.sh <<'SCRIPT_EOF'\n#!/bin/bash\n#\n# check-my-mac.sh \u2014 check the parts of a personal security setup that live on\n# your Mac, and print a report you can paste back into the checklist at\n# https://girishgupta.com/beyond-the-parrot\n#\n# bash check-my-mac.sh readable summary\n# bash check-my-mac.sh --json JSON, for pasting into the checklist\n#\n# WHAT IT DOES AND DOESN'T DO\n# It reads settings and reports them. That's all. It changes nothing,\n# installs nothing, needs no sudo, and sends nothing anywhere \u2014 there is no\n# network call in this file, and you can check that yourself.\n#\n# It can see whether FileVault is on. It cannot see whether you froze your\n# credit, enrolled a hardware key, or turned on a port-out PIN. Those are\n# the items that matter most, and they stay manual on the checklist.\n#\n# Checks that would need root report \"unknown\" rather than asking for your\n# password. Nothing here should ever prompt for it. If something does, stop\n# and work out why.\n#\n# Exit status: 0 if nothing failed, 1 if at least one check failed.\n\nset -uo pipefail\n\nJSON_ONLY=0\n[[ \"${1:-}\" == \"--json\" ]] && JSON_ONLY=1\n\nPASS=0\nFAIL=0\nUNKNOWN=0\nROWS=()\n\nrecord() {\n local key=\"$1\" status=\"$2\" detail=\"$3\"\n case \"$status\" in\n pass) PASS=$((PASS + 1)) ;;\n fail) FAIL=$((FAIL + 1)) ;;\n *) UNKNOWN=$((UNKNOWN + 1)); status=\"unknown\" ;;\n esac\n local esc=${detail//\\\\/\\\\\\\\}\n esc=${esc//\\\"/\\\\\\\"}\n esc=$(printf '%s' \"$esc\" | tr -d '\\000-\\037')\n ROWS+=(\" \\\"$key\\\": { \\\"status\\\": \\\"$status\\\", \\\"detail\\\": \\\"$esc\\\" }\")\n if [[ $JSON_ONLY -eq 0 ]]; then\n local mark\n case \"$status\" in\n pass) mark=\" ok \" ;;\n fail) mark=\"FAIL\" ;;\n unknown) mark=\" ? \" ;;\n esac\n printf '[%s] %-18s %s\\n' \"$mark\" \"$key\" \"$detail\"\n fi\n}\n\nhave() { command -v \"$1\" >/dev/null 2>&1; }\n\nNOW=$(date +%s)\n\n# Days between a unix timestamp and now. Prints nothing if given nothing.\ndays_since() {\n local then=\"${1:-}\"\n [[ -n \"$then\" && \"$then\" =~ ^[0-9]+$ ]] || return 1\n echo $(( (NOW - then) / 86400 ))\n}\n\nif [[ \"$(uname -s)\" != \"Darwin\" ]]; then\n echo \"This one only knows how to check macOS. Detected: $(uname -s)\" >&2\n exit 2\nfi\n\n[[ $JSON_ONLY -eq 0 ]] && {\n echo\n echo \"Checking $(sw_vers -productName) $(sw_vers -productVersion) on $(scutil --get ComputerName 2>/dev/null || hostname -s)\"\n echo\n}\n\n# \u2500\u2500 Disk encryption \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Without this, your laptop is a filing cabinet with the key taped to it.\nfv=\"$(fdesetup status 2>/dev/null)\"\nif grep -qi \"FileVault is On\" <<<\"$fv\"; then\n record \"encryption\" pass \"FileVault is on\"\nelif [[ -n \"$fv\" ]]; then\n record \"encryption\" fail \"$fv \u2014 turn it on in System Settings > Privacy & Security\"\nelse\n record \"encryption\" unknown \"could not read FileVault status\"\nfi\n\n# \u2500\u2500 Screen lock \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Two settings combine: how long until the screen sleeps, and the grace\n# period before a password is demanded. A short screensaver with a ten\n# minute grace period is not a lock.\nidle=$(defaults -currentHost read com.apple.screensaver idleTime 2>/dev/null)\nlock_raw=$(sysadminctl -screenLock status 2>&1 | tail -1)\ngrace=\"\"\ncase \"$lock_raw\" in\n *immediate*) grace=0 ;;\n *\"is off\"*) grace=off ;;\n *[0-9]*seconds*) grace=$(grep -oE \"[0-9]+\" <<<\"$lock_raw\" | head -1) ;;\nesac\nif [[ \"$grace\" == \"off\" ]]; then\n record \"screenlock\" fail \"no password is required after the screen sleeps\"\nelif [[ -n \"$idle\" && \"$idle\" =~ ^[0-9]+$ && \"$idle\" -eq 0 ]]; then\n record \"screenlock\" fail \"the screen never sleeps on its own, so it never locks\"\nelif [[ -n \"$grace\" && -n \"$idle\" && \"$idle\" =~ ^[0-9]+$ ]]; then\n total=$((idle + grace))\n if [[ \"$total\" -le 300 ]]; then\n record \"screenlock\" pass \"locks after ${total}s (${idle}s idle + ${grace}s grace)\"\n else\n record \"screenlock\" fail \"takes ${total}s to lock \u2014 aim for five minutes or less\"\n fi\nelse\n record \"screenlock\" unknown \"could not read the screen lock settings\"\nfi\n\n# \u2500\u2500 Automatic login \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# macOS can be set to log someone in at boot without asking for anything,\n# which turns a stolen laptop into an open laptop. FileVault mostly\n# suppresses it, so this matters most on machines without FileVault.\nautologin=$(defaults read /Library/Preferences/com.apple.loginwindow autoLoginUser 2>/dev/null)\nif [[ -n \"$autologin\" ]]; then\n record \"auto-login\" fail \"this Mac logs in as '$autologin' at boot without a password\"\nelse\n record \"auto-login\" pass \"no automatic login at boot\"\nfi\n\n# \u2500\u2500 Automatic updates \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Read the whole domain: individual keys are often absent, which means\n# \"left at the default\" (on), not \"off\". Then check the setting is actually\n# working \u2014 \"updates are enabled\" and \"updates are happening\" are different\n# claims, and only the second one keeps you patched.\nSU=/Library/Preferences/com.apple.SoftwareUpdate\nsu_dump=$(defaults read \"$SU\" 2>/dev/null)\nsu_key() { grep -oE \"^ *$1 = [0-9]+\" <<<\"$su_dump\" | grep -oE \"[0-9]+$\"; }\nif [[ -z \"$su_dump\" ]]; then\n record \"updates\" unknown \"could not read the software update settings\"\nelse\n auto_check=$(su_key AutomaticCheckEnabled)\n crit=$(su_key CriticalUpdateInstall)\n os_auto=$(su_key AutomaticallyInstallMacOSUpdates)\n\n # Last time a check actually succeeded, as recorded by macOS itself.\n last_su_raw=$(grep -oE 'LastSuccessfulDate = \"[^\"]+\"' <<<\"$su_dump\" | head -1 |\n sed -E 's/.*\"([0-9-]+ [0-9:]+).*/\\1/')\n last_su_epoch=\"\"\n [[ -n \"$last_su_raw\" ]] &&\n last_su_epoch=$(date -j -u -f \"%Y-%m-%d %H:%M:%S\" \"$last_su_raw\" +%s 2>/dev/null)\n su_age=$(days_since \"$last_su_epoch\") || su_age=\"\"\n\n if [[ \"${auto_check:-1}\" == \"0\" ]]; then\n record \"updates\" fail \"automatic update checks are turned off\"\n elif [[ \"${crit:-1}\" == \"0\" ]]; then\n record \"updates\" fail \"security responses are not installed automatically\"\n elif [[ -n \"$su_age\" && \"$su_age\" -gt 30 ]]; then\n record \"updates\" fail \"automatic updates are on, but the last successful check was ${su_age} days ago\"\n elif [[ -n \"$su_age\" ]]; then\n record \"updates\" pass \"automatic updates on and checking (last successful check ${su_age}d ago)\"\n else\n record \"updates\" pass \"automatic updates on (security=${crit:-default}, macOS=${os_auto:-default})\"\n fi\nfi\n\n# \u2500\u2500 Supported macOS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Apple ships security fixes for the current release and the two before it.\n# That list changes every autumn, so it lives here with a date attached: if\n# this script has gone stale the check says so instead of guessing at you.\nOS_TABLE_DATE=\"2026-08-21\"\nSUPPORTED_MAJORS=\"26 25 15 14\"\ntable_epoch=$(date -j -f \"%Y-%m-%d\" \"$OS_TABLE_DATE\" +%s 2>/dev/null)\ntable_age=$(days_since \"$table_epoch\") || table_age=\"\"\nmajor=$(sw_vers -productVersion | cut -d. -f1)\nif [[ -n \"$table_age\" && \"$table_age\" -gt 400 ]]; then\n record \"supported-os\" unknown \"this script's list of supported macOS versions is ${table_age} days old \u2014 too stale to judge macOS $(sw_vers -productVersion)\"\nelif [[ \" $SUPPORTED_MAJORS \" == *\" $major \"* ]]; then\n record \"supported-os\" pass \"macOS $(sw_vers -productVersion) still gets security updates\"\nelse\n record \"supported-os\" fail \"macOS $(sw_vers -productVersion) is past its last security update\"\nfi\n\n# \u2500\u2500 System Integrity Protection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# SIP stops even root from modifying system files or injecting into Apple's\n# own processes. It is on by default, and turning it off takes a deliberate\n# trip to Recovery \u2014 which people make for some dev tool and then forget.\nsip_raw=$(csrutil status 2>/dev/null)\nif grep -qi \"status: enabled\" <<<\"$sip_raw\"; then\n record \"sip\" pass \"System Integrity Protection is on\"\nelif grep -qi \"status: disabled\" <<<\"$sip_raw\"; then\n record \"sip\" fail \"System Integrity Protection is off \u2014 re-enable it from Recovery unless you know why it's off\"\nelif [[ -n \"$sip_raw\" ]]; then\n record \"sip\" fail \"System Integrity Protection is partly disabled: $(tr -d '\\n' <<<\"$sip_raw\" | cut -c1-90)\"\nelse\n record \"sip\" unknown \"could not read System Integrity Protection status\"\nfi\n\n# \u2500\u2500 Secure Boot \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Apple Silicon only. Full Security means the Mac will only boot a signed,\n# current OS. Reduced Security is what you get after enabling third-party\n# kernel extensions, and it is often deliberate \u2014 but it should be a choice\n# you remember making rather than one you find out about here.\nbridge=$(system_profiler SPiBridgeDataType 2>/dev/null)\nsb=$(grep -oE \"Secure Boot: .*\" <<<\"$bridge\" | head -1 | sed 's/Secure Boot: //')\nkext=$(grep -oE \"Allow All Kernel Extensions: .*\" <<<\"$bridge\" | head -1 | sed 's/.*: //')\nif [[ -z \"$bridge\" ]]; then\n record \"secure-boot\" unknown \"no boot policy reported \u2014 Intel Macs don't have this\"\nelif [[ \"$sb\" == \"Full Security\" ]]; then\n record \"secure-boot\" pass \"Secure Boot is at Full Security\"\nelif [[ -n \"$sb\" ]]; then\n record \"secure-boot\" fail \"Secure Boot is at ${sb}${kext:+, third-party kernel extensions allowed: $kext}\"\nelse\n record \"secure-boot\" unknown \"could not read the boot policy\"\nfi\n\n# \u2500\u2500 Firewall \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nFW=/usr/libexec/ApplicationFirewall/socketfilterfw\nif [[ -x \"$FW\" ]]; then\n if \"$FW\" --getglobalstate 2>/dev/null | grep -qi \"enabled\"; then\n stealth=\"\"\n \"$FW\" --getstealthmode 2>/dev/null | grep -qi \"on\" && stealth=\", stealth mode on\"\n record \"firewall\" pass \"the built-in firewall is on$stealth\"\n else\n record \"firewall\" fail \"the firewall is off \u2014 System Settings > Network > Firewall\"\n fi\nelse\n record \"firewall\" unknown \"could not find socketfilterfw\"\nfi\n\n# \u2500\u2500 Gatekeeper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nif spctl --status 2>&1 | grep -qi \"assessments enabled\"; then\n xp=$(defaults read /Library/Apple/System/Library/CoreServices/XProtect.bundle/Contents/Info.plist \\\n CFBundleShortVersionString 2>/dev/null)\n record \"gatekeeper\" pass \"Gatekeeper is on${xp:+, XProtect $xp}\"\nelse\n record \"gatekeeper\" fail \"Gatekeeper is disabled \u2014 it is the thing that stops you double-clicking malware\"\nfi\n\n# \u2500\u2500 Services listening to the network \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Anything bound to 127.0.0.1 is talking to your own machine and is nobody\n# else's business; development tools do it constantly. What matters is a\n# service bound to every interface, because that is the one that answers\n# strangers when you join caf\u00e9 wifi.\n#\n# This uses netstat rather than lsof deliberately. lsof run as you only\n# reports your own sockets, and sshd runs as root \u2014 so an lsof check would\n# cheerfully report a clean bill of health on a Mac with Remote Login on.\nlisteners=$(netstat -an -p tcp 2>/dev/null | awk '/LISTEN/ {print $4}')\nif [[ -z \"$listeners\" ]]; then\n record \"sharing\" unknown \"could not read the list of listening sockets\"\nelse\n # Strip loopback, then reduce each address to its port number.\n public_ports=$(grep -vE '^(127\\.0\\.0\\.1|::1|\\*\\.0$)' <<<\"$listeners\" |\n sed -E 's/.*[.:]([0-9]+)$/\\1/' | sort -un)\n named=\"\"\n for p in $public_ports; do\n case \"$p\" in\n 22) named=\"${named:+$named, }Remote Login (SSH)\" ;;\n 445) named=\"${named:+$named, }File Sharing (SMB)\" ;;\n 548) named=\"${named:+$named, }File Sharing (AFP)\" ;;\n 631) named=\"${named:+$named, }Printer Sharing\" ;;\n 3283) named=\"${named:+$named, }Remote Management\" ;;\n 5900) named=\"${named:+$named, }Screen Sharing\" ;;\n 5000|7000) named=\"${named:+$named, }AirPlay Receiver\" ;;\n esac\n done\n others=$(grep -cvE '^(22|445|548|631|3283|5900|5000|7000)$' <<<\"$public_ports\")\n if [[ -n \"$named\" ]]; then\n record \"sharing\" fail \"reachable from the network: $named \u2014 turn off what you don't use in System Settings > General > Sharing\"\n elif [[ \"$others\" -gt 0 ]]; then\n record \"sharing\" unknown \"$others service(s) listening on all interfaces, none of them a standard sharing service: $(tr '\\n' ' ' <<<\"$public_ports\")\"\n else\n record \"sharing\" pass \"nothing is listening to the network beyond your own machine\"\n fi\nfi\n\n# \u2500\u2500 Guest account \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nguest=$(defaults read /Library/Preferences/com.apple.loginwindow GuestEnabled 2>/dev/null)\nif [[ \"${guest:-0}\" == \"1\" ]]; then\n record \"guest-account\" fail \"the guest account is enabled\"\nelse\n record \"guest-account\" pass \"the guest account is off\"\nfi\n\n# \u2500\u2500 Find My \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# The thing that lets you wipe a laptop you are never getting back. Note\n# that the findmydeviced process runs whether or not the feature is on, so\n# it proves nothing; these two signals do.\nfmm=$(defaults read /Library/Preferences/com.apple.FindMyMac FMMEnabled 2>/dev/null)\nfmm_token=$(nvram -p 2>/dev/null | grep -c 'fmm-mobileme-token-FMM')\nif [[ \"${fmm:-0}\" == \"1\" || \"${fmm_token:-0}\" -gt 0 ]]; then\n record \"find-my\" pass \"Find My Mac is on \u2014 a lost machine can be wiped remotely\"\nelif [[ -n \"$fmm\" || \"${fmm_token:-0}\" == \"0\" ]]; then\n record \"find-my\" fail \"Find My Mac is off \u2014 System Settings > Apple Account > iCloud > Find My Mac\"\nelse\n record \"find-my\" unknown \"could not read Find My status\"\nfi\n\n# \u2500\u2500 SSH keys \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Only relevant if you have them, and if you do, an unencrypted private key\n# is a password sitting in a file. ssh-keygen with an empty passphrase\n# succeeds only on a key that has none, and never prompts.\nif [[ -d \"$HOME/.ssh\" ]]; then\n dir_mode=$(stat -f \"%A\" \"$HOME/.ssh\" 2>/dev/null)\n bare=() loose=()\n for k in \"$HOME\"/.ssh/id_* \"$HOME\"/.ssh/*.pem; do\n [[ -f \"$k\" ]] || continue\n [[ \"$k\" == *.pub ]] && continue\n ssh-keygen -y -P \"\" -f \"$k\" >/dev/null 2>&1 </dev/null && bare+=(\"$(basename \"$k\")\")\n mode=$(stat -f \"%A\" \"$k\" 2>/dev/null)\n [[ -n \"$mode\" && \"$mode\" != \"600\" && \"$mode\" != \"400\" ]] && loose+=(\"$(basename \"$k\") is $mode\")\n done\n problems=\"\"\n [[ ${#bare[@]} -gt 0 ]] && problems=\"no passphrase: ${bare[*]}\"\n [[ ${#loose[@]} -gt 0 ]] && problems=\"${problems:+$problems; }readable by others: ${loose[*]}\"\n [[ -n \"$dir_mode\" && \"$dir_mode\" != \"700\" ]] && problems=\"${problems:+$problems; }~/.ssh is $dir_mode, should be 700\"\n if [[ -n \"$problems\" ]]; then\n record \"ssh-keys\" fail \"$problems\"\n else\n record \"ssh-keys\" pass \"SSH keys and permissions look right\"\n fi\nelse\n record \"ssh-keys\" pass \"no ~/.ssh directory, so nothing to get wrong\"\nfi\n\n# \u2500\u2500 Password manager \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\npm=\"\"\nfor app in \"1Password\" \"Bitwarden\" \"Proton Pass\" \"Dashlane\"; do\n [[ -d \"/Applications/$app.app\" ]] && { pm=\"$app\"; break; }\ndone\nif [[ -n \"$pm\" ]]; then\n record \"password-manager\" pass \"$pm is installed\"\nelif [[ -d \"/System/Applications/Passwords.app\" ]]; then\n record \"password-manager\" unknown \"no third-party manager found; Apple Passwords is available but this script can't tell whether you use it\"\nelse\n record \"password-manager\" fail \"no password manager found in /Applications\"\nfi\n\n# \u2500\u2500 Backups \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# A backup you have never restored from is a hypothesis, not a backup \u2014 and\n# an app in /Applications is not even that. What matters is whether\n# something ran recently, so where a tool records its own history in a file\n# you can read, this reads it. Where it doesn't, it says so rather than\n# awarding a pass for owning the software.\nFRESH_DAYS=7\nbest_age=\"\" # smallest number of days since a verified backup\nverified=\"\" # tools with a real, dated last run\npresent=\"\" # tools installed but unverifiable from here\n\nnote_verified() {\n local name=\"$1\" age=\"$2\"\n verified=\"${verified:+$verified; }$name (${age}d ago)\"\n [[ -z \"$best_age\" || \"$age\" -lt \"$best_age\" ]] && best_age=\"$age\"\n}\n\n# Time Machine. A configured destination is not the point; a recent snapshot\n# is. Snapshot paths end in YYYY-MM-DD-HHMMSS.\nif have tmutil && tmutil destinationinfo >/dev/null 2>&1; then\n latest=$(tmutil latestbackup 2>/dev/null | tail -1)\n stamp=$(grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{6}' <<<\"$latest\" | tail -1)\n if [[ -n \"$stamp\" ]]; then\n tm_epoch=$(date -j -f \"%Y-%m-%d-%H%M%S\" \"$stamp\" +%s 2>/dev/null)\n tm_age=$(days_since \"$tm_epoch\") && note_verified \"Time Machine\" \"$tm_age\"\n else\n present=\"${present:+$present, }Time Machine (destination set, but no snapshot this script can date)\"\n fi\nfi\n\n# Arq. The agent keeps a world-readable log and writes a line on every\n# completed backup record, which is a far better signal than the app existing.\nif [[ -d /Applications/Arq.app ]]; then\n arq_log=$(ls -t /Library/Logs/ArqAgent/*.log 2>/dev/null | head -1)\n arq_done=\"\"\n if [[ -n \"$arq_log\" && -r \"$arq_log\" ]]; then\n arq_done=$(tail -c 2000000 \"$arq_log\" 2>/dev/null |\n grep -oE '[0-9]{2}-[A-Za-z]{3}-[0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2}' | tail -1)\n fi\n if [[ -n \"$arq_done\" ]]; then\n arq_epoch=$(date -j -f \"%d-%b-%Y %H:%M:%S\" \"$arq_done\" +%s 2>/dev/null)\n arq_age=$(days_since \"$arq_epoch\") && note_verified \"Arq\" \"$arq_age\"\n else\n present=\"${present:+$present, }Arq (installed, no readable backup record)\"\n fi\nfi\n\n# Everything else: detectable, but without a log this script can honestly\n# read, so they count as \"something is here\" rather than \"you are covered\".\nfor app in \"Backblaze\" \"Carbon Copy Cloner\" \"SuperDuper!\" \"ChronoSync\"; do\n [[ -d \"/Applications/$app.app\" ]] && present=\"${present:+$present, }$app (installed, last run not checkable from here)\"\ndone\nfor tool in restic borg kopia duplicacy rclone; do\n have \"$tool\" && present=\"${present:+$present, }$tool (command-line tool present, schedule unknown)\"\ndone\n\nif [[ -n \"$best_age\" && \"$best_age\" -le \"$FRESH_DAYS\" ]]; then\n record \"backups\" pass \"backed up within the last ${FRESH_DAYS} days \u2014 $verified\"\nelif [[ -n \"$best_age\" ]]; then\n record \"backups\" fail \"the most recent backup was ${best_age} days ago \u2014 $verified\"\nelif [[ -n \"$present\" ]]; then\n record \"backups\" unknown \"backup software found but no run this script can date: $present\"\nelse\n record \"backups\" fail \"no backup software found and no Time Machine destination \u2014 one dead disk from losing everything\"\nfi\n\n# \u2500\u2500 Hardware security key \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Presence proves you own one. It says nothing about whether you enrolled it\n# anywhere, which is the part that matters, so this never reports a pass.\nif system_profiler SPUSBDataType 2>/dev/null | grep -qiE \"yubikey|security key\"; then\n record \"hardware-key\" unknown \"a security key is plugged in \u2014 whether it's enrolled on your accounts can't be checked from here\"\nelse\n record \"hardware-key\" unknown \"no security key plugged in right now; enrolment can't be checked from here either way\"\nfi\n\n# \u2500\u2500 Report \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\njson() {\n echo \"{\"\n echo \" \\\"tool\\\": \\\"check-my-mac.sh\\\",\"\n echo \" \\\"version\\\": 2,\"\n echo \" \\\"generated\\\": \\\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\\\",\"\n echo \" \\\"platform\\\": \\\"macOS $(sw_vers -productVersion)\\\",\"\n echo \" \\\"summary\\\": { \\\"pass\\\": $PASS, \\\"fail\\\": $FAIL, \\\"unknown\\\": $UNKNOWN },\"\n echo \" \\\"checks\\\": {\"\n local n=${#ROWS[@]} idx=0\n for row in \"${ROWS[@]}\"; do\n idx=$((idx + 1))\n if [[ $idx -lt $n ]]; then echo \"$row,\"; else echo \"$row\"; fi\n done\n echo \" }\"\n echo \"}\"\n}\n\nif [[ $JSON_ONLY -eq 1 ]]; then\n json\nelse\n echo\n echo \"$PASS passed, $FAIL failed, $UNKNOWN unknown.\"\n echo\n echo \"The things this cannot see \u2014 your credit freeze, your hardware keys,\"\n echo \"your phone carrier's port-out PIN \u2014 are the ones worth doing first.\"\n echo\n echo \"To tick these off in the checklist:\"\n echo \" bash check-my-mac.sh --json | pbcopy\"\n echo \"then paste into the box in the post.\"\n echo\nfi\n\n[[ $FAIL -eq 0 ]]\nSCRIPT_EOF\nbash -n /Users/girish/Documents/personalwebsite/website/public/check-my-mac.sh && echo \"syntax OK\""
@@ -1622,3 +1622,54 @@ test("a file path is titled by its end, where the name is", () => {
1622
1622
  );
1623
1623
  assert.strictEqual(title, "down/ARCHITECTURE.md");
1624
1624
  });
1625
+
1626
+ test("a command substitution does not stop the scanner", () => {
1627
+ // $( matched no branch and the word reader could not move past $, so the page
1628
+ // hung for ever on any command using one. 58 of them in one real session.
1629
+ const html = run(`return sh('echo $(date) and $(basename /a/b)')`);
1630
+ assert.match(html, /sh-var/);
1631
+ assert.ok(html.includes("date"), "what is inside must still be there");
1632
+ });
1633
+
1634
+ test("a lone dollar is just a character", () => {
1635
+ const html = run(`return sh('echo 5$ and $')`);
1636
+ assert.ok(html.includes("5$"), "it should survive");
1637
+ });
1638
+
1639
+ test("every character survives the scanner", () => {
1640
+ // The strongest check there is: what goes in comes out, tags aside. A branch
1641
+ // that stops matching shows up here as mangled text rather than as a hang.
1642
+ const samples = [
1643
+ "echo $(date)",
1644
+ 'for f in $HOME/*; do echo "$f"; done',
1645
+ "awk '{print $1}' file | sort -u > /tmp/o",
1646
+ "x=$((1+2)); echo $x $$ $? $",
1647
+ "cat <<'EOF'\nliteral $(not a call)\nEOF",
1648
+ "curl -s https://x/a#b -o /tmp/x && echo done # a comment",
1649
+ ];
1650
+ for (const src of samples) {
1651
+ const out = run(`return sh(${JSON.stringify(src)})`);
1652
+ const back = out
1653
+ .replace(/<[^>]+>/g, "")
1654
+ .replace(/&quot;/g, '"')
1655
+ .replace(/&#39;/g, "'")
1656
+ .replace(/&lt;/g, "<")
1657
+ .replace(/&gt;/g, ">")
1658
+ .replace(/&amp;/g, "&");
1659
+ assert.strictEqual(back, src, `mangled: ${JSON.stringify(src)}`);
1660
+ }
1661
+ });
1662
+
1663
+ test("a real 20,000-character command draws, and quickly", () => {
1664
+ // This exact command hung the page: 448 lines of heredoc with 58 command
1665
+ // substitutions in it. Kept as a fixture because no invented sample had the
1666
+ // shape that broke it.
1667
+ const command = JSON.parse(
1668
+ readFileSync(join(__dirname, "fixtures", "big-command.json"), "utf8"),
1669
+ );
1670
+ const started = Date.now();
1671
+ const html = run(`return shBlock(${JSON.stringify(command)})`);
1672
+ const took = Date.now() - started;
1673
+ assert.ok(html.includes("class=\"sh\""), "it should render");
1674
+ assert.ok(took < 2000, `took ${took}ms, which is a page nobody waits for`);
1675
+ });
@@ -147,3 +147,34 @@ def test_the_session_count_is_said_in_one_place():
147
147
  assert script.count('count.textContent=') <= 2, (
148
148
  "the session count is being written in more than one place again"
149
149
  )
150
+
151
+
152
+ def _rule(selector: str) -> str:
153
+ """The declarations of one rule, as written."""
154
+ start = STYLE.index(selector + "{")
155
+ return STYLE[start + len(selector) + 1 : STYLE.index("}", start)]
156
+
157
+
158
+ def test_a_command_block_keeps_its_first_line_inside_itself():
159
+ """A negative text-indent pulls the first line out of the box.
160
+
161
+ `pre` carries overflow:auto, so the box then scrolls sideways to chase it:
162
+ the first word of every command disappeared and the prompt mark was left
163
+ stranded mid-block. Both symptoms, one declaration.
164
+ """
165
+ rule = _rule(".sh")
166
+ assert "text-indent:-" not in rule.replace(" ", ""), (
167
+ "a negative text-indent on .sh hides the start of the command"
168
+ )
169
+
170
+
171
+ def test_a_command_wraps_rather_than_scrolling():
172
+ rule = _rule(".sh").replace(" ", "").replace("\n", "")
173
+ assert "white-space:pre-wrap" in rule, "a command that runs off the side is not read"
174
+ assert "overflow-x:auto" not in rule, "it should wrap, not scroll"
175
+
176
+
177
+ def test_the_prompt_mark_stays_at_the_top_left():
178
+ rule = _rule(".sh::before").replace(" ", "").replace("\n", "")
179
+ assert "position:absolute" in rule
180
+ assert "left:11px" in rule and "top:10px" in rule
@@ -388,7 +388,7 @@ wheels = [
388
388
 
389
389
  [[package]]
390
390
  name = "transcript-viewer"
391
- version = "0.7.0"
391
+ version = "0.7.2"
392
392
  source = { editable = "." }
393
393
  dependencies = [
394
394
  { name = "atif-make" },