It’s a lot of work trying to keep up with what’s happening in your code repos, let alone deciding what to focus on, but thankfully here’s where AI can come in handy. I don’t think AI can actually do your job for you, but it provides excellent base content to work from, hence why AI is the perfect partner for this type of problem.
So you can access your GitHub issues with the command line and you have an AI chatbot that also uses a CLI. Why not write a little script that can summarize key trends in issues, both for bug reports and enhancement requests? That can help us identify what to focus on. Better yet, let’s automatically get those summaries via email to help us stay on top of things!
Required doodads
You’ll need to have Pop and Mods installed to use the provided script. Both of these programs also require environment variables to be set. You can define these variables in the script, as you’ll see in the example below.
Alternatively, you can set your environment variables in your
.bashrc
/.zshrc
, then source the file with a good ‘ol source ~/.zshrc
in
your existing terminal session - note that any new sessions will use the
updated rc
file anyway, so don’t worry about sourcing that file for new
sessions.
Because my dotfiles are public, I like to use Skate to protect my API keys, so I don’t accidentally leak any secrets. Here’s how the two options compare:
export OPENAI_API_KEY="<private key>"
vs export OPENAI_API_KEY=$(skate get openai-key)
Under the hood
Mods
Mods
- Built ItselfYep
AI on the command line.
Mods is a command line interface for interacting with AI chatbots, meaning, it’s entirely script-friendly. It works with OpenAI-compatible endpoints and LocalAI models. Mods uses GPT-4 by default and supports LocalAI models running on port 8080. It falls back to using GPT-3.5 Turbo if GPT-4 isn’t available. If you wanna learn more, you can read all about it.
For this script to work, you’ll need an OPENAI_API_KEY
environment variable
set. Get your key.
Pop
Pop
- EmailSent
Send emails from your terminal
Pop is a terminal-based program, offered as both a TUI and CLI, that allows you to send emails from your terminal. All emails send through Resend APIs or you can use a custom SMTP setup.
To use Pop with Resend you’ll need to export a RESEND_API_KEY
, you can also
set RESEND_FROM
to avoid having to type in your sending email. If you run
into any difficulties using your own domain with resend, you can always have it
send from onboarding@resend.dev
.
export RESEND_API_KEY=$(pass RESEND_API_KEY)
export RESEND_FROM=pop@charm.sh
Alternatively, you can use a custom SMTP configuration with Pop.
export POP_SMTP_HOST=smtp.gmail.com
export POP_SMTP_PORT=587
export POP_SMTP_USERNAME=pop@charm.sh
export POP_SMTP_PASSWORD=hunter2
For this script to work, you’ll need the RESEND_API_KEY
environment variable
or custom SMTP server configurations set. Get your key.
Putting it all together
Writing the script
Alright, now that you know how the whole thing works, let’s do something useful.
- Set up the
OPENAI_API_KEY
environment variable. - Set up the
RESEND_API_KEY
environment variable (or configure your SMTP setup). - Run the following script and provide the path to a local git repository as an argument
#!/bin/sh
export RESEND_API_KEY=$(skate get resend)
export OPENAI_API_KEY=$(skate get open-ai)
# fail if no repo provided
if [ $# -ne 1 ]
then
printf "Please provide an absolute path to a local code repository with a GitHub remote upstream."
exit 1
else
cd $1
( gh issue list |
mods "what are recurring themes in bug reports given this output" && gh issue list |
mods "what are recurring themes in enhancements given this output" ) |
pop \
--from onboarding@resend.dev \
--to youremail@resend.dev \
--subject $(basename $PWD)
fi
(PSSSST! basename $PWD
provides the name of the project whose issues are
being summarized)
If you wanted these summaries to recur on a weekly basis (or whatever other regular interval you’d like) you can choose to activate this script in a cron job.
Scheduling the script
Cron?!
Cron jobs are available on Unix-based systems and on Windows through a Unix-based subsystem like WSL (Windows Subsystem for Linux). If you’re on a Windows machine, you can also use Windows Task Scheduler to automate tasks, but that is beyond the scope of this post.
Cron is a daemon to execute scheduled commands. By default, it runs in the user’s home directory.
Crontabs are files that are used to schedule the execution of programs. These files have all the instructions that you might need for the cron job to run. Another, very popular alternative, is Systemd timer that also allows you to schedule tasks. Cron is the more popular option, but can be hard to troubleshoot by comparison to Systemd timers. If you’d like to learn more, you can do so here
Configuring cron
Run crontab -e
to create a new crontab (use man crontab
or tldr crontab
).
The first iteration might look something like this:
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/bashbunni/go/bin:/usr/local/go:/home/bashbunni/go
GOPATH=$HOME/go
*/1 * * * * /home/bashbunni/scripts/repo-summary /home/bashbunni/charm/bubbletea >> /home/bashbunni/scripts/repo-summary.log 2>&1
Alas, there’s still room to improve. Try to spot the differences!
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/bashbunni/go/bin:/usr/local/go:/home/bashbunni/go
GOPATH=$HOME/go
*/1 * * * * repo-summary $HOME/charm/bubbletea >> /var/log/repo-summary/bubbletea.log 2>&1
To clean up the script, you have to run chmod +x repo-summary
, and move the
executable to /usr/bin
which allows the script to be executable from anywhere.
You’ll also want to move logging to /var/log
which is typically where programs
post logs on Linux. If you’re not using Linux, you should change this to
whatever is idiomatic with your operating system. You need to change the
permissions on the /var/log/repo-summary
directory with sudo chmod ugo+w /var/log/repo-summary
so that the cron job has write access to the file.
That’s a fair amount of steps, so here’s that breakdown as a list:
chmod +x repo-summary
sudo mv repo-summary /usr/bin/repo-summary
sudo mkdir /var/log/repo-summary
sudo chmod ugo+w /var/log/repo-summary
You need sudo for some of these commands given that they’re in protected directories.
Overengineering with containers?!
After spending a bunch of time getting this working in Docker, it turns out it’s not worth containerizing given that it depends on a git repo to work. I included this section anyway because I’m sure at least one developer will look at this and say “I wonder if I could containerize it”… Just like I did.
Pros of a container:
- User of the script gets a “plug-and-play” experience.
- Export env variables from host to container.
- Centralize dependency management.
How to containerize it
The deciding factor is that you need a local git repo to use with the script, due to depending on the GitHub CLI results for mods to work. This gets complicated when you containerize it. If you are curious to see what the Dockerfile might look like, I’ve included it to indulge your curiosities.
FROM golang:1.21-alpine
# TODO Copy your own version of the script to run
# Don't forget, the file has to be in the same directory as the Dockerfile
COPY repo-summary /usr/bin/repo-summary
RUN chmod +x /usr/bin/repo-summary
# INSTALL DEPENDENCIES
ENV PATH="/usr/local/go/bin:${PATH}"
# Go apps
RUN go install github.com/charmbracelet/pop@latest
RUN go install github.com/charmbracelet/mods@latest
# gh cli
# If mods and pop were available through apk, we could have just used an alpine
# container instead of using the larger, go-alpine container.
RUN apk add github-cli
RUN rm /var/cache/apk/*
WORKDIR /home/bubbletea
CMD repo-summary
Then you can run it with
docker run -it -v $(pwd):/home/bubbletea -e
GH_TOKEN=$GH_TOKEN -e RESEND_API_KEY=$(skate get resend) -e
OPENAI_API_KEY=$(skate get open-ai) <image-id> repo-summary ./
Okay, okay, fine I’ll break down what that command does. You have to run
docker build --tag 'bubbletea-summary' .
in the same directory as your
Dockerfile to create your custom image. Run the image as a container and
start an interactive terminal session in that container with -it
. From there,
mount the desired repo as a volume (matches the WORKDIR
specified in the
Dockerfile) with -v
. Then pass along your environment variables from the host
machine to the container with -e
. It’s important to provide the image ID,
which you can see with docker images
. It will run the repo-summary
script
in the WORKDIR
of the container.
Notice that you need to define the environment variables needed by the
container as arguments to the docker run
command. If you were to specify
these environment variables in the Dockerfile, then they would only be
accessible at build time, not during the lifespan of the container. (Thank you,
Ayman!)
If you want to challenge yourself, you can try to set up a cron job in the docker container given the information provided. Let us know how you do!
Whatcha think?
Have some feedback on this post? We’d love to hear. Let us know in Discord or via email at vt100@charm.sh.