No description
  • Rust 99.5%
  • Shell 0.4%
Find a file
meganvw 282a642b2f
Some checks failed
Binaries / package (aarch64, macos-latest, apple-darwin) (push) Has been cancelled
Binaries / package (aarch64, ubuntu-latest, unknown-linux-gnu) (push) Has been cancelled
Binaries / package (x86_64, macos-latest, apple-darwin) (push) Has been cancelled
Binaries / package (x86_64, ubuntu-latest, unknown-linux-musl) (push) Has been cancelled
Binaries / package (x86_64, windows-latest, pc-windows-msvc) (push) Has been cancelled
CI / test (macos-latest) (push) Has been cancelled
CI / test (ubuntu-latest) (push) Has been cancelled
CI / test (windows-latest) (push) Has been cancelled
CI / Flake checks ❄️ (push) Has been cancelled
CI / Flake checks ❄️-1 (push) Has been cancelled
ipc work
2026-04-13 08:08:49 -07:00
.github Use cargo crane in Nix flake and set up cachix action (#539) 2025-10-25 22:44:19 +00:00
crates ipc work 2026-04-13 08:08:49 -07:00
docs ipc work 2026-04-13 08:08:49 -07:00
scripts release script 2026-03-24 16:08:37 -07:00
src checkpoint 2026-04-11 01:40:00 -07:00
.envrc Enable direnv for Nix flakes (#183) 2023-12-19 00:53:17 +00:00
.gitattributes Add an icon for iamb (#232) 2024-03-28 16:20:27 +00:00
.gitignore improve the backlog docs 2026-04-01 22:54:43 -07:00
.rustfmt.toml Fix rustfmt warning (#523) 2025-10-25 12:55:23 -07:00
AGENTS.md ipc overhaul 2026-03-25 11:44:21 -07:00
archive-issues.log fix drafts 2026-04-07 00:24:50 -07:00
backlog.md checkpoint 2026-04-11 01:40:00 -07:00
build.rs rework for dependency graph 2026-03-24 16:08:26 -07:00
build.sh sort modes 2026-03-24 16:08:35 -07:00
Cargo.lock checkpoint for testing framework improvements 2026-04-12 00:03:13 -07:00
Cargo.toml fix archiver regression 2026-04-03 06:35:15 -07:00
clippy.toml refactor for cli commands with ipc to tui 2026-01-23 11:41:29 -08:00
config.example.toml docs 2026-03-27 15:24:49 -07:00
contain.sh pinning and joining 2026-03-24 16:08:33 -07:00
CONTRIBUTING.md Support sending and displaying typing notifications (#9) 2023-01-03 13:57:28 -08:00
errors.log backlog 2026-04-12 17:15:29 -07:00
flake.lock Use cargo crane in Nix flake and set up cachix action (#539) 2025-10-25 22:44:19 +00:00
flake.nix Fix CI on main branch (#545) 2025-10-26 07:44:38 -07:00
iamb.desktop Add an icon for iamb (#232) 2024-03-28 16:20:27 +00:00
LICENSE Fix LICENSE file (#274) 2024-04-24 06:59:00 +00:00
PACKAGING.md rework for dependency graph 2026-03-24 16:08:26 -07:00
README.md fix drafts 2026-04-07 00:24:50 -07:00
ROADMAP.md roadmap 2026-03-24 16:08:25 -07:00
rust-toolchain.toml sending and backlog 2026-03-30 11:37:42 -07:00
todo.md checkpoint 2026-04-11 01:40:00 -07:00

Repository

About

iamb is a terminal Matrix client with Vim-style interaction, a full-screen TUI, and a growing automation-oriented CLI.

It supports:

  • E2EE, threads, spaces, replies, edits, reactions, read receipts, and mentions
  • Room, DM, invite, unread, and session workflows from both the UI and CLI
  • Archive/listen/download tooling for history, attachments, external media, hooks, and postback flows
  • Custom keybindings, multiple profiles, image previews, notifications, and configurable sorting

This repository is maintained at https://codeberg.org/microchipster/iamb.

Documentation

Prefer the built-in help first: iamb --help and iamb <command> --help stay closest to the code.

Use this README for project overview, installation, and a few higher-level workflows. Use docs/iamb.1 and docs/iamb.5 for concise man-page style references.

IPC and daemon model

iamb can reuse an authenticated Matrix client over IPC so CLI commands do not need to perform a fresh login or sync every time.

  • The UI and iamb daemon expose a per-profile Unix socket unless --no-ipc is set.
  • CLI commands first try that socket; if a daemon is reachable, the command is executed there and reuses the warm session.
  • If no daemon is available, the command falls back to local execution.
  • Socket lookup order is $XDG_RUNTIME_DIR/iamb/<profile>.sock, then $TMPDIR/iamb/<profile>.sock, then /tmp/iamb/<profile>.sock.
  • --ipc-socket <PATH> overrides the resolved socket path for both client and server.

This makes one-off commands much faster in normal use and keeps helpfully stateful operations, such as room lookups and uploads, consistent with the running UI session.

Useful CLI workflows

The CLI is meant for scripting, maintenance, and headless usage, not just account bootstrap.

  • iamb rooms <query> fuzzy-searches joined rooms; use . to list all rooms and --user <USER> to require specific members.
  • iamb rooms, iamb public-rooms, iamb users, and iamb status all support --format json when you want a stable automation surface instead of text output.
  • iamb spaces <query> fuzzy-searches joined spaces.
  • iamb room info <room> prints room diagnostics; iamb room versions lists locally known rooms by version in TSV form; iamb room threads <room> lists thread roots; iamb room uploads <room> lists recent uploads.
  • iamb dm @user:server creates or reuses a direct message room, and iamb public-rooms <query> / iamb public-rooms --server <server> <query> / iamb public-rooms --known-servers <query> / iamb users discoverable <query> / iamb users shared <query> / iamb users rooms <@user:server> / iamb users whois <@user:server> expose directory and joined-room user queries without opening the UI. Explicit public-room --server queries are now remembered per profile and folded into later --known-servers runs. Shared-user queries now include cached presence when it is available locally.
  • iamb invites lists pending invites; add --accept, --reject, or --picker for bulk handling.
  • iamb unreads lists unread rooms/messages and can clear them with public receipts, private receipts, or no receipt.
  • iamb mark-unread <room-id>... marks rooms unread locally; --picker opens an interactive selector.
  • iamb send, iamb upload, iamb create-room, iamb room-setting, iamb upgrade, iamb leave, iamb sessions, and iamb change-password cover common operational tasks without opening the TUI.
  • iamb media, iamb archive, iamb listen, iamb download, iamb postback, iamb search, and iamb redact support archival, media, and cleanup workflows.

TUI quick reference

The interactive : command set is broader than the short examples in this README. The most useful discovery-oriented commands are:

Press <Tab> in the command bar to complete command names, common subcommands, Matrix IDs, room aliases, file paths, and emoji shortcodes in the main flows such as :invite send, :join, :room ..., :space child ..., :upload, and :react. Command and search history are now persisted per profile too, so pressing Up/Down in the command bar can recall recent entries even after restart. In the message composer, typing a room alias like #room:server now sends a matrix.to permalink, and typing # then <Tab> inserts a known room alias without leaving the keyboard.

  • :rooms, :chats, and :dms accept optional sort modes, while :sort <mode> changes the active list ordering; the footer shows the current sort label.
  • :join <room> accepts room IDs, aliases, and matrix.to links; use :peek <room> to inspect a room before switching into a joined timeline. Shared matrix.to room/event links opened from messages now reuse the same internal join/navigation path instead of always launching a browser.
  • :public-rooms [query] and :users discoverable [query] [limit] / :users shared [query] / :users rooms <@user:server> / :users whois <@user:server> expose the public-room and user-query flows inside the TUI too. Shared-user output and member lists now surface cached presence when it is available locally.
  • :info opens the room report, and :pins / :pins open <n> let you browse pinned events and jump back into the timeline.
  • :markdown on enables one-message markdown authoring explicitly, while :markdown off reuses the plain-text send path for the next message only. Inline code and fenced code blocks now use stronger code styling so snippets stay visible.
  • :confetti and :snowfall now match the existing slash-command effects for the next message, so you can trigger those Matrix client-side visuals from the : command bar too.
  • :room show, :room avatar ..., :room encryption ..., :room retention ..., :room role ..., :room threshold ..., and :room admin show cover the room inspection and moderation surfaces that also exist under iamb room-setting.
  • :spaces! opens the hierarchical space tree, and :space child add / :space child remove ++pick help manage child rooms interactively.
  • :drafts, :status, :log, :reload, :initial-sync, and :perf are the main recovery/inspection commands when the UI needs a nudge or a quick health check. Drafts survive restart, rooms with saved drafts are marked with 📝 in list views, and :drafts opens the current saved-draft list. Use :log to see the active debug log path and :log flush to sync it to disk before grabbing it for support.

If you need the full command inventory in one place, prefer docs/iamb.1 or man ./docs/iamb.1.

iamb create-room provides a straightforward way to spin up a room with name/topic, directory visibility, invites, encryption, guest access, and join/history rule tweaks without dropping into the UI. Example:

iamb create-room --name "Project Workspace" --topic "Planning notes" \
  --visibility private --preset private-chat --invite @alice:example.com --invite @bob:example.com \
  --encrypted --guest-access --join-rule invite --history-visibility shared

iamb upgrade <room> upgrades the named joined room to the latest stable Matrix room version advertised by the homeserver. The command automatically restores the saved session or reuses the IPC daemon client, verifies m.room.tombstone permissions, calls /rooms/<room>/upgrade, and prints the replacement room ID so you can immediately follow the successor chain (or fall back to :upgrade follow after the tombstone is emitted). Rooms already running the preferred version simply report their current version. In the interactive UI you can run :upgrade latest (alias :upgrade current) to perform the same flow, show an in-place InfoMessage, and jump into the upgraded room while leaving the old room tombstoned so others can follow with :upgrade follow. :upgrade follow now makes the expectation explicit: it switches to the replacement room immediately when you are already joined, or joins it first if needed. Use :upgrade old to view the old version of an upgraded room. iamb room versions now adds predecessor/successor status and action columns so you can review which upgrade relationships are already joined locally before deciding what to follow next, and --predecessor-action / --successor-action let you filter that review output down to the rooms that still need a specific follow action.

The CLI flags map directly to the Matrix room creation API (preset, visibility, room_alias_name, join_rule, history_visibility, initial_state). Any of those settings can later be adjusted via iamb room-setting <room-id>.

Shell completions

iamb now ships with generated completions for every flag and subcommand. The scripts are emitted from the live clap command tree, so they track the real CLI surface instead of a hand-maintained copy. Install them by generating the script and dropping it in the right directory for your shell:

  • bash: mkdir -p ~/.config/iamb && iamb --completions bash > ~/.config/iamb/iamb.bash-completion. Add source ~/.config/iamb/iamb.bash-completion to your ~/.bashrc or drop the file into /etc/bash_completion.d/ if you prefer a system-wide install.
  • zsh: mkdir -p ~/.config/iamb && iamb --completions zsh > ~/.config/iamb/_iamb. Ensure ~/.config/iamb is on your fpath (e.g., fpath=(~/.config/iamb $fpath) inside ~/.zshrc), then run autoload -U compinit && compinit (or restart the shell) so the new completion becomes available.
  • fish: mkdir -p ~/.config/fish/completions && iamb --completions fish > ~/.config/fish/completions/iamb.fish. The shell auto-loads ~/.config/fish/completions/*.fish, so either restart or source ~/.config/fish/completions/iamb.fish.

Regenerate the outputs (iamb --completions <shell>) whenever the CLI gains new flags or subcommands.

Configuration

You can create a basic configuration in $CONFIG_DIR/iamb/config.toml that looks like:

[profiles."example.com"]
user_id = "@user:example.com"

If you homeserver is located on a different domain than the server part of the user_id and you don't have a /.well-known entry, then you can explicitly specify the homeserver URL to use:

[profiles."example.com"]
url = "https://example.com"
user_id = "@user:example.com"

To automatically log in without interactive prompts, point password_file at a local file containing the account password (the file is read every startup). Keep the file permission-restricted so only the owning user can read it.

Startup windows and layout

Startup behavior comes from the layout section plus settings.default_room:

  • layout.style = "restore" restores the last saved layout
  • layout.style = "new" opens settings.default_room when set, otherwise the welcome page
  • layout.style = "config" opens the exact tabs and splits you describe under [[layout.tabs]]

settings.default_room can point at a room, alias, DM user, or a special iamb window URI such as iamb://welcome, iamb://rooms, iamb://chats, or iamb://dms. If the configured target cannot be opened, iamb falls back to the welcome page.

To inspect the fully resolved configuration that the active profile is actually using, run iamb config current --format json or iamb config current --format toml.

Example fixed startup layout:

[settings]
default_room = "iamb://welcome"

[layout]
style = "config"

[[layout.tabs]]
window = "iamb://rooms"

[[layout.tabs]]
window = "iamb://chats"

[[layout.tabs]]
window = "iamb://welcome"

[[layout.tabs]]
split = [
    { window = "#iamb-users:0x.badd.cafe" },
    { window = "#iamb-dev:0x.badd.cafe" },
]

Low-memory profile

For very large or high-activity accounts, iamb now performs much better than it used to, but you can still trade some cache depth and prefetching for lower RAM usage.

The most useful knobs are:

  • sliding_sync: keep enabled unless you are debugging sync behavior
  • sliding_sync_active_timeline_limit: lower the active-room timeline depth
  • sliding_sync_all_timeline_limit: lower the background-room timeline depth
  • event_cache: keep enabled unless you are isolating event-cache problems
  • event_cache_clear_interval: lower values clear cached event data more often
  • log_rotate_size: cap each active log file before rotating it, using values like 1G or 100M
  • log_rotate_policy: choose whether rotated logs are kept in full (backup), kept as a warning/error-focused minified backup (backup-minified), or discarded (delete)
  • prefetch_recent_minutes: lower values reduce how much recent history is prefetched

Example low-memory profile:

[settings]
sliding_sync = true
sliding_sync_active_timeline_limit = 10
sliding_sync_all_timeline_limit = 2
event_cache = true
event_cache_clear_interval = 25
prefetch_recent_minutes = 30

Tradeoffs:

  • lower event_cache_clear_interval can reduce memory growth, but may increase cache churn
  • lower sliding-sync timeline limits reduce cached per-room timeline depth, which helps on large accounts but may slightly reduce how much recent history is instantly warm
  • lower prefetch_recent_minutes can reduce RAM significantly on busy accounts, but may make upward scrolling or unread catch-up feel a bit less warm
  • disabling sliding_sync or event_cache is not recommended as a first step; prefer tuning the two values above first

Responsiveness checks

For large or busy accounts, use :perf in the TUI after a burst of activity to inspect the current responsiveness snapshot. The report includes p50/p95/p99 input-to-draw latency, frame timing, action/lock timing, worker-event counters, and recent slow events.

If a room report or pinned-event view looks incomplete after first opening it, keep the room focused for a moment or run :initial-sync to force a catch-up pass.

Empty room recovery

If a room suddenly shows up as Empty Room after an upgrade or stale sync, iamb usually is not missing a dedicated feature; it is waiting for fresh room state to arrive again.

Practical recovery steps, in order:

  • keep the room focused briefly, then try :initial-sync to request a fresh catch-up pass for the current view
  • if the UI still looks stale, run :reload to reload configuration and related runtime state, then revisit the room
  • if you can safely do so, sending a new message in the room often forces the room to become visibly active again
  • profile changes made from another Matrix client, such as updating your display name or avatar, can also cause many rooms to refresh their visible state

Important caveats:

  • iamb does not currently promise a one-shot “repair this room” button for every stale-room case, so do not assume :reload or :initial-sync can reconstruct history the homeserver has not resent yet
  • avoid manually deleting iamb profile data or cache directories unless you understand the impact on local state and encryption material; export keys first with iamb keys export ... if you are considering destructive cleanup
  • :retrydecrypt / :clearcache help with undecryptable-message recovery, not general room-state resync

When validating responsiveness-sensitive changes locally, a good stress pass is:

  • let sync settle on a large account for 30-60 seconds
  • switch between busy rooms and list views
  • scroll media-rich history upward quickly
  • send a text message plus a file or clipboard image upload
  • run :perf and confirm p95 input->draw stays under 50ms and dropped worker events stay at 0

Archival utilities

listen

iamb listen streams timeline events into an archive directory suitable for later processing and inspection. It writes per-room NDJSON event files under events/, stores downloaded attachments and cached assets under media/, and keeps bookkeeping and job metadata in meta/ (for example synced markers, postback and failed URL logs). The listener collects in-room attachments and classifies external URLs, queuing jobs to fetch external media when appropriate.

Use --only-print-changes when you want long-running listener output to stay focused on new events and material activity while still showing warnings, errors, and the final summary.

External failures are appended to meta/failed-urls.ndjson; successful downloads that should be posted back are queued in meta/postback-queue.ndjson.

Usage example:

cargo run --bin iamb -- -P default listen --out ./archive

Custom bot commands (--command)

iamb listen can hand off each new message to an external helper by passing --command <program> alongside the rest of the listener invocation. The listener creates a small bot workspace inside the archive root (meta/bot/) and invokes your program like this for every decoded burst:

<program> <thread_id> <archive-root>/meta/bot/prompt.ndjson \
  --attachment <archive-root>/media/<room>/<file> --attachment <archive-root>/media/<room>/<file2> ...

thread_id is a deterministic alphanumeric identifier derived from the room plus the thread root event, so your helper can keep per-thread state in sync while it receives just the latest prompt. --prompt points at meta/bot/prompt.ndjson, which is newline-delimited JSON because the listener now waits roughly 2seconds after the first event before invoking your helper. This allows quick sequences (text after an image, multiple replies, etc.) to be bundled into one invocation. The shape of each entry is the same as before:

You can also pass --exclude-room <room_id> (repeatable) to keep specific rooms out of the listener and any bot invocations; persist the same list via archive.listener.excluded_rooms in your profile when you need the exclusion to survive across runs. Use --accept-invite-whitelist/--accept-invite-blacklist together with the archive.listener.invites.whitelist/archive.listener.invites.blacklist config arrays to teach the listener which invites should be accepted (room IDs and display names are matched against the supplied regexes).

{
  "room_id": "!room:example.com",
  "event_id": "$event:example.com",
  "sender": "@alice:example.com",
  "type": "m.room.message",
  "origin_server_ts": 1700000000000,
  "thread_root": "$thread:example.com",
  "content": {
    "msgtype": "m.text",
    "body": "Hello world"
  }
}

thread_root now always contains the root event that the conversation is happening in (it is only null for the very first message, which also becomes the thread root). The history files live under meta/bot/history and are named after the room and the percent-encoded thread root (<room>__main.ndjson vs <room>__thread__<event>.ndjson). Each history file is newline-delimited JSON that mixes previous incoming events and the replies that the helper already sent; the listener keeps that file trimmed to the most recent 512 lines so you can treat it as a rolling conversational context.

When a Matrix attachment is part of the batch, the helper receives one --attachment argument for each downloaded file (e.g. media/<room>/<file>). Any caption on that attachment already appears in the matching JSON entry's content.body; if there is no caption, the JSON line may be empty while the file is still supplied via --attachment. Your helper should parse every line in --prompt (it can contain multiple events) and inspect every --attachment path it receives, then write the text that should be sent back. Empty stdout or a non-zero exit code suppresses a reply, so send logs and diagnostics to stderr.

The snippet below illustrates a tiny standalone helper that reads the NDJSON prompt, enumerates the rolling history, reports the attachment paths, and prints a brief summary. It does not need to depend on the rest of iamb and can live in its own crate.

use clap::Parser;
use serde::Deserialize;
use std::{fs, path::PathBuf};

#[derive(Parser)]
struct Args {
    #[arg(long)]
    prompt: PathBuf,
    #[arg(long)]
    history: PathBuf,
    #[arg(long)]
    attachments: Vec<PathBuf>,
}

#[derive(Deserialize)]
struct EventContent {
    msgtype: String,
    body: String,
}

#[derive(Deserialize)]
struct EventLine {
    room_id: String,
    event_id: String,
    sender: String,
    #[serde(rename = "type")]
    kind: String,
    origin_server_ts: u64,
    thread_root: Option<String>,
    content: EventContent,
}

fn read_history(path: &PathBuf) -> Vec<EventLine> {
    fs::read_to_string(path)
        .ok()
        .into_iter()
        .flat_map(|text| text.lines().map(|line| serde_json::from_str(line)))
        .filter_map(Result::ok)
        .collect()
}

fn read_prompt(path: &PathBuf) -> Vec<EventLine> {
    fs::read_to_string(path)
        .ok()
        .into_iter()
        .flat_map(|text| text.lines().map(|line| serde_json::from_str(line)))
        .filter_map(Result::ok)
        .collect()
}

fn summarize_prompt(lines: &[EventLine]) -> String {
    if lines.is_empty() {
        "no recent events".to_string()
    } else {
        lines
            .iter()
            .map(|entry| format!("{}: {}", entry.sender, entry.content.body.trim()))
            .collect::<Vec<_>>()
            .join(" ⇢ ")
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args = Args::parse();
    let prompt = read_prompt(&args.prompt);
    let history = read_history(&args.history);
    let summary = summarize_prompt(&prompt);
    let attachments = args
        .attachments
        .iter()
        .map(|path| path.display().to_string())
        .collect::<Vec<_>>()
        .join(", ");
    println!(
        "Prompt {} entries · history {} entries · attachments [{}]\nSummary: {}",
        prompt.len(),
        history.len(),
        attachments,
        summary
    );
    Ok(())
}

Build this helper with cargo build --bin bot (or your preferred toolchain) and pass the resulting binary to iamb listen --command. Every time the listener encounters a fresh burst, the helper sees the NDJSON prompt, can reuse the rolling history for context, and prints whatever text should be sent back.

archive

iamb archive converts or bundles an existing event log into final artefacts such as HTML/Markdown exports or static copies. It consumes the files produced by iamb listen (notably the events/, media/, and meta/ hierarchies), and will read postback queue entries or hooks if present so cached external assets can be included or referenced in the generated outputs.

Use --only-print-changes when you want quieter archive runs that suppress unchanged-room chatter while still reporting real updates, warnings, errors, and the final summary.

Handling encrypted events

If iamb archive encounters events it still cannot decrypt after a handful of retries, it now stops with an error, lists the affected rooms, and records each event ID under meta/undecryptable.txt. The undecryptable ledger is maintained automatically by the tools: entries will be added when events cannot be decrypted and will be removed automatically once keys become available.

To unblock the run you can:

  1. Export keys from another verified client (iamb keys export /path/to/keys mypass), copy the file, and import them on the archive machine (iamb keys import /path/to/keys mypass).
  2. Re-run iamb archive once the keys are present.
  3. If the room or event should be skipped, prefer using the user-maintained ignore list in meta/ignored-events.txt. This file accepts one event ID per line and may optionally include the room id and/or a friendly label after the event id, for example:
$EVENTID                       # just the event
$EVENTID !roomid:example.com   # event + room
$EVENTID !roomid:example.com A friendly name

If you instead want to ignore an entire room, add it to meta/ignored-rooms.txt.

Notes:

  • meta/undecryptable.txt is managed by the archiver/listener and should not be edited by hand except for advanced use cases. The tools will remove entries from meta/undecryptable.txt once the missing keys are available and events can be decrypted.
  • Events explicitly listed in meta/ignored-events.txt will remain skipped even after keys are imported; use the ignored-events list to permanently suppress specific events from processing.

The live listener (iamb listen) never blocks on encrypted events, but it warns in the log and appends each unresolved event to the same meta/undecryptable.txt file so you know which rooms still need attention.

Decryption diagnostics

Pass the global --log-decrypt-errors flag when running the interactive UI or any CLI command that receives messages (the default launcher, iamb archive, iamb listen, and iamb unreads). When enabled, every event the client still cannot decrypt is appended as a newline-delimited JSON record to ~/.local/share/iamb/profiles/<profile>/encryption-errors.log. Each entry captures the room/event identifiers, sender, encryption metadata (algorithm/session_id/sender_key/device_id/withheld), the stage at which it was observed, and the full raw payload so you can investigate missing keys or bridging issues.

Retention cleanup (iamb redact)

iamb redact executes explicitly configured retention/cleanup policies from your profile's delete section. It does not create new policies from CLI flags — the flags only select which pre-defined policies to apply or temporarily override specific policy fields for that run.

How policy resolution works

  • The effective policy for a given room and store is resolved in this order (later items override earlier ones):
    1. Global store defaults: delete.stores.<Store> (lowest precedence)
    2. Named room sets: delete.room_sets.<Name> (applied in deterministic order)
    3. Per-room overrides: delete.rooms."<room_id>"
    4. Top-level explicit excludes: delete.exclude_rooms (these opt rooms out of global store defaults)
  • There are two common store kinds:
    • remote — operations that redact events on the homeserver (SDK redactions).
    • archive — operations that edit the local on-disk archive (events NDJSON and local media files).
  • If a policy requires archive presence (require_archive_presence), the archive index (the events/ files) is consulted to decide whether a remote deletion should be applied to a particular event.

Minimal example configuration

[delete]
allow_cli = true
default_rooms = ["!cleanup:matrix.org"]

# Global remote (server-side) policy
[delete.stores.remote]
enabled = true
include_media = true        # include non-message media events as candidates
skip_peer_events = true     # only affect messages sent by the profile user
older_than = "30d"         # target events older than 30 days

# Global archive (local) policy — operate on the archive files & media
[delete.stores.archive]
enabled = true
include_media = true
older_than = "30d"

Both remote and archive can be configured independently; per-room overrides are used when you need a different retention rule for a particular room.

Practical scenarios and configuration hints

  1. Cleaning old data from the server
  • Goal: redact messages older than N days from the remote homeserver.
  • Config: set delete.stores.remote.enabled = true and older_than = "30d" (or desired window).
  • Run: iamb -P profile redact --remote --dry-run
  • Once satisfied, re-run without --dry-run to perform remote redactions.
  1. Purging something you didnt want saved anywhere (remote + archive)
  • Goal: remove events both from the server and your local archive.
  • Config: enable and tune both stores (archive + remote) with matching older_than and include_media as needed.
  • Run: iamb -P profile redact --remote --archive --dry-run
  • Confirm and re-run without --dry-run to redact remotely and prune local NDJSON + media files.
  1. Configuring disappearing messages for a room
  • Goal: make a room automatically cleaned up after a time window.
  • Config: add a per-room entry under delete.rooms."<room_id>".stores.<Store> and set older_than and enabled = true. Optionally set leave_empty_rooms if you want the client to leave/forget empty rooms.
  • Use delete.room_sets to apply the same disappearing rule to many rooms.
  1. Automating periodic cleanup (cron)
  • Put a safe cron job that runs with --dry-run first when testing, then without once validated.
  • Example cron invocation (monthly run):
    • 0 3 1 * * /usr/bin/iamb -P myprofile redact --archive --remote --dry-run >> /var/log/iamb-delete.log 2>&1
    • After validating outputs, remove --dry-run.
  1. Fully purging a room (include leave + media removal)
  • Goal: remove archived events, delete associated media files, optionally leave the room when empty.
  • Config: per-room delete.rooms."<room_id>".stores.archive with enabled = true, include_media = true, and leave_empty_rooms = true.
  • Run: iamb -P profile redact --archive --dry-run (inspect) then re-run without --dry-run.

Important operational notes

  • CLI flags like --older-than and --media temporarily override policy fields for that invocation only; they do not create or persist a policy. To make changes persistent, edit your config.

  • Start with --dry-run every time and inspect results carefully.

  • Inspect resolved policies before running destructive operations: iamb -P profile config current --format toml This prints the resolved configuration (including merged defaults/overrides) so you can confirm what will be applied.

  • If you rely on archive checks (require_archive_presence or archive_before) ensure the archive root exists and is readable by the process that runs iamb redact.

  • Faster defaults are always active; the archiver keeps meta/room-sync.json up to date with the last archived event per room and skips rooms whose remote history has not advanced since the previous run. Use iamb archive --full (or delete that metadata file) to force a complete rescan if you suspect new events were missed, then tune archive.archiver.fetch_limit, archive.archiver.message_fetch_retry_delay_secs, archive.archiver.message_fetch_timeout_secs, archive.listener.heartbeat_interval_secs, archive.listener.idle_sleep_millis, and the archive.archiver.* / archive.listener.* external rate-limit entries when you need more conservative pacing. See docs/addressed/notes/archiver.md for guidance.

  • Keep rooms out of the listener by configuring archive.listener.excluded_rooms in your profile; the same list can be supplied temporarily using --exclude-room on the CLI. You can also use archive.listener.invites.whitelist/archive.listener.invites.blacklist (or the CLI equivalents) to control which invites are automatically accepted.

Sender and homeserver-aware cleanups

You can layer the --sender, --homeserver, and --homeserver-other flags on your remote policy runs to only target specific users or domains. Keep named room sets handy so you can easily point a cleanup at public rooms, encrypted rooms, or other slices of your archive.

[delete.room_sets.public]
rooms = ["!public1:example.com", "!public2:example.com"]

[delete.room_sets.unencrypted]
rooms = ["!open1:example.com", "!open2:example.com"]

[delete.room_sets.encrypted]
rooms = ["!secret1:example.com", "!secret2:example.com"]
  1. Redact only your own messages in the public room set between 3 and 12 months old:

iamb -P public redact --remote --sender @microchipster:underzsea.com --older-than "90d" --newer-than "365d"
--room !public1:example.com --room !public2:example.com --dry-run


2. Target non-encrypted rooms only when removing your own activity from the last 3090 days:

```bash
iamb -P public redact --remote --sender @microchipster:underzsea.com --older-than "30d" --newer-than "90d" \
--room !open1:example.com --room !open2:example.com --dry-run
  1. Repeat the same clean-up for encrypted rooms that match your delete.room_sets.encrypted list:

iamb -P public redact --remote --sender @microchipster:underzsea.com --older-than "30d" --newer-than "90d"
--room !secret1:example.com --room !secret2:example.com --dry-run


4. Redact every event originating from a specific home server (or servers) regardless of the sender:

```bash
iamb -P public redact --remote --homeserver matrix.org --homeserver example.org --older-than "180d" --newer-than "720d" --dry-run
  1. Clean up content from homeservers that are not your own (useful when bridging or roaming):

iamb -P public redact --remote --homeserver-other --older-than "90d" --newer-than "365d" --dry-run


These combinations give you the expressive filter power to redact messages by sender, room type, or server while still respecting your configured retention policies.

### mentions

By default, `@all` and `@channel` will send mentions to every member of the room (@room is specially handled by some clients so it is skipped). To opt out, set:

```toml
[settings]
expand_everyone_mentions = false

verify

iamb verify logs in and runs the verification flow on the command line in isolation to initialize a profile.

reset-secure-backup

Reset secret storage + key backup for a profile and generate a fresh recovery passphrase and recovery key (matching the Matrix SDK recovery module guidance). This is intended for first-login bootstrap on a trusted device.

If you only want a passphrase to stash first and use later, you can generate one without touching server state:

iamb -P profilename reset-secure-backup --generate-passphrase-only
iamb -P profilename reset-secure-backup

What it does

  • Restores the session if available; otherwise prompts for password or SSO, then performs an initial sync so encryption state is current.
  • Checks existing recovery status and refuses to overwrite unless --force is provided.
  • Generates a high-entropy passphrase (you can supply --passphrase <value>), prompts you to retype it unless --yes is set, then enables secure backup via the SDKs recovery.enable().wait_for_backups_to_upload() flow.
  • Prints both the recovery key and passphrase; store them immediately in a password manager or secure medium—losing them can permanently lock you out of encrypted history.

Flags

  • --passphrase <string> — supply your own passphrase instead of auto-generating one.
  • --generate-passphrase-only — print a secure-backup passphrase and exit without logging in or changing any backup state.
  • --force — recreate recovery even if already enabled.
  • --yes — skip the confirmation prompt (non-interactive / automation).
  • --password <pw>, --sso — optional login inputs, same as iamb verify.

Hooks, postback, and media configuration

External media jobs are classified and processed according to the MediaHandling configuration in your config.toml. Two related configuration areas control behavior:

  • media.hooks — fine-grained enable/disable and include/exclude pattern rules for each handler, either globally (media.hooks.default) or per-room (media.hooks.rooms). Rules are simple substring matches: an include list requires a substring to appear, and exclude prevents matches containing a substring. Rules also have an enabled flag to turn handlers on/off for matching URLs.

  • media.post_back — controls which fetched assets should be queued for postback. Legacy list-style options (default and rooms) continue to work, but you can now specify default_rules and room_rules to apply the same pattern-based controls used by media.hooks.

A minimal example config.toml excerpt that demonstrates enabling the single-file browser, restricting yt-dlp globally to example.com links, and enabling postback only for a special room:

[media]
single_file_browser = "/usr/bin/chromium"

# Hook rules: enable yt-dlp only when the URL contains "example.com"
[media.hooks.default]
# Handler keys map to MediaHandlingKind names (Direct, YtDlp, SingleFile, MatrixAttachment)
YtDlp = { enabled = true, include = ["example.com"], exclude = [] }
Direct = { enabled = true }

# Postback: legacy list is still supported, but you can use rule-based controls
[media.post_back]
default = [] # no legacy global postback

# Enable postback for yt-dlp only in a specific room via room_rules
[media.post_back.room_rules."!special:example.com".YtDlp]
enabled = true
include = []
exclude = []

Note: enabling the optional Cargo feature yt-dlp-python allows iamb to query yt-dlp's Python extractors at runtime to determine whether yt-dlp can handle particular URLs. This can improve handler classification for many streaming or platform URLs. Enable it by building with --features "yt-dlp-python".

Command prefixes in messages (for example !yt-dlp ...) can still be used to force YtDlp handling for a URL. Fetched assets now land under the canonical per-room media directory media/<sanitized-room>/, with filenames that include the handler label (for example 2024-01-01@12:00:00__alice__yt-dlp__some-title.mp4). Hook scripts or automation can consume meta/postback-queue.ndjson to act on queued items (for example sending follow-up messages that include cached assets).

Example workflows

# setup
mkdir -p ~/.config/iamb
$EDITOR ~/.config/iamb/config.toml

# authenticate once and verify another device if needed
iamb -P profilename verify

# run a reusable background session for fast CLI commands
iamb -P profilename daemon

# archive everything once
iamb -P profilename archive --verbose --out ~/.local/share/iamb-archive/profilename

# continue archiving from the newest saved point
iamb -P profilename archive --verbose --out ~/.local/share/iamb-archive/profilename --continue

# listen only for selected rooms
iamb -P profilename listen --verbose --out ~/.local/share/iamb-archive/profilename \
  --room <room-id0> --room <room-id1>

# clear unread markers without sending public receipts
iamb -P profilename unreads clear --no-receipt --all

Installation (from source)

Install Rust and Cargo using rustup, and then run from the directory containing the sources (ie: from a git clone):

cargo install --locked --path .

Installation (via crates.io)

Install Rust (1.83.0 or above) and Cargo, and then run:

cargo install --locked iamb

See Configuration for getting a profile set up.

Installation (via package managers)

Arch Linux

On Arch Linux a package is available in the Arch User Repositories (AUR). To install it simply run with your favorite AUR helper:

paru iamb-git

FreeBSD

On FreeBSD a package is available from the official repositories. To install it simply run:

pkg install iamb

Gentoo

On Gentoo, an ebuild is available from the community-managed GURU overlay.

You can enable the GURU overlay with:

eselect repository enable guru
emerge --sync guru

And then install iamb with:

emerge --ask iamb

macOS

On macOS a package is available in Homebrew's repository. To install it simply run:

brew install iamb

NetBSD

On NetBSD a package is available from the official repositories. To install it simply run:

pkgin install iamb

Nix / NixOS (flake)

nix profile install "git+https://codeberg.org/microchipster/iamb.git"

openSUSE Tumbleweed

On openSUSE Tumbleweed a package is available from the official repositories. To install it simply run:

zypper install iamb

Snap

A snap for Linux distributions which support the packaging system.

snap install iamb

License

iamb is released under the Apache License, Version 2.0.