Jabber
Read, search, and import Psi/Psi+ Jabber (XMPP) chat history — DMs and MUC conferences — into Obsidian vault as markdown notes
Global Arguments
| Argument | Type | Description |
|---|---|---|
| historyDir | string | Path to Psi/Psi+ Jabber client history directory (containing .history and conference log files) |
| vaultRoot? | string | Absolute path to the Obsidian vault directory. When set, importToObsidian writes notes directly to this directory (no Obsidian CLI, no desktop app needed) and takes precedence over CLI-based vault-name resolution. Overridden per-call by the importToObsidian method's own vaultPath argument. |
| timeoutMs? | number | Timeout (ms) for the 'obsidian' CLI subprocess used to resolve a vault name to a filesystem path. Only applies when neither vaultPath (method argument) nor vaultRoot (global argument) is set. Defaults to 30000 (30s). |
| obsidianBin? | string | Path or command name for the Obsidian CLI binary invoked to resolve a vault name to a path. Defaults to the bare command name 'obsidian' (resolved via $PATH). Only applies when neither vaultPath nor vaultRoot is set. |
| maxFileBytes? | number | Maximum size, in bytes, of a single history file read into memory. Files larger than this are skipped (with a warning) rather than read. Defaults to 52428800 (50 MiB). |
| Argument | Type | Description |
|---|---|---|
| chatType | enum | Filter by conversation type |
| Argument | Type | Description |
|---|---|---|
| jid | string | JID or substring to match (e.g. 'alice' or 'bob@jabber.example') |
| limit | number | Max messages to return (0 = all) |
| Argument | Type | Description |
|---|---|---|
| query | string | Text to search for (case-insensitive) |
| chatType | enum | Filter by conversation type |
| limit | number | Max results to return |
| Argument | Type | Description |
|---|---|---|
| vault? | string | Obsidian vault name (resolved via CLI) |
| vaultPath? | string | Direct filesystem path to the Obsidian vault (skips CLI resolution and the vaultRoot global argument) |
| folder | string | Target folder inside the vault |
| chatType | enum | Filter by conversation type |
Resources
2026.08.02.1
Fixes the eight latent bugs (#1-4, #6-9) that were tracked, unfixed, in the
LOCAL jabber-latent-bugs issue-lifecycle model (never filed to the swamp.club
Lab) as of 2026.08.01.1. Bug #5 (folder path traversal) is untouched -- it
was already fixed in 2026.08.01.1 by the resolveVaultPathSafe path-confinement
change, and that fix's headless vaultRoot/PR #141 behavior is unmodified here.
- #1 (decodeJid resilience):
decodeJidnow wrapsdecodeURIComponentin a try/catch and falls back to the_at_-replaced raw string on aURIError, instead of letting one malformed-%filename abort listing -- and therefore every method -- for the WHOLE directory.list/read/search/importToObsidianall now RESOLVE against a directory containing a poisoned filename; the poisoned entry surfaces with its fallback jid instead of taking down every other file with it. - #2 (sanitizeFilename collision dedup):
importToObsidiannow tracks used filename stems in aMapand appends(2),(3), ... on collision (the same patternfidonet_msgbase.tsalready uses for its Obsidian note paths), instead of silently letting the second write clobber the first. - #3 (frontmatter/body injection, both vectors): added
yamlEscape(copied verbatim fromlivejournal_import.ts) and applied it to thetitle/jid/accountfrontmatter fields (previously only"was escaped), plus a newneutralizeBodyDelimitershelper that backslash-escapes any message-body LINE that is, once trimmed, exactly a---/***/___delimiter. A%0A-encoded JID no longer injects a secondtitle:line; a body containing an internal---line no longer forges a second frontmatter block. (The prior #3b pin was VACUOUS -- its fixture body started with---but rendered inline as ordinary text, never as its own line; the rewritten test uses a body with an internal newline so the injected delimiter is a real line, and is non-vacuous: an unfixed model produces 3---lines, the fixed one produces exactly 2.) - #4 (was live-only, now offline-testable):
readnow validates each parsed pipe-format message against the model's ownMessageSchemaand drops (with alogger.warn) any that fail -- localizing the damage the same way the #1 fix localizes one bad filename -- instead of letting a malformed timestamp/direction pass straight through untouched. The regression test now directly assertsmodel.resources.conversation.schema.parse(payload)no longer throws, which is exactly the live-instance failure mode this bug used to describe as untestable in this backfill's fakewriteResourceharness. - #6/#7 (obsidian subprocess hardening):
getVaultPathnow spawns with anAbortController-derivedsignal(a manualsetTimeout/clearTimeoutpair, neverAbortSignal.timeout()-- see the doc comment ongetVaultPath) bounded by a newtimeoutMsglobal argument (default30000ms), so a hung Obsidian CLI no longer blocks the import indefinitely. A newobsidianBinglobal argument (default"obsidian", unchanged) lets an operator pin an absolute path instead of the bare PATH-resolved command name. - #8 (unbounded memory): a new
maxFileBytesglobal argument (default52428800= 50 MiB) routes every file read through a newreadFileWithCaphelper, whichDeno.stats the file first and skips it (with a warning) if it exceeds the cap, instead of always reading it whole regardless of size. The default is far above any realistic history file, so behavior is unchanged unless an operator explicitly lowers the cap. - #9 (lone-surrogate filename truncation):
sanitizeFilenamenow slices viaArray.from(cleaned).slice(0, 80).join("")(Unicode code points) instead of a raw UTF-16 code-unit.slice(0, 80), so a surrogate pair straddling the 80-character boundary is never split. - Added
upgrades[](previously absent on this model): a lineage-repair bridge2026.07.16.2->2026.08.01.1(closing the gap left when the headless-vaultRoot change shipped without anupgrades[]entry) and this change's own2026.08.01.1->2026.08.02.1entry. Neither changes the resource schema. - Extended all five test suites with FIXED-behavior pins for the flipped bugs,
plus new non-vacuous coverage: a mixed good+poison directory (#1), a rewritten
#3b fixture with an internal newline, a
maxFileBytes-capped skip test (#8), anobsidianBin-override pin (#7), and the #9 property test's arbitrary now includes an astral (surrogate-pair) character so the code-point-length invariant is exercised non-vacuously. manifest.yaml/modelversion:2026.08.01.1->2026.08.02.1.
2026.08.01.1
Adds an optional headless vaultRoot filesystem backend to importToObsidian,
so the import can run with the Obsidian desktop app closed (swamp-workspace #57;
mirrors the CLI/filesystem backend split done for @magistr/obsidian-vault in
PR #56 — see that PR for the path-confinement rationale). The Obsidian CLI
(getVaultPath) is kept as the fallback for when neither vaultPath (method
argument) nor vaultRoot (global argument) is set.
- Added the
vaultRootglobal argument.importToObsidian's destination now resolves with precedencevaultPath(method argument) >vaultRoot(global argument) > the existingvault(name) CLI lookup viagetVaultPath. jabber already wrote notes withDeno.writeTextFile(never through the Obsidian CLI'screatecommand), so only the destination resolution changed -- the write path itself is untouched. - Added
resolveVaultPath/resolveVaultPathSafe(realpath + symlink refusal..rejection), copied VERBATIM (same names/comments, per the approved plan's scope constraint against a shared cross-extension module -- swamp bundles each extension independently) fromobsidian-vault/extensions/models/obsidian_vault.ts(PR #56).importToObsidian'snoteDir/notePathnow resolve throughresolveVaultPathSafebefore everymkdir/write, regardless of which of the three precedence tiers produced the destination.
- This FIXES latent bug #5 (folder path traversal --
`${vaultPath}/${args.folder}`was concatenated unsanitized, so a../-relativefolderescaped the vault directory): a traversal attempt is now rejected before any directory is created. The other eight tracked latent bugs (#1-4, #6-9) are UNCHANGED and remain pinned in the LOCALjabber-latent-bugsissue-lifecycle model. - No
npm:yamldependency was added -- this model emits brand-new hand-built frontmatter into notes it owns, it never round-trips existing frontmatter, so PR #56's yaml-Documentrationale does not apply here. Every hand-built frontmatter string stays byte-for-byte identical to before this change. - Dot-dir/
.trashexclusion is N/A:importToObsidianwrites into a caller-named folder, it never walks the vault tree (covered by a covered-negative test in the adversarial suite).
- Extended all five existing test suites
(
jabber_test.ts/jabber_methods_test.ts/jabber_adversarial_test.ts/jabber_coverage_test.ts/jabber_property_test.ts) withvaultRootcoverage: a golden fs-backend run against the syntheticfixtures/goodcorpus, method-level tests proving the CLI is never invoked whenvaultRootis set (Deno.Commandstubbed to throw), a backend-selection precedence branch matrix, path-confinement adversarial tests (..traversal and symlinked folder segments refused,/var-vs-/private/varreal-root containment), and a property test asserting exactly one note per importable item with frontmatter round-trip and no path escaping the vault's real root, for any synthetic set of generated JIDs. No new fixture files were needed -- every new test builds its ownDeno.makeTempDirvault and reuses the existingfixtures/good/history/corpus. manifest.yaml/modelversion:2026.07.16.2->2026.08.01.1.
Release 2026.07.16.2 — align model versions with manifests
Maintenance release across the @magistr extensions. For most packages this
carries no functional change: the only edit is the model's version: field,
brought back in line with its manifest version so the published model type
version and the package version no longer drift.
Functional changes in this release are limited to:
anime-cron: normalizeTitle now strips a ": subtitle" suffix and a trailing parenthesized year before comparison, fixing dedup false-misses where the torrent title carries a subtitle or year that the AniList romaji does not.
arckit: first publish. Standalone ArcKit port — a 12-phase architecture governance state machine with 65 bundled templates, driven by a bundled skill.
Also tracks three extensions (kaiten, observability-agent, music-library) that previously existed only as untracked working-tree directories, recovered from stashes.
Added 1, removed 1 models
Merge pull request #4 from umag/extensions/magistr-grade-a-workspace
extensions: stage 15 @magistr extensions as Grade A workspace dirs + wire CI
- Has README or module doc2/2earned
- README has a code example1/1earned
- README is substantive1/1earned
- Most symbols documented1/1earned
- No slow types (deprecated)1/1earned
- Dependencies pass trust audit2/2earned
- Has description1/1earned
- Platform support declared (or universal)2/2earned
- License declared1/1earned
- Verified public repository2/2earned