shawwn/sharc

An Arc-to-Common-Lisp port of Arc and the News web app that powers Hacker News.

3

stars

1,181

commits

Common Lisp

primary language

Sep 2, 2026

updated

README

sharc

An Arc-to-Common-Lisp port of Arc and the News web app that powers Hacker News.

image

In September 2024, Hacker News migrated from Arc-on-Racket to Arc on SBCL using a compiler called Clarc that dang had been developing for years. The port lets HN run on multiple cores and was fast enough to retire pagination on long threads. See the announcement thread and Vincent Massol's write-up.

This repository is an independent open-source Arc-on-Common-Lisp runtime in the same spirit. It boots arc0.lisp (a port of Arc's ac.scm) under SBCL and then loads arc.arc and the rest of Arc on top of it, so News and other Arc programs run unmodified.

Requirements

You'll need SBCL installed (brew install sbcl on macOS, apt install sbcl on Debian/Ubuntu).

Running the tests

./test.arc

test.arc is adapted from lumen's test suite plus extra cases added during the port. A clean run prints something like 193 passed, 0 failed.

Running News

mkdir -p arc
echo "myname" > arc/admins
export DEV=t # reload code changes without needing to restart
./sharc news.arc # prepend with `rlwrap` for repl history

Then go to http://localhost:8080.

Click on login and create an account called myname. You should now be logged in as an admin.

Set DEV=t (or run (set autoreload*) in the repl) to automatically reload code changes without restarting the server.

For production deployments, instead of autoreload, you can manually git pull and then run (reload) in the repl to ship an update. (You could use autoreload in production, but then each request is slightly slower since it has to check the modification times of every arc file.)

How I run News

My daily driver for News is:

DEV=t lwrap ./sharc news.arc

lwrap is a wrapper over rlwrap that makes it nicer for lisp repl work. ("lisp-wrap.") Just put it in your PATH, install rlwrap, and you're good to go.

Email

News sends password reset emails via Resend's SMTP relay. Ask Claude how to set it up for your own domain, or follow Resend's guides: verify a sending domain, create an API key, and send with SMTP.

Then copy smtp.example.json to smtp.json and fill in your YOUR_RESEND_API_KEY (the username is the literal string resend). The optional from-name and reply-to fields set the sender's display name and a reply address.

Optionally, use ImprovMX to forward incoming emails to a personal gmail account, then go to gmail's gear icon (upper right) -> See All Settings -> Accounts and Import -> Send mail as -> "Add another email address." Point that address's SMTP server at Resend (smtp.resend.com, port 587, username resend, your API key as the password) so replies you send from gmail are signed for your domain.

Captcha

To slow down bulk signups, News can show a reCAPTCHA v2 ("I'm not a robot") challenge on account creation. It is conditional: like Hacker News, the create-account form looks normal until an IP has created a few accounts in a day, after which submitting it returns a "Validation required." page with the checkbox. When no keys are configured the feature is off entirely, so this is optional.

Create a key pair, then copy recaptcha.example.json to recaptcha.json and fill in the site-key and secret. threshold is how many accounts one IP may create per day before the captcha kicks in (default 2). The file is re-read on each request, so changes take effect without restarting the server.

Important: use a legacy key, not a Cloud/Enterprise one. News verifies tokens against the classic endpoint https://www.google.com/recaptcha/api/siteverify, which Google's newer reCAPTCHA (created in the Google Cloud console) no longer supports. Create the key from the classic admin instead: google.com/recaptcha/admin/create -> reCAPTCHA v2 -> "I'm not a robot" Checkbox. Add localhost and 127.0.0.1 to the key's allowed domains for local testing, plus your real domain for production.

You can check whether a secret works with the classic endpoint without a browser; a working legacy key rejects a bogus token with invalid-input-response, while a migrated key returns a Migrate your key ... error:

curl -sS -X POST https://www.google.com/recaptcha/api/siteverify \
  --data-urlencode "secret=YOUR_SECRET" --data-urlencode "response=test"

The keys may also be supplied via the RECAPTCHA_SITE_KEY, RECAPTCHA_SECRET, and RECAPTCHA_THRESHOLD environment variables, which override recaptcha.json. To exercise the flow locally without making real accounts, start the server with RECAPTCHA_THRESHOLD=0 so every signup attempt is challenged.

Customizing News

Change the variables at the top of news.arc.

Importing HN's front page

There's a built-in scraper that fetches the current Hacker News front page (and its comment trees, including flagged / dead / collapsed comments the official API doesn't expose) and imports it into your local News. See scrape.md for the full how-to.

You'll need a Hacker News account to log in to HN with -- the scraper needs a session to see flagged/dead content. An ordinary user account is fine, but create a fresh one for this purpose rather than using your real account, and turn showdead on in its preferences (so the HTML the scraper fetches includes dead comments). Put the username into scrape.json (copied from scrape.example.json on first run); the password is read at login time from HN_SCRAPER_PASSWORD, the password field of scrape.json, or an interactive prompt.

Performance tuning

(= static-max-age* 7200)    ; browsers can cache static files for 7200 sec

(= autoreload* t)           ; reload code changes without restarting

(declare 'explicit-flush t) ; you take responsibility for flushing output
                            ; (all existing news code already does)

Layout

  • arc0.lisp — Arc runtime for Common Lisp (port of ac.scm)
  • boot.lisp — script entry point loaded via sbcl --script; loads arc0.lisp, then either runs each given Arc file and exits, or drops into the Arc REPL when no files are given (analogue of arc3.2/as.scm)
  • sharc — thin shell wrapper: exec sbcl --script boot.lisp "$@"
  • arc.arc, libs.arc, strings.arc, code.arc, html.arc, pprint.arc, srv.arc, app.arc, prompt.arc — Arc itself, built on top of arc0
  • news.arc, blog.arc — the News and Blog applications
  • scrape.arc, json.arc — HN front-page scraper and JSON support (see scrape.md)
  • static/ — static assets served by srv.arc
  • test.arc — Arc test suite

Development history

The port was built incrementally; each step is recorded as a handoff note in docs/agents/handoff/, starting with 2026-04-25-001-arc0-port.md. Read those in order if you want to see how arc0 was bootstrapped, what broke along the way, and how each fix was reasoned through.

License

Copyright (c) Paul Graham and Robert Morris. Released under the MIT License with Paul Graham's permission. See copyright.

Acknowledgements

Thanks to Daniel Gackle aka "dang" for coming up with the name Sharc, and for answering dozens of emails over many months regarding HN and Clarc.

Contributors

shawwn

1,181 commits

shawwn/sharc

An Arc-to-Common-Lisp port of Arc and the News web app that powers Hacker News.

3

stars

1,181

commits

Common Lisp

primary language

Sep 2, 2026

updated

README

sharc

An Arc-to-Common-Lisp port of Arc and the News web app that powers Hacker News.

image

In September 2024, Hacker News migrated from Arc-on-Racket to Arc on SBCL using a compiler called Clarc that dang had been developing for years. The port lets HN run on multiple cores and was fast enough to retire pagination on long threads. See the announcement thread and Vincent Massol's write-up.

This repository is an independent open-source Arc-on-Common-Lisp runtime in the same spirit. It boots arc0.lisp (a port of Arc's ac.scm) under SBCL and then loads arc.arc and the rest of Arc on top of it, so News and other Arc programs run unmodified.

Requirements

You'll need SBCL installed (brew install sbcl on macOS, apt install sbcl on Debian/Ubuntu).

Running the tests

./test.arc

test.arc is adapted from lumen's test suite plus extra cases added during the port. A clean run prints something like 193 passed, 0 failed.

Running News

mkdir -p arc
echo "myname" > arc/admins
export DEV=t # reload code changes without needing to restart
./sharc news.arc # prepend with `rlwrap` for repl history

Then go to http://localhost:8080.

Click on login and create an account called myname. You should now be logged in as an admin.

Set DEV=t (or run (set autoreload*) in the repl) to automatically reload code changes without restarting the server.

For production deployments, instead of autoreload, you can manually git pull and then run (reload) in the repl to ship an update. (You could use autoreload in production, but then each request is slightly slower since it has to check the modification times of every arc file.)

How I run News

My daily driver for News is:

DEV=t lwrap ./sharc news.arc

lwrap is a wrapper over rlwrap that makes it nicer for lisp repl work. ("lisp-wrap.") Just put it in your PATH, install rlwrap, and you're good to go.

Email

News sends password reset emails via Resend's SMTP relay. Ask Claude how to set it up for your own domain, or follow Resend's guides: verify a sending domain, create an API key, and send with SMTP.

Then copy smtp.example.json to smtp.json and fill in your YOUR_RESEND_API_KEY (the username is the literal string resend). The optional from-name and reply-to fields set the sender's display name and a reply address.

Optionally, use ImprovMX to forward incoming emails to a personal gmail account, then go to gmail's gear icon (upper right) -> See All Settings -> Accounts and Import -> Send mail as -> "Add another email address." Point that address's SMTP server at Resend (smtp.resend.com, port 587, username resend, your API key as the password) so replies you send from gmail are signed for your domain.

Captcha

To slow down bulk signups, News can show a reCAPTCHA v2 ("I'm not a robot") challenge on account creation. It is conditional: like Hacker News, the create-account form looks normal until an IP has created a few accounts in a day, after which submitting it returns a "Validation required." page with the checkbox. When no keys are configured the feature is off entirely, so this is optional.

Create a key pair, then copy recaptcha.example.json to recaptcha.json and fill in the site-key and secret. threshold is how many accounts one IP may create per day before the captcha kicks in (default 2). The file is re-read on each request, so changes take effect without restarting the server.

Important: use a legacy key, not a Cloud/Enterprise one. News verifies tokens against the classic endpoint https://www.google.com/recaptcha/api/siteverify, which Google's newer reCAPTCHA (created in the Google Cloud console) no longer supports. Create the key from the classic admin instead: google.com/recaptcha/admin/create -> reCAPTCHA v2 -> "I'm not a robot" Checkbox. Add localhost and 127.0.0.1 to the key's allowed domains for local testing, plus your real domain for production.

You can check whether a secret works with the classic endpoint without a browser; a working legacy key rejects a bogus token with invalid-input-response, while a migrated key returns a Migrate your key ... error:

curl -sS -X POST https://www.google.com/recaptcha/api/siteverify \
  --data-urlencode "secret=YOUR_SECRET" --data-urlencode "response=test"

The keys may also be supplied via the RECAPTCHA_SITE_KEY, RECAPTCHA_SECRET, and RECAPTCHA_THRESHOLD environment variables, which override recaptcha.json. To exercise the flow locally without making real accounts, start the server with RECAPTCHA_THRESHOLD=0 so every signup attempt is challenged.

Customizing News

Change the variables at the top of news.arc.

Importing HN's front page

There's a built-in scraper that fetches the current Hacker News front page (and its comment trees, including flagged / dead / collapsed comments the official API doesn't expose) and imports it into your local News. See scrape.md for the full how-to.

You'll need a Hacker News account to log in to HN with -- the scraper needs a session to see flagged/dead content. An ordinary user account is fine, but create a fresh one for this purpose rather than using your real account, and turn showdead on in its preferences (so the HTML the scraper fetches includes dead comments). Put the username into scrape.json (copied from scrape.example.json on first run); the password is read at login time from HN_SCRAPER_PASSWORD, the password field of scrape.json, or an interactive prompt.

Performance tuning

(= static-max-age* 7200)    ; browsers can cache static files for 7200 sec

(= autoreload* t)           ; reload code changes without restarting

(declare 'explicit-flush t) ; you take responsibility for flushing output
                            ; (all existing news code already does)

Layout

  • arc0.lisp — Arc runtime for Common Lisp (port of ac.scm)
  • boot.lisp — script entry point loaded via sbcl --script; loads arc0.lisp, then either runs each given Arc file and exits, or drops into the Arc REPL when no files are given (analogue of arc3.2/as.scm)
  • sharc — thin shell wrapper: exec sbcl --script boot.lisp "$@"
  • arc.arc, libs.arc, strings.arc, code.arc, html.arc, pprint.arc, srv.arc, app.arc, prompt.arc — Arc itself, built on top of arc0
  • news.arc, blog.arc — the News and Blog applications
  • scrape.arc, json.arc — HN front-page scraper and JSON support (see scrape.md)
  • static/ — static assets served by srv.arc
  • test.arc — Arc test suite

Development history

The port was built incrementally; each step is recorded as a handoff note in docs/agents/handoff/, starting with 2026-04-25-001-arc0-port.md. Read those in order if you want to see how arc0 was bootstrapped, what broke along the way, and how each fix was reasoned through.

License

Copyright (c) Paul Graham and Robert Morris. Released under the MIT License with Paul Graham's permission. See copyright.

Acknowledgements

Thanks to Daniel Gackle aka "dang" for coming up with the name Sharc, and for answering dozens of emails over many months regarding HN and Clarc.

Contributors

shawwn

1,181 commits

Languages

Common Lisp

86.0%

Arc

12.2%