Skip to main content

Jabber

@magistr/jabberv2026.08.19.1· 23d agoMODELS
01README

Read, search, and import Psi/Psi+ Jabber (XMPP) chat history — DMs and MUC conferences — into Obsidian vault as markdown notes

02Models1
@magistr/jabber/historyv2026.08.19.1extensions/models/jabber_history.ts

Global Arguments

ArgumentTypeDescription
historyDirstringPath to Psi/Psi+ Jabber client history directory (containing .history and conference log files)
vaultRoot?stringAbsolute 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?numberTimeout (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?stringPath 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?numberMaximum 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).
fn list(chatType: enum)
List all conversations (DMs and conferences) with message counts
ArgumentTypeDescription
chatTypeenumFilter by conversation type
fn read(jid: string, limit: number)
Read messages from a specific conversation by JID pattern
ArgumentTypeDescription
jidstringJID or substring to match (e.g. 'alice' or 'bob@jabber.example')
limitnumberMax messages to return (0 = all)
fn search(query: string, chatType: enum, limit: number)
Search messages across all conversations by text pattern
ArgumentTypeDescription
querystringText to search for (case-insensitive)
chatTypeenumFilter by conversation type
limitnumberMax results to return
fn importToObsidian(vault?: string, vaultPath?: string, folder: string, chatType: enum)
Import Jabber conversations as markdown notes into an Obsidian vault
ArgumentTypeDescription
vault?stringObsidian vault name (resolved via CLI)
vaultPath?stringDirect filesystem path to the Obsidian vault (skips CLI resolution and the vaultRoot global argument)
folderstringTarget folder inside the vault
chatTypeenumFilter by conversation type

Resources

summary(infinite)— Summary of all conversations in the history directory
conversation(infinite)— Individual conversation with all messages
03Previous Versions7
2026.08.02.1

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): decodeJid now wraps decodeURIComponent in a try/catch and falls back to the _at_-replaced raw string on a URIError, instead of letting one malformed-% filename abort listing -- and therefore every method -- for the WHOLE directory. list/read/ search/importToObsidian all 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): importToObsidian now tracks used filename stems in a Map and appends (2), (3), ... on collision (the same pattern fidonet_msgbase.ts already 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 from livejournal_import.ts) and applied it to the title/jid/account frontmatter fields (previously only " was escaped), plus a new neutralizeBodyDelimiters helper that backslash-escapes any message-body LINE that is, once trimmed, exactly a ---/***/___ delimiter. A %0A-encoded JID no longer injects a second title: 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): read now validates each parsed pipe-format message against the model's own MessageSchema and drops (with a logger.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 asserts model.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 fake writeResource harness.
  • #6/#7 (obsidian subprocess hardening): getVaultPath now spawns with an AbortController-derived signal (a manual setTimeout/clearTimeout pair, never AbortSignal.timeout() -- see the doc comment on getVaultPath) bounded by a new timeoutMs global argument (default 30000ms), so a hung Obsidian CLI no longer blocks the import indefinitely. A new obsidianBin global argument (default "obsidian", unchanged) lets an operator pin an absolute path instead of the bare PATH-resolved command name.
  • #8 (unbounded memory): a new maxFileBytes global argument (default 52428800 = 50 MiB) routes every file read through a new readFileWithCap helper, which Deno.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): sanitizeFilename now slices via Array.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 bridge 2026.07.16.2 -> 2026.08.01.1 (closing the gap left when the headless-vaultRoot change shipped without an upgrades[] entry) and this change's own 2026.08.01.1 -> 2026.08.02.1 entry. 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), an obsidianBin-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/model version: 2026.08.01.1 -> 2026.08.02.1.
2026.08.01.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 vaultRoot global argument. importToObsidian's destination now resolves with precedence vaultPath (method argument) > vaultRoot (global argument) > the existing vault (name) CLI lookup via getVaultPath. jabber already wrote notes with Deno.writeTextFile (never through the Obsidian CLI's create command), 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) from obsidian-vault/extensions/models/obsidian_vault.ts (PR #56). importToObsidian's noteDir/notePath now resolve through resolveVaultPathSafe before every mkdir/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 ../-relative folder escaped 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 LOCAL jabber-latent-bugs issue-lifecycle model.
    • No npm:yaml dependency 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-Document rationale does not apply here. Every hand-built frontmatter string stays byte-for-byte identical to before this change.
    • Dot-dir/.trash exclusion is N/A: importToObsidian writes 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) with vaultRoot coverage: a golden fs-backend run against the synthetic fixtures/good corpus, method-level tests proving the CLI is never invoked when vaultRoot is set (Deno.Command stubbed to throw), a backend-selection precedence branch matrix, path-confinement adversarial tests (.. traversal and symlinked folder segments refused, /var-vs-/private/var real-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 own Deno.makeTempDir vault and reuses the existing fixtures/good/history/ corpus.
  • manifest.yaml/model version: 2026.07.16.2 -> 2026.08.01.1.
2026.07.16.2

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

2026.05.25.1

Merge pull request #4 from umag/extensions/magistr-grade-a-workspace

extensions: stage 15 @magistr extensions as Grade A workspace dirs + wire CI

2026.03.29.3
2026.03.29.2
2026.03.29.1
04Stats
A
100 / 100
Downloads
1
Archive size
21.2 KB
  • 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
05Platforms
06Labels