1# xmppc
2
3`xmppc` is a CLI XMPP client written in C on top of
4[libstrophe](http://strophe.im/libstrophe/). It's useful when you want a script,
5shell, or agent to do one XMPP thing and then exit, like list a roster, send a
6chat message, read recent archive entries, wait for a reply, inspect bookmarks,
7and so on.
8
9This repository is a fork β probably a hard fork β of the original
10[xmppc](https://codeberg.org/Anoxinon_e.V./xmppc). It may be renamed later.
11Please do not send issues from using this fork straight upstream; see
12[Issues](#issues).
13
14## Install from source
15
16On Debian or Ubuntu-like systems, install the build tools and libraries first:
17
18```sh
19sudo apt-get update
20sudo apt-get install -y \
21 autoconf \
22 automake \
23 gcc \
24 libglib2.0-dev \
25 libgpgme-dev \
26 libstrophe-dev \
27 libtool \
28 make \
29 pkg-config
30```
31
32Then build the binary:
33
34```sh
35./bootstrap.sh
36./configure
37make
38```
39
40Put the resulting `xmppc` somewhere in your `PATH`, for example:
41
42```sh
43mkdir -p ~/.local/bin
44mv xmppc ~/.local/bin/xmppc
45```
46
47## Files
48
49- `~/.config/xmppc/config` β account configuration.
50- `~/.config/xmppc/AGENTS.md` β optional instructions for LLM agents using
51 `xmppc`.
52
53## Accounts and passwords
54
55You can pass a JID directly:
56
57```sh
58xmppc --jid user@example.net --pwd 'secret' --mode roster list
59```
60
61If `--pwd` is omitted, `xmppc` asks for the password without echoing it. If you
62want to pipe a password in, use `--pwd -`:
63
64```sh
65pass show xmpp/example | xmppc --jid user@example.net --pwd - --mode roster list
66```
67
68Named accounts are probably nicer. `xmppc` reads them from
69`~/.config/xmppc/config`; the `[default]` account is used when neither `--account`
70nor `--jid` is provided.
71
72```ini
73[default]
74jid=user@example.net
75pwd=secret
76
77[work]
78jid=user@work.example
79pwd-cmd=pass show xmpp/work
80
81[bot]
82jid=bot@example.net
83pwd=another-secret
84```
85
86Prefer `pwd-cmd` if you already keep passwords in a local password manager. It
87runs locally and its stdout, minus the trailing newline, becomes the password.
88
89## Command shape
90
91Most commands follow this pattern:
92
93```sh
94xmppc [--account ACCOUNT] [--jid JID] [--pwd PASSWORD] --mode MODE COMMAND [args...]
95```
96
97Short options are available too:
98
99```sh
100xmppc -a work -m roster list
101xmppc -j user@example.net -p - -m mam list friend@example.net
102```
103
104Use `-v`, `-vv`, `-vvv`, or `-vvvv` for progressively noisier logging. Use
105`--help` for the built-in summary.
106
107## Modes
108
109### Roster
110
111```sh
112xmppc -a work -m roster list
113xmppc -a work -m roster export
114```
115
116`list` shows the account's contacts. `export` exports them.
117
118### Message
119
120```sh
121xmppc -a work -m message chat friend@example.net 'hello from xmppc'
122```
123
124`message chat` sends an unencrypted chat message. After the sent stanza appears
125in Message Archive Management (MAM), it prints one compact XML line:
126
127```xml
128<sent archive-id="mam-id" message-id="stanza-id" to="friend@example.net"/>
129```
130
131An important bit is the distinction between the two IDs:
132
133- `archive-id` is the MAM cursor. Use it with `mam receive after-id=...` when you
134 want to wait for the next reply.
135- `message-id` is the client stanza ID. It's useful for matching messages, but
136 it's not a MAM cursor.
137
138If `archive-id` is empty, the message was sent but the archive cursor wasn't
139resolved before the lookup timed out. See [Send, then wait for a
140reply](#send-then-wait-for-a-reply) for the fallback.
141
142### Message Archive Management (MAM)
143
144MAM commands use XEP-0313 to read archived messages.
145
146```sh
147xmppc -a work -m mam list friend@example.net
148xmppc -a work -m mam list with=friend@example.net start=-15m max=20
149xmppc -a work -m mam receive friend@example.net after-id=mam-id timeout=300s max=5
150```
151
152`mam list` accepts unordered `field=value` terms:
153
154- `with=<jid>` filters to a conversation. A bare JID is shorthand for `with=<jid>`.
155- `start=<time>` and `end=<time>` bound the query. They accept XMPP timestamps
156 or relative values such as `-30s`, `-5m`, `-2h`, or `-1d`.
157- `after-id=<mam-id>` and `before-id=<mam-id>` use RSM cursors.
158- `ids=<id>[,<id>...]` asks for specific archive IDs.
159- `max=<n>` limits the number of printed messages. It defaults to `5` and is
160 capped at `50`.
161
162If you don't supply any range or ID field, `mam list` defaults to `start=-5m` so
163it doesn't accidentally ask for an unbounded archive.
164
165`mam receive` is for βwait until someone replies.β It requires a conversation JID
166and `after-id=<mam-id>`, polls with real MAM/RSM `after` queries, and exits when
167at least one newer archived message is available or the timeout is reached.
168`timeout` defaults to `300s`; `max` defaults to `5`. If nothing arrives before
169the timeout, it exits without printing anything.
170
171Normal MAM output is one XML record per archived message:
172
173```xml
174<message id="mam-id" message-id="stanza-id" stamp="..." from="..." to="..."><body>escaped body</body></message>
175```
176
177Again, `id` is the MAM archive cursor. `message-id` is the stanza ID.
178
179There is also a nicer `pretty` command:
180
181```sh
182xmppc -a work -m mam pretty friend@example.net start=-5m
183```
184
185### Send, then wait for a reply
186
1871. Send the message and capture the `<sent .../>` line.
1882. Extract `archive-id`.
1893. Run `mam receive` for the same JID with `after-id=<archive-id>`.
190
191For example:
192
193```sh
194xmppc -a work -m message chat friend@example.net 'ping'
195# extract archive-id from the sent line
196xmppc -a work -m mam receive friend@example.net after-id='the-archive-id-from-sent'
197```
198
199That skips your outgoing message and waits only for newer archived messages in
200the conversation.
201
202If the sent line has an empty `archive-id`, fall back to a recent `mam list`,
203find the message whose `message-id` matches the sent `message-id`, then use that
204message line's `id` as the `after-id` cursor.
205
206### PGP and OpenPGP
207
208```sh
209xmppc -a work -m pgp chat friend@example.net 'encrypted with old-style XEP-0027 PGP'
210xmppc -a work -m openpgp signcrypt friend@example.net 'signed and encrypted with XEP-0373 OpenPGP'
211```
212
213The `pgp` mode supports XEP-0027. The `openpgp` mode supports XEP-0373
214`signcrypt`.
215
216### OMEMO
217
218```sh
219xmppc -a work -m omemo list
220xmppc -a work -m omemo delete-device-list
221```
222
223`list` shows OMEMO device IDs and fingerprints. `delete-device-list` removes the
224account's OMEMO device list.
225
226### Monitor
227
228```sh
229xmppc -a work -m monitor stanza
230xmppc -a work -m monitor monitor
231```
232
233`stanza` monitors raw stanzas. `monitor` watches XEP-0277 microblog events.
234
235### Bookmarks
236
237```sh
238xmppc -a work -m bookmark list
239```
240
241Lists XEP-0048 bookmarks.
242
243### Discovery
244
245```sh
246xmppc -a work -m discovery info conference.example.net
247xmppc -a work -m discovery item conference.example.net
248```
249
250Runs XEP-0030 service discovery `info` or `item` queries.
251
252## Agent support
253
254This fork has an `agent-skill` command for LLM agents:
255
256```sh
257xmppc agent-skill
258```
259
260It prints an agent skill they should follow, the MAM cursor rules, and explains
261that agents should read `~/.config/xmppc/AGENTS.md` when it exists for
262user-local instructions, but must not read `~/.config/xmppc/config`.
263
264To install a proper agent skill that points back to the one embedded in your
265binary, run:
266
267```sh
268xmppc agent-skill install
269```
270
271That writes:
272
273- `~/.agents/skills/communicating-through-xmppc/SKILL.md`
274- `~/.agents/skills/communicating-through-xmppc/references/installation.md`
275
276## Exit status
277
278- `0` means success.
279- `1` means failure.
280
281## Issues
282
283Please don't report problems related to this fork to upstream `xmppc`! Send me a
284message somewhere (it's pretty easy to find how) and I'll decide whether it's a
285bug in my fork or something I should report upstream. Please don't bother the
286original maintainer with issues I may have introduced.
287
288## License
289
290Copyright Β© 2020 Anoxinon e.V. This project is free software under the GNU
291General Public License. See [LICENSE](LICENSE) for the full text.