# xmppc

`xmppc` is a CLI XMPP client written in C on top of
[libstrophe](http://strophe.im/libstrophe/). It's useful when you want a script,
shell, or agent to do one XMPP thing and then exit, like list a roster, send a
chat message, read recent archive entries, wait for a reply, inspect bookmarks,
and so on.

This repository is a fork — probably a hard fork — of the original
[xmppc](https://codeberg.org/Anoxinon_e.V./xmppc). It may be renamed later.
Please do not send issues from using this fork straight upstream; see
[Issues](#issues).

## Install from source

On Debian or Ubuntu-like systems, install the build tools and libraries first:

```sh
sudo apt-get update
sudo apt-get install -y \
  autoconf \
  automake \
  gcc \
  libglib2.0-dev \
  libgpgme-dev \
  libstrophe-dev \
  libtool \
  make \
  pkg-config
```

Then build the binary:

```sh
./bootstrap.sh
./configure
make
```

Put the resulting `xmppc` somewhere in your `PATH`, for example:

```sh
mkdir -p ~/.local/bin
mv xmppc ~/.local/bin/xmppc
```

## Files

- `~/.config/xmppc/config` — account configuration.
- `~/.config/xmppc/AGENTS.md` — optional instructions for LLM agents using
  `xmppc`.

## Accounts and passwords

You can pass a JID directly:

```sh
xmppc --jid user@example.net --pwd 'secret' --mode roster list
```

If `--pwd` is omitted, `xmppc` asks for the password without echoing it. If you
want to pipe a password in, use `--pwd -`:

```sh
pass show xmpp/example | xmppc --jid user@example.net --pwd - --mode roster list
```

Named accounts are probably nicer. `xmppc` reads them from
`~/.config/xmppc/config`; the `[default]` account is used when neither `--account`
nor `--jid` is provided.

```ini
[default]
jid=user@example.net
pwd=secret

[work]
jid=user@work.example
pwd-cmd=pass show xmpp/work

[bot]
jid=bot@example.net
pwd=another-secret
```

Prefer `pwd-cmd` if you already keep passwords in a local password manager. It
runs locally and its stdout, minus the trailing newline, becomes the password.

## Command shape

Most commands follow this pattern:

```sh
xmppc [--account ACCOUNT] [--jid JID] [--pwd PASSWORD] --mode MODE COMMAND [args...]
```

Short options are available too:

```sh
xmppc -a work -m roster list
xmppc -j user@example.net -p - -m mam list friend@example.net
```

Use `-v`, `-vv`, `-vvv`, or `-vvvv` for progressively noisier logging. Use
`--help` for the built-in summary.

## Modes

### Roster

```sh
xmppc -a work -m roster list
xmppc -a work -m roster export
```

`list` shows the account's contacts. `export` exports them.

### Message

```sh
xmppc -a work -m message chat friend@example.net 'hello from xmppc'
```

`message chat` sends an unencrypted chat message. After the sent stanza appears
in Message Archive Management (MAM), it prints one compact XML line:

```xml
<sent archive-id="mam-id" message-id="stanza-id" to="friend@example.net"/>
```

An important bit is the distinction between the two IDs:

- `archive-id` is the MAM cursor. Use it with `mam receive after-id=...` when you
  want to wait for the next reply.
- `message-id` is the client stanza ID. It's useful for matching messages, but
  it's not a MAM cursor.

If `archive-id` is empty, the message was sent but the archive cursor wasn't
resolved before the lookup timed out. See [Send, then wait for a
reply](#send-then-wait-for-a-reply) for the fallback.

### Message Archive Management (MAM)

MAM commands use XEP-0313 to read archived messages.

```sh
xmppc -a work -m mam list friend@example.net
xmppc -a work -m mam list with=friend@example.net start=-15m max=20
xmppc -a work -m mam receive friend@example.net after-id=mam-id timeout=300s max=5
```

`mam list` accepts unordered `field=value` terms:

- `with=<jid>` filters to a conversation. A bare JID is shorthand for `with=<jid>`.
- `start=<time>` and `end=<time>` bound the query. They accept XMPP timestamps
  or relative values such as `-30s`, `-5m`, `-2h`, or `-1d`.
- `after-id=<mam-id>` and `before-id=<mam-id>` use RSM cursors.
- `ids=<id>[,<id>...]` asks for specific archive IDs.
- `max=<n>` limits the number of printed messages. It defaults to `5` and is
  capped at `50`.

If you don't supply any range or ID field, `mam list` defaults to `start=-5m` so
it doesn't accidentally ask for an unbounded archive.

`mam receive` is for “wait until someone replies.” It requires a conversation JID
and `after-id=<mam-id>`, polls with real MAM/RSM `after` queries, and exits when
at least one newer archived message is available or the timeout is reached.
`timeout` defaults to `300s`; `max` defaults to `5`. If nothing arrives before
the timeout, it exits without printing anything.

Normal MAM output is one XML record per archived message:

```xml
<message id="mam-id" message-id="stanza-id" stamp="..." from="..." to="..."><body>escaped body</body></message>
```

Again, `id` is the MAM archive cursor. `message-id` is the stanza ID.

There is also a nicer `pretty` command:

```sh
xmppc -a work -m mam pretty friend@example.net start=-5m
```

### Send, then wait for a reply

1. Send the message and capture the `<sent .../>` line.
2. Extract `archive-id`.
3. Run `mam receive` for the same JID with `after-id=<archive-id>`.

For example:

```sh
xmppc -a work -m message chat friend@example.net 'ping'
# extract archive-id from the sent line
xmppc -a work -m mam receive friend@example.net after-id='the-archive-id-from-sent'
```

That skips your outgoing message and waits only for newer archived messages in
the conversation.

If the sent line has an empty `archive-id`, fall back to a recent `mam list`,
find the message whose `message-id` matches the sent `message-id`, then use that
message line's `id` as the `after-id` cursor.

### PGP and OpenPGP

```sh
xmppc -a work -m pgp chat friend@example.net 'encrypted with old-style XEP-0027 PGP'
xmppc -a work -m openpgp signcrypt friend@example.net 'signed and encrypted with XEP-0373 OpenPGP'
```

The `pgp` mode supports XEP-0027. The `openpgp` mode supports XEP-0373
`signcrypt`.

### OMEMO

```sh
xmppc -a work -m omemo list
xmppc -a work -m omemo delete-device-list
```

`list` shows OMEMO device IDs and fingerprints. `delete-device-list` removes the
account's OMEMO device list.

### Monitor

```sh
xmppc -a work -m monitor stanza
xmppc -a work -m monitor monitor
```

`stanza` monitors raw stanzas. `monitor` watches XEP-0277 microblog events.

### Bookmarks

```sh
xmppc -a work -m bookmark list
```

Lists XEP-0048 bookmarks.

### Discovery

```sh
xmppc -a work -m discovery info conference.example.net
xmppc -a work -m discovery item conference.example.net
```

Runs XEP-0030 service discovery `info` or `item` queries.

## Agent support

This fork has an `agent-skill` command for LLM agents:

```sh
xmppc agent-skill
```

It prints an agent skill they should follow, the MAM cursor rules, and explains
that agents should read `~/.config/xmppc/AGENTS.md` when it exists for
user-local instructions, but must not read `~/.config/xmppc/config`.

To install a proper agent skill that points back to the one embedded in your
binary, run:

```sh
xmppc agent-skill install
```

That writes:

- `~/.agents/skills/communicating-through-xmppc/SKILL.md`
- `~/.agents/skills/communicating-through-xmppc/references/installation.md`

## Exit status

- `0` means success.
- `1` means failure.

## Issues

Please don't report problems related to this fork to upstream `xmppc`! Send me a
message somewhere (it's pretty easy to find how) and I'll decide whether it's a
bug in my fork or something I should report upstream. Please don't bother the
original maintainer with issues I may have introduced.

## License

Copyright © 2020 Anoxinon e.V. This project is free software under the GNU
General Public License. See [LICENSE](LICENSE) for the full text.
