···11+local comlink = require('comlink')
22+local watcher = require('watcher')
33+44+local function run_command_and_trim(command)
55+ -- Execute the command and capture output
66+ local handle = io.popen(command)
77+ assert(handle, "Failed to execute command")
88+99+ -- Read the output
1010+ local result = handle:read("*a")
1111+ local success, exit_type, exit_code = handle:close()
1212+1313+ -- Check if command executed successfully
1414+ assert(success, "Command failed with exit code: " .. (exit_code or "unknown"))
1515+1616+ -- Trim whitespace (including newlines)
1717+ if result then
1818+ result = result:match("^%s*(.-)%s*$")
1919+ end
2020+2121+ return result
2222+end
2323+2424+local config = {
2525+ server = 'chat.sr.ht',
2626+ user = 'reykjalin',
2727+ nick = 'reykjalin',
2828+ password = run_command_and_trim('op read "op://Private/chat.sr.ht app password/password"'),
2929+ tls = true,
3030+ real_name = 'Kristófer Reykjalín',
3131+}
3232+3333+-- Set channel watch config to receive notifications on any message in the provided channels.
3434+local connection = comlink.connect(config)
3535+connection.on_message = watcher.on_message
3636+3737+watcher.channels = {
3838+ "#comlink",
3939+ "#ghostty",
4040+ "#vaxis",
4141+}
4242+4343+-- Keybindings.
4444+comlink.bind("ctrl+c", "quit")
4545+comlink.bind("ctrl+n", "next-channel")
4646+comlink.bind("ctrl+j", "next-channel")
4747+comlink.bind("ctrl+p", "prev-channel")
4848+comlink.bind("ctrl+l", "redraw")
4949+5050+local function mark_read(_)
5151+ local maybe_channel = comlink.selected_channel()
5252+ if maybe_channel then
5353+ maybe_channel:mark_read()
5454+ end
5555+end
5656+comlink.bind("ctrl+r", mark_read)
5757+5858+-- /tableflip and /shrug.
5959+local function tableflip(_)
6060+ local channel = comlink.selected_channel()
6161+ if channel then
6262+ channel:send_msg("(╯°□°)╯︵ ┻━┻")
6363+ end
6464+end
6565+6666+local function shrug(_)
6767+ local channel = comlink.selected_channel()
6868+ if channel then
6969+ channel:send_msg("¯\\_(ツ)_/¯")
7070+ end
7171+end
7272+7373+comlink.add_command("tableflip", tableflip)
7474+comlink.add_command("shrug", shrug)
7575+7676+-- /fmt <text>.
7777+local function replace(string, old, new)
7878+ local s = string
7979+ local search_start_idx = 1
8080+8181+ while true do
8282+ local start_idx, end_idx = s:find(old, search_start_idx, true)
8383+ if not start_idx then
8484+ break
8585+ end
8686+8787+ local postfix = s:sub(end_idx + 1)
8888+ s = s:sub(1, (start_idx - 1)) .. new .. postfix
8989+9090+ search_start_idx = -1 * postfix:len()
9191+ end
9292+9393+ return s
9494+end
9595+9696+local function format(cmdline)
9797+ local channel = comlink.selected_channel()
9898+ if channel then
9999+ local s = replace(cmdline, "**", "\x02")
100100+ s = replace(s, "*", "\x1D")
101101+ s = replace(s, "~", "\x1E")
102102+ s = replace(s, "_", "\x1F")
103103+ s = replace(s, "<blue>", "\x0302")
104104+ s = replace(s, "</blue>", "\x03")
105105+ channel:send_msg(s)
106106+ end
107107+end
108108+comlink.add_command("fmt", format)
+62
comlink/watcher.lua
···11+local comlink = require("comlink")
22+33+---The Watcher module sends a notification for every message in a "watched" channel
44+---@class Watcher
55+---
66+---@field channels string[] List of channels that are being watched
77+local M = {
88+ channels = {},
99+}
1010+1111+---Adds a channel to the list of watchers
1212+---@param channel string The channel to add
1313+function M.add(channel)
1414+ if channel == nil or channel == "" then
1515+ local maybe_channel = comlink.selected_channel()
1616+ if maybe_channel then
1717+ channel = maybe_channel:name()
1818+ end
1919+ end
2020+ table.insert(M.channels, channel)
2121+ comlink.notify("comlink", "watching " .. channel)
2222+end
2323+2424+---Removes a channel from the list of watchers
2525+---@param channel string The channel to remove
2626+function M.remove(channel)
2727+ if channel == "" then
2828+ local maybe_channel = comlink.selected_channel()
2929+ if maybe_channel then
3030+ channel = maybe_channel:name()
3131+ end
3232+ end
3333+ for i, chan in ipairs(M.channels) do
3434+ if chan == channel then
3535+ table.remove(M.channels, i)
3636+ comlink.notify("comlink", "removed watcher for " .. channel)
3737+ return
3838+ end
3939+ end
4040+end
4141+4242+---Callback when a message is received
4343+---@param channel string The channel the message was sent in
4444+---@param sender string The sender of the message
4545+---@param msg string The sender of the message
4646+function M.on_message(channel, sender, msg)
4747+ for _, watched in ipairs(M.channels) do
4848+ if watched == channel then
4949+ if channel:sub(1, 2) == "#" then
5050+ comlink.notify(sender .. " in " .. channel, msg)
5151+ return
5252+ end
5353+ comlink.notify(sender, msg)
5454+ return
5555+ end
5656+ end
5757+end
5858+5959+comlink.add_command("watch", M.add)
6060+comlink.add_command("unwatch", M.remove)
6161+6262+return M
+23
wezterm/wezterm.lua
···11+-- Pull in the wezterm API
22+local wezterm = require 'wezterm'
33+44+-- This will hold the configuration.
55+local config = wezterm.config_builder()
66+77+-- This is where you actually apply your config choices
88+99+-- Hide the tab bar when only 1 tab.
1010+config.hide_tab_bar_if_only_one_tab = true
1111+1212+-- Use the simpler tab bar.
1313+config.use_fancy_tab_bar = false
1414+1515+-- Set fonts.
1616+config.font = wezterm.font_with_fallback({'0xProto', 'JetBrains Mono NL', 'FiraCode Nerd Font Mono'})
1717+config.font_size = 14.0
1818+1919+-- No window decorations.
2020+-- config.window_decorations = "RESIZE"
2121+2222+-- and finally, return the configuration to wezterm
2323+return config