git.strcat.st

/strcat/barevclients.git/ - summarytreelogarchive

subject
init
commit
b3406bf09f62d1eeb992fc8ca6a6952740cee444
date
2026-05-02T23:40:41Z
message
diff
 Makefile               |  39 ++++++++
 README.md              |  39 ++++++++
 barevclient-libbarev.c | 191 ++++++++++++++++++++++++++++++++++++
 barevclient.c          | 187 ++++++++++++++++++++++++++++++++++++
 barevclient.pas        | 255 +++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 711 insertions(+)

diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..883afe8
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,39 @@
+CC     = cc
+FPC    = fpc
+CFLAGS = -std=c89 -pedantic -Wall -Wextra -O2
+PREFIX = /usr/local
+
+.PHONY: barevclient-c-libbarev barevclient-c-libbarevc barevclient-pascal clean
+
+barevclient-c-libbarev:
+	$(CC) $(CFLAGS) \
+		barevclient-libbarev.c \
+		-I$(PREFIX)/include \
+		-L$(PREFIX)/lib -lbarev_c_api \
+		-Wl,-rpath,$(PREFIX)/lib \
+		-o barevclient-c-libbarev
+
+barevclient-c-libbarevc:
+	$(CC) $(CFLAGS) \
+		barevclient.c \
+		-I$(PREFIX)/include \
+		-L$(PREFIX)/lib -lbarevc \
+		-Wl,-rpath,$(PREFIX)/lib \
+		-o barevclient-c-libbarevc
+
+# set BAREV_PASCAL_DIR if not installed system wide.
+barevclient-pascal:
+	@if [ -n "$(BAREV_PASCAL_DIR)" ]; then \
+		$(FPC) -Fu$(BAREV_PASCAL_DIR) barevclient.pas; \
+	else \
+		$(FPC) barevclient.pas; \
+	fi
+	@if [ -f barevclient ]; then mv -f barevclient barevclient-pascal; fi
+
+clean:
+	rm -f \
+		barevclient-c-libbarev \
+		barevclient-c-libbarevc \
+		barevclient-pascal \
+		barevclient \
+		*.o *.ppu
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1f9f816
--- /dev/null
+++ b/README.md
@@ -0,0 +1,39 @@
+a few small barev protocol clients written in multiple languages
+and using multiple barev library implementations. these are to
+be used as a simple starting ground for creating your own client
+though these are considered feature complete and works for chatting 
+with your friends.
+
+# clients:
+
+- barevclient-libbarev.c
+a small client implemented in C using the barev pascal C API / layer
+
+- barevclient.c
+a small client implemented in C using the barev C library
+
+- barevclient.pas
+a small client implemented in PASCAL using the PASCAL library 
+
+# installation:
+
+## requirements
+a libc of sorts, prefferably musl untested with others though will likely work with glibc, openbsd libc etc.
+make - optional
+fpc
+compiler + toolchain
+a yggdrasil connection / ipv6 address on your computer.
+
+## building
+
+make <option>
+available options:
+barevclient-c-libbarev barevclient-c-libbarevc barevclient-pascal clean
+
+## installing
+
+just copy whichever client you compiled to a location in your PATH, should also probably rename it to just 'barevclient'
+
+# see also:
+
+- https://codeberg.org/norayr/barev-pascal
diff --git a/barevclient-libbarev.c b/barevclient-libbarev.c
new file mode 100644
index 0000000..dd42d3b
--- /dev/null
+++ b/barevclient-libbarev.c
@@ -0,0 +1,191 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+
+typedef void* BarevClient;
+typedef void* BarevBuddy;
+
+typedef void (*barev_log_cb)(const char* level, const char* msg, void* userdata);
+typedef void (*barev_typing_cb)(BarevBuddy buddy, int isTyping, void* userdata);
+typedef void (*barev_buddy_status_cb)(BarevBuddy buddy, int oldStatus, int newStatus, void* userdata);
+typedef void (*barev_message_cb)(BarevBuddy buddy, const char* msg, void* userdata);
+typedef void (*barev_conn_state_cb)(BarevBuddy buddy, int state, void* userdata);
+
+extern void  barev_strfree(char* p);
+extern BarevClient barev_client_new(const char* nick, const char* myipv6, uint16_t port);
+extern void        barev_client_free(BarevClient c);
+extern int         barev_client_start(BarevClient c);
+extern void        barev_client_stop(BarevClient c);
+extern void        barev_client_process(BarevClient c);
+extern char*       barev_client_myjid(BarevClient c);
+extern BarevBuddy  barev_client_add_buddy(BarevClient c, const char* buddynick, const char* buddyipv6, uint16_t port);
+extern int         barev_client_connect(BarevClient c, const char* buddyjid);
+extern int         barev_client_send_message(BarevClient c, const char* buddyjid, const char* msg);
+extern int         barev_client_send_typing(BarevClient c, const char* buddyjid);
+extern int         barev_client_send_paused(BarevClient c, const char* buddyjid);
+extern void        barev_set_userdata(BarevClient c, void* userdata);
+extern void        barev_set_log_cb(BarevClient c, barev_log_cb cb);
+extern void        barev_set_typing_cb(BarevClient c, barev_typing_cb cb);
+extern void        barev_set_buddy_status_cb(BarevClient c, barev_buddy_status_cb cb);
+extern void        barev_set_message_cb(BarevClient c, barev_message_cb cb);
+extern void        barev_set_conn_state_cb(BarevClient c, barev_conn_state_cb cb);
+extern char*       barev_buddy_get_jid(BarevBuddy b);
+
+#define LINE_MAX_LEN 2048
+
+static int running = 1;
+static BarevClient g_client = 0;
+
+static void on_log(const char *lvl, const char *msg, void *ud) {
+  (void)ud;
+  printf("[%s] %s\n", lvl ? lvl : "?", msg ? msg : "");
+}
+
+static void on_msg(BarevBuddy b, const char *msg, void *ud) {
+  char *jid;
+  (void)ud;
+  jid = barev_buddy_get_jid(b);
+  printf("\n*** Message from %s: %s\n", jid ? jid : "(unknown)", msg ? msg : "");
+  if (jid) barev_strfree(jid);
+}
+
+static void on_conn(BarevBuddy b, int st, void *ud) {
+  char *jid;
+  (void)ud;
+  jid = barev_buddy_get_jid(b);
+  printf("*** Conn %s state=%d\n", jid ? jid : "(unknown)", st);
+  if (jid) barev_strfree(jid);
+}
+
+static void on_typing(BarevBuddy b, int typing, void *ud) {
+  char *jid;
+  (void)ud;
+  jid = barev_buddy_get_jid(b);
+  printf("*** %s %s typing\n", jid ? jid : "(unknown)", typing ? "is" : "stopped");
+  if (jid) barev_strfree(jid);
+}
+
+static void on_buddy_status(BarevBuddy b, int oldst, int newst, void *ud) {
+  char *jid;
+  (void)oldst; (void)ud;
+  jid = barev_buddy_get_jid(b);
+  printf("*** %s status=%d\n", jid ? jid : "(unknown)", newst);
+  if (jid) barev_strfree(jid);
+}
+
+static void show_help(void) {
+  printf("Commands:\n");
+  printf("  help\n");
+  printf("  add <nick> <ipv6> [port]\n");
+  printf("  connect <jid>\n");
+  printf("  msg <jid> <text>\n");
+  printf("  typing <jid>\n");
+  printf("  paused <jid>\n");
+  printf("  quit\n");
+}
+
+static void run_command(char *line) {
+  char *cmd = strtok(line, " ");
+  if (!cmd || !*cmd) return;
+
+  if (strcmp(cmd, "help") == 0) {
+    show_help();
+  } else if (strcmp(cmd, "add") == 0) {
+    char *nick = strtok(0, " ");
+    char *ip = strtok(0, " ");
+    char *p = strtok(0, " ");
+    uint16_t port = (uint16_t)(p ? atoi(p) : 1337);
+    BarevBuddy b;
+    char *jid;
+    if (!nick || !ip) {
+      printf("usage: add <nick> <ipv6> [port]\n");
+      return;
+    }
+    b = barev_client_add_buddy(g_client, nick, ip, port);
+    jid = b ? barev_buddy_get_jid(b) : 0;
+    if (jid) {
+      printf("added %s\n", jid);
+      barev_strfree(jid);
+    } else {
+      printf("failed add\n");
+    }
+  } else if (strcmp(cmd, "connect") == 0) {
+    char *jid = strtok(0, " ");
+    if (!jid) printf("usage: connect <jid>\n");
+    else printf("connect %s\n", barev_client_connect(g_client, jid) ? "ok" : "fail");
+  } else if (strcmp(cmd, "msg") == 0) {
+    char *jid = strtok(0, " ");
+    char *txt = strtok(0, "");
+    if (!jid || !txt) printf("usage: msg <jid> <text>\n");
+    else printf("send %s\n", barev_client_send_message(g_client, jid, txt) ? "ok" : "fail");
+  } else if (strcmp(cmd, "typing") == 0) {
+    char *jid = strtok(0, " ");
+    if (!jid) printf("usage: typing <jid>\n");
+    else printf("typing %s\n", barev_client_send_typing(g_client, jid) ? "ok" : "fail");
+  } else if (strcmp(cmd, "paused") == 0) {
+    char *jid = strtok(0, " ");
+    if (!jid) printf("usage: paused <jid>\n");
+    else printf("paused %s\n", barev_client_send_paused(g_client, jid) ? "ok" : "fail");
+  } else if (strcmp(cmd, "quit") == 0 || strcmp(cmd, "exit") == 0) {
+    running = 0;
+  } else {
+    printf("unknown command: %s\n", cmd);
+  }
+}
+
+int main(int argc, char **argv) {
+  char line[LINE_MAX_LEN];
+  char *myjid;
+
+  if (argc < 3) {
+    printf("usage: %s <nick> <my_ipv6> [port]\n", argv[0]);
+    return 2;
+  }
+
+  g_client = barev_client_new(argv[1], argv[2], (uint16_t)((argc > 3) ? atoi(argv[3]) : 1337));
+  if (!g_client) return 1;
+
+  barev_set_userdata(g_client, 0);
+  barev_set_log_cb(g_client, on_log);
+  barev_set_typing_cb(g_client, on_typing);
+  barev_set_buddy_status_cb(g_client, on_buddy_status);
+  barev_set_message_cb(g_client, on_msg);
+  barev_set_conn_state_cb(g_client, on_conn);
+
+  if (!barev_client_start(g_client)) {
+    barev_client_free(g_client);
+    return 1;
+  }
+
+  myjid = barev_client_myjid(g_client);
+  printf("barevclient-libbarev started as %s\n", myjid ? myjid : "(unknown)");
+  if (myjid) barev_strfree(myjid);
+  show_help();
+
+  while (running) {
+    fd_set rfds;
+    struct timeval tv;
+    int rc;
+
+    barev_client_process(g_client);
+
+    FD_ZERO(&rfds);
+    FD_SET(0, &rfds);
+    tv.tv_sec = 0;
+    tv.tv_usec = 20000;
+    rc = select(1, &rfds, 0, 0, &tv);
+    if (rc > 0 && FD_ISSET(0, &rfds)) {
+      printf("> ");
+      fflush(stdout);
+      if (!fgets(line, sizeof(line), stdin)) break;
+      line[strcspn(line, "\r\n")] = '\0';
+      run_command(line);
+    }
+  }
+
+  barev_client_stop(g_client);
+  barev_client_free(g_client);
+  return 0;
+}
diff --git a/barevclient.c b/barevclient.c
new file mode 100644
index 0000000..1dccb9f
--- /dev/null
+++ b/barevclient.c
@@ -0,0 +1,187 @@
+#include <barev.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+
+#define LINE_MAX_LEN 2048
+
+static int running = 1;
+static barev_client_t *g_client = 0;
+
+static void on_log(const char *lvl, const char *msg, void *ud) {
+  (void)ud;
+  printf("[%s] %s\n", lvl ? lvl : "?", msg ? msg : "");
+}
+
+static void on_msg(barev_buddy_t *b, const char *msg, void *ud) {
+  (void)ud;
+  printf("\n*** Message from %s: %s\n> ", barev_buddy_get_jid(b), msg ? msg : "");
+  fflush(stdout);
+}
+
+static void on_conn(barev_buddy_t *b, int st, void *ud) {
+  (void)ud;
+  printf("*** Conn %s state=%d\n", barev_buddy_get_jid(b), st);
+}
+
+static void on_typing(barev_buddy_t *b, int typing, void *ud) {
+  (void)ud;
+  printf("*** %s %s typing\n", barev_buddy_get_jid(b), typing ? "is" : "stopped");
+}
+
+static void on_ft_offer(barev_buddy_t *b, const char *sid, const char *name, long sz, void *ud) {
+  (void)ud;
+  printf("*** FT offer from %s sid=%s file=%s size=%ld\n", barev_buddy_get_jid(b), sid, name, sz);
+}
+
+static void on_ft_progress(barev_buddy_t *b, const char *sid, long done, long total, void *ud) {
+  (void)ud; (void)b;
+  printf("*** FT %s progress %ld/%ld\n", sid, done, total);
+}
+
+static void on_ft_complete(barev_buddy_t *b, const char *sid, const char *path, void *ud) {
+  (void)ud;
+  printf("*** FT complete with %s sid=%s path=%s\n", barev_buddy_get_jid(b), sid, path);
+}
+
+static void on_ft_error(barev_buddy_t *b, const char *sid, const char *err, void *ud) {
+  (void)ud;
+  printf("*** FT error with %s sid=%s err=%s\n", barev_buddy_get_jid(b), sid, err ? err : "");
+}
+
+static void show_help(void) {
+  printf("Commands:\n");
+  printf("  help\n");
+  printf("  add <nick> <ipv6> [port]\n");
+  printf("  connect <jid>\n");
+  printf("  msg <jid> <text>\n");
+  printf("  typing <jid>\n");
+  printf("  paused <jid>\n");
+  printf("  sendfile <jid> <name> <size>\n");
+  printf("  acceptfile <sid> [save_path]\n");
+  printf("  rejectfile <sid>\n");
+  printf("  quit\n");
+}
+
+static void run_command(char *line) {
+  char *cmd = strtok(line, " ");
+  if (!cmd || !*cmd) return;
+
+  if (strcmp(cmd, "help") == 0) {
+    show_help();
+  } else if (strcmp(cmd, "add") == 0) {
+    char *nick = strtok(0, " ");
+    char *ip = strtok(0, " ");
+    char *p = strtok(0, " ");
+    unsigned short port = (unsigned short)(p ? atoi(p) : BAREV_DEFAULT_PORT);
+    barev_buddy_t *b;
+    if (!nick || !ip) {
+      printf("usage: add <nick> <ipv6> [port]\n");
+      return;
+    }
+    b = barev_client_add_buddy(g_client, nick, ip, port);
+    if (b) printf("added %s\n", barev_buddy_get_jid(b));
+    else printf("failed add\n");
+  } else if (strcmp(cmd, "connect") == 0) {
+    char *jid = strtok(0, " ");
+    if (!jid) printf("usage: connect <jid>\n");
+    else printf("connect %s\n", barev_client_connect(g_client, jid) ? "ok" : "fail");
+  } else if (strcmp(cmd, "msg") == 0) {
+    char *jid = strtok(0, " ");
+    char *txt = strtok(0, "");
+    if (!jid || !txt) printf("usage: msg <jid> <text>\n");
+    else printf("send %s\n", barev_client_send_message(g_client, jid, txt) ? "ok" : "fail");
+  } else if (strcmp(cmd, "typing") == 0) {
+    char *jid = strtok(0, " ");
+    if (!jid) printf("usage: typing <jid>\n");
+    else printf("typing %s\n", barev_client_send_typing(g_client, jid) ? "ok" : "fail");
+  } else if (strcmp(cmd, "paused") == 0) {
+    char *jid = strtok(0, " ");
+    if (!jid) printf("usage: paused <jid>\n");
+    else printf("paused %s\n", barev_client_send_paused(g_client, jid) ? "ok" : "fail");
+  } else if (strcmp(cmd, "sendfile") == 0) {
+    char *jid = strtok(0, " ");
+    char *name = strtok(0, " ");
+    char *sz = strtok(0, " ");
+    if (!jid || !name || !sz) {
+      printf("usage: sendfile <jid> <name> <size>\n");
+    } else {
+      printf("sendfile %s\n", barev_client_send_file_offer(g_client, jid, name, atol(sz)) ? "ok" : "fail");
+    }
+  } else if (strcmp(cmd, "acceptfile") == 0) {
+    char *sid = strtok(0, " ");
+    char *path = strtok(0, "");
+    int ok;
+    if (!sid) {
+      printf("usage: acceptfile <sid> [save_path]\n");
+      return;
+    }
+    ok = path ? barev_client_accept_file_offer_as(g_client, sid, path)
+              : barev_client_accept_file_offer(g_client, sid);
+    printf("acceptfile %s\n", ok ? "ok" : "fail");
+  } else if (strcmp(cmd, "rejectfile") == 0) {
+    char *sid = strtok(0, " ");
+    if (!sid) printf("usage: rejectfile <sid>\n");
+    else printf("rejectfile %s\n", barev_client_reject_file_offer(g_client, sid) ? "ok" : "fail");
+  } else if (strcmp(cmd, "quit") == 0 || strcmp(cmd, "exit") == 0) {
+    running = 0;
+  } else {
+    printf("unknown command: %s\n", cmd);
+  }
+}
+
+int main(int argc, char **argv) {
+  char line[LINE_MAX_LEN];
+
+  if (argc < 3) {
+    printf("usage: %s <nick> <my_ipv6> [port]\n", argv[0]);
+    return 2;
+  }
+
+  g_client = barev_client_new(argv[1], argv[2], (unsigned short)((argc > 3) ? atoi(argv[3]) : BAREV_DEFAULT_PORT));
+  if (!g_client) return 1;
+
+  barev_set_log_cb(g_client, on_log);
+  barev_set_message_cb(g_client, on_msg);
+  barev_set_conn_state_cb(g_client, on_conn);
+  barev_set_typing_cb(g_client, on_typing);
+  barev_set_ft_offer_cb(g_client, on_ft_offer);
+  barev_set_ft_progress_cb(g_client, on_ft_progress);
+  barev_set_ft_complete_cb(g_client, on_ft_complete);
+  barev_set_ft_error_cb(g_client, on_ft_error);
+
+  if (!barev_client_start(g_client)) {
+    barev_client_free(g_client);
+    return 1;
+  }
+
+  printf("barvclient started as %s\n", barev_client_myjid(g_client));
+  show_help();
+
+  while (running) {
+    fd_set rfds;
+    struct timeval tv;
+    int rc;
+
+    barev_client_process(g_client);
+
+    FD_ZERO(&rfds);
+    FD_SET(0, &rfds);
+    tv.tv_sec = 0;
+    tv.tv_usec = 20000;
+    rc = select(1, &rfds, 0, 0, &tv);
+    if (rc > 0 && FD_ISSET(0, &rfds)) {
+      printf("> ");
+      fflush(stdout);
+      if (!fgets(line, sizeof(line), stdin)) break;
+      line[strcspn(line, "\r\n")] = '\0';
+      run_command(line);
+    }
+  }
+
+  barev_client_stop(g_client);
+  barev_client_free(g_client);
+  return 0;
+}
diff --git a/barevclient.pas b/barevclient.pas
new file mode 100644
index 0000000..19608de
--- /dev/null
+++ b/barevclient.pas
@@ -0,0 +1,255 @@
+program BarevClientPascal;
+
+{$mode objfpc}{$H+}
+
+uses
+  {$IFDEF UNIX}
+  cthreads,
+  {$ENDIF}
+  Classes, SysUtils,
+  Barev, BarevTypes;
+
+type
+  THandler = class
+    procedure OnLog(const L, M: string);
+    procedure OnStatus(B: TBarevBuddy; OldS, NewS: TBuddyStatus);
+    procedure OnMsg(B: TBarevBuddy; const Msg: string);
+    procedure OnConn(B: TBarevBuddy; S: TConnectionState);
+    procedure OnTyping(B: TBarevBuddy; IsTyping: Boolean);
+    procedure OnFTOffer(B: TBarevBuddy; const Sid, Name: string; Size: Int64);
+    procedure OnFTProgress(B: TBarevBuddy; const Sid: string; Done, Total: Int64);
+    procedure OnFTComplete(B: TBarevBuddy; const Sid, Path: string);
+    procedure OnFTError(B: TBarevBuddy; const Sid, Err: string);
+  end;
+
+  TNetThread = class(TThread)
+  private
+    C: TBarevClient;
+  protected
+    procedure Execute; override;
+  public
+    constructor Create(AClient: TBarevClient);
+  end;
+
+var
+  Client: TBarevClient;
+
+procedure THandler.OnLog(const L, M: string);
+begin
+  WriteLn('[', L, '] ', M);
+end;
+
+procedure THandler.OnStatus(B: TBarevBuddy; OldS, NewS: TBuddyStatus);
+begin
+  WriteLn('*** ', B.Nick, ' is now ', StatusToString(NewS));
+end;
+
+procedure THandler.OnMsg(B: TBarevBuddy; const Msg: string);
+begin
+  WriteLn;
+  WriteLn('*** Message from ', B.Nick, ': ', Msg);
+  Write('> '); Flush(Output);
+end;
+
+procedure THandler.OnConn(B: TBarevBuddy; S: TConnectionState);
+begin
+  WriteLn('*** Connection to ', B.Nick, ': ', Ord(S));
+end;
+
+procedure THandler.OnTyping(B: TBarevBuddy; IsTyping: Boolean);
+begin
+  WriteLn;
+  if IsTyping then WriteLn('*** ', B.Nick, ' is typing...')
+  else WriteLn('*** ', B.Nick, ' stopped typing');
+  Write('> '); Flush(Output);
+end;
+
+procedure THandler.OnFTOffer(B: TBarevBuddy; const Sid, Name: string; Size: Int64);
+begin
+  WriteLn;
+  WriteLn('*** FT offer from ', B.JID, ' sid=', Sid, ' file=', Name, ' size=', Size);
+  Write('> '); Flush(Output);
+end;
+
+procedure THandler.OnFTProgress(B: TBarevBuddy; const Sid: string; Done, Total: Int64);
+begin
+  WriteLn('*** FT ', Sid, ' ', Done, '/', Total);
+end;
+
+procedure THandler.OnFTComplete(B: TBarevBuddy; const Sid, Path: string);
+begin
+  WriteLn('*** FT complete sid=', Sid, ' path=', Path);
+end;
+
+procedure THandler.OnFTError(B: TBarevBuddy; const Sid, Err: string);
+begin
+  WriteLn('*** FT error sid=', Sid, ' err=', Err);
+end;
+
+constructor TNetThread.Create(AClient: TBarevClient);
+begin
+  inherited Create(False);
+  FreeOnTerminate := False;
+  C := AClient;
+end;
+
+procedure TNetThread.Execute;
+begin
+  while not Terminated do
+  begin
+    C.Process;
+    Sleep(20);
+  end;
+end;
+
+procedure ShowHelp;
+begin
+  WriteLn('Commands:');
+  WriteLn('  help');
+  WriteLn('  add <nick> <ipv6> [port]');
+  WriteLn('  connect <jid>');
+  WriteLn('  msg <jid> <text>');
+  WriteLn('  typing <jid>');
+  WriteLn('  paused <jid>');
+  WriteLn('  sendfile <jid> <path>');
+  WriteLn('  acceptfile <sid> <saveas>');
+  WriteLn('  rejectfile <sid>');
+  WriteLn('  quit');
+end;
+
+procedure RunLoop;
+var
+  Line, Cmd, A1, A2, A3, Sid: string;
+  Parts: TStringList;
+  B: TBarevBuddy;
+begin
+  Parts := TStringList.Create;
+  try
+    while True do
+    begin
+      Write('> ');
+      ReadLn(Line);
+      Line := Trim(Line);
+      if Line = '' then Continue;
+
+      Parts.Clear;
+      ExtractStrings([' '], [], PChar(Line), Parts);
+      if Parts.Count = 0 then Continue;
+
+      Cmd := LowerCase(Parts[0]);
+
+      if Cmd = 'help' then ShowHelp
+      else if Cmd = 'quit' then Break
+      else if Cmd = 'add' then
+      begin
+        if Parts.Count < 3 then begin WriteLn('usage: add <nick> <ipv6> [port]'); Continue; end;
+        A1 := Parts[1]; A2 := Parts[2];
+        if Parts.Count >= 4 then A3 := Parts[3] else A3 := IntToStr(BAREV_DEFAULT_PORT);
+        B := Client.AddBuddy(A1, A2, StrToIntDef(A3, BAREV_DEFAULT_PORT));
+        if Assigned(B) then WriteLn('added ', B.JID) else WriteLn('add failed');
+      end
+      else if Cmd = 'connect' then
+      begin
+        if Parts.Count < 2 then WriteLn('usage: connect <jid>')
+        else if Client.ConnectToBuddy(Parts[1]) then WriteLn('connect ok')
+        else WriteLn('connect failed');
+      end
+      else if Cmd = 'msg' then
+      begin
+        if Parts.Count < 3 then begin WriteLn('usage: msg <jid> <text>'); Continue; end;
+        A1 := Parts[1];
+        A2 := Copy(Line, Pos(A1, Line) + Length(A1) + 1, MaxInt);
+        if Client.SendMessage(A1, A2) then WriteLn('send ok') else WriteLn('send failed');
+      end
+      else if Cmd = 'typing' then
+      begin
+        if Parts.Count < 2 then WriteLn('usage: typing <jid>')
+        else if Client.SendTyping(Parts[1]) then WriteLn('typing ok')
+        else WriteLn('typing failed');
+      end
+      else if Cmd = 'paused' then
+      begin
+        if Parts.Count < 2 then WriteLn('usage: paused <jid>')
+        else if Client.SendPaused(Parts[1]) then WriteLn('paused ok')
+        else WriteLn('paused failed');
+      end
+      else if Cmd = 'sendfile' then
+      begin
+        if Parts.Count < 3 then WriteLn('usage: sendfile <jid> <path>')
+        else begin
+          B := Client.FindBuddyByJID(Parts[1]);
+          if not Assigned(B) then WriteLn('buddy not found')
+          else begin
+            Sid := Client.FileTransfer.OfferFile(B, Parts[2]);
+            if Sid <> '' then WriteLn('offer sent sid=', Sid) else WriteLn('offer failed');
+          end;
+        end;
+      end
+      else if Cmd = 'acceptfile' then
+      begin
+        if Parts.Count < 3 then WriteLn('usage: acceptfile <sid> <saveas>')
+        else if Client.FileTransfer.AcceptOffer(Parts[1], Parts[2]) then WriteLn('accept ok')
+        else WriteLn('accept failed');
+      end
+      else if Cmd = 'rejectfile' then
+      begin
+        if Parts.Count < 2 then WriteLn('usage: rejectfile <sid>')
+        else if Client.FileTransfer.RejectOffer(Parts[1]) then WriteLn('reject ok')
+        else WriteLn('reject failed');
+      end
+      else WriteLn('unknown command');
+    end;
+  finally
+    Parts.Free;
+  end;
+end;
+
+var
+  H: THandler;
+  T: TNetThread;
+  Nick, Ip, PStr: string;
+  Port: Word;
+begin
+  if ParamCount < 2 then
+  begin
+    WriteLn('usage: barevclient <nick> <my_ipv6> [port]');
+    Halt(2);
+  end;
+
+  Nick := ParamStr(1);
+  Ip := ParamStr(2);
+  if ParamCount >= 3 then PStr := ParamStr(3) else PStr := IntToStr(BAREV_DEFAULT_PORT);
+  Port := StrToIntDef(PStr, BAREV_DEFAULT_PORT);
+
+  H := THandler.Create;
+  Client := TBarevClient.Create(Nick, Ip, Port);
+  try
+    Client.OnLog := @H.OnLog;
+    Client.OnBuddyStatus := @H.OnStatus;
+    Client.OnMessageReceived := @H.OnMsg;
+    Client.OnConnectionState := @H.OnConn;
+    Client.OnTypingNotification := @H.OnTyping;
+    Client.FileTransfer.OnFileOffer := @H.OnFTOffer;
+    Client.FileTransfer.OnProgress := @H.OnFTProgress;
+    Client.FileTransfer.OnComplete := @H.OnFTComplete;
+    Client.FileTransfer.OnError := @H.OnFTError;
+
+    if not Client.Start then Halt(1);
+
+    T := TNetThread.Create(Client);
+    try
+      WriteLn('barevclient (pascal) started as ', Client.MyJID);
+      ShowHelp;
+      RunLoop;
+    finally
+      T.Terminate;
+      T.WaitFor;
+      T.Free;
+    end;
+
+    Client.Stop;
+  finally
+    Client.Free;
+    H.Free;
+  end;
+end.