- Rust 99.5%
- Shell 0.4%
|
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
|
||
|---|---|---|
| .github | ||
| crates | ||
| docs | ||
| scripts | ||
| src | ||
| .envrc | ||
| .gitattributes | ||
| .gitignore | ||
| .rustfmt.toml | ||
| AGENTS.md | ||
| archive-issues.log | ||
| backlog.md | ||
| build.rs | ||
| build.sh | ||
| Cargo.lock | ||
| Cargo.toml | ||
| clippy.toml | ||
| config.example.toml | ||
| contain.sh | ||
| CONTRIBUTING.md | ||
| errors.log | ||
| flake.lock | ||
| flake.nix | ||
| iamb.desktop | ||
| LICENSE | ||
| PACKAGING.md | ||
| README.md | ||
| ROADMAP.md | ||
| rust-toolchain.toml | ||
| todo.md | ||
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 daemonexpose a per-profile Unix socket unless--no-ipcis 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, andiamb statusall support--format jsonwhen 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 versionslists 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:servercreates or reuses a direct message room, andiamb 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--serverqueries are now remembered per profile and folded into later--known-serversruns. Shared-user queries now include cached presence when it is available locally.iamb inviteslists pending invites; add--accept,--reject, or--pickerfor bulk handling.iamb unreadslists unread rooms/messages and can clear them with public receipts, private receipts, or no receipt.iamb mark-unread <room-id>...marks rooms unread locally;--pickeropens an interactive selector.iamb send,iamb upload,iamb create-room,iamb room-setting,iamb upgrade,iamb leave,iamb sessions, andiamb change-passwordcover common operational tasks without opening the TUI.iamb media,iamb archive,iamb listen,iamb download,iamb postback,iamb search, andiamb redactsupport 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:dmsaccept optional sort modes, while:sort <mode>changes the active list ordering; the footer shows the current sort label.:join <room>accepts room IDs, aliases, andmatrix.tolinks; use:peek <room>to inspect a room before switching into a joined timeline. Sharedmatrix.toroom/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.:infoopens the room report, and:pins/:pins open <n>let you browse pinned events and jump back into the timeline.:markdown onenables one-message markdown authoring explicitly, while:markdown offreuses 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.:confettiand:snowfallnow 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 showcover the room inspection and moderation surfaces that also exist underiamb room-setting.:spaces!opens the hierarchical space tree, and:space child add/:space child remove ++pickhelp manage child rooms interactively.:drafts,:status,:log,:reload,:initial-sync, and:perfare 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:draftsopens the current saved-draft list. Use:logto see the active debug log path and:log flushto 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. Addsource ~/.config/iamb/iamb.bash-completionto your~/.bashrcor 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/iambis on yourfpath(e.g.,fpath=(~/.config/iamb $fpath)inside~/.zshrc), then runautoload -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 orsource ~/.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 layoutlayout.style = "new"openssettings.default_roomwhen set, otherwise the welcome pagelayout.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 behaviorsliding_sync_active_timeline_limit: lower the active-room timeline depthsliding_sync_all_timeline_limit: lower the background-room timeline depthevent_cache: keep enabled unless you are isolating event-cache problemsevent_cache_clear_interval: lower values clear cached event data more oftenlog_rotate_size: cap each active log file before rotating it, using values like1Gor100Mlog_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_intervalcan 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_minutescan reduce RAM significantly on busy accounts, but may make upward scrolling or unread catch-up feel a bit less warm - disabling
sliding_syncorevent_cacheis 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-syncto request a fresh catch-up pass for the current view - if the UI still looks stale, run
:reloadto 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
:reloador:initial-synccan 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/:clearcachehelp 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
:perfand 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 2 seconds 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:
- 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). - Re-run
iamb archiveonce the keys are present. - 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.txtis managed by the archiver/listener and should not be edited by hand except for advanced use cases. The tools will remove entries frommeta/undecryptable.txtonce the missing keys are available and events can be decrypted.- Events explicitly listed in
meta/ignored-events.txtwill 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):
- Global store defaults:
delete.stores.<Store>(lowest precedence) - Named room sets:
delete.room_sets.<Name>(applied in deterministic order) - Per-room overrides:
delete.rooms."<room_id>" - Top-level explicit excludes:
delete.exclude_rooms(these opt rooms out of global store defaults)
- 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 (theevents/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
- Cleaning old data from the server
- Goal: redact messages older than N days from the remote homeserver.
- Config: set
delete.stores.remote.enabled = trueandolder_than = "30d"(or desired window). - Run: iamb -P profile redact --remote --dry-run
- Once satisfied, re-run without --dry-run to perform remote redactions.
- Purging something you didn’t 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_thanandinclude_mediaas 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.
- 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 setolder_thanandenabled = true. Optionally setleave_empty_roomsif you want the client to leave/forget empty rooms. - Use
delete.room_setsto apply the same disappearing rule to many rooms.
- Automating periodic cleanup (cron)
- Put a safe cron job that runs with
--dry-runfirst 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.
- 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.archivewithenabled = true,include_media = true, andleave_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-thanand--mediatemporarily 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-runevery 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
archivechecks (require_archive_presenceorarchive_before) ensure the archive root exists and is readable by the process that runsiamb redact. -
Faster defaults are always active; the archiver keeps
meta/room-sync.jsonup to date with the last archived event per room and skips rooms whose remote history has not advanced since the previous run. Useiamb archive --full(or delete that metadata file) to force a complete rescan if you suspect new events were missed, then tunearchive.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 thearchive.archiver.*/archive.listener.*external rate-limit entries when you need more conservative pacing. Seedocs/addressed/notes/archiver.mdfor guidance. -
Keep rooms out of the listener by configuring
archive.listener.excluded_roomsin your profile; the same list can be supplied temporarily using--exclude-roomon the CLI. You can also usearchive.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"]
-
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 30–90 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
-
Repeat the same clean-up for encrypted rooms that match your
delete.room_sets.encryptedlist:
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
-
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
--forceis provided. - Generates a high-entropy passphrase (you can supply
--passphrase <value>), prompts you to retype it unless--yesis set, then enables secure backup via the SDK’srecovery.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 asiamb 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: anincludelist requires a substring to appear, andexcludeprevents matches containing a substring. Rules also have anenabledflag to turn handlers on/off for matching URLs. -
media.post_back— controls which fetched assets should be queued for postback. Legacy list-style options (defaultandrooms) continue to work, but you can now specifydefault_rulesandroom_rulesto apply the same pattern-based controls used bymedia.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.