Awesome
AFA (AI for All)
$$\text{AI for All} := \forall x \in X , \exists \mathrm{AI}(x)$$
AFA is a terminal-friendly AI command. It enables new behaviors through ad-hoc prompts without requiring programming implementation.
AFA processes text streams as its input and output. Text is a flexible and universal interface, making it easy to interact across various environments and tools. Additionally, it integrates with rich TUI tools to provide an interactive chat experience in the terminal.
With AFA, let's collaborate with both existing and unknown commands in line with the UNIX philosophy.
- Prompt programs that do one thing and do it well.
- Prompt programs to work together.
- Prompt programs to handle text streams, because that is a universal interface.
Demo
Chat using a Rich TUI
Command Suggestions using ZLE
Code Suggestions using Vim
Features
- Acts as a terminal-friendly AI command.
- Acts as a chat client with a rich terminal user interface (TUI).
- Supports contextual prompts for both system and user using templates.
- Accepts prompts, standard input, and file paths as context.
- Manages sessions, allowing for quick resumption via the
resume
sub-command. - Supports structured output with a safely escaped JSON option, facilitating easy integration with other commands.
- The core application operates independently of third-party libraries.
- Supports
OpenAI
as an AI model (support for other AI models is planned for the future).
Usage
Run the interactive chat with:
afa new
Use a rich TUI viewer in chat mode with:
afa new -V
Start the interactive chat with additional information by executing:
echo $ERROR_MESSAGE | afa new -p "What is happening?" /path/to/file1 /path/to/file2
# Please be cautious; when standard input is provided, interactive mode is disabled.
# Consider using process substitution.
#=> afa new -p "What is happening?" /path/to/file1 /path/to/file2 <( echo $ERROR_MESSAGE )
Continue from the last session with:
afa resume
Continue from a specified session with:
# The command `afa list` displays past sessions.
afa source -l SESSION_NAME
Specify the user prompt with:
# `Message`, `MessageStdin`, and `Files` that include `File` with `Name` and `Content` members can be used in the template file.
echo "Please explain the following.\n{{ (index .Files 0).Content }}" > CONFIG_PATH/templates/user/explain.tmpl
afa -u explain /path/to/file
Run script mode with:
# When no subcommand is specified, afa run as `afa new -script` which means `-I=false -H=false -S=false -V=false -L=false`
pbpaste | afa -p "Transrate this" | pbcopy
Specify the schema for structured output with:
cat <<< EOS > CONFIG_PATH/schemas/command_suggestion.json
{
"type": "object",
"properties": {
"suggested_command": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"suggested_command"
]
}
EOS
P="List Go files from the directory named 'internal' and print the first line of each file."
afa new -script -j command_suggestion -p $P | jq -r '.suggested_command'
#=> find internal -name '*.go' -exec head -n 1 {} \;
Installation
Follow these steps to install the tool and viewer:
# Install the core application
go install github.com/monochromegane/afa@latest
# Install the TUI viewer
go install github.com/monochromegane/afa-tui@latest
You can download binaries from gihub releases.
- AFA: https://github.com/monochromegane/afa/releases
- AFA-TUI: https://github.com/monochromegane/afa-tui/releases
You can also use Homebrew:
brew install monochromegane/tap/afa monochromegane/tap/afa-tui
Configuration
Initialize the setup:
afa init
Default Options
The configuration file named afa/option.json
should be located at the path specified by Go's os.UserConfigDir.
On Unix systems, it returns
$XDG_CONFIG_HOME
as specified by https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html if non-empty, else$HOME/.config
. On Darwin, it returns$HOME/Library/Application Support
. On Windows, it returns%AppData%
. On Plan 9, it returns$home/lib
.
API Keys
The configuration file named CONFIG_PATH/afa/secrets.json
.
Tempates
AFA supports the use of template files, which can be placed in the templates/{system,user}
directories with the .tmpl
extension.
You can specify the template to use by providing the name without the extension using -s
(for system templates) or -u
(for user templates) options.
Templates allow you to dynamically insert information. You can utilize the following placeholders within your templates:
Message
: A string that can be replaced with a specific message by-p
opition.MessageStdin
: This placeholder can take input from the standard input as a message.Files
: A collection of file objects, where each file hasName
andContent
members.
Schemas
Similar to templates, schema files can be placed in the schemas
directory and should have a .json
extension.
You can specify the schema to use by providing the name without the .json
extension using the -j
option.
Cache
Sessions
The session files named afa/sessions/SESSION_NAME.json
should be located at the path specified by Go's os.UserCacheDir.
On Unix systems, it returns
$XDG_CACHE_HOME
as specified by https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html if non-empty, else$HOME/.cache
. On Darwin, it returns$HOME/Library/Caches
. On Windows, it returns%LocalAppData%
. On Plan 9, it returns$home/lib/cache
.
Practical Examples
Command Suggestions using ZLE
Firstly, we prepare a wrapper for suggestions named afa_command_suggestion.zsh
, as shown below:
#!/bin/zsh
prompt_=""
while getopts p: OPT
do
case $OPT in
"p" ) prompt_="$OPTARG";;
*) echo "Error: Invalid option." >&2; exit 1;;
esac
done
shift `expr $OPTIND - 1`
suggested_command=$(afa new -script -j command_suggestion -u command_suggestion -p "$prompt_")
if [ $? -ne 0 ]; then
echo "Error: Failed to generate suggested command." >&2
exit 1
fi
command_new=$(printf "%s" "$suggested_command" | jq -r '.suggested_command')
if [ $? -ne 0 ]; then
echo "Error: No suggested command received." >&2
exit 1
fi
echo "$command_new"
And we also prepare user prompt and schema files.
CONFIG_PATH/afa/templates/user/command_suggestion.json
{
"type": "object",
"properties": {
"suggested_command": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"suggested_command"
]
}
CONFIG_PATH/afa/schemas/command_suggestion.tmpl
You are an assistant supporting operations in the terminal. Please suggest commands based on the following requirements.
## Objective
{{ .Message }}
## Requirements
- Commands executable in macOS's zsh.
- Suggest command only, without explanations or comments.
- The output should follow the provided json_schema.
## json_schema
```json
{
"suggested_command": "<Final suggested command>"
}
```
Next, we prepare a function for ZLE:
function _afa-suggest-command() {
local command=$(afa_command_suggestion.zsh -p "$BUFFER")
if [ -n "$command" ]; then
BUFFER="$command"
fi
CURSOR=$#BUFFER
zle reset-prompt
}
Finally, we add a function setting and key bindings to the .zshrc
as follows:
autoload -Uz _afa-suggest-command
zle -N afa-suggest-command _afa-suggest-command
bindkey '^G^K' afa-suggest-command
Code Suggestions using Vim
First, we prepare a wrapper for suggestions named afa_code_suggestion.zsh
, as shown below:
#!/bin/zsh
file_path=""
file_type=""
prompt_=""
while getopts f:e:p: OPT
do
case $OPT in
"f" ) file_path="$OPTARG";;
"e" ) file_type="$OPTARG";;
"p" ) prompt_="$OPTARG";;
*) echo "Error: Invalid option." >&2; exit 1;;
esac
done
shift `expr $OPTIND - 1`
code_org=$(cat)
suggested_code=$(echo "$code_org" | afa new -script -j code_suggestion -u code_suggestion -p "$prompt_" <(echo "$file_path") <(echo "$file_type") "$@")
if [ $? -ne 0 ]; then
echo "Error: Failed to generate suggested code." >&2
echo "$code_org"
exit 1
fi
code_new=$(printf "%s" "$suggested_code" | jq -r '.suggested_code')
if [ $? -ne 0 ]; then
echo "Error: No suggested code received." >&2
echo "$code_org"
exit 1
fi
echo "$code_new"
And we also prepare user prompt and schema files.
CONFIG_PATH/afa/templates/user/code_suggestion.json
{
"type": "object",
"properties": {
"suggested_code": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"suggested_code"
]
}
CONFIG_PATH/afa/schemas/code_suggestion.tmpl
You are an assistant supporting coding tasks. Please suggest code modifications or generate new code based on the following requirements.
{{- if .MessageStdin }}
## Current Code
- File: {{ (index .Files 0).Content -}}
- Language: {{ (index .Files 1).Content -}}
```
{{ .MessageStdin }}```
{{ end -}}
{{- if ge (len .Files) 3 }}
## Content of Related Files
{{ range $i, $f := .Files }}
{{- if ge $i 2 }}
- File: {{ $f.Name }}
```
{{ $f.Content }}```
{{ end -}}
{{ end }}
{{ end -}}
## Objective
{{ .Message }}
## Requirements
- Maintain the current structure of the existing functions and classes as much as possible.
- Necessary libraries are already installed, so installation steps are not required.
- Suggest code only, without explanations or comments.
- The output should follow the provided json_schema.
## json_schema
```json
{
"suggested_code": "<Final suggested code>"
}
```
Finally, we add a command for Vim:
set splitright
command DiffOrig vert new | set bt=nofile | r ++edit # | 0d_
\ | diffthis | wincmd p | diffthis
command -nargs=* -range=% -complete=file Afa <line1>,<line2>call AfaFn(<f-args>)
function AfaFn(...) range
let user_input = input("Enter prompt: ")
redraw
let cmd = a:firstline . ',' . a:lastline . '! afa_code_suggestion.zsh -f % -e %:e -p ' . shellescape(user_input) . ' ' . join(a:000, ' ')
execute cmd
endfunction
Error Message Explanations using TMUX Capture Panel Function
We prepare a Zsh function to capture the output of the last command:
function _afa-capture() {
local start_line=-100
local last_command=$(fc -l -n -1)
local capture=$(tmux capture-pane -S "$start_line" -p)
if match=$(echo "$capture" | grep -F -n "$last_command"); then
local last_line_num=$(echo "$match" | tail -n 1 | cut -d":" -f1)
local result=$(echo "$capture" | awk -v start="$last_line_num" 'NR >= start')
echo $result | afa new -u explain
else
echo "\"$last_command\" not found in the capture panel."
fi
zle reset-prompt
}
Here is an example of a general explain
user prompt template:
Please explain the following commands and their results, as well as the content of the provided files. Additionally, provide solutions if necessary.
{{ .Message }}
{{ if .MessageStdin }}
```
{{ .MessageStdin }}```
{{- end }}
{{ range .Files }}
- File: {{ .Name }}
```
{{ .Content }}```
{{ end -}}
And we add a function setting and key bindings to the .zshrc
as follows:
autoload -Uz _afa-capture
zle -N afa-capture _afa-capture
bindkey '^G^E' afa-capture
GitHub Pull Request Content Suggestions using ZLE and gh Command
Firstly, we prepare a wrapper for suggestions named afa_github_pull_request.zsh
, as shown below:
#!/bin/zsh
prompt_=""
while getopts p: OPT
do
case $OPT in
"p" ) prompt_="$OPTARG";;
*) echo "Error: Invalid option." >&2; exit 1;;
esac
done
shift `expr $OPTIND - 1`
pull_request_template=".github/pull_request_template.md"
current_branch=$(git branch --show-current)
git fetch --quiet
if ! git diff --quiet HEAD origin/"$current_branch"; then
echo "You have unsynced changes. Please push to the remote." >&2
exit 1
fi
pull_request=$(afa new -script -u github_pull_request -j github_pull_request -p "$prompt_" <( git diff --no-ext-diff origin ) <( git log --format="- %s" --no-merges origin..HEAD ))
if [ $? -ne 0 ]; then
echo "Error: Failed to generate suggested github pull request." >&2
exit 1
fi
title=$(printf "%s" "$pull_request" | jq -r '.title_for_github_pull_request')
if [ $? -ne 0 ]; then
echo "Error: No suggested github pull request received." >&2
exit 1
fi
body=$(printf "%s" "$pull_request" | jq -r '.body_for_github_pull_request')
if [ $? -ne 0 ]; then
echo "Error: No suggested github pull request received." >&2
exit 1
fi
if [[ -f "$pull_request_template" ]]; then
template_body=$(<"$pull_request_template")
body_with_template=$(printf "%s\n\n---\n%s" "$body" "$template_body")
else
body_with_template=$body
fi
gh pr create --web --title="$title" --body="$body_with_template"
And we also prepare user prompt and schema files.
CONFIG_PATH/afa/templates/user/github_pull_request.json
{
"type": "object",
"properties": {
"title_for_github_pull_request": {
"type": "string"
},
"body_for_github_pull_request": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"title_for_github_pull_request",
"body_for_github_pull_request"
]
}
CONFIG_PATH/afa/schemas/github_pull_request.tmpl
Based on the following information, please propose a title and body for a GitHub pull request.
## Summary of Changes (from git diff information):
{{ (index .Files 0).Content }}
## Related Commit Messages (from git log information):
{{ (index .Files 1).Content }}
{{- if ne .Message "" }}
## Background of Changes
{{ .Message }}
{{ end }}
## Requirements:
- Use Markdown format.
- Focus on the purpose and background rather than the details of the changes.
- Propose only the title and body without extra explanations or comments.
- Start by "# Automatically Generated Pull Request Description".
- And then:
- "## Summary"
- "## Changes", List multiple key changes. Provide a brief explanation for each key change.
- Write Outlines only.
Next, we prepare a function for ZLE:
function _afa-github-pull-request() {
afa_github_pull_request.zsh -p "$BUFFER"
BUFFER=""
CURSOR=$#BUFFER
zle reset-prompt
}
Finally, we add a function setting and key bindings to the .zshrc
as follows:
autoload -Uz _afa-github-pull-request
zle -N afa-github-pull-request _afa-github-pull-request
bindkey '^G^P' afa-github-pull-request
Session Selection using peco
We prepare a function for ZLE:
function _afa-source() {
local selected_session=$(afa list | peco --query "$LBUFFER")
if [ -n "$selected_session" ]; then
local session_name=$(echo "$selected_session" | cut -f1)
BUFFER="afa source -l ${session_name}"
zle accept-line
fi
zle clear-screen
}
We add a function setting and key bindings to the .zshrc
as follows:
autoload -Uz _afa-source
zle -N afa-source _afa-source
bindkey '^G^S' afa-source