Skip to content
Obserf

Open source · local-first · CLI

Hundreds of results. A handful worth your reply.

Obserf searches Hacker News, Reddit, GitHub and Brave for public conversations where your developer tool is a genuine answer, ranks the few that survive, and drafts what to say.

Then it stops. You read the thread, check the venue's rules, edit the draft, and post it from your own account. There is no obserf post.

$ bunx @obserf/cli init

Needs Bun 1.4 or later and Claude Code signed in with a subscription — no Anthropic API key, and no Obserf account. A scan spends the same Claude Code limits your own sessions do. Obserf runs on Bun itself: there is no Node build.

  • hn — Algolia full-text search over stories and comments
  • reddit — where “what should I use” gets asked
  • github — issue threads, and awesome-* lists
  • brave — everything with no API of its own
  • up to four sources, one project

One scan, illustrated

dropped in code118 duplicate63 stale31 blocked24 thin+3 more rules

312
candidates
what the queries returned
41
assessed
past the gates, sent to the model with evidence
6
worth reading
scored above zero — best 71
Illustrative proportions — the shape of a scan, not a run anyone performed. The rule names and the summary line are the CLI's own.

The gate

Finding them is easy. Reading them is what costs you.

A good query returns hundreds of pages. Almost all of them are a competitor's content marketing, a question settled three years ago, or a forum where a maintainer linking their own work gets banned for it. Sorting them takes longer than the good ones are worth.

Obserf rejects most of that in code, before a model reads anything. Each rule encodes a fact, and every rejection is counted and named in the output.

Rejected in code, by rule

271 of 312 · illustrative

  1. 118duplicateThe same URL, twice in one scan.
  2. 22settledYou dismissed it, or you already acted on it.
  3. 31blockedA host you excluded — your own pages first of all.
  4. 63stalePublished past the age limit. A question answered in 2019 is not a place to add a link.
  5. 24thinToo little text to judge. A title and nine words is not evidence.
  6. 9unchangedSeen before, nothing new since, and the cooldown has not run out.
  7. 4ruled-outThe model disqualified it recently enough for that to still hold.

41

assessed — everything the rules could not reject on a fact, and the only candidates a model is ever asked to read.

No rule here judges relevance. A keyword pre-filter would reject exactly the paraphrased, high-intent questions that are worth the most.

The pipeline

Five steps of code around one step of judgment.

The model is never asked whether a search snippet is a marketing opportunity. By the time it is asked anything, code has rejected what it can prove does not qualify and fetched the evidence for what is left.

code
deterministic, reproducible, no model calls
assess
the one step that judges anything
  1. discover

    codeFour adapters run the queries in your project profile and return a URL, a title, an excerpt and a date — with the thread's points and comments where the source reports them. Missing credentials skip a source with a reason rather than failing the scan.

  2. gate

    codeSeven rules reject on facts — seen before, too old, too thin, already settled, excluded host. Never on relevance: that judgment is what the model is for.

  3. enrich

    codeOnly survivors earn more requests. A curated list's stars say nothing about whether it merges what strangers send, so GitHub spends three requests on a year of its pull requests — merged, closed unmerged, still open.

  4. assess

    Model

    Claude reads that evidence and your profile, then rates relevance, intent, welcome and reach from 0 to 5 and gives one sentence of reasoning. It has no tools: it cannot get what the stages above did not.

    In
    The candidate's text, the engagement or pull-request facts that were fetched, and your project profile.
    Out
    Four ratings from 0 to 5, one sentence naming the deciding factor — and no score.
  5. score

    code0.35·relevance + 0.30·intent + 0.20·welcome + 0.15·reach, halved every thirty days of age and floored at 0.15. Three hard zeros — no relevance, no welcome, disqualified — end it outright. Listings never decay.

  6. store

    codeOne SQLite file in your workspace. Your triage decisions and notes go in and never come out.

A finding

What one survivor looks like.

This is the whole product surface: a link, the evidence it was judged on, four ratings, one sentence of reasoning, and a draft you are expected to rewrite.

news.ycombinator.com · hn · status: new

Ask HN: how do you keep track of where to talk about your side project?

Evidence — fetched, not inferred

points
214
comments
96
published
6d ago

Excerpt, as the adapter returned it

I have a side project that genuinely helps with a problem people ask about, and every few weeks I find a thread where it would have been the right answer — a month late. Searching manually turns up mostly listicles, and by the time I have read enough of them to find the real question I have lost the hour I set aside. I do not want to spam anyone, and I do not want a tool that posts for me. How do you keep track of where the conversation actually is?

This, capped at 2,000 characters, plus your project profile, is what the model is handed. It has no tools — it cannot open the thread or read the venue's rules, so it judges this or nothing.

Assessment — the model's judgment

relevance
4 of 5
Is this about the problem the project solves?
intent
5 of 5
Is someone looking for an answer now?
welcome
3 of 5
Would a mention be welcome under this venue's norms?
reach
4 of 5
Will anyone actually read it?
opportunity
question
Which of five shapes this is, and so which kind of draft it takes.

Asks for a process rather than for product recommendations, and several answers describe doing this by hand — but nothing in the evidence establishes how this venue treats self-promotion, which is why welcome stays at 3.

Score, computed in code from the four above

71

0.35·4 + 0.3·5 + 0.2·3 + 0.15·4 = 4.10 / 5 = 0.82
× 0.87 freshness (6 days old) × 100 = 71

Draft (reply) — yours to rewrite

The queries matter more than the tooling. Write three or four that surface people describing the problem in their own words rather than people reviewing tools — the second kind is almost always content marketing. Keep them in a file and re-run them monthly.

I maintain a CLI that does this triage on top of that. The query list is the part doing the work, and it costs nothing to keep by hand.

Review it, edit it, and post it yourself. Obserf never posts.

The ratings are the model's. The score is arithmetic over them, done in code — reproducible, and not the model's opinion of its own confidence. The working is beside it; check it.

An invented thread, showing the shape of a finding. The example makes no claim about anyone's results.

The last step

The machine narrows. You decide.

Obserf can

  • find the thread, in four places you were not reading
  • say in one sentence why it matched, on evidence it can show you
  • write a reply that is useful with the link taken out

Only you can

  • read the venue's rules and decide it deserves a reply
  • rewrite the draft until it sounds like you wrote it
  • post it, from your own account, under your own name

A missed opportunity costs one comment nobody read. A wrong one is public, under your name, in the exact community the project was trying to reach. The upside is bounded. The downside is not. That is enough to decide it.The longer argument →

The command surface, in full

  • scandiscover, gate, enrich, assess, store
  • listthe ranked inbox, best first
  • showone finding, with its evidence and its drafts
  • draftwrite the comment, reply or submission
  • triagenew · shortlisted · dismissed · acted
  • servethe same inbox in a browser, on 127.0.0.1
  • rescorerecompute scores with no model calls
  • runswhat each scan ran, dropped, and spent
  • projectswhat the workspace has profiles for
  • initscaffold a workspace here
  • backupsnapshot the database, and say where
  • backupslist this database's snapshots
  • restoreput a snapshot back
  • postdoes not exist, and is not a setting. Posting would be a new code path — the one nobody is going to write.

Where things live

Local, and specific about what is not.

Not “we don’t look at your data” — there is nowhere for us to look. What does leave your machine leaves because you ran a command, and it goes to the provider it was always going to go to.

“Local-first” is a claim about where the database and the decisions live, not a claim that nothing ever leaves: a scan sends your queries to up to four search APIs and the survivors to a model.

  1. your project profileyours

    projects/example.ts — queries, pitch, what the project is not for

  2. obserf, on your machineobserf

    discover · gate · enrich · assess · score · store

    • Leaves the machine here, and only here
    • search queries → hn · reddit · github · brave
    • gate survivors, their evidence and your profile text → Claude Code, to be assessed
    • the thread's text, re-fetched where it can be, and your profile → Claude Code, when you ask for a draft

    an Obserf serverdoes not exist — there is no other end of the connection, and no account to have one with.

  3. .obserf/obserf.dbfile

    every candidate, score, note and draft — one SQLite file, never leaves

  4. obserf list · obserf serveobserf

    the ranked inbox, in your terminal or on 127.0.0.1

  5. youhuman

    read the thread, edit the draft, post it yourself

What the workspace holds

obserf.config.ts
the marker that makes this a workspace
projects/*.ts
your profiles — you write these, you commit these
.env
search credentials, read by Bun, never in a prompt
.obserf/
the database and its snapshots — git-ignored, as is .env

Credentials authenticate the search requests to the providers they belong to and never enter a model prompt. Model calls go through Claude Code on your own subscription, whose network behaviour is its own — the honest limit of what “local” claims here.

Boundaries

The things it will not do are the design.

Each of these is a feature someone will eventually ask for. They are absent on purpose.

No audience discovery
Your positioning is an input, not an output. If the project cannot yet say who it is not for, Obserf amplifies that gap rather than closing it.
No model-side lookup
The model sees what was fetched and your project profile, and it has no tools to fetch more. Its rubric caps a venue whose rules it cannot see at “plausibly fine”, and you are the one who checks.
Nothing from Slack, Discord or email
Four adapters, and none of them reads a chat. If your users talk somewhere Obserf cannot search, there is nothing for it to rank.
No volume charts, no sentiment, no share of voice, no analytics for your own pages
No contacts, no cadence, no pipeline stages
No Obserf account, and no second operator

What is left is small: a local tool that finds the few public conversations where a reply from you could help, and stops there.

Get it

One directory, one file, a few commands.

The whole engine is small and meant to be read — the pipeline order, the gate rules and the scoring weights are code, not configuration. If you want them different, fork it.

  1. 01

    Scaffold a workspace

    $ bunx @obserf/cli init ~/dev/marketing
    Workspace created in /Users/you/dev/marketing
      + obserf.config.ts
      + projects/example.ts
      + .env.example
      + .gitignore
      + package.json
      + tsconfig.json
  2. 02

    Describe one project

    $ cd ~/dev/marketing && bun install$ $EDITOR projects/example.ts

    projects/example.ts — excerpt

    export default defineProject({
      key: "example",
      solves: ["Schema changes you have to read before they run"],
      notFor: ["Teams that want migrations applied on deploy"],
      queries: {
        search: ["postgres migration tool that shows the sql"],
        subreddits: ["PostgreSQL", "devops"],
      },
    });

    The scaffold is already valid — you replace its project truth. What it is not for does the most work: that is what stops a plausible, confident, wrong match.

  3. 03

    See what a scan would cost

    $ bun run obserf scan --project example --dry-run

    Illustrative output

      312 candidates → 41 would be assessed (dropped: 118 duplicate,
      22 settled, 31 blocked, 63 stale, 24 thin, 9 unchanged, 4 ruled-out)

    A dry run makes no model calls and writes nothing, though the searches still run. Tune the queries here, before spending model quota.

  4. 04

    Run it, and read what survived

    $ bun run obserf scan --project example$ bun run obserf list
  5. Illustrative inbox — all six findings

    1. 71#128Ask HN: how do you keep track of where to talk…
    2. 64#131awesome-devtools — accepting submissions
    3. 58#133What do you use to find where to post about…
    4. 44#137Best approach for monitoring niche communities?
    5. 29#140Show HN: I built a thing for indie maintainers
    6. 16#142Weekly thread: what are you working on?

    Six rows, ranked. You open the first one, read the thread, and decide.