Updates §7.2, §7.3, §12 to reflect the actual architecture from
aiolabs/lnbits#9 (reframed since the earlier commit) and #18 (the
concrete phase 2 bunker integration using nsecbunkerd).
Three shifts:
- LocalSigner demoted to transitional/migration helper. RemoteBunker
Signer is the steady state for every bound user. New accounts MUST
NOT default to LocalSigner. Earlier framing treated them as
equivalent choices — they're not.
- Binding artifact is a per-device NIP-46 connection token with
scoped permissions, not just a (mxid → user_id) mapping row. Calls
out the security property: compromise of one client device
(tracker, ATM, webapp) leaks only that token's scope, not the
user's full identity. Revocation is one RPC at the bunker.
- §12 redrawn around the operator-IdP-with-sidecar-bunker pattern.
Names nsecbunkerd as the canonical bunker for the aiolabs ref
impl, points at #9 + #18 for the LNbits side. Pattern is reusable
beyond LNbits — any operator providing identity-as-a-service can
run this shape.
NIP-26 explicitly out (Nostr ecosystem has deprecated; NIP-46 covers
the use case). §11 open questions trimmed accordingly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three connected updates triggered by aiolabs/lnbits#9 (in-progress
hardening of user Nostr keypair storage with NIP-46 bunker support):
§7.2 expanded — per-user signing is no longer abstract "future
work". Defines the 4-implementation signer abstraction (BotSigner /
LocalSigner / RemoteBunkerSigner / ClientSideOnlySigner) matching
lnbits#9's design. MUST design plugins with this abstraction even
when v1 only ships BotSigner — Phase 2c plugs in user signers
without refactor.
§7.3 new — external-identity binding. Any system that can answer
"for chat handle X, what signer should I use?" works. LNbits is the
reference identity provider but not the only valid one. Binding MUST
be opt-in, verifiable, revocable.
§11 + §12 updated — open question on bunker UX folds into the new
sections; reference identity provider added to §12 with pointer to
lnbits#9.
Spec stays runtime-agnostic — LNbits is one valid provider, not
mandatory. Communities without an existing Nostr identity stack stay
on v1 BotSigner indefinitely.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New maubot plugin that points at any Quartz-rendered docs site and
answers chat queries by full-text searching its emitted
/static/contentIndex.json. Default config targets docs.ariege.io
(castle-docs).
Commands:
!ask <query> search corpus; top-N hits with snippet + link
!doc <slug-or-title> open a specific page (fuzzy title match)
!wiki / !wiki refresh status / force re-index
Architecture:
- Periodic fetch (default 10 min) of /static/contentIndex.json
- In-memory inverted-ish scoring: title hit 5pt, content hit 1pt + freq
- No LLM — pure deterministic keyword search; RAG is future Phase 2b
- No DB — index is upstream-derived cache, repopulates on bot restart
Deployment posture: docs.ariege.io is served from cfaun alongside
maubot, so the bot hits it over the host's internal network — works
during WAN outages. base-config.yaml exposes docs_url + index_path
for adopters pointing at their own site.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sibling to journal/, implements the Community Organizer spec
(docs/community-organizer-spec.md) over maubot:
!add <text> freeform inbox capture; rules classify
!task <text> [#tag…] explicit task
!sidequest [#tag…] optional / passion-project item
!remind in <N>(s|m|h|d) <text> chat-side timed reminder
!done <id> close task or sidequest
!list [type] query open items
!setup add/remove per-room shortcut config
One @command.passive dispatcher routes universal verbs to handlers
and unknown verbs through the per-room shortcut table. Avoids the
multi-line @command.new footgun (per ~/dev/CLAUDE.md) and lets
shortcuts coexist with universal verbs without decorator priority
games.
Rules classifier (classify.py) is intentionally conservative — only
buckets on clear shapes (buy keywords, past-tense markers, todo
intent, remind prefixes); ambiguous capture lands in `!list inbox`.
LLM fallback is Phase 2b on a dedicated inference node.
Reminders (scheduler.py) replay from DB on bot start so restarts
don't lose pending timers; missed-while-down stay open for query.
`nostr_event_id` column reserved for Phase 2a — Matrix-local only
for now.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two vocabulary additions from real Ariège use:
- !sidequest as a distinct kind alongside task/journal/remind.
Same lifecycle as tasks but renderers surface separately so the
"wouldn't it be cool to…" backlog doesn't clutter day-to-day
operations.
- 5-level priority scale (urgent/crucial/important/future/
frequent-ongoing) mapping to RFC 5545 VTODO PRIORITY 1/3/5/7/9.
Level 5 is a v1 shorthand for recurring obligations until a
proper RRULE-equivalent lands; flagged in §11 open questions.
Per-community priority shortcuts (!urgent, !chores) reuse the
existing per-community shortcut mechanism — no special-casing.
Adds worked examples §8.2a (sidequest+priority) and §8.2b
(priority shortcuts + frequent/ongoing semantics).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The spec is runtime-agnostic — what matters is conformant events per
§4 and community scoping per §5. Adopters who want a richer agent
layer than a focused Matrix bot can satisfy the spec on top of
ZeroClaw (Rust, Apache/MIT, ships Matrix + Nostr channels + Ollama
provider). Renderers filter by community a-tag, not by client tag,
so multi-runtime ecosystems work without coordination.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Defines the vocabulary, NIP-52 event shapes, NIP-72 community model,
and lifecycle for a chat-captured + Nostr-stored community organizer
spanning the `tracker` maubot plugin (forthcoming) and renderers like
inky-impression.
Reuses existing standards (RFC 5545 VTODO, NIP-52, NIP-72,
ActivityStreams vocab) instead of inventing new event kinds, so other
communities can adopt the same shape and renderers interop across
implementations. Spec lands before any plugin code so the contract
isn't an after-the-fact derivation from the implementation.
CLAUDE.md + README now point at the spec as the source of truth for
verb/event/tag changes — future sessions update the spec first, not
the plugin code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Points sessions opened here at the repo's purpose, layout, build
loop, and where to find related context (per-plugin READMEs,
~/dev/CLAUDE.md for maubot patterns, server-deploy for daemon
configuration). Establishes this repo as the canonical place for
plugin code so server-deploy sessions don't accidentally edit it
from across the workspace boundary.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root README orients new contributors on the build/upload/iterate
loop and points at ~/dev/CLAUDE.md for maubot patterns. journal/
README covers the three commands, the SQLite schema, known
quirks (edits don't re-trigger, subcommand detection scope), and
documents why this plugin uses @command.passive instead of the
more obvious @command.new.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`@command.new` silently drops `!journal\n<content>` because
maubot's parser only treats a *space* as the command/args
delimiter — when a newline immediately follows the command
name, the parser fails to recognise the command at all (no
handler invoked, no error). Real users WILL paste:
!journal
- thing one
- thing two
and lose the entry without any feedback.
Switching to `@command.passive` with a regex that admits
[ \t\r\n] as the delimiter catches every form. Subcommand
dispatch (show/today) moves into the handler body — small
loss of decorator ergonomics, big gain in robustness for the
dominant use case (freeform multi-line entries).
Bumped to 0.2.0 since the structural change warrants a minor
bump (not a fix-level patch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Records what people did each day, scoped per-room/sender/timestamp.
Stored in maubot's per-instance SQLite plugin DB.
Commands:
- !journal <text> record an entry
- !journal show [@u] last 10 entries, optionally filtered by user
- !journal today everything logged today (UTC)
The command shape requires @command.new(arg_fallthrough=False) so
the parent's pass_raw=True `text` argument doesn't swallow
subcommand keywords like "show" and "today" — same pattern the
upstream maubot reminder plugin uses.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>