fuzzyworld-tools / files / bin / fw
fw
623239b7 2026-10-26 Pavel Safronov | 1 | #!/bin/bash |
f9ca0752 2026-10-27 Pavel Safronov | 2 | # fw (laptop side). |
| 3 | # fw deploy [--ref <branch>] [--here] [--dry-run] build and publish this repo's app (see lib/deploy.mjs) | |
b75ceb4e 2026-10-27 Pavel Safronov | 4 | # fw repoint [DIR] [--https] [--dry-run] move working copies' origin from GitLab to DreamHost |
623239b7 2026-10-26 Pavel Safronov | 5 | # fw list | new <name> [--public] [--description TEXT] | public <name> | private <name> |
03124f41 2026-10-31 Pavel Safronov | 6 | # fw usage [--all|--now] Claude Code token use per session (see lib/usage.py) |
0e603cf7 2026-10-31 Pavel Safronov | 7 | # fw blog list|build|preview|deploy <config> static blog from Markdown (see server/blog.py); |
9bb22e48 2026-11-03 Pavel Safronov | 8 | # build takes --out DIR and --no-htaccess; |
| 9 | # deploy takes --dry-run and --yes (else it asks) | |
0e603cf7 2026-10-31 Pavel Safronov | 10 | # fw blog import-wp <config> --wp URL [--dry-run] [--force] [--refresh] [--probe-links] |
| 11 | # import the WordPress blog (see lib/wp_import.py) | |
| 12 | # fw blog check <config> [--out DIR] compare the built site with the cached WordPress text | |
9bb22e48 2026-11-03 Pavel Safronov | 13 | # fw blog watch <config> [--once] [--dry-run] mirror the source into git and auto-deploy edits (see server/blog_watch.py); |
| 14 | # fw blog watch install|uninstall <config> [--print] the launchd agent that runs it every 60 s (ONE machine only) | |
a5a72f55 2026-10-27 Pavel Safronov | 15 | # fw describe <name> TEXT | publish <name> | publish-all | ci <name> [branch] | ci-list | ci-watch |
85702b14 2026-11-04 Pavel Safronov | 16 | # fw admin [--port N] [--no-open] [--code-dir DIR] local web page for reports and blog comments (see lib/admin.py); |
| 17 | # runs the server-side fw commands over ssh (FW_HOST); FW_CODE_DIR = app folders | |
e9fdc5db 2026-11-01 Pavel Safronov | 18 | # fw comments list|show|approve|reject|remove|untrust|publish|export|ingest|imap-check (blog comments) |
55e5fc3e 2026-11-04 Pavel Safronov | 19 | # fw issues <repo> public|reviewed|private | list|show|approve|hide|close|reopen|publish|publish-all|ingest|imap-check (bug-report issues) |
1758548c 2026-10-27 Pavel Safronov | 20 | # (run on DreamHost over ssh) |
623239b7 2026-10-26 Pavel Safronov | 21 | set -euo pipefail |
| 22 | HOST=${FW_HOST:-dh} | |
f9ca0752 2026-10-27 Pavel Safronov | 23 | # Follow the symlink npm creates in node_modules/.bin to find this package. |
| 24 | HERE=$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/.." && pwd) | |
623239b7 2026-10-26 Pavel Safronov | 25 | |
0e603cf7 2026-10-31 Pavel Safronov | 26 | # fw blog: runs on the laptop. Markdown and Pygments live in ~/fw/pylib (installed here when missing). |
| 27 | blog() { | |
| 28 | local sub=${1:-} config=${2:-} tmp=${TMPDIR:-/tmp} | |
| 29 | tmp=${tmp%/} | |
| 30 | local pylib=${FW_PYLIB:-$HOME/fw/pylib} blogpy="$HERE/server/blog.py" | |
| 31 | case "$sub" in | |
9bb22e48 2026-11-03 Pavel Safronov | 32 | list|build|preview|deploy|import-wp|check|watch) ;; |
| 33 | *) echo "usage: fw blog list|build|preview|deploy|import-wp|check|watch <config> [options]" >&2; exit 2 ;; | |
0e603cf7 2026-10-31 Pavel Safronov | 34 | esac |
| 35 | [ -n "$config" ] || { echo "fw blog $sub: needs the config file (e.g. ~/Blog/fw-blog.json)" >&2; exit 2; } | |
| 36 | shift 2 | |
| 37 | if ! PYTHONPATH="$pylib" python3 -c 'import markdown, pygments' 2>/dev/null; then | |
9bb22e48 2026-11-03 Pavel Safronov | 38 | # The launchd agent (watch --once) must never run pip: only an interactive command installs the libraries. |
| 39 | if [ "$sub" = watch ] && [[ " $* " == *" --once "* ]]; then | |
| 40 | echo "fw: Markdown and Pygments are missing from $pylib; run any other fw blog command once to install them" >&2 | |
| 41 | exit 1 | |
| 42 | fi | |
0e603cf7 2026-10-31 Pavel Safronov | 43 | echo "fw: installing Markdown and Pygments into $pylib" >&2 |
| 44 | python3 -m pip install --quiet --target "$pylib" Markdown==3.7 Pygments==2.21.0 | |
| 45 | fi | |
| 46 | case "$sub" in | |
| 47 | import-wp) | |
| 48 | # Reads the WordPress REST API (cached in <source>/wordpress/_api/); --dry-run writes only that cache and the report. | |
| 49 | exec python3 "$HERE/lib/wp_import.py" import "$config" "$@" ;; | |
| 50 | check) | |
| 51 | exec python3 "$HERE/lib/wp_import.py" check "$config" "$@" ;; | |
| 52 | list) | |
| 53 | python3 "$blogpy" list "$config" ;; | |
| 54 | build) | |
| 55 | local out=$tmp/fw-blog-build extra=() | |
| 56 | while [ $# -gt 0 ]; do | |
| 57 | case "$1" in | |
| 58 | --out) out=${2:?--out needs a folder}; shift 2 ;; | |
| 59 | --no-htaccess) extra+=(--no-htaccess); shift ;; | |
| 60 | *) echo "fw blog build: unknown option $1 (use --out DIR, --no-htaccess)" >&2; exit 2 ;; | |
| 61 | esac | |
| 62 | done | |
| 63 | python3 "$blogpy" build "$config" --out "$out" ${extra[@]+"${extra[@]}"} ;; | |
| 64 | preview) | |
| 65 | # No .htaccess here (python's server ignores it, so redirects are not tested); the feed is at /feed/index.xml. | |
| 66 | local out=$tmp/fw-blog-preview port=${1:-8000} | |
| 67 | python3 "$blogpy" build "$config" --out "$out" --no-htaccess | |
| 68 | echo "preview: http://127.0.0.1:$port/ (ctrl-c to stop)" | |
| 69 | exec python3 -m http.server --bind 127.0.0.1 --directory "$out" "$port" ;; | |
9bb22e48 2026-11-03 Pavel Safronov | 70 | watch) |
| 71 | # One tick (--once, what the launchd agent runs) or a loop; or install/uninstall the agent: `watch install <config>`. | |
| 72 | exec python3 "$HERE/server/blog_watch.py" "$config" "$@" ;; | |
0e603cf7 2026-10-31 Pavel Safronov | 73 | deploy) |
9bb22e48 2026-11-03 Pavel Safronov | 74 | # Uploads ONLY a freshly built, audited output folder: never the source folder. Asks first unless --yes. |
| 75 | local args=() a | |
0e603cf7 2026-10-31 Pavel Safronov | 76 | for a in "$@"; do |
9bb22e48 2026-11-03 Pavel Safronov | 77 | case "$a" in --dry-run|--yes) args+=("$a") ;; *) echo "fw blog deploy: unknown option $a" >&2; exit 2 ;; esac |
0e603cf7 2026-10-31 Pavel Safronov | 78 | done |
9bb22e48 2026-11-03 Pavel Safronov | 79 | exec python3 "$blogpy" deploy "$config" --out "$tmp/fw-blog-deploy" ${args[@]+"${args[@]}"} ;; |
0e603cf7 2026-10-31 Pavel Safronov | 80 | esac |
| 81 | } | |
| 82 | ||
623239b7 2026-10-26 Pavel Safronov | 83 | case "${1:-help}" in |
0e603cf7 2026-10-31 Pavel Safronov | 84 | blog) |
| 85 | shift; blog "$@" ;; | |
f9ca0752 2026-10-27 Pavel Safronov | 86 | deploy) |
| 87 | shift; exec node "$HERE/lib/deploy.mjs" "$@" ;; | |
03124f41 2026-10-31 Pavel Safronov | 88 | usage) |
| 89 | shift; exec python3 "$HERE/lib/usage.py" "$@" ;; | |
85702b14 2026-11-04 Pavel Safronov | 90 | admin) |
| 91 | shift; exec python3 "$HERE/lib/admin.py" "$@" ;; | |
b75ceb4e 2026-10-27 Pavel Safronov | 92 | repoint) |
| 93 | shift; exec bash "$HERE/lib/repoint.sh" "$@" ;; | |
55e5fc3e 2026-11-04 Pavel Safronov | 94 | list|new|public|private|describe|publish|publish-all|ci|ci-list|ci-watch|comments|issues|help|-h|--help) |
623239b7 2026-10-26 Pavel Safronov | 95 | # printf %q keeps arguments (e.g. a description with spaces) intact through ssh. |
| 96 | exec ssh "$HOST" "~/bin/fw $(printf '%q ' "$@")" ;; | |
| 97 | *) | |
| 98 | echo "fw: unknown command '$1' (try: fw help)" >&2; exit 2 ;; | |
| 99 | esac |