Compare commits
200 Commits
fa85fcfcce
...
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
| c00ab074f8 | |||
| aed1f1957f | |||
| c6a959e2e1 | |||
| 02b7ed37f6 | |||
| 0d84aaabb9 | |||
| 6efdcf9610 | |||
| 4266d317d8 | |||
| 4ce7aafcbd | |||
| 35d8b69f92 | |||
| 562057e608 | |||
| b7024e5340 | |||
| 088588231b | |||
| eff117d3d9 | |||
| 968c535709 | |||
| c8b6fa7b11 | |||
| 0aa334b54e | |||
| 78a49f841d | |||
| 43b2bd937e | |||
| a4326875ba | |||
| eb31a58346 | |||
| a6b0acc35d | |||
| cc7fcd0b5b | |||
| 02fe59b913 | |||
| 6fd5f47089 | |||
| 2a2922760e | |||
| a3793460fd | |||
| e0927a04d9 | |||
| 8665604bab | |||
| d4c3c135b3 | |||
| 60bd5e493c | |||
| 0753b2d841 | |||
| 17e6fbd692 | |||
| 0710441650 | |||
| 20a76cee3e | |||
| cb64785867 | |||
| e6e26103c4 | |||
| 15529a14f1 | |||
| 86839188e0 | |||
| 39701b378b | |||
| 45ff6da737 | |||
| a260dd1503 | |||
| 57859301df | |||
| 8c968d3f53 | |||
| 0034bfbe46 | |||
| a733b9247a | |||
| e0afa349b9 | |||
| 7d0ce94907 | |||
| 9045763c35 | |||
| 29898552d7 | |||
| 9d7c2f5c2f | |||
| 5c0fa42351 | |||
| ab045b0ef3 | |||
| 41e6843db1 | |||
| 911ec3c9b9 | |||
| fc6f0a1a7b | |||
| 21873da278 | |||
| d1cd6be2c9 | |||
| 0c0ae41bca | |||
| c9ed7a904a | |||
| d200a8f554 | |||
| 3d04c8fcf1 | |||
| f53f165d91 | |||
| e5645e4064 | |||
| 95e15ca8c4 | |||
| dbf7329e87 | |||
| ed6c3ae431 | |||
| 214d2ecc67 | |||
| 29c95671de | |||
| 238f93a096 | |||
| c76877e7b3 | |||
| 12e5a9c5aa | |||
| 7f4be2ca3f | |||
| 29ffe12d8c | |||
| d34bed4f15 | |||
| aec7ea7e80 | |||
| 5938e1af29 | |||
| 60902297c5 | |||
| 12a95aa6fa | |||
| 78fc459a97 | |||
| 281565804c | |||
| 33a32fd9c8 | |||
| b64aad55e9 | |||
| 2392958114 | |||
| ec04e8e24a | |||
| 4e14ee7f50 | |||
| 7ba4ab0608 | |||
| fd816112fb | |||
| d0ee85be40 | |||
| 9448704af3 | |||
| 9dad9d6ca8 | |||
| 3f41abed7c | |||
| debcbab445 | |||
| 7fcabf1de7 | |||
| e116a1841d | |||
| cd3103ca14 | |||
| 50d07a4b13 | |||
| ed1352936e | |||
| f4b4156a0c | |||
| 5cf2cce0e3 | |||
| 249453d829 | |||
| c14939cecc | |||
| 72f516abb1 | |||
| 66478ed264 | |||
| 6b10dff41d | |||
| f8cc736482 | |||
| a0794fecfc | |||
| c68059e5b3 | |||
| 832ca6b0de | |||
| 89ee43830e | |||
| f7cf13901e | |||
| ad41fa93fb | |||
| 617b7dcd49 | |||
| 417ea032c4 | |||
| b77bb6e200 | |||
| 1fa3b4a600 | |||
| 99bd502f62 | |||
| 25a271dc95 | |||
| 5002ac7716 | |||
| d92a559460 | |||
| 3d571e1a31 | |||
| d338daa4b6 | |||
| 6f802c2a58 | |||
| a3f0168817 | |||
| 677702655f | |||
| b0bbd0c083 | |||
| 5cbf23a1f4 | |||
| 39eb9b34ec | |||
| 5da8616518 | |||
| b267fe05cd | |||
| 29f7ebe559 | |||
| bbffaca511 | |||
| 80532836c3 | |||
| 9474f4f322 | |||
| 93a09d3a9f | |||
| e3935ce699 | |||
| 58c15e7833 | |||
| fd2b7f3aa0 | |||
| 5ccbc629d1 | |||
| e98ff5e8e5 | |||
| a6fffa7b57 | |||
| 3ac153dd06 | |||
| 8db3108c94 | |||
| e25ff4ad19 | |||
| 21e76c6461 | |||
| 103aa1a432 | |||
| d2f4fefcf3 | |||
| 629527988d | |||
| 7f520f1346 | |||
| e28619b55a | |||
| f474e6130e | |||
| 4b5bcb45ac | |||
| 50565a0f17 | |||
| cf37db4fa2 | |||
| ad9b4097ef | |||
| c22c01c6c3 | |||
| 31f7f50c4a | |||
| a7f6ed4b16 | |||
| 73ada5a221 | |||
| 2f96256893 | |||
| 23d9e0775f | |||
| 72ade39144 | |||
| ec64c68777 | |||
| 80932e069f | |||
| 2f9b154b07 | |||
| 20bf911732 | |||
| 65a3dbb228 | |||
| 5844cc93ca | |||
| 4d23ce58c4 | |||
| 2bb592d5f6 | |||
| 3146b20c15 | |||
| 455cf67750 | |||
| a6d6a877b0 | |||
| a7bd54471c | |||
| fe5f803163 | |||
| 66a9b5362a | |||
| f3569cf68b | |||
| 2573f14726 | |||
| f1fb2d6abf | |||
| 4934e0ff0a | |||
| f772a80501 | |||
| 8950843be2 | |||
| 9b89e68908 | |||
| ba134ca53f | |||
| 21dbd9c057 | |||
| 40a68f8e05 | |||
| 37d861a631 | |||
| 31f3e885ce | |||
| 7ffaab2012 | |||
| 35b7946b0d | |||
| 3a05a8e712 | |||
| 294a1149ef | |||
| 8d80370014 | |||
| 1cbdef36cf | |||
| 4c8accbfc1 | |||
| c4c2d9cb93 | |||
| 7aed112326 | |||
| 216a3d53cd | |||
| e0823b343b | |||
| cb0bc65ee4 | |||
| 5b9ab6636f |
@@ -0,0 +1,4 @@
|
|||||||
|
--artifact-server-path=./.act/artifacts
|
||||||
|
--cache-server-path=./.act/cache
|
||||||
|
--container-options --privileged
|
||||||
|
--env ACT=true
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[tool.commitizen]
|
||||||
|
name = "cz_conventional_commits"
|
||||||
|
tag_format = "v$version"
|
||||||
|
version_scheme = "semver"
|
||||||
|
version_provider = "cargo"
|
||||||
|
update_changelog_on_bump = true
|
||||||
|
major_version_zero = true
|
||||||
|
|
||||||
|
[tool.commitizen.hooks]
|
||||||
|
pre-commit = "git add Cargo.toml Cargo.lock"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
github: Dark-Alex-17
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
name: Blank Issue
|
||||||
|
about: Create a blank issue.
|
||||||
|
---
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Create a report to help us improve
|
||||||
|
labels: bug
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: Thank you for filing a bug report!
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Summary
|
||||||
|
description: >
|
||||||
|
Please provide a short summary of the bug, along with any information
|
||||||
|
you feel relevant to replicate the bug.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: reproduction-steps
|
||||||
|
attributes:
|
||||||
|
label: Reproduction Steps
|
||||||
|
value: |
|
||||||
|
I tried this:
|
||||||
|
|
||||||
|
1. `loki`
|
||||||
|
|
||||||
|
I expected this to happen:
|
||||||
|
|
||||||
|
Instead, this happened:
|
||||||
|
- type: textarea
|
||||||
|
id: loki-log
|
||||||
|
attributes:
|
||||||
|
label: Loki log
|
||||||
|
description: Include the Loki log file to help diagnose the issue. (`loki --info` to see the log_path)
|
||||||
|
value: |
|
||||||
|
| OS | Log file location |
|
||||||
|
| ------- | ----------------------------------------------------- |
|
||||||
|
| Linux | `~/.cache/loki/loki.log` |
|
||||||
|
| Mac | `~/Library/Logs/loki/loki.log` |
|
||||||
|
| Windows | `C:\Users\<User>\AppData\Local\loki\loki.log` |
|
||||||
|
|
||||||
|
```
|
||||||
|
please provide a copy of your loki log file here if possible; you may need to redact some of the lines
|
||||||
|
```
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: platform
|
||||||
|
attributes:
|
||||||
|
label: Platform
|
||||||
|
placeholder: Linux / macOS / Windows
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: terminal-emulator
|
||||||
|
attributes:
|
||||||
|
label: Terminal Emulator
|
||||||
|
placeholder: wezterm 20220101-133340-7edc5b5a
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: loki-version
|
||||||
|
attributes:
|
||||||
|
label: Loki Version
|
||||||
|
description: >
|
||||||
|
Loki version (`loki --version` if using a release, `git describe` if building
|
||||||
|
from main).
|
||||||
|
**Make sure that you are using the [latest loki release](https://github.com/Dark-Alex-17/loki/releases) or a newer main build**
|
||||||
|
placeholder: "loki 0.1.0"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
name: Enhancement
|
||||||
|
about: Suggest an improvement
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Your enhancement may already be reported!
|
||||||
|
Please search on the issue tracker before creating a new issue.
|
||||||
|
If this is an idea for a feature, please open an "Idea" Discussion instead.
|
||||||
|
-->
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
all:
|
||||||
|
name: All
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- ubuntu-latest
|
||||||
|
- macos-latest
|
||||||
|
- windows-latest
|
||||||
|
|
||||||
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
|
env:
|
||||||
|
RUSTFLAGS: --deny warnings
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Rust Toolchain Components
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: cargo test --all
|
||||||
|
|
||||||
|
- name: Clippy
|
||||||
|
run: cargo clippy --all --all-targets -- -D warnings
|
||||||
|
|
||||||
|
- name: Format
|
||||||
|
run: cargo fmt --all --check
|
||||||
@@ -0,0 +1,458 @@
|
|||||||
|
name: Create release
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
bump_type:
|
||||||
|
description: "Specify the type of version bump"
|
||||||
|
required: true
|
||||||
|
default: "patch"
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- patch
|
||||||
|
- minor
|
||||||
|
- major
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
bump-version:
|
||||||
|
name: bump-version
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Configure SSH for Git
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "${{ secrets.RELEASE_BOT_SSH_KEY }}" > ~/.ssh/id_ed25519
|
||||||
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
|
ssh-keyscan -H github.com >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ssh-key: ${{ secrets.RELEASE_BOT_SSH_KEY }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
if: env.ACT == 'true'
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.10"
|
||||||
|
|
||||||
|
- name: Install Commitizen
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install commitizen
|
||||||
|
npm install -g conventional-changelog-cli
|
||||||
|
|
||||||
|
- name: Configure Git user
|
||||||
|
run: |
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
|
||||||
|
- name: Bump version with Commitizen
|
||||||
|
run: |
|
||||||
|
cz bump --yes --increment ${{ github.event.inputs.bump_type }}
|
||||||
|
|
||||||
|
- name: Amend commit message to include '[skip ci]'
|
||||||
|
run: |
|
||||||
|
git commit --amend --no-edit -m "$(git log -1 --pretty=%B) [skip ci]"
|
||||||
|
|
||||||
|
- name: Install Rust stable
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- name: Get the new version tag
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
mkdir -p artifacts
|
||||||
|
NEW_TAG=$(cz version --project)
|
||||||
|
echo "New version: $NEW_TAG"
|
||||||
|
echo "version=$NEW_TAG" >> $GITHUB_ENV
|
||||||
|
echo "$NEW_TAG" > artifacts/release-version
|
||||||
|
|
||||||
|
- name: Get the previous version tag
|
||||||
|
id: prev_version
|
||||||
|
run: |
|
||||||
|
PREV_TAG=$(git describe --tags --abbrev=0 ${GITHUB_SHA}^)
|
||||||
|
echo "Previous tag: $PREV_TAG"
|
||||||
|
echo "prev_version=$PREV_TAG" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Bump Cargo.toml version
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{ github.workspace }}
|
||||||
|
env:
|
||||||
|
VERSION: ${{ env.version }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
: "${VERSION:?env.version is empty}"
|
||||||
|
|
||||||
|
# Ignore Act's local artifact dir noise
|
||||||
|
echo artifacts/ >> .git/info/exclude || true
|
||||||
|
|
||||||
|
# Edit the version line right after name="loki"
|
||||||
|
sed -E -i '
|
||||||
|
/^[[:space:]]*name[[:space:]]*=[[:space:]]*"loki"[[:space:]]*$/ {
|
||||||
|
n
|
||||||
|
s|^[[:space:]]*version[[:space:]]*=[[:space:]]*"[^"]*"|version = "'"$VERSION"'"|
|
||||||
|
}
|
||||||
|
' Cargo.toml
|
||||||
|
|
||||||
|
cargo update || true
|
||||||
|
|
||||||
|
# Git config that helps in Act
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||||
|
|
||||||
|
git status --porcelain
|
||||||
|
git diff --name-only -- Cargo.toml Cargo.lock || true
|
||||||
|
|
||||||
|
if ! git diff --quiet -- Cargo.toml Cargo.lock; then
|
||||||
|
git add -u -- Cargo.toml Cargo.lock
|
||||||
|
git commit -m "chore: bump Cargo.toml to $VERSION"
|
||||||
|
else
|
||||||
|
echo "No changes to commit (already at $VERSION)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Generate changelog for the version bump
|
||||||
|
id: changelog
|
||||||
|
run: |
|
||||||
|
conventional-changelog -p conventionalcommits -i CHANGELOG.md --from ${{ env.prev_version }} --to v${{ env.version }} > artifacts/changelog.md
|
||||||
|
|
||||||
|
- name: Push changes
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
git push origin --follow-tags
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
|
||||||
|
- name: Upload the changed Cargo files (Act)
|
||||||
|
if: env.ACT == 'true'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: bumped-cargo-files
|
||||||
|
path: |
|
||||||
|
Cargo.toml
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
|
publish-github-release:
|
||||||
|
name: build-release
|
||||||
|
needs: [bump-version]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
env:
|
||||||
|
RUST_BACKTRACE: 1
|
||||||
|
BUILD_CMD: cargo
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- target: aarch64-unknown-linux-musl
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: aarch64-apple-darwin
|
||||||
|
os: macos-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: aarch64-pc-windows-msvc
|
||||||
|
os: windows-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: x86_64-apple-darwin
|
||||||
|
os: macos-latest
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: x86_64-pc-windows-msvc
|
||||||
|
os: windows-latest
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: x86_64-unknown-linux-musl
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: x86_64-unknown-linux-gnu
|
||||||
|
os: ubuntu-latest
|
||||||
|
cargo-flags: ""
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check if actor is repository owner
|
||||||
|
if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }}
|
||||||
|
run: |
|
||||||
|
echo "You are not authorized to run this workflow."
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Ensure repository is up-to-date
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
run: |
|
||||||
|
git fetch --all
|
||||||
|
git pull
|
||||||
|
|
||||||
|
- name: Download all artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Ensure repository is up-to-date
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
run: |
|
||||||
|
git fetch --all
|
||||||
|
git pull
|
||||||
|
|
||||||
|
- name: Set environment variables
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
release_version="$(cat ./artifacts/release-version)"
|
||||||
|
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Validate release environment variables
|
||||||
|
run: |
|
||||||
|
echo "Release version: ${{ env.RELEASE_VERSION }}"
|
||||||
|
echo "Changelog body: $(cat artifacts/changelog.md)"
|
||||||
|
|
||||||
|
- name: Get bumped Cargo files (Act)
|
||||||
|
if: env.ACT == 'true'
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: bumped-cargo-files
|
||||||
|
path: ${{ github.workspace }}
|
||||||
|
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
name: Set Rust toolchain
|
||||||
|
with:
|
||||||
|
targets: ${{ matrix.target }}
|
||||||
|
|
||||||
|
- name: Install cross
|
||||||
|
if: matrix.use-cross
|
||||||
|
uses: taiki-e/install-action@v2
|
||||||
|
with:
|
||||||
|
tool: cross
|
||||||
|
|
||||||
|
- name: Overwrite build command env variable
|
||||||
|
if: matrix.use-cross
|
||||||
|
shell: bash
|
||||||
|
run: echo "BUILD_CMD=cross" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Install latest LLVM/Clang
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
run: |
|
||||||
|
wget https://apt.llvm.org/llvm.sh
|
||||||
|
chmod +x llvm.sh
|
||||||
|
# omit the version to get the latest stable for your Ubuntu (24.04 "noble" on ubuntu-latest)
|
||||||
|
sudo ./llvm.sh all
|
||||||
|
# ensure libclang dev package is present (adjust the "22" if a newer major exists)
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libclang-20-dev libclang-dev
|
||||||
|
|
||||||
|
- name: Show Version Information (Rust, cargo, GCC)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
gcc --version || true
|
||||||
|
rustup -V
|
||||||
|
rustup toolchain list
|
||||||
|
rustup default
|
||||||
|
cargo -V
|
||||||
|
rustc -V
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
shell: bash
|
||||||
|
run: $BUILD_CMD build --locked --release --target=${{ matrix.target }} ${{ matrix.cargo-flags }}
|
||||||
|
|
||||||
|
- name: Verify file
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
file target/${{ matrix.target }}/release/loki
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
if: matrix.target != 'aarch64-apple-darwin' && matrix.target != 'aarch64-pc-windows-msvc'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euxo pipefail
|
||||||
|
if [[ "${{ matrix.use-cross || 'false' }}" == 'true' ]]; then
|
||||||
|
cross test --release --locked --target=${{ matrix.target }} --verbose
|
||||||
|
else
|
||||||
|
cargo test --release --locked --target=${{ matrix.target }} --verbose
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build Archive
|
||||||
|
shell: bash
|
||||||
|
id: package
|
||||||
|
env:
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
run: |
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
bin=${GITHUB_REPOSITORY##*/}
|
||||||
|
dist_dir=`pwd`/dist
|
||||||
|
name=$bin-$target
|
||||||
|
executable=target/$target/release/$bin
|
||||||
|
|
||||||
|
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
||||||
|
executable=$executable.exe
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir $dist_dir
|
||||||
|
cp $executable $dist_dir
|
||||||
|
cd $dist_dir
|
||||||
|
|
||||||
|
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
||||||
|
archive=$dist_dir/$name.zip
|
||||||
|
sha=$dist_dir/$name.sha256
|
||||||
|
7z a $archive *
|
||||||
|
certutil -hashfile $archive sha256 | grep -E [A-Fa-f0-9]{64} > $sha
|
||||||
|
echo "archive=dist/$name.zip" >> $GITHUB_OUTPUT
|
||||||
|
echo "sha=dist/$name.sha256" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
archive=$dist_dir/$name.tar.gz
|
||||||
|
sha=$dist_dir/$name.sha256
|
||||||
|
tar -czf $archive *
|
||||||
|
shasum -a 256 $archive > $sha
|
||||||
|
echo "archive=dist/$name.tar.gz" >> $GITHUB_OUTPUT
|
||||||
|
echo "sha=dist/$name.sha256" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Publish Archive and SHA
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
${{ steps.package.outputs.archive }}
|
||||||
|
${{ steps.package.outputs.sha }}
|
||||||
|
tag_name: v${{ env.RELEASE_VERSION }}
|
||||||
|
name: "v${{ env.RELEASE_VERSION }}"
|
||||||
|
body_path: artifacts/changelog.md
|
||||||
|
prerelease: false
|
||||||
|
|
||||||
|
- name: Add artifacts
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
[[ -d artifacts ]] || mkdir -p artifacts
|
||||||
|
cp ${{ steps.package.outputs.archive }} artifacts/
|
||||||
|
cp ${{ steps.package.outputs.sha }} artifacts/
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: artifacts-v${{ env.RELEASE_VERSION }}-${{ matrix.target }}
|
||||||
|
path: artifacts
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
|
publish-homebrew-formula:
|
||||||
|
needs: [publish-github-release]
|
||||||
|
name: Update Homebrew formulas
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check if actor is repository owner
|
||||||
|
if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }}
|
||||||
|
run: |
|
||||||
|
echo "You are not authorized to run this workflow."
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Get release artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Set release assets and version
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# Set environment variables
|
||||||
|
macos_sha="$(cat ./artifacts/loki-x86_64-apple-darwin.sha256 | awk '{print $1}')"
|
||||||
|
echo "MACOS_SHA=$macos_sha" >> $GITHUB_ENV
|
||||||
|
macos_sha_arm="$(cat ./artifacts/loki-aarch64-apple-darwin.sha256 | awk '{print $1}')"
|
||||||
|
echo "MACOS_SHA_ARM=$macos_sha_arm" >> $GITHUB_ENV
|
||||||
|
linux_sha="$(cat ./artifacts/loki-x86_64-unknown-linux-musl.sha256 | awk '{print $1}')"
|
||||||
|
echo "LINUX_SHA=$linux_sha" >> $GITHUB_ENV
|
||||||
|
release_version="$(cat ./artifacts/release-version)"
|
||||||
|
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Validate release environment variables
|
||||||
|
run: |
|
||||||
|
echo "Release SHA macos: ${{ env.MACOS_SHA }}"
|
||||||
|
echo "Release SHA macos-arm: ${{ env.MACOS_SHA_ARM }}"
|
||||||
|
echo "Release SHA linux musl: ${{ env.LINUX_SHA }}"
|
||||||
|
echo "Release version: ${{ env.RELEASE_VERSION }}"
|
||||||
|
|
||||||
|
- name: Execute Homebrew packaging script
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
run: |
|
||||||
|
# run packaging script
|
||||||
|
python "./deployment/homebrew/packager.py" ${{ env.RELEASE_VERSION }} "./deployment/homebrew/loki.rb.template" "./loki.rb" ${{ env.MACOS_SHA }} ${{ env.MACOS_SHA_ARM }} ${{ env.LINUX_SHA }}
|
||||||
|
|
||||||
|
- name: Push changes to Homebrew tap
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ secrets.LOKI_GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
# push to Git
|
||||||
|
git config --global user.name "Dark-Alex-17"
|
||||||
|
git config --global user.email "alex.j.tusa@gmail.com"
|
||||||
|
git clone https://Dark-Alex-17:${{ secrets.LOKI_GITHUB_TOKEN }}@github.com/Dark-Alex-17/homebrew-loki.git
|
||||||
|
rm homebrew-loki/Formula/loki.rb
|
||||||
|
cp loki.rb homebrew-loki/Formula
|
||||||
|
cd homebrew-loki
|
||||||
|
git add .
|
||||||
|
git diff-index --quiet HEAD || git commit -am "Update formula for Loki release ${{ env.RELEASE_VERSION }}"
|
||||||
|
git push https://$TOKEN@github.com/Dark-Alex-17/homebrew-loki.git
|
||||||
|
|
||||||
|
publish-crate:
|
||||||
|
needs: publish-github-release
|
||||||
|
name: Publish Crate
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check if actor is repository owner
|
||||||
|
if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }}
|
||||||
|
run: |
|
||||||
|
echo "You are not authorized to run this workflow."
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Get bumped Cargo files (Act)
|
||||||
|
if: env.ACT == 'true'
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: bumped-cargo-files
|
||||||
|
path: ${{ github.workspace }}
|
||||||
|
|
||||||
|
- name: Ensure repository is up-to-date
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
run: |
|
||||||
|
git fetch --all
|
||||||
|
git pull
|
||||||
|
|
||||||
|
- name: Install Rust stable
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
|
- uses: katyo/publish-crates@v2
|
||||||
|
if: env.ACT != 'true'
|
||||||
|
with:
|
||||||
|
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/target
|
||||||
|
/tmp
|
||||||
|
/.env
|
||||||
|
!cli/**
|
||||||
|
.idea/
|
||||||
|
/loki.iml
|
||||||
|
/.idea/
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
repos:
|
||||||
|
- hooks:
|
||||||
|
- id: commitizen
|
||||||
|
- id: commitizen-branch
|
||||||
|
stages:
|
||||||
|
- pre-push
|
||||||
|
repo: https://github.com/commitizen-tools/commitizen
|
||||||
|
rev: v3.30.0
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
## v0.2.0 (2026-02-14)
|
||||||
|
|
||||||
|
### Feat
|
||||||
|
|
||||||
|
- Simplified sisyphus prompt to improve functionality
|
||||||
|
- Supported the injection of RAG sources into the prompt, not just via the `.sources rag` command in the REPL so models can directly reference the documents that supported their responses
|
||||||
|
- Created the Sisyphus agent to make Loki function like Claude Code, Gemini, Codex, etc.
|
||||||
|
- Created the Oracle agent to handle high-level architectural decisions and design questions about a given codebase
|
||||||
|
- Updated the coder agent to be much more task-focused and to be delegated to by Sisyphus
|
||||||
|
- Created the explore agent for exploring codebases to help answer questions
|
||||||
|
- Use the official atlassian MCP server for the jira-helper agent
|
||||||
|
- Created fs_glob to enable more targeted file exploration utilities
|
||||||
|
- Created a new tool 'fs_grep' to search a given file's contents for relevant lines to reduce token usage for smaller models
|
||||||
|
- Created the new fs_read tool to enable controlled reading of a file
|
||||||
|
- Let agent level variables be defined to bypass guard protections for tool invocations
|
||||||
|
- Implemented a built-in task management system to help smaller LLMs complete larger multistep tasks and minimize context drift
|
||||||
|
- Improved tool and MCP invocation error handling by returning stderr to the model when it is available
|
||||||
|
- Added variable interpolation for conversation starters in agents
|
||||||
|
- Implemented retry logic for failed tool invocations so the LLM can learn from the result and try again; Also implemented chain loop detection to prevent loops
|
||||||
|
- Added gemini-3-pro to the supported vertexai models
|
||||||
|
- Added an environment variable that lets users bypass guard operations in bash scripts. This is useful for agent routing
|
||||||
|
- Added support for thought-signatures for Gemini 3+ models
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
- Improved continuation prompt to not make broad todo-items
|
||||||
|
- Allow auto-continuation to work in agents after a session is compressed and if there's still unfinish items in the to-do list
|
||||||
|
- fs_ls and fs_cat outputs should always redirect to "$LLM_OUTPUT" including on errors.
|
||||||
|
- Claude tool calls work incorrectly when tool doesn't require any arguments or flags; would provide an empty JSON object or error on no args
|
||||||
|
- Fixed a bug where --agent-variable values were not being passed to the agents
|
||||||
|
|
||||||
|
## v0.1.3 (2025-12-13)
|
||||||
|
|
||||||
|
### Feat
|
||||||
|
|
||||||
|
- Improved MCP implementation to minimize the tokens needed to utilize it so it doesn't quickly overwhelm the token space for a given model
|
||||||
|
|
||||||
|
## v0.1.2 (2025-11-08)
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Gave the GitHub MCP server a default placeholder value that doesn't require the vault
|
||||||
|
|
||||||
|
## v0.1.1 (2025-11-08)
|
||||||
|
|
||||||
|
## v0.1.0 (2025-11-07)
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Updated to the most recent Rust version with 2024 syntax
|
||||||
|
|
||||||
|
## v0.0.1 (2025-11-07)
|
||||||
|
|
||||||
|
### Feat
|
||||||
|
|
||||||
|
- Added the agents directory to sysinfo output
|
||||||
|
- Added built-in macros
|
||||||
|
- Updated the example role configuration file to also have the prompt field
|
||||||
|
- Updated the code role
|
||||||
|
- Secret injection as environment variables into agent tools
|
||||||
|
- Removed the server functionality
|
||||||
|
- Require Vault set up for first-time setup so all passed in secrets can be encrypted right off the bat
|
||||||
|
- Added static completions via a --completions flag
|
||||||
|
- Support for secret injection into the global config file (API keys, for example)
|
||||||
|
- Improved MCP handling toggle handling
|
||||||
|
- Secret injection into the MCP configuration
|
||||||
|
- added REPL support for interacting with the Loki vault
|
||||||
|
- Integrated gman with Loki to create a vault and added flags to configure the Loki vault
|
||||||
|
- Added a default session to the jira helper to make interaction more natural
|
||||||
|
- Created the repo-analyzer role
|
||||||
|
- Created the coder and sql agents
|
||||||
|
- Cleaned the built-in functions to not have leftover dependencies
|
||||||
|
- Created additional built-in roles for slack, repo analysis, and github
|
||||||
|
- Install built-in agents
|
||||||
|
- Embedded baseline MCP config and global tools
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
- Corrected a typo for sourcing the bash utility script in some agent definitions
|
||||||
|
|
||||||
|
### Refactor
|
||||||
|
|
||||||
|
- Changed the name of the summary_prompt setting to summary_context_prompt
|
||||||
|
- Renamed summarize_prompt setting to summarization_prompt
|
||||||
|
- Renamed the compress_threshold setting to compression_threshold
|
||||||
|
- Migrated around the location of some of the more large documents for documentation
|
||||||
|
- Factored out the macros structs from the large config module
|
||||||
|
- Refactored mcp_servers and function_calling to mcp_server_support and function_calling_support to make the purpose of the fields more clear
|
||||||
|
- Refactored the use_mcp_servers field to enabled_mcp_servers to make the purpose of the field more clear
|
||||||
|
- Refactored use_tools field to enabled_tools field to make the use of the field more clear
|
||||||
|
- Removed the use of the tools.txt file and added tool visibility declarations to the global configuration file
|
||||||
|
- Agents that depend on global tools now have all binaries compiled and stored in the agent's bin directory so multiple agents can run at once
|
||||||
|
- Removed the git MCP server and used the newer, better mcp-server-docker for local docker integration
|
||||||
|
- Renamed the argument for the --completions flag to SHELL
|
||||||
|
- Updated the instructions for the jira-helper agent
|
||||||
|
- Modified the default PS1 look
|
||||||
|
- Fixed a linting issue for Windows builds
|
||||||
|
- Changed the name of agent_prelude to agent_session to make its purpose more clear
|
||||||
|
- Removed leftover javascript function support; will not implement
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the
|
||||||
|
overall community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or
|
||||||
|
advances of any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email
|
||||||
|
address, without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at
|
||||||
|
alex.j.tusa@gmail.com.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or
|
||||||
|
permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.0, available at
|
||||||
|
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||||
|
enforcement ladder](https://github.com/mozilla/diversity).
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
https://www.contributor-covenant.org/faq. Translations are available at
|
||||||
|
https://www.contributor-covenant.org/translations.
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
# Contributing
|
||||||
|
Contributors are very welcome! **No contribution is too small and all contributions are valued.**
|
||||||
|
|
||||||
|
## Rust
|
||||||
|
You'll need to have the stable Rust toolchain installed in order to develop Loki.
|
||||||
|
|
||||||
|
The Rust toolchain (stable) can be installed via rustup using the following command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This will install `rustup`, `rustc` and `cargo`. For more information, refer to the [official Rust installation documentation](https://www.rust-lang.org/tools/install).
|
||||||
|
|
||||||
|
## Commitizen
|
||||||
|
[Commitizen](https://github.com/commitizen-tools/commitizen?tab=readme-ov-file) is a nifty tool that helps us write better commit messages. It ensures that our
|
||||||
|
commits have a consistent style and makes it easier to generate CHANGELOGS. Additionally,
|
||||||
|
Commitizen is used to run pre-commit checks to enforce style constraints.
|
||||||
|
|
||||||
|
To install `commitizen` and the `pre-commit` prerequisite, run the following command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
python3 -m pip install commitizen pre-commit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Commitizen Quick Guide
|
||||||
|
To see an example commit to get an idea for the Commitizen style, run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cz example
|
||||||
|
```
|
||||||
|
|
||||||
|
To see the allowed types of commits and their descriptions, run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cz info
|
||||||
|
```
|
||||||
|
|
||||||
|
If you'd like to create a commit using Commitizen with an interactive prompt to help you get
|
||||||
|
comfortable with the style, use:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cz commit
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup workspace
|
||||||
|
|
||||||
|
1. Clone this repo
|
||||||
|
2. Run `cargo test` to set up hooks
|
||||||
|
3. Make changes
|
||||||
|
4. Run the application using `just run` or `just run`
|
||||||
|
- Install `just` (`cargo install just`) if you haven't already to use the [justfile](./justfile) in this project.
|
||||||
|
5. Commit changes. This will trigger pre-commit hooks that will run format, test and lint. If there are errors or
|
||||||
|
warnings from Clippy, please fix them.
|
||||||
|
6. Push your code to a new branch named after the feature/bug/etc. you're adding. This will trigger pre-push hooks that
|
||||||
|
will run lint and test.
|
||||||
|
7. Create a PR
|
||||||
|
|
||||||
|
### CI/CD Testing with Act
|
||||||
|
If you also are planning on testing out your changes before pushing them with [Act](https://github.com/nektos/act), you will need to set up `act`,
|
||||||
|
`docker`, and configure your local system to run different architectures:
|
||||||
|
|
||||||
|
1. Install `docker` by following the instructions on the [official Docker installation page](https://docs.docker.com/get-docker/).
|
||||||
|
2. Install `act` by following the instructions on the [official Act installation page](https://nektosact.com/installation/index.html).
|
||||||
|
3. Install `binfmt` on your system once so that `act` can run the correct architecture for the CI/CD workflows.
|
||||||
|
You can do this by running:
|
||||||
|
```shell
|
||||||
|
sudo docker run --rm --privileged tonistiigi/binfmt --install all
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you can run workflows locally without having to commit and see if the GitHub action passes or fails.
|
||||||
|
|
||||||
|
**For example**: To test the [release.yml](.github/workflows/release.yaml) workflow locally, you can run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
act -W .github/workflows/release.yml --input_type bump=minor
|
||||||
|
```
|
||||||
|
|
||||||
|
## Questions? Reach out to me!
|
||||||
|
If you encounter any questions while developing Loki, please don't hesitate to reach out to me at
|
||||||
|
alex.j.tusa@gmail.com. I'm happy to help contributors in any way I can, regardless of if they're new or experienced!
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
# Credits
|
||||||
|
|
||||||
|
## AIChat
|
||||||
|
Loki originally started as a fork of the fantastic
|
||||||
|
[AIChat CLI](https://github.com/sigoden/aichat). The initial goal was simply
|
||||||
|
to fix a bug in how MCP servers worked with AIChat, allowing different MCP
|
||||||
|
servers to be specified per agent. Since then, Loki has evolved far beyond
|
||||||
|
its original scope and grown into a passion project with a life of its own.
|
||||||
|
|
||||||
|
Today, Loki includes first-class MCP server support (for both local and remote
|
||||||
|
servers), a built-in vault for interpolating secrets in configuration files,
|
||||||
|
built-in agents and macros, dynamic tab completions, integrated custom
|
||||||
|
functions (no external `argc` dependency), improved documentation, and much
|
||||||
|
more with many more ideas planned for the future.
|
||||||
|
|
||||||
|
Loki is now developed and maintained as an independent project. Full credit
|
||||||
|
for the original foundation goes to the developers of the wonderful
|
||||||
|
AIChat project.
|
||||||
|
|
||||||
|
This project is not affiliated with or endorsed by the AIChat maintainers.
|
||||||
|
|
||||||
|
## AIChat
|
||||||
|
|
||||||
|
Loki originally began as a fork of [AIChat CLI](https://github.com/sigoden/aichat),
|
||||||
|
created and maintained by the AIChat contributors.
|
||||||
|
|
||||||
|
While Loki has since diverged significantly and is now developed as an
|
||||||
|
independent project, its early foundation and inspiration came from the
|
||||||
|
AIChat project.
|
||||||
|
|
||||||
|
AIChat is licensed under the MIT License.
|
||||||
Generated
+7361
File diff suppressed because it is too large
Load Diff
+138
@@ -0,0 +1,138 @@
|
|||||||
|
[package]
|
||||||
|
name = "loki-ai"
|
||||||
|
version = "0.2.0"
|
||||||
|
edition = "2024"
|
||||||
|
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||||
|
description = "An all-in-one, batteries included LLM CLI Tool"
|
||||||
|
keywords = ["chatgpt", "llm", "cli", "ai", "repl"]
|
||||||
|
homepage = "https://github.com/Dark-Alex-17/loki"
|
||||||
|
repository = "https://github.com/Dark-Alex-17/loki"
|
||||||
|
categories = ["command-line-utilities"]
|
||||||
|
readme = "README.md"
|
||||||
|
license = "MIT"
|
||||||
|
rust-version = "1.89.0"
|
||||||
|
exclude = [".github", "CONTRIBUTING.md"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.69"
|
||||||
|
bytes = "1.4.0"
|
||||||
|
clap = { version = "4.5.40", features = ["cargo", "derive", "wrap_help"] }
|
||||||
|
dirs = "6.0.0"
|
||||||
|
futures-util = "0.3.29"
|
||||||
|
inquire = "0.7.0"
|
||||||
|
is-terminal = "0.4.9"
|
||||||
|
reedline = "0.40.0"
|
||||||
|
serde = { version = "1.0.152", features = ["derive"] }
|
||||||
|
serde_json = { version = "1.0.93", features = ["preserve_order"] }
|
||||||
|
serde_yaml = "0.9.17"
|
||||||
|
tokio = { version = "1.34.0", features = [
|
||||||
|
"rt",
|
||||||
|
"time",
|
||||||
|
"macros",
|
||||||
|
"signal",
|
||||||
|
"rt-multi-thread",
|
||||||
|
"full",
|
||||||
|
] }
|
||||||
|
tokio-graceful = "0.2.2"
|
||||||
|
tokio-stream = { version = "0.1.15", default-features = false, features = [
|
||||||
|
"sync",
|
||||||
|
] }
|
||||||
|
crossterm = "0.28.1"
|
||||||
|
chrono = "0.4.23"
|
||||||
|
bincode = { version = "2.0.0", features = [
|
||||||
|
"serde",
|
||||||
|
"std",
|
||||||
|
], default-features = false }
|
||||||
|
parking_lot = "0.12.1"
|
||||||
|
fancy-regex = "0.14.0"
|
||||||
|
base64 = "0.22.0"
|
||||||
|
nu-ansi-term = "0.50.0"
|
||||||
|
async-trait = "0.1.74"
|
||||||
|
textwrap = "0.16.0"
|
||||||
|
ansi_colours = "1.2.2"
|
||||||
|
reqwest-eventsource = "0.6.0"
|
||||||
|
log = "0.4.28"
|
||||||
|
log4rs = { version = "1.4.0", features = ["file_appender"] }
|
||||||
|
shell-words = "1.1.0"
|
||||||
|
sha2 = "0.10.8"
|
||||||
|
unicode-width = "0.2.0"
|
||||||
|
async-recursion = "1.1.1"
|
||||||
|
http = "1.1.0"
|
||||||
|
http-body-util = "0.1"
|
||||||
|
hyper = { version = "1.0", features = ["full"] }
|
||||||
|
hyper-util = { version = "0.1", features = ["server-auto", "client-legacy"] }
|
||||||
|
time = { version = "0.3.36", features = ["macros"] }
|
||||||
|
indexmap = { version = "2.2.6", features = ["serde"] }
|
||||||
|
hmac = "0.12.1"
|
||||||
|
aws-smithy-eventstream = "0.60.4"
|
||||||
|
urlencoding = "2.1.3"
|
||||||
|
unicode-segmentation = "1.11.0"
|
||||||
|
json-patch = { version = "4.0.0", default-features = false }
|
||||||
|
bitflags = "2.5.0"
|
||||||
|
path-absolutize = "3.1.1"
|
||||||
|
hnsw_rs = "0.3.0"
|
||||||
|
rayon = "1.10.0"
|
||||||
|
uuid = { version = "1.9.1", features = ["v4"] }
|
||||||
|
scraper = { version = "0.23.1", default-features = false, features = [
|
||||||
|
"deterministic",
|
||||||
|
] }
|
||||||
|
sys-locale = "0.3.1"
|
||||||
|
html_to_markdown = "0.1.0"
|
||||||
|
rust-embed = "8.5.0"
|
||||||
|
os_info = { version = "3.8.2", default-features = false }
|
||||||
|
bm25 = { version = "2.0.1", features = ["parallelism"] }
|
||||||
|
which = "8.0.0"
|
||||||
|
fuzzy-matcher = "0.3.7"
|
||||||
|
terminal-colorsaurus = "0.4.8"
|
||||||
|
duct = "1.0.0"
|
||||||
|
argc = "1.23.0"
|
||||||
|
strum_macros = "0.27.2"
|
||||||
|
indoc = "2.0.6"
|
||||||
|
rmcp = { version = "0.6.1", features = ["client", "transport-child-process"] }
|
||||||
|
num_cpus = "1.17.0"
|
||||||
|
rustpython-parser = "0.4.0"
|
||||||
|
rustpython-ast = "0.4.0"
|
||||||
|
colored = "3.0.0"
|
||||||
|
clap_complete = { version = "4.5.58", features = ["unstable-dynamic"] }
|
||||||
|
gman = "0.3.0"
|
||||||
|
clap_complete_nushell = "4.5.9"
|
||||||
|
|
||||||
|
[dependencies.reqwest]
|
||||||
|
version = "0.12.0"
|
||||||
|
features = [
|
||||||
|
"json",
|
||||||
|
"multipart",
|
||||||
|
"socks",
|
||||||
|
"rustls-tls",
|
||||||
|
"rustls-tls-native-roots",
|
||||||
|
]
|
||||||
|
default-features = false
|
||||||
|
|
||||||
|
[dependencies.syntect]
|
||||||
|
version = "5.0.0"
|
||||||
|
default-features = false
|
||||||
|
features = ["parsing", "regex-onig", "plist-load"]
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
crossterm = { version = "0.28.1", features = ["use-dev-tty"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
arboard = { version = "3.3.0", default-features = false, features = [
|
||||||
|
"wayland-data-control",
|
||||||
|
] }
|
||||||
|
|
||||||
|
[target.'cfg(not(any(target_os = "linux", target_os = "android", target_os = "emscripten")))'.dependencies]
|
||||||
|
arboard = { version = "3.3.0", default-features = false }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
pretty_assertions = "1.4.0"
|
||||||
|
rand = "0.9.0"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "loki"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
strip = true
|
||||||
|
opt-level = "z"
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2025 sigoden
|
||||||
|
Copyright (c) 2025 Alexander J. Clarke
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -1,2 +1,257 @@
|
|||||||
# loki
|
# Loki: All-in-one, batteries-included LLM CLI Tool
|
||||||
An all-in-one, batteries included LLM CLI tool
|
|
||||||
|

|
||||||
|

|
||||||
|
[](https://crates.io/crates/loki-ai)
|
||||||
|

|
||||||
|

|
||||||
|
[](https://github.com/Dark-Alex-17/loki/releases)
|
||||||
|
|
||||||
|
Loki is an all-in-one, batteries-included, LLM CLI tool featuring Shell Assistant, CLI & REPL Mode, RAG, AI Tools &
|
||||||
|
Agents, and More.
|
||||||
|
|
||||||
|
It is designed to include a number of useful agents, roles, macros, and more so users can get up and running with Loki
|
||||||
|
in as little time as possible.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Coming from [AIChat](https://github.com/sigoden/aichat)? Follow the [migration guide](./docs/AICHAT-MIGRATION.md) to get started.
|
||||||
|
|
||||||
|
## Quick Links
|
||||||
|
* [AIChat Migration Guide](./docs/AICHAT-MIGRATION.md): Coming from AIChat? Follow the migration guide to get started.
|
||||||
|
* [Installation](#install): Install Loki
|
||||||
|
* [Getting Started](#getting-started): Get started with Loki by doing first-run setup steps.
|
||||||
|
* [REPL](./docs/REPL.md): Interactive Read-Eval-Print Loop for conversational interactions with LLMs and Loki.
|
||||||
|
* [Custom REPL Prompt](./docs/REPL-PROMPT.md): Customize the REPL prompt to provide useful contextual information.
|
||||||
|
* [Vault](./docs/VAULT.md): Securely store and manage sensitive information such as API keys and credentials.
|
||||||
|
* [Shell Integrations](./docs/SHELL-INTEGRATIONS.md): Seamlessly integrate Loki with your shell environment for enhanced command-line assistance.
|
||||||
|
* [Function Calling](./docs/function-calling/TOOLS.md#Tools): Leverage function calling capabilities to extend Loki's functionality with custom tools
|
||||||
|
* [Creating Custom Tools](./docs/function-calling/CUSTOM-TOOLS.md): You can create your own custom tools to enhance Loki's capabilities.
|
||||||
|
* [Create Custom Python Tools](./docs/function-calling/CUSTOM-TOOLS.md#custom-python-based-tools)
|
||||||
|
* [Create Custom Bash Tools](./docs/function-calling/CUSTOM-BASH-TOOLS.md)
|
||||||
|
* [Bash Prompt Utilities](./docs/function-calling/BASH-PROMPT-HELPERS.md)
|
||||||
|
* [First-Class MCP Server Support](./docs/function-calling/MCP-SERVERS.md): Easily connect and interact with MCP servers for advanced functionality.
|
||||||
|
* [Macros](./docs/MACROS.md): Automate repetitive tasks and workflows with Loki "scripts" (macros).
|
||||||
|
* [RAG](./docs/RAG.md): Retrieval-Augmented Generation for enhanced information retrieval and generation.
|
||||||
|
* [Sessions](/docs/SESSIONS.md): Manage and persist conversational contexts and settings across multiple interactions.
|
||||||
|
* [Roles](./docs/ROLES.md): Customize model behavior for specific tasks or domains.
|
||||||
|
* [Agents](/docs/AGENTS.md): Leverage AI agents to perform complex tasks and workflows.
|
||||||
|
* [Todo System](./docs/TODO-SYSTEM.md): Built-in task tracking for improved agent reliability with smaller models.
|
||||||
|
* [Environment Variables](./docs/ENVIRONMENT-VARIABLES.md): Override and customize your Loki configuration at runtime with environment variables.
|
||||||
|
* [Client Configurations](./docs/clients/CLIENTS.md): Configuration instructions for various LLM providers.
|
||||||
|
* [Patching API Requests](./docs/clients/PATCHES.md): Learn how to patch API requests for advanced customization.
|
||||||
|
* [Custom Themes](./docs/THEMES.md): Change the look and feel of Loki to your preferences with custom themes.
|
||||||
|
* [History](#history): A history of how Loki came to be.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
Loki requires the following tools to be installed on your system:
|
||||||
|
* [jq](https://github.com/jqlang/jq)
|
||||||
|
* `brew install jq`
|
||||||
|
* [jira (optional)](https://github.com/ankitpokhrel/jira-cli/wiki/Installation) (For the `query_jira_issues` tool)
|
||||||
|
* `brew tap ankitpokhrel/jira-cli && brew install jira-cli`
|
||||||
|
* You'll need to [create a JIRA API token](https://id.atlassian.com/manage-profile/security/api-tokens) for authentication
|
||||||
|
* Then, save it as an environment variable to your shell profile:
|
||||||
|
```sh
|
||||||
|
# ~/.bashrc or ~/.zshrc
|
||||||
|
export JIRA_API_TOKEN="your_jira_api_token_here"
|
||||||
|
```
|
||||||
|
* Then run `jira init`, select installation type as `cloud`, and provide the required details to generate a config
|
||||||
|
file for the Jira CLI.
|
||||||
|
* [usql](https://github.com/xo/usql) (For the `sql` agent)
|
||||||
|
* `brew install xo/xo/usql`
|
||||||
|
* [docker](https://docs.docker.com/engine/install/)
|
||||||
|
* [uv](https://docs.astral.sh/uv/getting-started/installation/)
|
||||||
|
* `curl -LsSf https://astral.sh/uv/install.sh | sh`
|
||||||
|
|
||||||
|
These tools are used to provide various functionalities within Loki, such as document processing, JSON manipulation,
|
||||||
|
interaction with Jira, and they are used within agents and tools.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
### Cargo
|
||||||
|
If you have Cargo installed, then you can install `loki` from Crates.io:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo install loki-ai # Binary name is `loki`
|
||||||
|
|
||||||
|
# If you encounter issues installing, try installing with '--locked'
|
||||||
|
cargo install --locked loki-ai
|
||||||
|
```
|
||||||
|
|
||||||
|
### Homebrew (Mac/Linux)
|
||||||
|
To install Loki from Homebrew, install the `loki` tap. Then you'll be able to install `loki`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
brew tap Dark-Alex-17/loki
|
||||||
|
brew install loki
|
||||||
|
|
||||||
|
# If you need to be more specific, use:
|
||||||
|
brew install Dark-Alex-17/loki/loki
|
||||||
|
```
|
||||||
|
|
||||||
|
To upgrade `loki` using Homebrew:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
brew upgrade loki
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scripts
|
||||||
|
#### Linux/MacOS (`bash`)
|
||||||
|
You can use the following command to run a bash script that downloads and installs the latest version of `loki` for your
|
||||||
|
OS (Linux/MacOS) and architecture (x86_64/arm64):
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -fsSL https://raw.githubusercontent.com/Dark-Alex-17/loki/main/install_loki.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Windows/Linux/MacOS (`PowerShell`)
|
||||||
|
You can use the following command to run a PowerShell script that downloads and installs the latest version of `loki`
|
||||||
|
for your OS (Windows/Linux/MacOS) and architecture (x86_64/arm64):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/Dark-Alex-17/loki/main/scripts/install_loki.ps1 | iex"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual
|
||||||
|
Binaries are available on the [releases](https://github.com/Dark-Alex-17/loki/releases) page for the following platforms:
|
||||||
|
|
||||||
|
| Platform | Architecture(s) |
|
||||||
|
|----------------|-----------------|
|
||||||
|
| macOS | x86_64, arm64 |
|
||||||
|
| Linux GNU/MUSL | x86_64, aarch64 |
|
||||||
|
| Windows | x86_64, aarch64 |
|
||||||
|
|
||||||
|
#### Windows Instructions
|
||||||
|
To use a binary from the releases page on Windows, do the following:
|
||||||
|
|
||||||
|
1. Download the latest [binary](https://github.com/Dark-Alex-17/loki/releases) for your OS.
|
||||||
|
2. Use 7-Zip or TarTool to unpack the Tar file.
|
||||||
|
3. Run the executable `loki.exe`!
|
||||||
|
|
||||||
|
#### Linux/MacOS Instructions
|
||||||
|
To use a binary from the releases page on Linux/MacOS, do the following:
|
||||||
|
|
||||||
|
1. Download the latest [binary](https://github.com/Dark-Alex-17/loki/releases) for your OS.
|
||||||
|
2. `cd` to the directory where you downloaded the binary.
|
||||||
|
3. Extract the binary with `tar -C /usr/local/bin -xzf loki-<arch>.tar.gz` (Note: This may require `sudo`)
|
||||||
|
4. Now you can run `loki`!
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
After installation, you can generate the configuration files and directories by simply running:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
loki --info
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you need to set up the Loki vault by creating a vault password file. Loki will do this for you automatically and
|
||||||
|
guide you through the process when you first attempt to access the vault. So, to get started, you can run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
loki --list-secrets
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tab-Completions
|
||||||
|
You can also enable tab completions to make using Loki easier. To do so, add the following to your shell profile:
|
||||||
|
```shell
|
||||||
|
# Bash
|
||||||
|
# (add to: `~/.bashrc`)
|
||||||
|
source <(COMPLETE=bash loki)
|
||||||
|
|
||||||
|
# Zsh
|
||||||
|
# (add to: `~/.zshrc`)
|
||||||
|
source <(COMPLETE=zsh loki)
|
||||||
|
|
||||||
|
# Fish
|
||||||
|
# (add to: `~/.config/fish/config.fish`)
|
||||||
|
source <(COMPLETE=fish loki | psub)
|
||||||
|
|
||||||
|
# Elvish
|
||||||
|
# (add to: `~/.elvish/rc.elv`)
|
||||||
|
eval (E:COMPLETE=elvish loki | slurp)
|
||||||
|
|
||||||
|
# PowerShell
|
||||||
|
# (add to: `$PROFILE`)
|
||||||
|
$env:COMPLETE = "powershell"
|
||||||
|
loki | Out-String | Invoke-Expression
|
||||||
|
```
|
||||||
|
|
||||||
|
### Shell Integration
|
||||||
|
You can integrate Loki's Shell Assistant into your shell for enhanced command-line assistance. Add the code in the
|
||||||
|
corresponding [shell integration script](./scripts/shell-integration) to your shell. Then, you can invoke Loki to convert natural language to
|
||||||
|
shell commands by pressing `Alt-e`. For example:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ find all markdown files<Alt-e>
|
||||||
|
# Will be converted to:
|
||||||
|
find . -name "*.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
The location of the global Loki configuration varies between systems, so you can use the following command to find your
|
||||||
|
`config.yaml` file:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
loki --info | grep 'config_file' | awk '{print $2}'
|
||||||
|
```
|
||||||
|
|
||||||
|
The configuration file consists of a number of settings. To see a full example configuration file with every setting
|
||||||
|
defined, refer to the [example configuration file](./config.example.yaml).
|
||||||
|
|
||||||
|
### Default LLM
|
||||||
|
The following settings are available to configure the default LLM that is used when you start Loki, and its
|
||||||
|
hyperparameters:
|
||||||
|
|
||||||
|
| Setting | Description |
|
||||||
|
|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `model` | The default LLM to use when no model is provided |
|
||||||
|
| `temperature` | The default `temperature` parameter for all models (0,1); Used unless explicitly overridden |
|
||||||
|
| `top_p` | The default `top_p` hyperparameter value to use for all models, with a range of (0,1) (or (0,2) for some models); <br>Used unless explicitly overridden |
|
||||||
|
|
||||||
|
### CLI Behavior
|
||||||
|
You can use the following settings to modify the behavior of Loki:
|
||||||
|
|
||||||
|
| Setting | Default Value | Description |
|
||||||
|
|---------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `stream` | `true` | Controls whether to use stream-style APIs when querying for completions from LLM providers |
|
||||||
|
| `save` | `true` | Controls whether to save each query/response to every model to `messages.md` for posterity; Useful for debugging |
|
||||||
|
| `keybindings` | `emacs` | Specifies which keybinding schema to use; can either be `emacs` or `vi` |
|
||||||
|
| `editor` | `null` | What text editor Loki should use to edit the input buffer or session (e.g. `vim`, `emacs`, `nano`, `hx`); <br>Defaults to `$EDITOR` |
|
||||||
|
| `wrap` | `no` | Controls whether text is wrapped (can be `no`, `auto`, or some `<max_width>` |
|
||||||
|
| `wrap_code` | `false` | Enables or disables the wrapping of code blocks |
|
||||||
|
|
||||||
|
### Preludes
|
||||||
|
Preludes let you define the default behavior for the different operating modes of Loki. The available settings are
|
||||||
|
shown below:
|
||||||
|
|
||||||
|
| Setting | Description |
|
||||||
|
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `repl_prelude` | This setting lets you specify a default `session` or `role` to use when starting Loki in [REPL](./docs/REPL.md) mode. <br>Values can be <ul><li>`role:<name>` to define a role</li><li>`session:<name>` to define a session</li><li>`<session>:<role>` to define both a session and a role to use</li></ul> |
|
||||||
|
| `cmd_prelude` | This setting lets you specify a default `session` or `role` to use when running one-off queries in Loki via the CLI. <br>Values can be <ul><li>`role:<name>` to define a role</li><li>`session:<name>` to define a session</li><li>`<session>:<role>` to define both a session and a role to use</li></ul> |
|
||||||
|
| `agent_session` | This setting is used to specify a default session that all agents should start into, unless otherwise specified in the agent configuration. (e.g. `temp`, `default`) |
|
||||||
|
|
||||||
|
### Appearance
|
||||||
|
The appearance of Loki can be modified using the following settings:
|
||||||
|
|
||||||
|
| Setting | Default Value | Description |
|
||||||
|
|---------------|---------------|------------------------------------------------------|
|
||||||
|
| `highlight` | `true` | This setting enables or disables syntax highlighting |
|
||||||
|
| `light_theme` | `false` | This setting toggles light mode in Loki |
|
||||||
|
|
||||||
|
### Miscellaneous Settings
|
||||||
|
| Setting | Default Value | Description |
|
||||||
|
|----------------------|---------------|------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `user_agent` | `null` | The name of the `User-Agent` that should be passed in the `User-Agent` header on all requests to model providers |
|
||||||
|
| `save_shell_history` | `true` | Enables or disables REPL command history |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## History
|
||||||
|
|
||||||
|
Loki began as a fork of [AIChat CLI](https://github.com/sigoden/aichat) and has since evolved into an independent project.
|
||||||
|
|
||||||
|
See [CREDITS.md](./CREDITS.md) for full attribution and background.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Creator
|
||||||
|
* [Alex Clarke](https://github.com/Dark-Alex-17)
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
Only latest version of the software will be supported with security patches.
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| -------- | ------------------ |
|
||||||
|
| latest | :white_check_mark: |
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
If you find a vulnerability, please reach out to me via email (alex.j.tusa@gmail.com).
|
||||||
|
If you yourself decide you'd like to tackle a fix, please submit a PR with the fix and I'll review it as soon as
|
||||||
|
possible.
|
||||||
Executable
+447
@@ -0,0 +1,447 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Shared Agent Utilities - Minimal, focused helper functions
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
#############################
|
||||||
|
## CONTEXT FILE MANAGEMENT ##
|
||||||
|
#############################
|
||||||
|
|
||||||
|
get_context_file() {
|
||||||
|
local project_dir="${LLM_AGENT_VAR_PROJECT_DIR:-.}"
|
||||||
|
echo "${project_dir}/.loki-context"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialize context file for a new task
|
||||||
|
# Usage: init_context "Task description"
|
||||||
|
init_context() {
|
||||||
|
local task="$1"
|
||||||
|
local project_dir="${LLM_AGENT_VAR_PROJECT_DIR:-.}"
|
||||||
|
local context_file
|
||||||
|
context_file=$(get_context_file)
|
||||||
|
|
||||||
|
cat > "${context_file}" <<EOF
|
||||||
|
## Project: ${project_dir}
|
||||||
|
## Task: ${task}
|
||||||
|
## Started: $(date -Iseconds)
|
||||||
|
|
||||||
|
### Prior Findings
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Append findings to the context file
|
||||||
|
# Usage: append_context "agent_name" "finding summary
|
||||||
|
append_context() {
|
||||||
|
local agent="$1"
|
||||||
|
local finding="$2"
|
||||||
|
local context_file
|
||||||
|
context_file=$(get_context_file)
|
||||||
|
|
||||||
|
if [[ -f "${context_file}" ]]; then
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "[${agent}]:"
|
||||||
|
echo "${finding}"
|
||||||
|
} >> "${context_file}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read the current context (returns empty string if no context)
|
||||||
|
# Usage: context=$(read_context)
|
||||||
|
read_context() {
|
||||||
|
local context_file
|
||||||
|
context_file=$(get_context_file)
|
||||||
|
|
||||||
|
if [[ -f "${context_file}" ]]; then
|
||||||
|
cat "${context_file}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clear the context file
|
||||||
|
clear_context() {
|
||||||
|
local context_file
|
||||||
|
context_file=$(get_context_file)
|
||||||
|
rm -f "${context_file}"
|
||||||
|
}
|
||||||
|
|
||||||
|
#######################
|
||||||
|
## PROJECT DETECTION ##
|
||||||
|
#######################
|
||||||
|
|
||||||
|
# Cache file name for detected project info
|
||||||
|
_LOKI_PROJECT_CACHE=".loki-project.json"
|
||||||
|
|
||||||
|
# Read cached project detection if valid
|
||||||
|
# Usage: _read_project_cache "/path/to/project"
|
||||||
|
# Returns: cached JSON on stdout (exit 0) or nothing (exit 1)
|
||||||
|
_read_project_cache() {
|
||||||
|
local dir="$1"
|
||||||
|
local cache_file="${dir}/${_LOKI_PROJECT_CACHE}"
|
||||||
|
|
||||||
|
if [[ -f "${cache_file}" ]]; then
|
||||||
|
local cached
|
||||||
|
cached=$(cat "${cache_file}" 2>/dev/null) || return 1
|
||||||
|
if echo "${cached}" | jq -e '.type and .build != null and .test != null and .check != null' &>/dev/null; then
|
||||||
|
echo "${cached}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Write project detection result to cache
|
||||||
|
# Usage: _write_project_cache "/path/to/project" '{"type":"rust",...}'
|
||||||
|
_write_project_cache() {
|
||||||
|
local dir="$1"
|
||||||
|
local json="$2"
|
||||||
|
local cache_file="${dir}/${_LOKI_PROJECT_CACHE}"
|
||||||
|
|
||||||
|
echo "${json}" > "${cache_file}" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
_detect_heuristic() {
|
||||||
|
local dir="$1"
|
||||||
|
|
||||||
|
# Rust
|
||||||
|
if [[ -f "${dir}/Cargo.toml" ]]; then
|
||||||
|
echo '{"type":"rust","build":"cargo build","test":"cargo test","check":"cargo check"}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Go
|
||||||
|
if [[ -f "${dir}/go.mod" ]]; then
|
||||||
|
echo '{"type":"go","build":"go build ./...","test":"go test ./...","check":"go vet ./..."}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Node.JS/Deno/Bun
|
||||||
|
if [[ -f "${dir}/deno.json" ]] || [[ -f "${dir}/deno.jsonc" ]]; then
|
||||||
|
echo '{"type":"deno","build":"deno task build","test":"deno test","check":"deno lint"}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "${dir}/package.json" ]]; then
|
||||||
|
local pm="npm"
|
||||||
|
|
||||||
|
[[ -f "${dir}/bun.lockb" ]] || [[ -f "${dir}/bun.lock" ]] && pm="bun"
|
||||||
|
[[ -f "${dir}/pnpm-lock.yaml" ]] && pm="pnpm"
|
||||||
|
[[ -f "${dir}/yarn.lock" ]] && pm="yarn"
|
||||||
|
|
||||||
|
echo "{\"type\":\"nodejs\",\"build\":\"${pm} run build\",\"test\":\"${pm} test\",\"check\":\"${pm} run lint\"}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Python
|
||||||
|
if [[ -f "${dir}/pyproject.toml" ]] || [[ -f "${dir}/setup.py" ]] || [[ -f "${dir}/setup.cfg" ]]; then
|
||||||
|
local test_cmd="pytest"
|
||||||
|
local check_cmd="ruff check ."
|
||||||
|
|
||||||
|
if [[ -f "${dir}/poetry.lock" ]]; then
|
||||||
|
test_cmd="poetry run pytest"
|
||||||
|
check_cmd="poetry run ruff check ."
|
||||||
|
elif [[ -f "${dir}/uv.lock" ]]; then
|
||||||
|
test_cmd="uv run pytest"
|
||||||
|
check_cmd="uv run ruff check ."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "{\"type\":\"python\",\"build\":\"\",\"test\":\"${test_cmd}\",\"check\":\"${check_cmd}\"}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# JVM (Maven)
|
||||||
|
if [[ -f "${dir}/pom.xml" ]]; then
|
||||||
|
echo '{"type":"java","build":"mvn compile","test":"mvn test","check":"mvn verify"}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# JVM (Gradle)
|
||||||
|
if [[ -f "${dir}/build.gradle" ]] || [[ -f "${dir}/build.gradle.kts" ]]; then
|
||||||
|
local gw="gradle"
|
||||||
|
[[ -f "${dir}/gradlew" ]] && gw="./gradlew"
|
||||||
|
echo "{\"type\":\"java\",\"build\":\"${gw} build\",\"test\":\"${gw} test\",\"check\":\"${gw} check\"}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# .NET / C#
|
||||||
|
if compgen -G "${dir}/*.sln" &>/dev/null || compgen -G "${dir}/*.csproj" &>/dev/null; then
|
||||||
|
echo '{"type":"dotnet","build":"dotnet build","test":"dotnet test","check":"dotnet build --warnaserrors"}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# C/C++ (CMake)
|
||||||
|
if [[ -f "${dir}/CMakeLists.txt" ]]; then
|
||||||
|
echo '{"type":"cmake","build":"cmake --build build","test":"ctest --test-dir build","check":"cmake --build build"}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ruby
|
||||||
|
if [[ -f "${dir}/Gemfile" ]]; then
|
||||||
|
local test_cmd="bundle exec rake test"
|
||||||
|
[[ -f "${dir}/Rakefile" ]] && grep -q "rspec" "${dir}/Gemfile" 2>/dev/null && test_cmd="bundle exec rspec"
|
||||||
|
echo "{\"type\":\"ruby\",\"build\":\"\",\"test\":\"${test_cmd}\",\"check\":\"bundle exec rubocop\"}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Elixir
|
||||||
|
if [[ -f "${dir}/mix.exs" ]]; then
|
||||||
|
echo '{"type":"elixir","build":"mix compile","test":"mix test","check":"mix credo"}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# PHP
|
||||||
|
if [[ -f "${dir}/composer.json" ]]; then
|
||||||
|
echo '{"type":"php","build":"","test":"./vendor/bin/phpunit","check":"./vendor/bin/phpstan analyse"}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Swift
|
||||||
|
if [[ -f "${dir}/Package.swift" ]]; then
|
||||||
|
echo '{"type":"swift","build":"swift build","test":"swift test","check":"swift build"}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Zig
|
||||||
|
if [[ -f "${dir}/build.zig" ]]; then
|
||||||
|
echo '{"type":"zig","build":"zig build","test":"zig build test","check":"zig build"}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generic build systems (last resort before LLM)
|
||||||
|
if [[ -f "${dir}/justfile" ]] || [[ -f "${dir}/Justfile" ]]; then
|
||||||
|
echo '{"type":"just","build":"just build","test":"just test","check":"just lint"}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "${dir}/Makefile" ]] || [[ -f "${dir}/makefile" ]] || [[ -f "${dir}/GNUmakefile" ]]; then
|
||||||
|
echo '{"type":"make","build":"make build","test":"make test","check":"make lint"}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gather lightweight evidence about a project for LLM analysis
|
||||||
|
# Usage: _gather_project_evidence "/path/to/project"
|
||||||
|
# Returns: evidence string on stdout
|
||||||
|
_gather_project_evidence() {
|
||||||
|
local dir="$1"
|
||||||
|
local evidence=""
|
||||||
|
|
||||||
|
evidence+="Root files and directories:"$'\n'
|
||||||
|
evidence+=$(ls -1 "${dir}" 2>/dev/null | head -50)
|
||||||
|
evidence+=$'\n\n'
|
||||||
|
|
||||||
|
evidence+="File extension counts:"$'\n'
|
||||||
|
evidence+=$(find "${dir}" -type f \
|
||||||
|
-not -path '*/.git/*' \
|
||||||
|
-not -path '*/node_modules/*' \
|
||||||
|
-not -path '*/target/*' \
|
||||||
|
-not -path '*/dist/*' \
|
||||||
|
-not -path '*/__pycache__/*' \
|
||||||
|
-not -path '*/vendor/*' \
|
||||||
|
-not -path '*/.build/*' \
|
||||||
|
2>/dev/null \
|
||||||
|
| sed 's/.*\.//' | sort | uniq -c | sort -rn | head -10)
|
||||||
|
evidence+=$'\n\n'
|
||||||
|
|
||||||
|
local config_patterns=("*.toml" "*.yaml" "*.yml" "*.json" "*.xml" "*.gradle" "*.gradle.kts" "*.cabal" "*.pro" "Makefile" "justfile" "Justfile" "Dockerfile" "Taskfile*" "BUILD" "WORKSPACE" "flake.nix" "shell.nix" "default.nix")
|
||||||
|
local found_configs=0
|
||||||
|
|
||||||
|
for pattern in "${config_patterns[@]}"; do
|
||||||
|
if [[ ${found_configs} -ge 5 ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
local files
|
||||||
|
files=$(find "${dir}" -maxdepth 1 -name "${pattern}" -type f 2>/dev/null)
|
||||||
|
|
||||||
|
while IFS= read -r f; do
|
||||||
|
if [[ -n "${f}" && ${found_configs} -lt 5 ]]; then
|
||||||
|
local basename
|
||||||
|
basename=$(basename "${f}")
|
||||||
|
evidence+="--- ${basename} (first 30 lines) ---"$'\n'
|
||||||
|
evidence+=$(head -30 "${f}" 2>/dev/null)
|
||||||
|
evidence+=$'\n\n'
|
||||||
|
found_configs=$((found_configs + 1))
|
||||||
|
fi
|
||||||
|
done <<< "${files}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "${evidence}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# LLM-based project detection fallback
|
||||||
|
# Usage: _detect_with_llm "/path/to/project"
|
||||||
|
# Returns: JSON on stdout or empty (exit 1)
|
||||||
|
_detect_with_llm() {
|
||||||
|
local dir="$1"
|
||||||
|
local evidence
|
||||||
|
evidence=$(_gather_project_evidence "${dir}")
|
||||||
|
local prompt
|
||||||
|
prompt=$(cat <<-EOF
|
||||||
|
|
||||||
|
Analyze this project directory and determine the project type, primary language, and the correct shell commands to build, test, and check (lint/typecheck) it.
|
||||||
|
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
prompt+=$'\n'"${evidence}"$'\n'
|
||||||
|
prompt+=$(cat <<-EOF
|
||||||
|
|
||||||
|
Respond with ONLY a valid JSON object. No markdown fences, no explanation, no extra text.
|
||||||
|
The JSON must have exactly these 4 keys:
|
||||||
|
{"type":"<language>","build":"<build command>","test":"<test command>","check":"<lint or typecheck command>"}
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- "type" must be a single lowercase word (e.g. rust, go, python, nodejs, java, ruby, elixir, cpp, c, zig, haskell, scala, kotlin, dart, swift, php, dotnet, etc.)
|
||||||
|
- If a command doesn't apply to this project, use an empty string, ""
|
||||||
|
- Use the most standard/common commands for the detected ecosystem
|
||||||
|
- If you detect a package manager lockfile, use that package manager (e.g. pnpm over npm)
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
local llm_response
|
||||||
|
llm_response=$(loki --no-stream "${prompt}" 2>/dev/null) || return 1
|
||||||
|
|
||||||
|
llm_response=$(echo "${llm_response}" | sed 's/^```json//;s/^```//;s/```$//' | tr -d '\n' | sed 's/^[[:space:]]*//')
|
||||||
|
llm_response=$(echo "${llm_response}" | grep -o '{[^}]*}' | head -1)
|
||||||
|
|
||||||
|
if echo "${llm_response}" | jq -e '.type and .build != null and .test != null and .check != null' &>/dev/null; then
|
||||||
|
echo "${llm_response}" | jq -c '{type: (.type // "unknown"), build: (.build // ""), test: (.test // ""), check: (.check // "")}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Detect project type and return build/test commands
|
||||||
|
# Uses: cached result -> fast heuristics -> LLM fallback
|
||||||
|
detect_project() {
|
||||||
|
local dir="${1:-.}"
|
||||||
|
|
||||||
|
local cached
|
||||||
|
if cached=$(_read_project_cache "${dir}"); then
|
||||||
|
echo "${cached}" | jq -c '{type, build, test, check}'
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local result
|
||||||
|
if result=$(_detect_heuristic "${dir}"); then
|
||||||
|
local enriched
|
||||||
|
enriched=$(echo "${result}" | jq -c '. + {"_detected_by":"heuristic","_cached_at":"'"$(date -Iseconds)"'"}')
|
||||||
|
|
||||||
|
_write_project_cache "${dir}" "${enriched}"
|
||||||
|
|
||||||
|
echo "${result}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if result=$(_detect_with_llm "${dir}"); then
|
||||||
|
local enriched
|
||||||
|
enriched=$(echo "${result}" | jq -c '. + {"_detected_by":"llm","_cached_at":"'"$(date -Iseconds)"'"}')
|
||||||
|
|
||||||
|
_write_project_cache "${dir}" "${enriched}"
|
||||||
|
|
||||||
|
echo "${result}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo '{"type":"unknown","build":"","test":"","check":""}'
|
||||||
|
}
|
||||||
|
|
||||||
|
######################
|
||||||
|
## AGENT INVOCATION ##
|
||||||
|
######################
|
||||||
|
|
||||||
|
# Invoke a subagent with optional context injection
|
||||||
|
# Usage: invoke_agent <agent_name> <prompt> [extra_args...]
|
||||||
|
invoke_agent() {
|
||||||
|
local agent="$1"
|
||||||
|
local prompt="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
local context
|
||||||
|
context=$(read_context)
|
||||||
|
|
||||||
|
local full_prompt
|
||||||
|
if [[ -n "${context}" ]]; then
|
||||||
|
full_prompt="## Orchestrator Context
|
||||||
|
|
||||||
|
The orchestrator (sisyphus) has gathered this context from prior work:
|
||||||
|
|
||||||
|
<context>
|
||||||
|
${context}
|
||||||
|
</context>
|
||||||
|
|
||||||
|
## Your Task
|
||||||
|
|
||||||
|
${prompt}"
|
||||||
|
else
|
||||||
|
full_prompt="${prompt}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
env AUTO_CONFIRM=true loki --agent "${agent}" "$@" "${full_prompt}" 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Invoke a subagent and capture a summary of its findings
|
||||||
|
# Usage: result=$(invoke_agent_with_summary "explore" "find auth patterns")
|
||||||
|
invoke_agent_with_summary() {
|
||||||
|
local agent="$1"
|
||||||
|
local prompt="$2"
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
local output
|
||||||
|
output=$(invoke_agent "${agent}" "${prompt}" "$@")
|
||||||
|
|
||||||
|
local summary=""
|
||||||
|
|
||||||
|
if echo "${output}" | grep -q "FINDINGS:"; then
|
||||||
|
summary=$(echo "${output}" | sed -n '/FINDINGS:/,/^[A-Z_]*COMPLETE/p' | grep "^- " | sed 's/^- / - /')
|
||||||
|
elif echo "${output}" | grep -q "CODER_COMPLETE:"; then
|
||||||
|
summary=$(echo "${output}" | grep "CODER_COMPLETE:" | sed 's/CODER_COMPLETE: *//')
|
||||||
|
elif echo "${output}" | grep -q "ORACLE_COMPLETE"; then
|
||||||
|
summary=$(echo "${output}" | sed -n '/^## Recommendation/,/^## /{/^## Recommendation/d;/^## /d;p}' | sed '/^$/d' | head -10)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Failsafe: extract up to 5 meaningful lines if no markers found
|
||||||
|
if [[ -z "${summary}" ]]; then
|
||||||
|
summary=$(echo "${output}" | grep -v "^$" | grep -v "^#" | grep -v "^\-\-\-" | tail -10 | head -5)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${summary}" ]]; then
|
||||||
|
append_context "${agent}" "${summary}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${output}"
|
||||||
|
}
|
||||||
|
|
||||||
|
###########################
|
||||||
|
## FILE SEARCH UTILITIES ##
|
||||||
|
###########################
|
||||||
|
|
||||||
|
search_files() {
|
||||||
|
local pattern="$1"
|
||||||
|
local dir="${2:-.}"
|
||||||
|
|
||||||
|
find "${dir}" -type f -name "${pattern}" \
|
||||||
|
-not -path '*/target/*' \
|
||||||
|
-not -path '*/node_modules/*' \
|
||||||
|
-not -path '*/.git/*' \
|
||||||
|
-not -path '*/dist/*' \
|
||||||
|
-not -path '*/__pycache__/*' \
|
||||||
|
2>/dev/null | head -25
|
||||||
|
}
|
||||||
|
|
||||||
|
get_tree() {
|
||||||
|
local dir="${1:-.}"
|
||||||
|
local depth="${2:-3}"
|
||||||
|
|
||||||
|
if command -v tree &>/dev/null; then
|
||||||
|
tree -L "${depth}" --noreport -I 'node_modules|target|dist|.git|__pycache__|*.pyc' "${dir}" 2>/dev/null || find "${dir}" -maxdepth "${depth}" -type f | head -50
|
||||||
|
else
|
||||||
|
find "${dir}" -maxdepth "${depth}" -type f \
|
||||||
|
-not -path '*/target/*' \
|
||||||
|
-not -path '*/node_modules/*' \
|
||||||
|
-not -path '*/.git/*' \
|
||||||
|
2>/dev/null | head -50
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Coder
|
||||||
|
|
||||||
|
An AI agent that assists you with your coding tasks.
|
||||||
|
|
||||||
|
This agent is designed to be delegated to by the **[Sisyphus](../sisyphus/README.md)** agent to implement code specifications. Sisyphus
|
||||||
|
acts as the coordinator/architect, while Coder handles the implementation details.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 🏗️ Intelligent project structure creation and management
|
||||||
|
- 🖼️ Convert screenshots into clean, functional code
|
||||||
|
- 📁 Comprehensive file system operations (create folders, files, read/write files)
|
||||||
|
- 🧐 Advanced code analysis and improvement suggestions
|
||||||
|
- 📊 Precise diff-based file editing for controlled code modifications
|
||||||
|
|
||||||
|
It can also be used as a standalone tool for direct coding assistance.
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
name: coder
|
||||||
|
description: Implementation agent - writes code, follows patterns, verifies with builds
|
||||||
|
version: 1.0.0
|
||||||
|
temperature: 0.1
|
||||||
|
top_p: 0.95
|
||||||
|
|
||||||
|
auto_continue: true
|
||||||
|
max_auto_continues: 15
|
||||||
|
inject_todo_instructions: true
|
||||||
|
|
||||||
|
variables:
|
||||||
|
- name: project_dir
|
||||||
|
description: Project directory to work in
|
||||||
|
default: '.'
|
||||||
|
|
||||||
|
global_tools:
|
||||||
|
- fs_read.sh
|
||||||
|
- fs_grep.sh
|
||||||
|
- fs_glob.sh
|
||||||
|
- fs_write.sh
|
||||||
|
- fs_patch.sh
|
||||||
|
- execute_command.sh
|
||||||
|
|
||||||
|
instructions: |
|
||||||
|
You are a senior engineer. You write code that works on the first try.
|
||||||
|
|
||||||
|
## Your Mission
|
||||||
|
|
||||||
|
Given an implementation task:
|
||||||
|
1. Understand what to build (from context provided)
|
||||||
|
2. Study existing patterns (read 1-2 similar files)
|
||||||
|
3. Write the code (using tools, NOT chat output)
|
||||||
|
4. Verify it compiles/builds
|
||||||
|
5. Signal completion
|
||||||
|
|
||||||
|
## Todo System
|
||||||
|
|
||||||
|
For multi-file changes:
|
||||||
|
1. `todo__init` with the implementation goal
|
||||||
|
2. `todo__add` for each file to create/modify
|
||||||
|
3. Implement each, calling `todo__done` immediately after
|
||||||
|
|
||||||
|
## Writing Code
|
||||||
|
|
||||||
|
**CRITICAL**: Write code using `write_file` tool, NEVER paste code in chat.
|
||||||
|
|
||||||
|
Correct:
|
||||||
|
```
|
||||||
|
write_file --path "src/user.rs" --content "pub struct User { ... }"
|
||||||
|
```
|
||||||
|
|
||||||
|
Wrong:
|
||||||
|
```
|
||||||
|
Here's the implementation:
|
||||||
|
\`\`\`rust
|
||||||
|
pub struct User { ... }
|
||||||
|
\`\`\`
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Reading Strategy (IMPORTANT - minimize token usage)
|
||||||
|
|
||||||
|
1. **Use grep to find relevant code** - `fs_grep --pattern "fn handle_request" --include "*.rs"` finds where things are
|
||||||
|
2. **Read only what you need** - `fs_read --path "src/main.rs" --offset 50 --limit 30` reads lines 50-79
|
||||||
|
3. **Never cat entire large files** - If 500+ lines, read the relevant section after grepping for it
|
||||||
|
4. **Use glob to find files** - `fs_glob --pattern "*.rs" --path src/` discovers files by name
|
||||||
|
|
||||||
|
## Pattern Matching
|
||||||
|
|
||||||
|
Before writing ANY file:
|
||||||
|
1. Find a similar existing file (use `fs_grep` to locate, then `fs_read` to examine)
|
||||||
|
2. Match its style: imports, naming, structure
|
||||||
|
3. Follow the same patterns exactly
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
After writing files:
|
||||||
|
1. Run `verify_build` to check compilation
|
||||||
|
2. If it fails, fix the error (minimal change)
|
||||||
|
3. Don't move on until build passes
|
||||||
|
|
||||||
|
## Completion Signal
|
||||||
|
|
||||||
|
End with:
|
||||||
|
```
|
||||||
|
CODER_COMPLETE: [summary of what was implemented]
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if failed:
|
||||||
|
```
|
||||||
|
CODER_FAILED: [what went wrong]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. **Write code via tools** - Never output code to chat
|
||||||
|
2. **Follow patterns** - Read existing files first
|
||||||
|
3. **Verify builds** - Don't finish without checking
|
||||||
|
4. **Minimal fixes** - If build fails, fix precisely
|
||||||
|
5. **No refactoring** - Only implement what's asked
|
||||||
|
|
||||||
|
## Context
|
||||||
|
- Project: {{project_dir}}
|
||||||
|
- CWD: {{__cwd__}}
|
||||||
|
- Shell: {{__shell__}}
|
||||||
|
|
||||||
Executable
+196
@@ -0,0 +1,196 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
source "$LLM_ROOT_DIR/agents/.shared/utils.sh"
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout
|
||||||
|
# @env LLM_AGENT_VAR_PROJECT_DIR=.
|
||||||
|
# @describe Coder agent tools for implementing code changes
|
||||||
|
|
||||||
|
_project_dir() {
|
||||||
|
local dir="${LLM_AGENT_VAR_PROJECT_DIR:-.}"
|
||||||
|
(cd "${dir}" 2>/dev/null && pwd) || echo "${dir}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Read a file's contents before modifying
|
||||||
|
# @option --path! Path to the file (relative to project root)
|
||||||
|
read_file() {
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
local file_path="${argc_path}"
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
local full_path="${project_dir}/${file_path}"
|
||||||
|
|
||||||
|
if [[ ! -f "${full_path}" ]]; then
|
||||||
|
warn "File not found: ${file_path}" >> "$LLM_OUTPUT"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
info "Reading: ${file_path}"
|
||||||
|
echo ""
|
||||||
|
cat "${full_path}"
|
||||||
|
} >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Write complete file contents
|
||||||
|
# @option --path! Path for the file (relative to project root)
|
||||||
|
# @option --content! Complete file contents to write
|
||||||
|
write_file() {
|
||||||
|
local file_path="${argc_path}"
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
local content="${argc_content}"
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
local full_path="${project_dir}/${file_path}"
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "${full_path}")"
|
||||||
|
echo "${content}" > "${full_path}"
|
||||||
|
|
||||||
|
green "Wrote: ${file_path}" >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Find files similar to a given path (for pattern matching)
|
||||||
|
# @option --path! Path to find similar files for
|
||||||
|
find_similar_files() {
|
||||||
|
local file_path="${argc_path}"
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
local ext="${file_path##*.}"
|
||||||
|
local dir
|
||||||
|
dir=$(dirname "${file_path}")
|
||||||
|
|
||||||
|
info "Similar files to: ${file_path}" >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local results
|
||||||
|
results=$(find "${project_dir}/${dir}" -maxdepth 1 -type f -name "*.${ext}" \
|
||||||
|
! -name "$(basename "${file_path}")" \
|
||||||
|
! -name "*test*" \
|
||||||
|
! -name "*spec*" \
|
||||||
|
2>/dev/null | head -3)
|
||||||
|
|
||||||
|
if [[ -z "${results}" ]]; then
|
||||||
|
results=$(find "${project_dir}/src" -type f -name "*.${ext}" \
|
||||||
|
! -name "*test*" \
|
||||||
|
! -name "*spec*" \
|
||||||
|
-not -path '*/target/*' \
|
||||||
|
2>/dev/null | head -3)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${results}" ]]; then
|
||||||
|
echo "${results}" >> "$LLM_OUTPUT"
|
||||||
|
else
|
||||||
|
warn "No similar files found" >> "$LLM_OUTPUT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Verify the project builds successfully
|
||||||
|
verify_build() {
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
local project_info
|
||||||
|
project_info=$(detect_project "${project_dir}")
|
||||||
|
local build_cmd
|
||||||
|
build_cmd=$(echo "${project_info}" | jq -r '.check // .build')
|
||||||
|
|
||||||
|
if [[ -z "${build_cmd}" ]] || [[ "${build_cmd}" == "null" ]]; then
|
||||||
|
warn "No build command detected" >> "$LLM_OUTPUT"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Running: ${build_cmd}" >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local output exit_code=0
|
||||||
|
output=$(cd "${project_dir}" && eval "${build_cmd}" 2>&1) || exit_code=$?
|
||||||
|
|
||||||
|
echo "${output}" >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
if [[ ${exit_code} -eq 0 ]]; then
|
||||||
|
green "BUILD SUCCESS" >> "$LLM_OUTPUT"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
error "BUILD FAILED (exit code: ${exit_code})" >> "$LLM_OUTPUT"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Run project tests
|
||||||
|
run_tests() {
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
local project_info
|
||||||
|
project_info=$(detect_project "${project_dir}")
|
||||||
|
local test_cmd
|
||||||
|
test_cmd=$(echo "${project_info}" | jq -r '.test')
|
||||||
|
|
||||||
|
if [[ -z "${test_cmd}" ]] || [[ "${test_cmd}" == "null" ]]; then
|
||||||
|
warn "No test command detected" >> "$LLM_OUTPUT"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Running: ${test_cmd}" >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local output exit_code=0
|
||||||
|
output=$(cd "${project_dir}" && eval "${test_cmd}" 2>&1) || exit_code=$?
|
||||||
|
|
||||||
|
echo "${output}" >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
if [[ ${exit_code} -eq 0 ]]; then
|
||||||
|
green "TESTS PASSED" >> "$LLM_OUTPUT"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
error "TESTS FAILED (exit code: ${exit_code})" >> "$LLM_OUTPUT"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Get project structure for context
|
||||||
|
get_project_structure() {
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
local project_info
|
||||||
|
project_info=$(detect_project "${project_dir}")
|
||||||
|
|
||||||
|
{
|
||||||
|
info "Project: $(echo "${project_info}" | jq -r '.type')"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
get_tree "${project_dir}" 2
|
||||||
|
} >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Search for content in the codebase
|
||||||
|
# @option --pattern! Pattern to search for
|
||||||
|
search_code() {
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
local pattern="${argc_pattern}"
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
info "Searching: ${pattern}" >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local results
|
||||||
|
results=$(grep -rn "${pattern}" "${project_dir}" 2>/dev/null | \
|
||||||
|
grep -v '/target/' | \
|
||||||
|
grep -v '/node_modules/' | \
|
||||||
|
grep -v '/.git/' | \
|
||||||
|
head -20) || true
|
||||||
|
|
||||||
|
if [[ -n "${results}" ]]; then
|
||||||
|
echo "${results}" >> "$LLM_OUTPUT"
|
||||||
|
else
|
||||||
|
warn "No matches" >> "$LLM_OUTPUT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Demo
|
||||||
|
|
||||||
|
This agent serves as a demo to guide agent development and showcase various agent capabilities.
|
||||||
|
|
||||||
|
To enable tools, Loki will look for the first `tools.py` or `tools.sh` file it finds in this directory.
|
||||||
|
|
||||||
|
The base configuration using `tools.py`. To switch to using `tools.sh`, rename or remove `tools.py`.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
name: Demo
|
||||||
|
description: An AI agent that demonstrates agent capabilities
|
||||||
|
version: 0.1.0
|
||||||
|
global_tools:
|
||||||
|
- execute_command.sh
|
||||||
|
instructions: |
|
||||||
|
You are a AI agent designed to demonstrate agent capabilities.
|
||||||
|
|
||||||
|
<tools>
|
||||||
|
{{__tools__}}
|
||||||
|
</tools>
|
||||||
|
|
||||||
|
<system>
|
||||||
|
os: {{__os__}}
|
||||||
|
os_family: {{__os_family__}}
|
||||||
|
arch: {{__arch__}}
|
||||||
|
shell: {{__shell__}}
|
||||||
|
locale: {{__locale__}}
|
||||||
|
now: {{__now__}}
|
||||||
|
cwd: {{__cwd__}}
|
||||||
|
</system>
|
||||||
|
|
||||||
|
<user>
|
||||||
|
username: {{username}}
|
||||||
|
</user>
|
||||||
|
variables:
|
||||||
|
- name: username
|
||||||
|
description: Your user name
|
||||||
|
conversation_starters:
|
||||||
|
- What is my username?
|
||||||
|
- What is my current shell?
|
||||||
|
- What is my ip?
|
||||||
|
- How much disk space is left on my PC??
|
||||||
|
- How to create an agent?
|
||||||
|
documents:
|
||||||
|
- README.md
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import urllib.request
|
||||||
|
|
||||||
|
def get_ipinfo():
|
||||||
|
"""
|
||||||
|
Get the ip info
|
||||||
|
"""
|
||||||
|
with urllib.request.urlopen("https://httpbin.org/ip") as response:
|
||||||
|
data = response.read()
|
||||||
|
return data.decode('utf-8')
|
||||||
Executable
+10
@@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
# @cmd Get the ip info
|
||||||
|
get_ipinfo() {
|
||||||
|
curl -fsSL https://httpbin.org/ip >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Explore
|
||||||
|
|
||||||
|
An AI agent specialized in exploring codebases, finding patterns, and understanding project structures.
|
||||||
|
|
||||||
|
This agent is designed to be delegated to by the **[Sisyphus](../sisyphus/README.md)** agent to gather information and context. Sisyphus
|
||||||
|
acts as the coordinator/architect, while Explore handles the research and discovery phase.
|
||||||
|
|
||||||
|
It can also be used as a standalone tool for understanding codebases and finding specific information.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 🔍 Deep codebase exploration and pattern matching
|
||||||
|
- 📂 File system navigation and content analysis
|
||||||
|
- 🧠 Context gathering for complex tasks
|
||||||
|
- 🛡️ Read-only operations for safe investigation
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
name: explore
|
||||||
|
description: Fast codebase exploration agent - finds patterns, structures, and relevant files
|
||||||
|
version: 1.0.0
|
||||||
|
temperature: 0.1
|
||||||
|
top_p: 0.95
|
||||||
|
|
||||||
|
variables:
|
||||||
|
- name: project_dir
|
||||||
|
description: Project directory to explore
|
||||||
|
default: '.'
|
||||||
|
|
||||||
|
global_tools:
|
||||||
|
- fs_read.sh
|
||||||
|
- fs_grep.sh
|
||||||
|
- fs_glob.sh
|
||||||
|
- fs_ls.sh
|
||||||
|
|
||||||
|
instructions: |
|
||||||
|
You are a codebase explorer. Your job: Search, find, report. Nothing else.
|
||||||
|
|
||||||
|
## Your Mission
|
||||||
|
|
||||||
|
Given a search task, you:
|
||||||
|
1. Search for relevant files and patterns
|
||||||
|
2. Read key files to understand structure
|
||||||
|
3. Report findings concisely
|
||||||
|
4. Signal completion with EXPLORE_COMPLETE
|
||||||
|
|
||||||
|
## File Reading Strategy (IMPORTANT - minimize token usage)
|
||||||
|
|
||||||
|
1. **Find first, read second** - Never read a file without knowing why
|
||||||
|
2. **Use grep to locate** - `fs_grep --pattern "struct User" --include "*.rs"` finds exactly where things are
|
||||||
|
3. **Use glob to discover** - `fs_glob --pattern "*.rs" --path src/` finds files by name
|
||||||
|
4. **Read targeted sections** - `fs_read --path "src/main.rs" --offset 50 --limit 30` reads only lines 50-79
|
||||||
|
5. **Never read entire large files** - If a file is 500+ lines, read the relevant section only
|
||||||
|
|
||||||
|
## Available Actions
|
||||||
|
|
||||||
|
- `fs_grep --pattern "struct User" --include "*.rs"` - Find content across files
|
||||||
|
- `fs_glob --pattern "*.rs" --path src/` - Find files by name pattern
|
||||||
|
- `fs_read --path "src/main.rs"` - Read a file (with line numbers)
|
||||||
|
- `fs_read --path "src/main.rs" --offset 100 --limit 50` - Read lines 100-149 only
|
||||||
|
- `get_structure` - See project layout
|
||||||
|
- `search_content --pattern "struct User"` - Agent-level content search
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
Always end your response with a findings summary:
|
||||||
|
|
||||||
|
```
|
||||||
|
FINDINGS:
|
||||||
|
- [Key finding 1]
|
||||||
|
- [Key finding 2]
|
||||||
|
- Relevant files: [list]
|
||||||
|
|
||||||
|
EXPLORE_COMPLETE
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. **Be fast** - Don't read every file, read representative ones
|
||||||
|
2. **Be focused** - Answer the specific question asked
|
||||||
|
3. **Be concise** - Report findings, not your process
|
||||||
|
4. **Never modify files** - You are read-only
|
||||||
|
5. **Limit reads** - Max 5 file reads per exploration
|
||||||
|
|
||||||
|
## Context
|
||||||
|
- Project: {{project_dir}}
|
||||||
|
- CWD: {{__cwd__}}
|
||||||
|
|
||||||
|
conversation_starters:
|
||||||
|
- 'Find how authentication is implemented'
|
||||||
|
- 'What patterns are used for API endpoints'
|
||||||
|
- 'Show me the project structure'
|
||||||
Executable
+157
@@ -0,0 +1,157 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
source "$LLM_ROOT_DIR/agents/.shared/utils.sh"
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout
|
||||||
|
# @env LLM_AGENT_VAR_PROJECT_DIR=.
|
||||||
|
# @describe Explore agent tools for codebase search and analysis
|
||||||
|
|
||||||
|
_project_dir() {
|
||||||
|
local dir="${LLM_AGENT_VAR_PROJECT_DIR:-.}"
|
||||||
|
(cd "${dir}" 2>/dev/null && pwd) || echo "${dir}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Get project structure and layout
|
||||||
|
get_structure() {
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
info "Project structure:" >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local project_info
|
||||||
|
project_info=$(detect_project "${project_dir}")
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "Type: $(echo "${project_info}" | jq -r '.type')"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
get_tree "${project_dir}" 3
|
||||||
|
} >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Search for files by name pattern
|
||||||
|
# @option --pattern! File name pattern (e.g., "*.rs", "config*", "*test*")
|
||||||
|
search_files() {
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
local pattern="${argc_pattern}"
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
info "Files matching: ${pattern}" >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local results
|
||||||
|
results=$(search_files "${pattern}" "${project_dir}")
|
||||||
|
|
||||||
|
if [[ -n "${results}" ]]; then
|
||||||
|
echo "${results}" >> "$LLM_OUTPUT"
|
||||||
|
else
|
||||||
|
warn "No files found" >> "$LLM_OUTPUT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Search for content in files
|
||||||
|
# @option --pattern! Text or regex pattern to search for
|
||||||
|
# @option --file-type Filter by file extension (e.g., "rs", "py", "ts")
|
||||||
|
search_content() {
|
||||||
|
local pattern="${argc_pattern}"
|
||||||
|
local file_type="${argc_file_type:-}"
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
info "Searching: ${pattern}" >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local include_arg=""
|
||||||
|
if [[ -n "${file_type}" ]]; then
|
||||||
|
include_arg="--include=*.${file_type}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local results
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
results=$(grep -rn ${include_arg} "${pattern}" "${project_dir}" 2>/dev/null | \
|
||||||
|
grep -v '/target/' | \
|
||||||
|
grep -v '/node_modules/' | \
|
||||||
|
grep -v '/.git/' | \
|
||||||
|
grep -v '/dist/' | \
|
||||||
|
head -30) || true
|
||||||
|
|
||||||
|
if [[ -n "${results}" ]]; then
|
||||||
|
echo "${results}" >> "$LLM_OUTPUT"
|
||||||
|
else
|
||||||
|
warn "No matches found" >> "$LLM_OUTPUT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Read a file's contents
|
||||||
|
# @option --path! Path to the file (relative to project root)
|
||||||
|
# @option --lines Maximum lines to read (default: 200)
|
||||||
|
read_file() {
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
local file_path="${argc_path}"
|
||||||
|
local max_lines="${argc_lines:-200}"
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
local full_path="${project_dir}/${file_path}"
|
||||||
|
|
||||||
|
if [[ ! -f "${full_path}" ]]; then
|
||||||
|
error "File not found: ${file_path}" >> "$LLM_OUTPUT"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
info "File: ${file_path}"
|
||||||
|
echo ""
|
||||||
|
} >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
head -n "${max_lines}" "${full_path}" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local total_lines
|
||||||
|
total_lines=$(wc -l < "${full_path}")
|
||||||
|
if [[ "${total_lines}" -gt "${max_lines}" ]]; then
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
warn "... truncated (${total_lines} total lines)" >> "$LLM_OUTPUT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Find similar files to a given file (for pattern matching)
|
||||||
|
# @option --path! Path to the reference file
|
||||||
|
find_similar() {
|
||||||
|
local file_path="${argc_path}"
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
local ext="${file_path##*.}"
|
||||||
|
local dir
|
||||||
|
dir=$(dirname "${file_path}")
|
||||||
|
|
||||||
|
info "Files similar to: ${file_path}" >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local results
|
||||||
|
results=$(find "${project_dir}/${dir}" -maxdepth 1 -type f -name "*.${ext}" \
|
||||||
|
! -name "$(basename "${file_path}")" \
|
||||||
|
! -name "*test*" \
|
||||||
|
! -name "*spec*" \
|
||||||
|
2>/dev/null | head -5)
|
||||||
|
|
||||||
|
if [[ -n "${results}" ]]; then
|
||||||
|
echo "${results}" >> "$LLM_OUTPUT"
|
||||||
|
else
|
||||||
|
results=$(find "${project_dir}" -type f -name "*.${ext}" \
|
||||||
|
! -name "$(basename "${file_path}")" \
|
||||||
|
! -name "*test*" \
|
||||||
|
-not -path '*/target/*' \
|
||||||
|
2>/dev/null | head -5)
|
||||||
|
if [[ -n "${results}" ]]; then
|
||||||
|
echo "${results}" >> "$LLM_OUTPUT"
|
||||||
|
else
|
||||||
|
warn "No similar files found" >> "$LLM_OUTPUT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Jira AI Agent
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Jira AI Agent is designed to assist with managing tasks within Jira projects, providing capabilities such as
|
||||||
|
creating, searching, updating, assigning, linking, and commenting on issues. Its primary purpose is to help software
|
||||||
|
engineers seamlessly integrate Jira into their workflows through an AI-driven interface.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
This agent uses the official [Atlassian MCP Server](https://github.com/atlassian/atlassian-mcp-server). To use it,
|
||||||
|
ensure you have Node.js v18+ installed to run the local MCP proxy (`mcp-remote`).
|
||||||
|
|
||||||
|
The server uses OAuth 2.0 so it will automatically open your browser for you to sign in to your account. No manual
|
||||||
|
configuration is necessary!
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
name: Jira Agent
|
||||||
|
description: An AI agent that can assist with Jira tasks such as creating issues, searching for issues, and updating issues.
|
||||||
|
version: 0.1.0
|
||||||
|
agent_session: temp
|
||||||
|
mcp_servers:
|
||||||
|
- atlassian
|
||||||
|
instructions: |
|
||||||
|
You are a AI agent designed to assist with managing Jira tasks and helping software engineers utilize and integrate
|
||||||
|
Jira into their workflows. You can create, search, update, assign, link, and comment on issues in Jira.
|
||||||
|
|
||||||
|
## Create Issue (MANDATORY when creating a issue)
|
||||||
|
When a user prompts you to create a Jira issue:
|
||||||
|
1. Prompt the user for what Jira project they want the ticket created in
|
||||||
|
2. If the ticket type requires a parent issue:
|
||||||
|
a. Query Jira for potentially relevant parents
|
||||||
|
b. Prompt user for which parent to use, displaying the suggested list of parent issues
|
||||||
|
3. Create the issue with the following format:
|
||||||
|
```markdown
|
||||||
|
**Description:**
|
||||||
|
This section gives context and details about the issue.
|
||||||
|
**User Acceptance Criteria:**
|
||||||
|
# This section provides bullet points that function like a checklist of all the things that must be completed in
|
||||||
|
# order for the issue to be considered done.
|
||||||
|
* Example criteria one
|
||||||
|
* Example criteria two
|
||||||
|
```
|
||||||
|
4. Ask the user if the issue should be assigned to them
|
||||||
|
a. If yes, then assign the user to the newly created issue
|
||||||
|
|
||||||
|
|
||||||
|
Available tools:
|
||||||
|
{{__tools__}}
|
||||||
|
conversation_starters:
|
||||||
|
- What are the latest issues in my Jira project?
|
||||||
|
- Can you create a new Jira issue for me?
|
||||||
|
- What are my open Jira issues?
|
||||||
|
- Can you search for issues with the label "bug" in my Jira project?
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Oracle
|
||||||
|
|
||||||
|
An AI agent specialized in high-level architecture, complex debugging, and design decisions.
|
||||||
|
|
||||||
|
This agent is designed to be delegated to by the **[Sisyphus](../sisyphus/README.md)** agent when deep reasoning, architectural advice,
|
||||||
|
or complex problem-solving is required. Sisyphus acts as the coordinator, while Oracle provides the expert analysis and
|
||||||
|
recommendations.
|
||||||
|
|
||||||
|
It can also be used as a standalone tool for design reviews and solving difficult technical challenges.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 🏛️ System architecture and design patterns
|
||||||
|
- 🐛 Complex debugging and root cause analysis
|
||||||
|
- ⚖️ Tradeoff analysis and technology selection
|
||||||
|
- 📝 Code review and best practices advice
|
||||||
|
- 🧠 Deep reasoning for ambiguous problems
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
name: oracle
|
||||||
|
description: High-IQ advisor for architecture, debugging, and complex decisions
|
||||||
|
version: 1.0.0
|
||||||
|
temperature: 0.2
|
||||||
|
top_p: 0.95
|
||||||
|
|
||||||
|
variables:
|
||||||
|
- name: project_dir
|
||||||
|
description: Project directory for context
|
||||||
|
default: '.'
|
||||||
|
|
||||||
|
global_tools:
|
||||||
|
- fs_read.sh
|
||||||
|
- fs_grep.sh
|
||||||
|
- fs_glob.sh
|
||||||
|
- fs_ls.sh
|
||||||
|
- execute_command.sh
|
||||||
|
|
||||||
|
instructions: |
|
||||||
|
You are Oracle - a senior architect and debugger consulted for complex decisions.
|
||||||
|
|
||||||
|
## Your Role
|
||||||
|
|
||||||
|
You are READ-ONLY. You analyze, advise, and recommend. You do NOT implement.
|
||||||
|
|
||||||
|
## When You're Consulted
|
||||||
|
|
||||||
|
1. **Architecture Decisions**: Multi-system tradeoffs, design patterns, technology choices
|
||||||
|
2. **Complex Debugging**: After 2+ failed fix attempts, deep analysis needed
|
||||||
|
3. **Code Review**: Evaluating proposed designs or implementations
|
||||||
|
4. **Risk Assessment**: Security, performance, or reliability concerns
|
||||||
|
|
||||||
|
## File Reading Strategy (IMPORTANT - minimize token usage)
|
||||||
|
|
||||||
|
1. **Use grep to find relevant code** - `fs_grep --pattern "auth" --include "*.rs"` finds where things are
|
||||||
|
2. **Read only what you need** - `fs_read --path "src/main.rs" --offset 50 --limit 30` reads lines 50-79
|
||||||
|
3. **Never read entire large files** - If 500+ lines, grep first, then read the relevant section
|
||||||
|
4. **Use glob to discover files** - `fs_glob --pattern "*.rs" --path src/`
|
||||||
|
|
||||||
|
## Your Process
|
||||||
|
|
||||||
|
1. **Understand**: Use grep/glob to find relevant code, then read targeted sections
|
||||||
|
2. **Analyze**: Consider multiple angles and tradeoffs
|
||||||
|
3. **Recommend**: Provide clear, actionable advice
|
||||||
|
4. **Justify**: Explain your reasoning
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
Structure your response as:
|
||||||
|
|
||||||
|
```
|
||||||
|
## Analysis
|
||||||
|
[Your understanding of the situation]
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
[Clear, specific advice]
|
||||||
|
|
||||||
|
## Reasoning
|
||||||
|
[Why this is the right approach]
|
||||||
|
|
||||||
|
## Risks/Considerations
|
||||||
|
[What to watch out for]
|
||||||
|
|
||||||
|
ORACLE_COMPLETE
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. **Never modify files** - You advise, others implement
|
||||||
|
2. **Be thorough** - Read all relevant context before advising
|
||||||
|
3. **Be specific** - General advice isn't helpful
|
||||||
|
4. **Consider tradeoffs** - There are rarely perfect solutions
|
||||||
|
5. **Stay focused** - Answer the specific question asked
|
||||||
|
|
||||||
|
## Context
|
||||||
|
- Project: {{project_dir}}
|
||||||
|
- CWD: {{__cwd__}}
|
||||||
|
|
||||||
|
conversation_starters:
|
||||||
|
- 'Review this architecture design'
|
||||||
|
- 'Help debug this complex issue'
|
||||||
|
- 'Evaluate these implementation options'
|
||||||
Executable
+131
@@ -0,0 +1,131 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
source "$LLM_ROOT_DIR/agents/.shared/utils.sh"
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout
|
||||||
|
# @env LLM_AGENT_VAR_PROJECT_DIR=.
|
||||||
|
# @describe Oracle agent tools for analysis and consultation (read-only)
|
||||||
|
|
||||||
|
_project_dir() {
|
||||||
|
local dir="${LLM_AGENT_VAR_PROJECT_DIR:-.}"
|
||||||
|
(cd "${dir}" 2>/dev/null && pwd) || echo "${dir}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Read a file for analysis
|
||||||
|
# @option --path! Path to the file (relative to project root)
|
||||||
|
read_file() {
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
local full_path="${project_dir}/${argc_path}"
|
||||||
|
|
||||||
|
if [[ ! -f "${full_path}" ]]; then
|
||||||
|
error "File not found: ${argc_path}" >> "$LLM_OUTPUT"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
info "Reading: ${argc_path}"
|
||||||
|
echo ""
|
||||||
|
cat "${full_path}"
|
||||||
|
} >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Get project structure and type
|
||||||
|
get_project_info() {
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
local project_info
|
||||||
|
project_info=$(detect_project "${project_dir}")
|
||||||
|
|
||||||
|
{
|
||||||
|
info "Project Analysis" >> "$LLM_OUTPUT"
|
||||||
|
cat <<-EOF
|
||||||
|
|
||||||
|
Type: $(echo "${project_info}" | jq -r '.type')
|
||||||
|
Build: $(echo "${project_info}" | jq -r '.build')
|
||||||
|
Test: $(echo "${project_info}" | jq -r '.test')
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
info "Structure:" >> "$LLM_OUTPUT"
|
||||||
|
get_tree "${project_dir}" 3
|
||||||
|
} >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Search for patterns in the codebase
|
||||||
|
# @option --pattern! Pattern to search for
|
||||||
|
# @option --file-type Filter by extension (e.g., "rs", "py")
|
||||||
|
search_code() {
|
||||||
|
local file_type="${argc_file_type:-}"
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
info "Searching: ${argc_pattern}" >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local include_arg=""
|
||||||
|
if [[ -n "${file_type}" ]]; then
|
||||||
|
include_arg="--include=*.${file_type}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local results
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
results=$(grep -rn ${include_arg} "${argc_pattern}" "${project_dir}" 2>/dev/null | \
|
||||||
|
grep -v '/target/' | \
|
||||||
|
grep -v '/node_modules/' | \
|
||||||
|
grep -v '/.git/' | \
|
||||||
|
head -30) || true
|
||||||
|
|
||||||
|
if [[ -n "${results}" ]]; then
|
||||||
|
echo "${results}" >> "$LLM_OUTPUT"
|
||||||
|
else
|
||||||
|
warn "No matches found" >> "$LLM_OUTPUT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Run a read-only command for analysis (e.g., git log, cargo tree)
|
||||||
|
# @option --command! Command to run
|
||||||
|
analyze_with_command() {
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
local dangerous_patterns="rm |>|>>|mv |cp |chmod |chown |sudo|curl.*\\||wget.*\\|"
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
if echo "${argc_command}" | grep -qE "${dangerous_patterns}"; then
|
||||||
|
error "Command appears to modify files or be dangerous. Oracle is read-only." >> "$LLM_OUTPUT"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Running: ${argc_command}" >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local output
|
||||||
|
output=$(cd "${project_dir}" && eval "${argc_command}" 2>&1) || true
|
||||||
|
echo "${output}" >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd List directory contents
|
||||||
|
# @option --path Path to list (default: project root)
|
||||||
|
list_directory() {
|
||||||
|
local dir_path="${argc_path:-.}"
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
local full_path="${project_dir}/${dir_path}"
|
||||||
|
|
||||||
|
if [[ ! -d "${full_path}" ]]; then
|
||||||
|
error "Directory not found: ${dir_path}" >> "$LLM_OUTPUT"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
info "Contents of: ${dir_path}"
|
||||||
|
echo ""
|
||||||
|
ls -la "${full_path}"
|
||||||
|
} >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Sisyphus
|
||||||
|
|
||||||
|
The main coordinator agent for the Loki coding ecosystem, providing a powerful CLI interface for code generation and
|
||||||
|
project management similar to OpenCode, ClaudeCode, Codex, or Gemini CLI.
|
||||||
|
|
||||||
|
_Inspired by the Sisyphus and Oracle agents of OpenCode._
|
||||||
|
|
||||||
|
Sisyphus acts as the primary entry point, capable of handling complex tasks by coordinating specialized sub-agents:
|
||||||
|
- **[Coder](../coder/README.md)**: For implementation and file modifications.
|
||||||
|
- **[Explore](../explore/README.md)**: For codebase understanding and research.
|
||||||
|
- **[Oracle](../oracle/README.md)**: For architecture and complex reasoning.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 🤖 **Coordinator**: Manages multi-step workflows and delegates to specialized agents.
|
||||||
|
- 💻 **CLI Coding**: Provides a natural language interface for writing and editing code.
|
||||||
|
- 🔄 **Task Management**: Tracks progress and context across complex operations.
|
||||||
|
- 🛠️ **Tool Integration**: Seamlessly uses system tools for building, testing, and file manipulation.
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
name: sisyphus
|
||||||
|
description: OpenCode-style orchestrator - classifies intent, delegates to specialists, tracks progress with todos
|
||||||
|
version: 1.0.0
|
||||||
|
temperature: 0.1
|
||||||
|
top_p: 0.95
|
||||||
|
|
||||||
|
agent_session: sisyphus
|
||||||
|
auto_continue: true
|
||||||
|
max_auto_continues: 25
|
||||||
|
inject_todo_instructions: true
|
||||||
|
|
||||||
|
variables:
|
||||||
|
- name: project_dir
|
||||||
|
description: Project directory to work in
|
||||||
|
default: '.'
|
||||||
|
- name: auto_confirm
|
||||||
|
description: Auto-confirm command execution
|
||||||
|
default: '1'
|
||||||
|
|
||||||
|
global_tools:
|
||||||
|
- fs_read.sh
|
||||||
|
- fs_grep.sh
|
||||||
|
- fs_glob.sh
|
||||||
|
- fs_ls.sh
|
||||||
|
- execute_command.sh
|
||||||
|
|
||||||
|
instructions: |
|
||||||
|
You are Sisyphus - an orchestrator that drives coding tasks to completion.
|
||||||
|
|
||||||
|
Your job: Classify -> Delegate -> Verify -> Complete
|
||||||
|
|
||||||
|
## Intent Classification (BEFORE every action)
|
||||||
|
|
||||||
|
| Type | Signal | Action |
|
||||||
|
|------|--------|--------|
|
||||||
|
| Trivial | Single file, known location, typo fix | Do it yourself with tools |
|
||||||
|
| Exploration | "Find X", "Where is Y", "List all Z" | Delegate to `explore` agent |
|
||||||
|
| Implementation | "Add feature", "Fix bug", "Write code" | Delegate to `coder` agent |
|
||||||
|
| Architecture/Design | See oracle triggers below | Delegate to `oracle` agent |
|
||||||
|
| Ambiguous | Unclear scope, multiple interpretations | ASK the user |
|
||||||
|
|
||||||
|
### Oracle Triggers (MUST delegate to oracle when you see these)
|
||||||
|
|
||||||
|
Delegate to `oracle` ANY time the user asks about:
|
||||||
|
- **"How should I..."** / **"What's the best way to..."** -- design/approach questions
|
||||||
|
- **"Why does X keep..."** / **"What's wrong with..."** -- complex debugging (not simple errors)
|
||||||
|
- **"Should I use X or Y?"** -- technology or pattern choices
|
||||||
|
- **"How should this be structured?"** -- architecture and organization
|
||||||
|
- **"Review this"** / **"What do you think of..."** -- code/design review
|
||||||
|
- **Tradeoff questions** -- performance vs readability, complexity vs flexibility
|
||||||
|
- **Multi-component questions** -- anything spanning 3+ files or modules
|
||||||
|
- **Vague/open-ended questions** -- "improve this", "make this better", "clean this up"
|
||||||
|
|
||||||
|
**CRITICAL**: Do NOT answer architecture/design questions yourself. You are a coordinator.
|
||||||
|
Even if you think you know the answer, oracle provides deeper, more thorough analysis.
|
||||||
|
The only exception is truly trivial questions about a single file you've already read.
|
||||||
|
|
||||||
|
## Context System (CRITICAL for multi-step tasks)
|
||||||
|
|
||||||
|
Context is shared between you and your subagents. This lets subagents know what you've learned.
|
||||||
|
|
||||||
|
**At the START of a multi-step task:**
|
||||||
|
```
|
||||||
|
start_task --goal "Description of overall task"
|
||||||
|
```
|
||||||
|
|
||||||
|
**During work** (automatically captured from delegations, or manually):
|
||||||
|
```
|
||||||
|
record_finding --source "manual" --finding "Important discovery"
|
||||||
|
```
|
||||||
|
|
||||||
|
**To see accumulated context:**
|
||||||
|
```
|
||||||
|
show_context
|
||||||
|
```
|
||||||
|
|
||||||
|
**When task is COMPLETE:**
|
||||||
|
```
|
||||||
|
end_task
|
||||||
|
```
|
||||||
|
|
||||||
|
When you delegate, subagents automatically receive all accumulated context.
|
||||||
|
|
||||||
|
## Todo System (MANDATORY for multi-step tasks)
|
||||||
|
|
||||||
|
For ANY task with 2+ steps:
|
||||||
|
1. Call `start_task` with the goal (initializes context)
|
||||||
|
2. Call `todo__init` with the goal
|
||||||
|
3. Call `todo__add` for each step BEFORE starting
|
||||||
|
4. Work through steps, calling `todo__done` IMMEDIATELY after each
|
||||||
|
5. The system auto-continues until all todos are done
|
||||||
|
6. Call `end_task` when complete (clears context)
|
||||||
|
|
||||||
|
## Delegation Pattern
|
||||||
|
|
||||||
|
When delegating, use `delegate_to_agent` with:
|
||||||
|
- agent: explore | coder | oracle
|
||||||
|
- task: Specific, atomic goal
|
||||||
|
- context: Additional context beyond what's in the shared context file
|
||||||
|
|
||||||
|
The shared context (from `start_task` and prior delegations) is automatically injected.
|
||||||
|
|
||||||
|
**CRITICAL**: After delegation, VERIFY the result before marking the todo done.
|
||||||
|
|
||||||
|
## Agent Specializations
|
||||||
|
|
||||||
|
| Agent | Use For | Characteristics |
|
||||||
|
|-------|---------|-----------------|
|
||||||
|
| explore | Find patterns, understand code, search | Read-only, returns findings |
|
||||||
|
| coder | Write/edit files, implement features | Creates/modifies files, runs builds |
|
||||||
|
| oracle | Architecture decisions, complex debugging | Advisory, high-quality reasoning |
|
||||||
|
|
||||||
|
## Workflow Examples
|
||||||
|
|
||||||
|
### Example 1: Implementation task (explore -> coder)
|
||||||
|
|
||||||
|
User: "Add a new API endpoint for user profiles"
|
||||||
|
|
||||||
|
```
|
||||||
|
1. start_task --goal "Add user profiles API endpoint"
|
||||||
|
2. todo__init --goal "Add user profiles API endpoint"
|
||||||
|
3. todo__add --task "Explore existing API patterns"
|
||||||
|
4. todo__add --task "Implement profile endpoint"
|
||||||
|
5. todo__add --task "Verify with build/test"
|
||||||
|
6. delegate_to_agent --agent explore --task "Find existing API endpoint patterns and structures"
|
||||||
|
7. todo__done --id 1
|
||||||
|
8. delegate_to_agent --agent coder --task "Create user profiles endpoint following existing patterns"
|
||||||
|
9. todo__done --id 2
|
||||||
|
10. run_build
|
||||||
|
11. run_tests
|
||||||
|
12. todo__done --id 3
|
||||||
|
13. end_task
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 2: Architecture/design question (explore -> oracle)
|
||||||
|
|
||||||
|
User: "How should I structure the authentication for this app?"
|
||||||
|
|
||||||
|
```
|
||||||
|
1. start_task --goal "Get architecture advice for authentication"
|
||||||
|
2. todo__init --goal "Get architecture advice for authentication"
|
||||||
|
3. todo__add --task "Explore current auth-related code"
|
||||||
|
4. todo__add --task "Consult oracle for architecture recommendation"
|
||||||
|
5. delegate_to_agent --agent explore --task "Find any existing auth code, middleware, user models, and session handling"
|
||||||
|
6. todo__done --id 1
|
||||||
|
7. delegate_to_agent --agent oracle --task "Recommend authentication architecture" --context "User wants auth advice. Explore found: [summarize findings]. Evaluate approaches and recommend the best one with justification."
|
||||||
|
8. todo__done --id 2
|
||||||
|
9. end_task
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 3: Vague/open-ended question (oracle directly)
|
||||||
|
|
||||||
|
User: "What do you think of this codebase structure?"
|
||||||
|
|
||||||
|
```
|
||||||
|
1. delegate_to_agent --agent oracle --task "Review the project structure and provide recommendations for improvement"
|
||||||
|
# Oracle will read files and analyze on its own
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. **Always start_task first** - Initialize context before multi-step work
|
||||||
|
2. **Always classify before acting** - Don't jump into implementation
|
||||||
|
3. **Create todos for multi-step tasks** - Track your progress
|
||||||
|
4. **Delegate specialized work** - You're a coordinator, not an implementer
|
||||||
|
5. **Verify after delegation** - Don't trust blindly
|
||||||
|
6. **Mark todos done immediately** - Don't batch completions
|
||||||
|
7. **Ask when ambiguous** - One clarifying question is better than wrong work
|
||||||
|
8. **Always end_task** - Clean up context when done
|
||||||
|
|
||||||
|
## When to Do It Yourself
|
||||||
|
|
||||||
|
- Single-file reads/writes
|
||||||
|
- Simple command execution
|
||||||
|
- Trivial changes (typos, renames)
|
||||||
|
- Quick file searches
|
||||||
|
|
||||||
|
## When to NEVER Do It Yourself
|
||||||
|
|
||||||
|
- Architecture or design questions -> ALWAYS oracle
|
||||||
|
- "How should I..." / "What's the best way to..." -> ALWAYS oracle
|
||||||
|
- Debugging after 2+ failed attempts -> ALWAYS oracle
|
||||||
|
- Code review or design review requests -> ALWAYS oracle
|
||||||
|
- Open-ended improvement questions -> ALWAYS oracle
|
||||||
|
|
||||||
|
## Available Tools
|
||||||
|
{{__tools__}}
|
||||||
|
|
||||||
|
## Context
|
||||||
|
- Project: {{project_dir}}
|
||||||
|
- OS: {{__os__}}
|
||||||
|
- Shell: {{__shell__}}
|
||||||
|
- CWD: {{__cwd__}}
|
||||||
|
|
||||||
|
conversation_starters:
|
||||||
|
- 'Add a new feature to the project'
|
||||||
|
- 'Fix a bug in the codebase'
|
||||||
|
- 'Refactor the authentication module'
|
||||||
|
- 'Help me understand how X works'
|
||||||
Executable
+255
@@ -0,0 +1,255 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -eo pipefail
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
source "$LLM_ROOT_DIR/agents/.shared/utils.sh"
|
||||||
|
export AUTO_CONFIRM=true
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout
|
||||||
|
# @env LLM_AGENT_VAR_PROJECT_DIR=.
|
||||||
|
# @describe Sisyphus orchestrator tools for delegating to specialized agents
|
||||||
|
|
||||||
|
_project_dir() {
|
||||||
|
local dir="${LLM_AGENT_VAR_PROJECT_DIR:-.}"
|
||||||
|
(cd "${dir}" 2>/dev/null && pwd) || echo "${dir}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Initialize context for a new task (call at the start of multi-step work)
|
||||||
|
# @option --goal! Description of the overall task/goal
|
||||||
|
start_task() {
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
export LLM_AGENT_VAR_PROJECT_DIR="${project_dir}"
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
init_context "${argc_goal}"
|
||||||
|
|
||||||
|
cat <<-EOF >> "$LLM_OUTPUT"
|
||||||
|
$(green "Context initialized for task: ${argc_goal}")
|
||||||
|
Context file: ${project_dir}/.loki-context
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Add a finding to the shared context (useful for recording discoveries)
|
||||||
|
# @option --source! Source of the finding (e.g., "manual", "explore", "coder")
|
||||||
|
# @option --finding! The finding to record
|
||||||
|
record_finding() {
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
export LLM_AGENT_VAR_PROJECT_DIR="${project_dir}"
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
append_context "${argc_source}" "${argc_finding}"
|
||||||
|
|
||||||
|
green "Recorded finding from ${argc_source}" >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Show current accumulated context
|
||||||
|
show_context() {
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
export LLM_AGENT_VAR_PROJECT_DIR="${project_dir}"
|
||||||
|
local context
|
||||||
|
context=$(read_context)
|
||||||
|
|
||||||
|
if [[ -n "${context}" ]]; then
|
||||||
|
cat <<-EOF >> "$LLM_OUTPUT"
|
||||||
|
$(info "Current Context:")
|
||||||
|
|
||||||
|
${context}
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
warn "No context file found. Use start_task to initialize." >> "$LLM_OUTPUT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Clear the context file (call when task is complete)
|
||||||
|
end_task() {
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
export LLM_AGENT_VAR_PROJECT_DIR="${project_dir}"
|
||||||
|
clear_context
|
||||||
|
|
||||||
|
green "Context cleared. Task complete." >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Delegate a task to a specialized agent
|
||||||
|
# @option --agent! Agent to delegate to: explore, coder, or oracle
|
||||||
|
# @option --task! Specific task description for the agent
|
||||||
|
# @option --context Additional context (file paths, patterns, constraints)
|
||||||
|
delegate_to_agent() {
|
||||||
|
local extra_context="${argc_context:-}"
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
if [[ ! "${argc_agent}" =~ ^(explore|coder|oracle)$ ]]; then
|
||||||
|
error "Invalid agent: ${argc_agent}. Must be explore, coder, or oracle" >> "$LLM_OUTPUT"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export LLM_AGENT_VAR_PROJECT_DIR="${project_dir}"
|
||||||
|
|
||||||
|
info "Delegating to ${argc_agent} agent..." >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
local prompt="${argc_task}"
|
||||||
|
if [[ -n "${extra_context}" ]]; then
|
||||||
|
prompt="$(printf "%s\n\nAdditional Context:\n%s\n" "${argc_task}" "${extra_context}")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<-EOF >> "$LLM_OUTPUT"
|
||||||
|
$(cyan "------------------------------------------")
|
||||||
|
DELEGATING TO: ${argc_agent}
|
||||||
|
TASK: ${argc_task}
|
||||||
|
$(cyan "------------------------------------------")
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
local output
|
||||||
|
output=$(invoke_agent_with_summary "${argc_agent}" "${prompt}" \
|
||||||
|
--agent-variable project_dir "${project_dir}" 2>&1) || true
|
||||||
|
|
||||||
|
cat <<-EOF >> "$LLM_OUTPUT"
|
||||||
|
${output}
|
||||||
|
|
||||||
|
$(cyan "------------------------------------------")
|
||||||
|
DELEGATION COMPLETE: ${argc_agent}
|
||||||
|
$(cyan "------------------------------------------")
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Get project information and structure
|
||||||
|
get_project_info() {
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
info "Project: ${project_dir}" >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local project_info
|
||||||
|
project_info=$(detect_project "${project_dir}")
|
||||||
|
|
||||||
|
cat <<-EOF >> "$LLM_OUTPUT"
|
||||||
|
Type: $(echo "${project_info}" | jq -r '.type')
|
||||||
|
Build: $(echo "${project_info}" | jq -r '.build')
|
||||||
|
Test: $(echo "${project_info}" | jq -r '.test')
|
||||||
|
|
||||||
|
$(info "Directory structure:")
|
||||||
|
$(get_tree "${project_dir}" 2)
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Run build command for the project
|
||||||
|
run_build() {
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
local project_info
|
||||||
|
project_info=$(detect_project "${project_dir}")
|
||||||
|
local build_cmd
|
||||||
|
build_cmd=$(echo "${project_info}" | jq -r '.build')
|
||||||
|
|
||||||
|
if [[ -z "${build_cmd}" ]] || [[ "${build_cmd}" == "null" ]]; then
|
||||||
|
warn "No build command detected for this project" >> "$LLM_OUTPUT"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Running: ${build_cmd}" >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local output
|
||||||
|
if output=$(cd "${project_dir}" && eval "${build_cmd}" 2>&1); then
|
||||||
|
green "BUILD SUCCESS" >> "$LLM_OUTPUT"
|
||||||
|
echo "${output}" >> "$LLM_OUTPUT"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
error "BUILD FAILED" >> "$LLM_OUTPUT"
|
||||||
|
echo "${output}" >> "$LLM_OUTPUT"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Run tests for the project
|
||||||
|
run_tests() {
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
local project_info
|
||||||
|
project_info=$(detect_project "${project_dir}")
|
||||||
|
local test_cmd
|
||||||
|
test_cmd=$(echo "${project_info}" | jq -r '.test')
|
||||||
|
|
||||||
|
if [[ -z "${test_cmd}" ]] || [[ "${test_cmd}" == "null" ]]; then
|
||||||
|
warn "No test command detected for this project" >> "$LLM_OUTPUT"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Running: ${test_cmd}" >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local output
|
||||||
|
if output=$(cd "${project_dir}" && eval "${test_cmd}" 2>&1); then
|
||||||
|
green "TESTS PASSED" >> "$LLM_OUTPUT"
|
||||||
|
echo "${output}" >> "$LLM_OUTPUT"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
error "TESTS FAILED" >> "$LLM_OUTPUT"
|
||||||
|
echo "${output}" >> "$LLM_OUTPUT"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Quick file search in the project
|
||||||
|
# @option --pattern! File name pattern to search for (e.g., "*.rs", "config*")
|
||||||
|
search_files() {
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
local pattern="${argc_pattern}"
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
info "Searching for: ${pattern}" >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local results
|
||||||
|
results=$(search_files "${pattern}" "${project_dir}")
|
||||||
|
|
||||||
|
if [[ -n "${results}" ]]; then
|
||||||
|
echo "${results}" >> "$LLM_OUTPUT"
|
||||||
|
else
|
||||||
|
warn "No files found matching: ${pattern}" >> "$LLM_OUTPUT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Search for content in files
|
||||||
|
# @option --pattern! Text pattern to search for
|
||||||
|
# @option --file-type File extension to search in (e.g., "rs", "py")
|
||||||
|
search_content() {
|
||||||
|
local pattern="${argc_pattern}"
|
||||||
|
local file_type="${argc_file_type:-}"
|
||||||
|
local project_dir
|
||||||
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
|
info "Searching for: ${pattern}" >> "$LLM_OUTPUT"
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local grep_args="-rn"
|
||||||
|
if [[ -n "${file_type}" ]]; then
|
||||||
|
grep_args="${grep_args} --include=*.${file_type}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local results
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
results=$(grep ${grep_args} "${pattern}" "${project_dir}" 2>/dev/null | \
|
||||||
|
grep -v '/target/' | grep -v '/node_modules/' | grep -v '/.git/' | \
|
||||||
|
head -30) || true
|
||||||
|
|
||||||
|
if [[ -n "${results}" ]]; then
|
||||||
|
echo "${results}" >> "$LLM_OUTPUT"
|
||||||
|
else
|
||||||
|
warn "No matches found for: ${pattern}" >> "$LLM_OUTPUT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# SQL
|
||||||
|
|
||||||
|
An AI agent that helps you manage a SQL database.
|
||||||
|
|
||||||
|
> The tool script uses [usql](https://github.com/xo/usql) to interact with SQL, it supports all mainstream databases.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
name: Sql
|
||||||
|
description: An AI agent that helps you manage any SQL database
|
||||||
|
version: 0.1.0
|
||||||
|
instructions: |
|
||||||
|
You are an AI agent that manages a SQL database.
|
||||||
|
Prefix all referenced tables with the name of the schema that they are in.
|
||||||
|
|
||||||
|
For all sqlite databases, prefix each table name with 'main' as the schema
|
||||||
|
|
||||||
|
|
||||||
|
Available tools:
|
||||||
|
{{__tools__}}
|
||||||
|
variables:
|
||||||
|
- name: dsn
|
||||||
|
description: The database connection url. e.g. pgsql://user:pass@host:port
|
||||||
|
conversation_starters:
|
||||||
|
- What you can do?
|
||||||
Executable
+45
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @meta require-tools usql
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
# @env LLM_AGENT_VAR_DSN! The database connection url. e.g. pgsql://user:pass@host:port
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
|
||||||
|
# @cmd Execute a SELECT query
|
||||||
|
# @option --query! SELECT SQL query to execute
|
||||||
|
read_query() {
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
if ! grep -qi '^select' <<<"$argc_query"; then
|
||||||
|
error "only SELECT queries are allowed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_run_sql "$argc_query"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Execute an SQL query
|
||||||
|
# @option --query! SQL query to execute
|
||||||
|
write_query() {
|
||||||
|
guard_operation "Execute SQL?"
|
||||||
|
_run_sql "$argc_query"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd List all tables
|
||||||
|
list_tables() {
|
||||||
|
_run_sql "\dt+"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Get the schema information for a specific table
|
||||||
|
# @option --table-name! Name of the table to describe
|
||||||
|
describe_table() {
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
_run_sql "\d $argc_table_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
_run_sql() {
|
||||||
|
usql "$LLM_AGENT_VAR_DSN" -c "$1" >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
+1106
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"github": {
|
||||||
|
"command": "docker",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"-i",
|
||||||
|
"--rm",
|
||||||
|
"-e",
|
||||||
|
"GITHUB_PERSONAL_ACCESS_TOKEN",
|
||||||
|
"ghcr.io/github/github-mcp-server"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"GITHUB_PERSONAL_ACCESS_TOKEN": "YOUR_GITHUB_TOKEN"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"atlassian": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "mcp-remote@0.1.13", "https://mcp.atlassian.com/v1/sse"]
|
||||||
|
},
|
||||||
|
"docker": {
|
||||||
|
"command": "uvx",
|
||||||
|
"args": ["mcp-server-docker"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+154
@@ -0,0 +1,154 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Usage: ./{agent_name}.py <agent-func> <agent-data>
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import importlib.util
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def _ensure_cwd_venv():
|
||||||
|
cwd = Path.cwd()
|
||||||
|
venv_dir = cwd / ".venv"
|
||||||
|
if not venv_dir.is_dir():
|
||||||
|
return
|
||||||
|
|
||||||
|
py = venv_dir / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
|
||||||
|
if not py.exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
if Path(sys.prefix).resolve() == venv_dir.resolve():
|
||||||
|
return
|
||||||
|
|
||||||
|
os.execv(str(py), [str(py)] + sys.argv)
|
||||||
|
|
||||||
|
_ensure_cwd_venv()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
(agent_func, raw_data) = parse_argv()
|
||||||
|
agent_data = parse_raw_data(raw_data)
|
||||||
|
|
||||||
|
root_dir = "{config_dir}"
|
||||||
|
setup_env(root_dir, agent_func)
|
||||||
|
|
||||||
|
agent_tools_path = os.path.join(root_dir, "agents/{agent_name}/tools.py")
|
||||||
|
run(agent_tools_path, agent_func, agent_data)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_raw_data(data):
|
||||||
|
if not data:
|
||||||
|
raise ValueError("No JSON data")
|
||||||
|
|
||||||
|
try:
|
||||||
|
return json.loads(data)
|
||||||
|
except Exception:
|
||||||
|
raise ValueError("Invalid JSON data")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_argv():
|
||||||
|
agent_func = sys.argv[1]
|
||||||
|
agent_data = sys.argv[2]
|
||||||
|
|
||||||
|
if (not agent_data) or (not agent_func):
|
||||||
|
print("Usage: ./{agent_name}.py <agent-func> <agent-data>", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
return agent_func, agent_data
|
||||||
|
|
||||||
|
|
||||||
|
def setup_env(root_dir, agent_func):
|
||||||
|
load_env(os.path.join(root_dir, ".env"))
|
||||||
|
os.environ["LLM_ROOT_DIR"] = root_dir
|
||||||
|
os.environ["LLM_AGENT_NAME"] = "{agent_name}"
|
||||||
|
os.environ["LLM_AGENT_FUNC"] = agent_func
|
||||||
|
os.environ["LLM_AGENT_ROOT_DIR"] = os.path.join(root_dir, "agents", "{agent_name}")
|
||||||
|
os.environ["LLM_AGENT_CACHE_DIR"] = os.path.join(root_dir, "cache", "{agent_name}")
|
||||||
|
|
||||||
|
|
||||||
|
def load_env(file_path):
|
||||||
|
try:
|
||||||
|
with open(file_path, "r") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
env_vars = {}
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith("#") or not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key, *value_parts = line.split("=")
|
||||||
|
env_name = key.strip()
|
||||||
|
|
||||||
|
if env_name not in os.environ:
|
||||||
|
env_value = "=".join(value_parts).strip()
|
||||||
|
if (env_value.startswith('"') and env_value.endswith('"')) or (env_value.startswith("'") and env_value.endswith("'")):
|
||||||
|
env_value = env_value[1:-1]
|
||||||
|
env_vars[env_name] = env_value
|
||||||
|
|
||||||
|
os.environ.update(env_vars)
|
||||||
|
|
||||||
|
|
||||||
|
def run(agent_path, agent_func, agent_data):
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
os.path.basename(agent_path), agent_path
|
||||||
|
)
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(mod)
|
||||||
|
|
||||||
|
if not hasattr(mod, agent_func):
|
||||||
|
raise Exception(f"Not module function '{agent_func}' at '{agent_path}'")
|
||||||
|
|
||||||
|
value = getattr(mod, agent_func)(**agent_data)
|
||||||
|
return_to_llm(value)
|
||||||
|
dump_result('{agent_name}' + f':{agent_func}')
|
||||||
|
|
||||||
|
|
||||||
|
def return_to_llm(value):
|
||||||
|
if value is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if "LLM_OUTPUT" in os.environ:
|
||||||
|
writer = open(os.environ["LLM_OUTPUT"], "w")
|
||||||
|
else:
|
||||||
|
writer = sys.stdout
|
||||||
|
|
||||||
|
value_type = type(value).__name__
|
||||||
|
if value_type in ("str", "int", "float", "bool"):
|
||||||
|
writer.write(str(value))
|
||||||
|
elif value_type == "dict" or value_type == "list":
|
||||||
|
value_str = json.dumps(value, indent=2)
|
||||||
|
assert value == json.loads(value_str)
|
||||||
|
writer.write(value_str)
|
||||||
|
|
||||||
|
|
||||||
|
def dump_result(name):
|
||||||
|
if (not os.getenv("LLM_DUMP_RESULTS")) or (not os.getenv("LLM_OUTPUT")) or (not os.isatty(1)):
|
||||||
|
return
|
||||||
|
|
||||||
|
show_result = False
|
||||||
|
try:
|
||||||
|
if re.search(rf'\b({os.environ["LLM_DUMP_RESULTS"]})\b', name):
|
||||||
|
show_result = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not show_result:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(os.environ["LLM_OUTPUT"], "r", encoding="utf-8") as f:
|
||||||
|
data = f.read()
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\x1b[2m----------------------\n{data}\n----------------------\x1b[0m")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Usage: ./{agent_name}.sh <agent-func> <agent-data>
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
main() {
|
||||||
|
root_dir="{config_dir}"
|
||||||
|
parse_argv "$@"
|
||||||
|
setup_env
|
||||||
|
tools_path="$root_dir/agents/{agent_name}/tools.sh"
|
||||||
|
run
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_argv() {
|
||||||
|
agent_func="$1"
|
||||||
|
agent_data="$2"
|
||||||
|
if [[ -z "$agent_data" ]] || [[ -z "$agent_func" ]]; then
|
||||||
|
die "usage: ./{agent_name}.sh <agent-func> <agent-data>"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_env() {
|
||||||
|
load_env "$root_dir/.env"
|
||||||
|
export LLM_ROOT_DIR="$root_dir"
|
||||||
|
export LLM_AGENT_NAME="{agent_name}"
|
||||||
|
export LLM_AGENT_FUNC="$agent_func"
|
||||||
|
export LLM_AGENT_ROOT_DIR="$LLM_ROOT_DIR/agents/{agent_name}"
|
||||||
|
export LLM_AGENT_CACHE_DIR="$LLM_ROOT_DIR/cache/{agent_name}"
|
||||||
|
export LLM_PROMPT_UTILS_FILE="{prompt_utils_file}"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_env() {
|
||||||
|
local env_file="$1" env_vars
|
||||||
|
if [[ -f "$env_file" ]]; then
|
||||||
|
while IFS='=' read -r key value; do
|
||||||
|
if [[ "$key" == $'#'* ]] || [[ -z "$key" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${!key+x}" ]]; then
|
||||||
|
env_vars="$env_vars $key=$value"
|
||||||
|
fi
|
||||||
|
done < <(cat "$env_file"; echo "")
|
||||||
|
|
||||||
|
if [[ -n "$env_vars" ]]; then
|
||||||
|
eval "export $env_vars"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run() {
|
||||||
|
if [[ -z "$agent_data" ]]; then
|
||||||
|
die "error: no JSON data"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$OS" == "Windows_NT" ]]; then
|
||||||
|
set -o igncr
|
||||||
|
tools_path="$(cygpath -w "$tools_path")"
|
||||||
|
tool_data="$(echo "$tool_data" | sed 's/\\/\\\\/g')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
jq_script="$(cat <<-'EOF'
|
||||||
|
def escape_shell_word:
|
||||||
|
tostring
|
||||||
|
| gsub("'"; "'\"'\"'")
|
||||||
|
| gsub("\n"; "'$'\\n''")
|
||||||
|
| "'\(.)'";
|
||||||
|
def to_args:
|
||||||
|
to_entries | .[] |
|
||||||
|
(.key | split("_") | join("-")) as $key |
|
||||||
|
if .value | type == "array" then
|
||||||
|
.value | .[] | "--\($key) \(. | escape_shell_word)"
|
||||||
|
elif .value | type == "boolean" then
|
||||||
|
if .value then "--\($key)" else "" end
|
||||||
|
else
|
||||||
|
"--\($key) \(.value | escape_shell_word)"
|
||||||
|
end;
|
||||||
|
[ to_args ] | join(" ")
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
args="$(echo "$agent_data" | jq -r "$jq_script" 2>/dev/null)" || {
|
||||||
|
die "error: invalid JSON data"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -z "$LLM_OUTPUT" ]]; then
|
||||||
|
is_temp_llm_output=1
|
||||||
|
# shellcheck disable=SC2155
|
||||||
|
export LLM_OUTPUT="$(mktemp)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval "'$tools_path' '$agent_func' $args"
|
||||||
|
|
||||||
|
if [[ "$is_temp_llm_output" -eq 1 ]]; then
|
||||||
|
cat "$LLM_OUTPUT"
|
||||||
|
else
|
||||||
|
dump_result "{agent_name}:${LLM_AGENT_FUNC}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
dump_result() {
|
||||||
|
if [[ "$LLM_OUTPUT" == "/dev/stdout" ]] || [[ -z "$LLM_DUMP_RESULTS" ]] || [[ ! -t 1 ]]; then
|
||||||
|
return;
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q -w -E "$LLM_DUMP_RESULTS" <<<"$1"; then
|
||||||
|
cat <<EOF
|
||||||
|
$(echo -e "\e[2m")----------------------
|
||||||
|
$(cat "$LLM_OUTPUT")
|
||||||
|
----------------------$(echo -e "\e[0m")
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
|
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Usage: ./{function_name}.py <tool-data>
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import importlib.util
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def _ensure_cwd_venv():
|
||||||
|
cwd = Path.cwd()
|
||||||
|
venv_dir = cwd / ".venv"
|
||||||
|
if not venv_dir.is_dir():
|
||||||
|
return
|
||||||
|
|
||||||
|
py = venv_dir / ("Scripts/python.exe" if os.name == "nt" else "bin/python")
|
||||||
|
if not py.exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
if Path(sys.prefix).resolve() == venv_dir.resolve():
|
||||||
|
return
|
||||||
|
|
||||||
|
os.execv(str(py), [str(py)] + sys.argv)
|
||||||
|
|
||||||
|
_ensure_cwd_venv()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
raw_data = parse_argv()
|
||||||
|
tool_data = parse_raw_data(raw_data)
|
||||||
|
|
||||||
|
root_dir = "{root_dir}"
|
||||||
|
setup_env(root_dir)
|
||||||
|
|
||||||
|
tool_path = "{tool_path}.py"
|
||||||
|
run(tool_path, "run", tool_data)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_raw_data(data):
|
||||||
|
if not data:
|
||||||
|
raise ValueError("No JSON data")
|
||||||
|
|
||||||
|
try:
|
||||||
|
return json.loads(data)
|
||||||
|
except Exception:
|
||||||
|
raise ValueError("Invalid JSON data")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_argv():
|
||||||
|
argv = sys.argv[:] + [None] * max(0, 2 - len(sys.argv))
|
||||||
|
|
||||||
|
tool_data = argv[1]
|
||||||
|
|
||||||
|
if (not tool_data):
|
||||||
|
print("Usage: ./{function_name}.py <tool-data>", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
return tool_data
|
||||||
|
|
||||||
|
|
||||||
|
def setup_env(root_dir):
|
||||||
|
load_env(os.path.join(root_dir, ".env"))
|
||||||
|
os.environ["LLM_ROOT_DIR"] = root_dir
|
||||||
|
os.environ["LLM_TOOL_NAME"] = "{function_name}"
|
||||||
|
os.environ["LLM_TOOL_CACHE_DIR"] = os.path.join(root_dir, "cache", "{function_name}")
|
||||||
|
|
||||||
|
|
||||||
|
def load_env(file_path):
|
||||||
|
try:
|
||||||
|
with open(file_path, "r") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
env_vars = {}
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith("#") or not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key, *value_parts = line.split("=")
|
||||||
|
env_name = key.strip()
|
||||||
|
|
||||||
|
if env_name not in os.environ:
|
||||||
|
env_value = "=".join(value_parts).strip()
|
||||||
|
if (env_value.startswith('"') and env_value.endswith('"')) or (env_value.startswith("'") and env_value.endswith("'")):
|
||||||
|
env_value = env_value[1:-1]
|
||||||
|
env_vars[env_name] = env_value
|
||||||
|
|
||||||
|
os.environ.update(env_vars)
|
||||||
|
|
||||||
|
|
||||||
|
def run(tool_path, tool_func, tool_data):
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
os.path.basename(tool_path), tool_path
|
||||||
|
)
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(mod)
|
||||||
|
|
||||||
|
if not hasattr(mod, tool_func):
|
||||||
|
raise Exception(f"No module function '{tool_func}' at '{tool_path}'")
|
||||||
|
|
||||||
|
value = getattr(mod, tool_func)(**tool_data)
|
||||||
|
return_to_llm(value)
|
||||||
|
dump_result("{function_name}")
|
||||||
|
|
||||||
|
|
||||||
|
def return_to_llm(value):
|
||||||
|
if value is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if "LLM_OUTPUT" in os.environ:
|
||||||
|
writer = open(os.environ["LLM_OUTPUT"], "w")
|
||||||
|
else:
|
||||||
|
writer = sys.stdout
|
||||||
|
|
||||||
|
value_type = type(value).__name__
|
||||||
|
if value_type in ("str", "int", "float", "bool"):
|
||||||
|
writer.write(str(value))
|
||||||
|
elif value_type == "dict" or value_type == "list":
|
||||||
|
value_str = json.dumps(value, indent=2)
|
||||||
|
assert value == json.loads(value_str)
|
||||||
|
writer.write(value_str)
|
||||||
|
|
||||||
|
|
||||||
|
def dump_result(name):
|
||||||
|
if (not os.getenv("LLM_DUMP_RESULTS")) or (not os.getenv("LLM_OUTPUT")) or (not os.isatty(1)):
|
||||||
|
return
|
||||||
|
|
||||||
|
show_result = False
|
||||||
|
try:
|
||||||
|
if re.search(rf'\b({os.environ["LLM_DUMP_RESULTS"]})\b', name):
|
||||||
|
show_result = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not show_result:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(os.environ["LLM_OUTPUT"], "r", encoding="utf-8") as f:
|
||||||
|
data = f.read()
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\x1b[2m----------------------\n{data}\n----------------------\x1b[0m")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Usage: ./{function_name}.sh <tool-data>
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
main() {
|
||||||
|
root_dir="{root_dir}"
|
||||||
|
parse_argv "$@"
|
||||||
|
setup_env
|
||||||
|
tool_path="{tool_path}.sh"
|
||||||
|
run
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_argv() {
|
||||||
|
tool_data="$1"
|
||||||
|
if [[ -z "$tool_data" ]]; then
|
||||||
|
die "usage: ./{function_name}.sh <tool-data>"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_env() {
|
||||||
|
load_env "$root_dir/.env"
|
||||||
|
export LLM_ROOT_DIR="$root_dir"
|
||||||
|
export LLM_TOOL_NAME="{function_name}"
|
||||||
|
export LLM_TOOL_CACHE_DIR="$LLM_ROOT_DIR/cache/{function_name}"
|
||||||
|
export LLM_PROMPT_UTILS_FILE="{prompt_utils_file}"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_env() {
|
||||||
|
local env_file="$1" env_vars
|
||||||
|
if [[ -f "$env_file" ]]; then
|
||||||
|
while IFS='=' read -r key value; do
|
||||||
|
if [[ "$key" == $'#'* ]] || [[ -z "$key" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${!key+x}" ]]; then
|
||||||
|
env_vars="$env_vars $key=$value"
|
||||||
|
fi
|
||||||
|
done < <(cat "$env_file"; echo "")
|
||||||
|
|
||||||
|
if [[ -n "$env_vars" ]]; then
|
||||||
|
eval "export $env_vars"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run() {
|
||||||
|
if [[ -z "$tool_data" ]]; then
|
||||||
|
die "error: no JSON data"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$OS" == "Windows_NT" ]]; then
|
||||||
|
set -o igncr
|
||||||
|
tool_path="$(cygpath -w "$tool_path")"
|
||||||
|
tool_data="$(echo "$tool_data" | sed 's/\\/\\\\/g')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
jq_script="$(cat <<-'EOF'
|
||||||
|
def escape_shell_word:
|
||||||
|
tostring
|
||||||
|
| gsub("'"; "'\"'\"'")
|
||||||
|
| gsub("\n"; "'$'\\n''")
|
||||||
|
| "'\(.)'";
|
||||||
|
def to_args:
|
||||||
|
to_entries | .[] |
|
||||||
|
(.key | split("_") | join("-")) as $key |
|
||||||
|
if .value | type == "array" then
|
||||||
|
.value | .[] | "--\($key) \(. | escape_shell_word)"
|
||||||
|
elif .value | type == "boolean" then
|
||||||
|
if .value then "--\($key)" else "" end
|
||||||
|
else
|
||||||
|
"--\($key) \(.value | escape_shell_word)"
|
||||||
|
end;
|
||||||
|
[ to_args ] | join(" ")
|
||||||
|
EOF
|
||||||
|
)"
|
||||||
|
args="$(echo "$tool_data" | jq -r "$jq_script" 2>/dev/null)" || {
|
||||||
|
die "error: invalid JSON data"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -z "$LLM_OUTPUT" ]]; then
|
||||||
|
is_temp_llm_output=1
|
||||||
|
# shellcheck disable=SC2155
|
||||||
|
export LLM_OUTPUT="$(mktemp)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval "'$tool_path' $args"
|
||||||
|
|
||||||
|
if [[ "$is_temp_llm_output" -eq 1 ]]; then
|
||||||
|
cat "$LLM_OUTPUT"
|
||||||
|
else
|
||||||
|
dump_result "{function_name}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
dump_result() {
|
||||||
|
if [[ "$LLM_OUTPUT" == "/dev/stdout" ]] || [[ -z "$LLM_DUMP_RESULTS" ]] || [[ ! -t 1 ]]; then
|
||||||
|
return;
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q -w -E "$LLM_DUMP_RESULTS" <<<"$1"; then
|
||||||
|
cat <<EOF
|
||||||
|
$(echo -e "\e[2m")----------------------
|
||||||
|
$(cat "$LLM_OUTPUT")
|
||||||
|
----------------------$(echo -e "\e[0m")
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import os
|
||||||
|
from typing import List, Literal, Optional
|
||||||
|
|
||||||
|
def run(
|
||||||
|
string: str,
|
||||||
|
string_enum: Literal["foo", "bar"],
|
||||||
|
boolean: bool,
|
||||||
|
integer: int,
|
||||||
|
number: float,
|
||||||
|
array: List[str],
|
||||||
|
string_optional: Optional[str] = None,
|
||||||
|
array_optional: Optional[List[str]] = None,
|
||||||
|
):
|
||||||
|
"""Demonstrates how to create a tool using Python and how to use comments.
|
||||||
|
Args:
|
||||||
|
string: Define a required string property
|
||||||
|
string_enum: Define a required string property with enum
|
||||||
|
boolean: Define a required boolean property
|
||||||
|
integer: Define a required integer property
|
||||||
|
number: Define a required number property
|
||||||
|
array: Define a required string array property
|
||||||
|
string_optional: Define an optional string property
|
||||||
|
array_optional: Define an optional string array property
|
||||||
|
"""
|
||||||
|
output = f"""string: {string}
|
||||||
|
string_enum: {string_enum}
|
||||||
|
string_optional: {string_optional}
|
||||||
|
boolean: {boolean}
|
||||||
|
integer: {integer}
|
||||||
|
number: {number}
|
||||||
|
array: {array}
|
||||||
|
array_optional: {array_optional}"""
|
||||||
|
|
||||||
|
for key, value in os.environ.items():
|
||||||
|
if key.startswith("LLM_"):
|
||||||
|
output = f"{output}\n{key}: {value}"
|
||||||
|
|
||||||
|
return output
|
||||||
Executable
+29
@@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Demonstrate how to create a tool using Bash and how to use comment tags.
|
||||||
|
# @option --string! Define a required string property
|
||||||
|
# @option --string-enum![foo|bar] Define a required string property with enum
|
||||||
|
# @option --string-optional Define a optional string property
|
||||||
|
# @flag --boolean Define a boolean property
|
||||||
|
# @option --integer! <INT> Define a required integer property
|
||||||
|
# @option --number! <NUM> Define a required number property
|
||||||
|
# @option --array+ <VALUE> Define a required string array property
|
||||||
|
# @option --array-optional* Define a optional string array property
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
main() {
|
||||||
|
cat <<EOF >> "$LLM_OUTPUT"
|
||||||
|
string: ${argc_string}
|
||||||
|
string_enum: ${argc_string_enum}
|
||||||
|
string_optional: ${argc_string_optional}
|
||||||
|
boolean: ${argc_boolean}
|
||||||
|
integer: ${argc_integer}
|
||||||
|
number: ${argc_number}
|
||||||
|
array: ${argc_array[@]}
|
||||||
|
array_optional: ${argc_array_optional[@]}
|
||||||
|
$(printenv | grep '^LLM_')
|
||||||
|
EOF
|
||||||
|
}
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Execute the shell command.
|
||||||
|
# @option --command! The command to execute.
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
|
||||||
|
main() {
|
||||||
|
guard_operation
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
eval "$argc_command" >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import ast
|
||||||
|
import io
|
||||||
|
from contextlib import redirect_stdout
|
||||||
|
|
||||||
|
|
||||||
|
def run(code: str):
|
||||||
|
"""Execute the given Python code.
|
||||||
|
Args:
|
||||||
|
code: The Python code to execute, such as `print("hello world")`
|
||||||
|
"""
|
||||||
|
output = io.StringIO()
|
||||||
|
with redirect_stdout(output):
|
||||||
|
value = exec_with_return(code, {}, {})
|
||||||
|
|
||||||
|
if value is not None:
|
||||||
|
output.write(str(value))
|
||||||
|
|
||||||
|
return output.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def exec_with_return(code: str, globals: dict, locals: dict):
|
||||||
|
a = ast.parse(code)
|
||||||
|
last_expression = None
|
||||||
|
if a.body:
|
||||||
|
if isinstance(a_last := a.body[-1], ast.Expr):
|
||||||
|
last_expression = ast.unparse(a.body.pop())
|
||||||
|
elif isinstance(a_last, ast.Assign):
|
||||||
|
last_expression = ast.unparse(a_last.targets[0])
|
||||||
|
elif isinstance(a_last, (ast.AnnAssign, ast.AugAssign)):
|
||||||
|
last_expression = ast.unparse(a_last.target)
|
||||||
|
exec(ast.unparse(a), globals, locals)
|
||||||
|
if last_expression:
|
||||||
|
return eval(last_expression, globals, locals)
|
||||||
Executable
+21
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Execute sql code.
|
||||||
|
# @option --code! The code to execute.
|
||||||
|
|
||||||
|
# @meta require-tools usql
|
||||||
|
|
||||||
|
# @env USQL_DSN! The database connection url. e.g. pgsql://user:pass@host:port
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
main() {
|
||||||
|
if ! grep -qi '^select' <<<"$argc_code"; then
|
||||||
|
guard_operation ""
|
||||||
|
fi
|
||||||
|
usql -c "$argc_code" "$USQL_DSN" >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
Executable
+18
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Extract the content from a given URL.
|
||||||
|
# @option --url! The URL to scrape.
|
||||||
|
|
||||||
|
# @meta require-tools pandoc
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
main() {
|
||||||
|
# span and div tags are dropped from the HTML https://pandoc.org/MANUAL.html#raw-htmltex and sed removes any inline SVG images in image tags from the Markdown content.
|
||||||
|
curl -fsSL "$argc_url" | \
|
||||||
|
pandoc -f html-native_divs-native_spans -t gfm-raw_html --wrap=none | \
|
||||||
|
sed -E 's/!\[[^]]*\]\([^)]*\)//g' \
|
||||||
|
>> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
Executable
+17
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Extract the content from a given URL.
|
||||||
|
# @option --url! The URL to scrape.
|
||||||
|
|
||||||
|
# @env JINA_API_KEY Your Jina API key
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
main() {
|
||||||
|
curl_args=()
|
||||||
|
if [[ -n "$JINA_API_KEY" ]]; then
|
||||||
|
curl_args+=("-H" "Authorization: Bearer $JINA_API_KEY")
|
||||||
|
fi
|
||||||
|
curl -fsSL "${curl_args[@]}" "https://r.jina.ai/$argc_url" >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
Executable
+14
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Read the contents of a file at the specified path.
|
||||||
|
# Use this when you need to examine the contents of an existing file.
|
||||||
|
|
||||||
|
# @option --path! The path of the file to read
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
main() {
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
cat "$argc_path" >> "$LLM_OUTPUT" 2>&1 || echo "No such file or path: $argc_path" >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Find files by glob pattern. Returns matching file paths sorted by modification time.
|
||||||
|
# Use this to discover files before reading them.
|
||||||
|
|
||||||
|
# @option --pattern! The glob pattern to match files against (e.g. "**/*.rs", "src/**/*.ts", "*.yaml")
|
||||||
|
# @option --path The directory to search in (defaults to current working directory)
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
MAX_RESULTS=100
|
||||||
|
|
||||||
|
main() {
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
local glob_pattern="$argc_pattern"
|
||||||
|
local search_path="${argc_path:-.}"
|
||||||
|
|
||||||
|
if [[ ! -d "$search_path" ]]; then
|
||||||
|
echo "Error: directory not found: $search_path" >> "$LLM_OUTPUT"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local results
|
||||||
|
if command -v fd &>/dev/null; then
|
||||||
|
results=$(fd --type f --glob "$glob_pattern" "$search_path" \
|
||||||
|
--exclude '.git' \
|
||||||
|
--exclude 'node_modules' \
|
||||||
|
--exclude 'target' \
|
||||||
|
--exclude 'dist' \
|
||||||
|
--exclude '__pycache__' \
|
||||||
|
--exclude 'vendor' \
|
||||||
|
--exclude '.build' \
|
||||||
|
2>/dev/null | head -n "$MAX_RESULTS") || true
|
||||||
|
else
|
||||||
|
results=$(find "$search_path" -type f -name "$glob_pattern" \
|
||||||
|
-not -path '*/.git/*' \
|
||||||
|
-not -path '*/node_modules/*' \
|
||||||
|
-not -path '*/target/*' \
|
||||||
|
-not -path '*/dist/*' \
|
||||||
|
-not -path '*/__pycache__/*' \
|
||||||
|
-not -path '*/vendor/*' \
|
||||||
|
-not -path '*/.build/*' \
|
||||||
|
2>/dev/null | head -n "$MAX_RESULTS") || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$results" ]]; then
|
||||||
|
echo "No files found matching: $glob_pattern" >> "$LLM_OUTPUT"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$results" >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local count
|
||||||
|
count=$(echo "$results" | wc -l)
|
||||||
|
if [[ "$count" -ge "$MAX_RESULTS" ]]; then
|
||||||
|
printf "\n(Results limited to %s files. Use a more specific pattern.)\n" "$MAX_RESULTS" >> "$LLM_OUTPUT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Search file contents using regular expressions. Returns matching file paths and lines.
|
||||||
|
# Use this to find relevant code before reading files. Much faster than reading files to search.
|
||||||
|
|
||||||
|
# @option --pattern! The regex pattern to search for in file contents
|
||||||
|
# @option --path The directory to search in (defaults to current working directory)
|
||||||
|
# @option --include File pattern to filter by (e.g. "*.rs", "*.{ts,tsx}", "*.py")
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
MAX_RESULTS=50
|
||||||
|
MAX_LINE_LENGTH=2000
|
||||||
|
|
||||||
|
main() {
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
local search_pattern="$argc_pattern"
|
||||||
|
local search_path="${argc_path:-.}"
|
||||||
|
local include_filter="${argc_include:-}"
|
||||||
|
|
||||||
|
if [[ ! -d "$search_path" ]]; then
|
||||||
|
echo "Error: directory not found: $search_path" >> "$LLM_OUTPUT"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local grep_args=(-rn --color=never)
|
||||||
|
|
||||||
|
grep_args+=(
|
||||||
|
--exclude-dir='.git'
|
||||||
|
--exclude-dir='node_modules'
|
||||||
|
--exclude-dir='target'
|
||||||
|
--exclude-dir='dist'
|
||||||
|
--exclude-dir='build'
|
||||||
|
--exclude-dir='__pycache__'
|
||||||
|
--exclude-dir='vendor'
|
||||||
|
--exclude-dir='.build'
|
||||||
|
--exclude-dir='.next'
|
||||||
|
--exclude='*.min.js'
|
||||||
|
--exclude='*.min.css'
|
||||||
|
--exclude='*.map'
|
||||||
|
--exclude='*.lock'
|
||||||
|
--exclude='package-lock.json'
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ -n "$include_filter" ]]; then
|
||||||
|
grep_args+=("--include=$include_filter")
|
||||||
|
fi
|
||||||
|
|
||||||
|
local results
|
||||||
|
results=$(grep "${grep_args[@]}" -E "$search_pattern" "$search_path" 2>/dev/null | head -n "$MAX_RESULTS") || true
|
||||||
|
|
||||||
|
if [[ -z "$results" ]]; then
|
||||||
|
echo "No matches found for: $search_pattern" >> "$LLM_OUTPUT"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$results" | while IFS= read -r line; do
|
||||||
|
if [[ ${#line} -gt $MAX_LINE_LENGTH ]]; then
|
||||||
|
line="${line:0:$MAX_LINE_LENGTH}... (truncated)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$line"
|
||||||
|
done >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
local count
|
||||||
|
count=$(echo "$results" | wc -l)
|
||||||
|
if [[ "$count" -ge "$MAX_RESULTS" ]]; then
|
||||||
|
printf "\n(Results limited to %s matches. Narrow your search with --include or a more specific pattern.)\n" "$MAX_RESULTS" >> "$LLM_OUTPUT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe List all files and directories at the specified path.
|
||||||
|
|
||||||
|
# @option --path! The path of the directory to list
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
main() {
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
ls -1 "$argc_path" >> "$LLM_OUTPUT" 2>&1 || echo "No such path: $argc_path" >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
Executable
+14
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Create a new directory at the specified path.
|
||||||
|
|
||||||
|
# @option --path! The path of the directory to create
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
main() {
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
mkdir -p "$argc_path"
|
||||||
|
echo "Directory created: $argc_path" >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
Executable
+30
@@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Apply a patch to a file at the specified path.
|
||||||
|
# This can be used to edit a file without having to rewrite the whole file.
|
||||||
|
|
||||||
|
# @option --path! The path of the file to apply the patch to
|
||||||
|
# @option --contents! The patch to apply to the file
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
main() {
|
||||||
|
if [[ ! -f "$argc_path" ]]; then
|
||||||
|
error "Unable to find the specified file: $argc_path"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
new_contents="$(patch_file "$argc_path" <(printf "%s" "$argc_contents"))"
|
||||||
|
printf "%s" "$new_contents" | git diff --no-index "$argc_path" - || true
|
||||||
|
|
||||||
|
guard_operation "Apply changes?"
|
||||||
|
|
||||||
|
printf "%s" "$new_contents" > "$argc_path"
|
||||||
|
|
||||||
|
info "Applied the patch to: $argc_path" >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Read a file with line numbers, offset, and limit. For directories, lists entries.
|
||||||
|
# Prefer this over fs_cat for controlled reading. Use offset/limit to read specific sections.
|
||||||
|
# Use the grep tool to find specific content before reading, then read with offset to target the relevant section.
|
||||||
|
|
||||||
|
# @option --path! The absolute path to the file or directory to read
|
||||||
|
# @option --offset The line number to start reading from (1-indexed, default: 1)
|
||||||
|
# @option --limit The maximum number of lines to read (default: 2000)
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
MAX_LINE_LENGTH=2000
|
||||||
|
MAX_BYTES=51200
|
||||||
|
|
||||||
|
main() {
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
local target="$argc_path"
|
||||||
|
local offset="${argc_offset:-1}"
|
||||||
|
local limit="${argc_limit:-2000}"
|
||||||
|
|
||||||
|
if [[ ! -e "$target" ]]; then
|
||||||
|
echo "Error: path not found: $target" >> "$LLM_OUTPUT"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "$target" ]]; then
|
||||||
|
ls -1 "$target" >> "$LLM_OUTPUT" 2>&1
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local total_lines file_bytes
|
||||||
|
total_lines=$(wc -l < "$target" 2>/dev/null || echo 0)
|
||||||
|
file_bytes=$(wc -c < "$target" 2>/dev/null || echo 0)
|
||||||
|
|
||||||
|
if [[ "$file_bytes" -gt "$MAX_BYTES" ]] && [[ "$offset" -eq 1 ]] && [[ "$limit" -ge 2000 ]]; then
|
||||||
|
{
|
||||||
|
echo "Warning: Large file (${file_bytes} bytes, ${total_lines} lines). Showing first ${limit} lines."
|
||||||
|
echo "Use --offset and --limit to read specific sections, or use the grep tool to find relevant lines first."
|
||||||
|
echo ""
|
||||||
|
} >> "$LLM_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local end_line=$((offset + limit - 1))
|
||||||
|
|
||||||
|
sed -n "${offset},${end_line}p" "$target" 2>/dev/null | {
|
||||||
|
local line_num=$offset
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if [[ ${#line} -gt $MAX_LINE_LENGTH ]]; then
|
||||||
|
line="${line:0:$MAX_LINE_LENGTH}... (truncated)"
|
||||||
|
fi
|
||||||
|
printf "%d: %s\n" "$line_num" "$line"
|
||||||
|
line_num=$((line_num + 1))
|
||||||
|
done
|
||||||
|
} >> "$LLM_OUTPUT"
|
||||||
|
|
||||||
|
if [[ "$end_line" -lt "$total_lines" ]]; then
|
||||||
|
echo "" >> "$LLM_OUTPUT"
|
||||||
|
echo "(${total_lines} total lines. Use --offset $((end_line + 1)) to read more.)" >> "$LLM_OUTPUT"
|
||||||
|
fi
|
||||||
|
}
|
||||||
Executable
+21
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Remove the file or directory at the specified path.
|
||||||
|
|
||||||
|
# @option --path! The path of the file or directory to remove
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
main() {
|
||||||
|
if [[ -f "$argc_path" ]]; then
|
||||||
|
guard_path "$argc_path" "Remove '$argc_path'?"
|
||||||
|
rm -rf "$argc_path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Path removed: $argc_path" >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
Executable
+26
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Write the full file contents to a file at the specified path.
|
||||||
|
|
||||||
|
# @option --path! The path of the file to write to
|
||||||
|
# @option --contents! The full contents to write to the file
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
main() {
|
||||||
|
if [[ -f "$argc_path" ]]; then
|
||||||
|
printf "%s" "$argc_contents" | git diff --no-index "$argc_path" - || true
|
||||||
|
guard_operation "Apply changes?"
|
||||||
|
else
|
||||||
|
guard_path "$argc_path" "Write '$argc_path'?"
|
||||||
|
mkdir -p "$(dirname "$argc_path")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%s" "$argc_contents" > "$argc_path"
|
||||||
|
echo "The File contents were written to: $argc_path" >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
Executable
+10
@@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Get the current time.
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
main() {
|
||||||
|
date >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
from urllib.parse import quote_plus
|
||||||
|
from urllib.request import urlopen
|
||||||
|
|
||||||
|
|
||||||
|
def run(
|
||||||
|
location: str,
|
||||||
|
llm_output: Optional[str] = None,
|
||||||
|
) -> str:
|
||||||
|
"""Get the current weather in a given location
|
||||||
|
|
||||||
|
Args:
|
||||||
|
location (str): The city and optionally the state or country (e.g., "London", "San Francisco, CA").
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: A single-line formatted weather string from wttr.in (``format=4`` with metric units).
|
||||||
|
"""
|
||||||
|
url = f"https://wttr.in/{quote_plus(location)}?format=4&M"
|
||||||
|
|
||||||
|
with urlopen(url, timeout=10) as resp:
|
||||||
|
weather = resp.read().decode("utf-8", errors="replace")
|
||||||
|
|
||||||
|
dest = llm_output if llm_output is not None else os.environ.get("LLM_OUTPUT", "/dev/stdout")
|
||||||
|
|
||||||
|
if dest not in {"-", "/dev/stdout"}:
|
||||||
|
path = Path(dest)
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with path.open("a", encoding="utf-8") as fh:
|
||||||
|
fh.write(weather)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return weather
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Get the current weather in a given location.
|
||||||
|
# @option --location! The city and optionally the state or country, e.g., "London", "San Francisco, CA".
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
main() {
|
||||||
|
curl -fsSL "https://wttr.in/$(echo "$argc_location" | sed 's/ /+/g')?format=4&M" \
|
||||||
|
>> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @meta require-tools jira
|
||||||
|
# @describe Query for jira issues using a Jira Query Language (JQL) query
|
||||||
|
# @option --jql-query! The Jira Query Language query to execute
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
main() {
|
||||||
|
jira issue ls -q "$argc_jql_query" --plain >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Search arXiv using the given search query and return the top papers.
|
||||||
|
|
||||||
|
# @option --query! The search query.
|
||||||
|
|
||||||
|
# @env ARXIV_MAX_RESULTS=3 The max results to return.
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
main() {
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
encoded_query="$(jq -nr --arg q "$argc_query" '$q|@uri')"
|
||||||
|
url="http://export.arxiv.org/api/query?search_query=all:$encoded_query&max_results=$ARXIV_MAX_RESULTS"
|
||||||
|
curl -fsSL "$url" >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
Executable
+29
@@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Search Wikipedia using the given search query.
|
||||||
|
# Use it to get detailed information about a public figure, interpretation of a complex scientific concept or in-depth connectivity of a significant historical event, etc.
|
||||||
|
|
||||||
|
# @option --query! The search query.
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
main() {
|
||||||
|
encoded_query="$(jq -nr --arg q "$argc_query" '$q|@uri')"
|
||||||
|
base_url="https://en.wikipedia.org/w/api.php"
|
||||||
|
url="$base_url?action=query&list=search&srprop=&srlimit=1&limit=1&srsearch=$encoded_query&srinfo=suggestion&format=json"
|
||||||
|
json="$(curl -fsSL "$url")"
|
||||||
|
# suggestion="$(echo "$json" | jq -r '.query.searchinfo.suggestion // empty')"
|
||||||
|
title="$(echo "$json" | jq -r '.query.search[0].title // empty')"
|
||||||
|
pageid="$(echo "$json" | jq -r '.query.search[0].pageid // empty')"
|
||||||
|
|
||||||
|
if [[ -z "$title" || -z "$pageid" ]]; then
|
||||||
|
echo "error: no results for '$argc_query'" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
title="$(echo "$title" | tr ' ' '_')"
|
||||||
|
url="$base_url?action=query&prop=extracts&explaintext=&titles=$title&exintro=&format=json"
|
||||||
|
curl -fsSL "$url" | jq -r '.query.pages["'"$pageid"'"].extract' >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Get an answer to a question using Wolfram Alpha. The input query should be in English.
|
||||||
|
# Use it to answer user questions that require computation, detailed facts, data analysis, or complex queries.
|
||||||
|
|
||||||
|
# @option --query! The search/computation query to pass to Wolfram Alpha
|
||||||
|
|
||||||
|
# @env WOLFRAM_API_ID! Your Wolfram Alpha API ID
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
main() {
|
||||||
|
encoded_query="$(jq -nr --arg q "$argc_query" '$q|@uri')"
|
||||||
|
url="https://api.wolframalpha.com/v2/query?appid=${WOLFRAM_API_ID}&input=$encoded_query&output=json&format=plaintext"
|
||||||
|
|
||||||
|
curl -fsSL "$url" | jq '[.queryresult | .pods[] | {title:.title, values:[.subpods[].plaintext | select(. != "")]}]' \
|
||||||
|
>> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
Executable
+30
@@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Send an email.
|
||||||
|
# @option --recipient! The recipient of the email.
|
||||||
|
# @option --subject! The subject of the email.
|
||||||
|
# @option --body! The body of the email.
|
||||||
|
|
||||||
|
# @env EMAIL_SMTP_ADDR! The SMTP Address, e.g. smtps://smtp.gmail.com:465
|
||||||
|
# @env EMAIL_SMTP_USER! The SMTP User, e.g. alice@gmail.com
|
||||||
|
# @env EMAIL_SMTP_PASS! The SMTP Password
|
||||||
|
# @env EMAIL_SENDER_NAME The sender's name
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
main() {
|
||||||
|
sender_name="${EMAIL_SENDER_NAME:-$(echo "$EMAIL_SMTP_USER" | awk -F'@' '{print $1}')}"
|
||||||
|
printf "%s\n" "From: $sender_name <$EMAIL_SMTP_USER>
|
||||||
|
To: $argc_recipient
|
||||||
|
Subject: $argc_subject
|
||||||
|
|
||||||
|
$argc_body" | \
|
||||||
|
curl -fsS --ssl-reqd \
|
||||||
|
--url "$EMAIL_SMTP_ADDR" \
|
||||||
|
--user "$EMAIL_SMTP_USER:$EMAIL_SMTP_PASS" \
|
||||||
|
--mail-from "$EMAIL_SMTP_USER" \
|
||||||
|
--mail-rcpt "$argc_recipient" \
|
||||||
|
--upload-file -
|
||||||
|
echo "Email sent successfully" >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
Executable
+50
@@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Send SMS or Twilio Messaging Channels messages using the Twilio API.
|
||||||
|
# @option --to-number! The recipient's phone number. Prefix it with 'whatsapp:' for WhatsApp messages, e.g. whatsapp:+1234567890
|
||||||
|
# @option --message! The content of the message to be sent
|
||||||
|
|
||||||
|
# @env TWILIO_ACCOUNT_SID! The twilio account sid
|
||||||
|
# @env TWILIO_AUTH_TOKEN! The twilio auth token
|
||||||
|
# @env TWILIO_FROM_NUMBER! The twilio from number
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
main() {
|
||||||
|
from_number="$TWILIO_FROM_NUMBER"
|
||||||
|
to_number="$argc_to_number"
|
||||||
|
|
||||||
|
if [[ "$to_number" == 'whatsapp:'* ]]; then
|
||||||
|
from_number="whatsapp:$from_number"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$to_number" != 'whatsapp:'* && "$to_number" != '+'* ]]; then
|
||||||
|
to_number="+$to_number"
|
||||||
|
fi
|
||||||
|
|
||||||
|
res="$(curl -s -X POST "https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Messages.json" \
|
||||||
|
-u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
|
||||||
|
-w "\n%{http_code}" \
|
||||||
|
--data-urlencode "From=$from_number" \
|
||||||
|
--data-urlencode "To=$to_number" \
|
||||||
|
--data-urlencode "Body=$argc_message")"
|
||||||
|
status="$(echo "$res" | tail -n 1)"
|
||||||
|
body="$(echo "$res" | head -n -1)"
|
||||||
|
|
||||||
|
if [[ "$status" -ge 200 && "$status" -lt 300 ]]; then
|
||||||
|
if [[ "$(echo "$body" | jq -r 'has("sid")')" == "true" ]]; then
|
||||||
|
echo "Message sent successfully" >> "$LLM_OUTPUT"
|
||||||
|
else
|
||||||
|
_die "error: $body"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
_die "error: $body"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_die() {
|
||||||
|
echo "$*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
Executable
+36
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Perform a web search to get up-to-date information or additional context.
|
||||||
|
# Use this when you need current information or feel a search could provide a better answer.
|
||||||
|
|
||||||
|
# @option --query! The search query.
|
||||||
|
|
||||||
|
# @meta require-tools loki
|
||||||
|
|
||||||
|
# @env WEB_SEARCH_MODEL=gemini:gemini-2.5-flash The model for web-searching.
|
||||||
|
#
|
||||||
|
# supported loki models:
|
||||||
|
# - gemini:gemini-2.0-*
|
||||||
|
# - vertexai:gemini-*
|
||||||
|
# - perplexity:*
|
||||||
|
# - ernie:*
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
main() {
|
||||||
|
client="${WEB_SEARCH_MODEL%%:*}"
|
||||||
|
|
||||||
|
if [[ "$client" == "gemini" ]]; then
|
||||||
|
export LOKI_PATCH_GEMINI_CHAT_COMPLETIONS='{".*":{"body":{"tools":[{"google_search":{}}]}}}'
|
||||||
|
elif [[ "$client" == "vertexai" ]]; then
|
||||||
|
export LOKI_PATCH_VERTEXAI_CHAT_COMPLETIONS='{
|
||||||
|
"gemini-1.5-.*":{"body":{"tools":[{"googleSearchRetrieval":{}}]}},
|
||||||
|
"gemini-2.0-.*":{"body":{"tools":[{"google_search":{}}]}}
|
||||||
|
}'
|
||||||
|
elif [[ "$client" == "ernie" ]]; then
|
||||||
|
export LOKI_PATCH_ERNIE_CHAT_COMPLETIONS='{".*":{"body":{"web_search":{"enable":true}}}}'
|
||||||
|
fi
|
||||||
|
|
||||||
|
loki -m "$WEB_SEARCH_MODEL" "$argc_query" >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Perform a web search using the Perplexity API to get up-to-date information or additional context.
|
||||||
|
# Use this when you need current information or feel a search could provide a better answer.
|
||||||
|
|
||||||
|
# @option --query! The search query.
|
||||||
|
|
||||||
|
# @env PERPLEXITY_API_KEY! Your Perplexity API key
|
||||||
|
# @env PERPLEXITY_WEB_SEARCH_MODEL=llama-3.1-sonar-small-128k-online The LLM model to use for the search
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
main() {
|
||||||
|
curl -fsS -X POST https://api.perplexity.ai/chat/completions \
|
||||||
|
-H "authorization: Bearer $PERPLEXITY_API_KEY" \
|
||||||
|
-H "accept: application/json" \
|
||||||
|
-H "content-type: application/json" \
|
||||||
|
--data '
|
||||||
|
{
|
||||||
|
"model": "'"$PERPLEXITY_WEB_SEARCH_MODEL"'",
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "'"$argc_query"'"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
' | \
|
||||||
|
jq -r '.choices[0].message.content' \
|
||||||
|
>> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
Executable
+24
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @describe Perform a web search using the Tavily API to get up-to-date information or additional context.
|
||||||
|
# Use this when you need current information or feel a search could provide a better answer.
|
||||||
|
|
||||||
|
# @option --query! The search query.
|
||||||
|
|
||||||
|
# @env TAVILY_API_KEY! Your Tavile API key
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path The output path
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
main() {
|
||||||
|
curl -fsSL -X POST https://api.tavily.com/search \
|
||||||
|
-H "content-type: application/json" \
|
||||||
|
-d '
|
||||||
|
{
|
||||||
|
"api_key": "'"$TAVILY_API_KEY"'",
|
||||||
|
"query": "'"$argc_query"'",
|
||||||
|
"include_answer": true
|
||||||
|
}' | \
|
||||||
|
jq -r '.answer' >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
|
|
||||||
Executable
+718
@@ -0,0 +1,718 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Bash helper utilities for prompting users.
|
||||||
|
# This is a modified version of the excellent Bash TUI toolkit
|
||||||
|
# (https://github.com/timo-reymann/bash-tui-toolkit)
|
||||||
|
#
|
||||||
|
# It includes the following functions for you to use in your
|
||||||
|
# bash tool commands:
|
||||||
|
#
|
||||||
|
# - password Password prompt
|
||||||
|
# - checked Checkbox
|
||||||
|
# - text Text input with validation
|
||||||
|
# - list Select an option from a given list
|
||||||
|
# - range Prompt the user for a value within a range
|
||||||
|
# - confirm Confirmation prompt
|
||||||
|
# - editor Open the user's preferred editor for input
|
||||||
|
# - detect_os Detect the current OS
|
||||||
|
# - get_opener Get the file opener for the current OS
|
||||||
|
# - open_link Open the given link in the default browser
|
||||||
|
# See https://github.com/timo-reymann/bash-tui-toolkit/blob/main/test.sh
|
||||||
|
# for examples on how to use these commands
|
||||||
|
#
|
||||||
|
# - guard_operation Prompt for permission to run an operation
|
||||||
|
# - guard_path Prompt for permission to perform path operations
|
||||||
|
# - patch_file Patch a file
|
||||||
|
# - error Log an error
|
||||||
|
# - warn Log a warning
|
||||||
|
# - info Log info
|
||||||
|
# - debug Log a debug message
|
||||||
|
# - trace Log a trace message
|
||||||
|
# - red Output given text in red
|
||||||
|
# - green Output given text in green
|
||||||
|
# - gold Output given text in gold
|
||||||
|
# - blue Output given text in blue
|
||||||
|
# - magenta Output given text in magenta
|
||||||
|
# - cyan Output given text in cyan
|
||||||
|
# - white Output given text in white
|
||||||
|
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
red=$(tput setaf 1)
|
||||||
|
green=$(tput setaf 2)
|
||||||
|
gold=$(tput setaf 3)
|
||||||
|
blue=$(tput setaf 4)
|
||||||
|
magenta=$(tput setaf 5)
|
||||||
|
cyan=$(tput setaf 6)
|
||||||
|
white=$(tput setaf 7)
|
||||||
|
|
||||||
|
default=$(tput sgr0)
|
||||||
|
gray=$(tput setaf 243)
|
||||||
|
|
||||||
|
bold=$(tput bold)
|
||||||
|
underlined=$(tput smul)
|
||||||
|
|
||||||
|
error() {
|
||||||
|
echo -e "${red}${bold}ERROR:${default}${red} $1${default}"
|
||||||
|
}
|
||||||
|
|
||||||
|
warn() {
|
||||||
|
echo -e "${gold}${bold}WARN:${default}${gold} $1${default}"
|
||||||
|
}
|
||||||
|
|
||||||
|
info() {
|
||||||
|
echo -e "${cyan}${bold}INFO:${default}${cyan} $1${default}"
|
||||||
|
}
|
||||||
|
|
||||||
|
debug() {
|
||||||
|
echo -e "${blue}${bold}DEBUG:${default}${blue} $1${default}"
|
||||||
|
}
|
||||||
|
|
||||||
|
trace() {
|
||||||
|
echo -e "${gray}${bold}TRACE:${default}${gray} $1${default}"
|
||||||
|
}
|
||||||
|
|
||||||
|
success() {
|
||||||
|
echo -e "${green}${bold}SUCCESS:${default}${green} $1${default}"
|
||||||
|
}
|
||||||
|
|
||||||
|
red() {
|
||||||
|
echo -e "${red}$1${default}"
|
||||||
|
}
|
||||||
|
|
||||||
|
green() {
|
||||||
|
echo -e "${green}$1${default}"
|
||||||
|
}
|
||||||
|
|
||||||
|
gold() {
|
||||||
|
echo -e "${gold}$1${default}"
|
||||||
|
}
|
||||||
|
|
||||||
|
blue() {
|
||||||
|
echo -e "${blue}$1${default}"
|
||||||
|
}
|
||||||
|
|
||||||
|
magenta() {
|
||||||
|
echo -e "${magenta}$1${default}"
|
||||||
|
}
|
||||||
|
|
||||||
|
cyan() {
|
||||||
|
echo -e "${cyan}$1${default}"
|
||||||
|
}
|
||||||
|
|
||||||
|
white() {
|
||||||
|
echo -e "${white}$1${default}"
|
||||||
|
}
|
||||||
|
|
||||||
|
_read_stdin() {
|
||||||
|
read -r "$@" </dev/tty
|
||||||
|
}
|
||||||
|
|
||||||
|
_get_cursor_row() {
|
||||||
|
declare IFS=';'
|
||||||
|
_read_stdin -sdR -p $'\E[6n' ROW COL
|
||||||
|
echo "${ROW#*[}"
|
||||||
|
}
|
||||||
|
|
||||||
|
_cursor_blink_on() {
|
||||||
|
echo -en "\033[?25h" >&2
|
||||||
|
}
|
||||||
|
_cursor_blink_off() {
|
||||||
|
echo -en "\033[?25l" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
_cursor_to() {
|
||||||
|
echo -en "\033[$1;$2H" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
_key_input() {
|
||||||
|
declare ESC=$'\033'
|
||||||
|
declare IFS=''
|
||||||
|
_read_stdin -rsn1 a
|
||||||
|
if [[ "$ESC" == "$a" ]]; then
|
||||||
|
_read_stdin -rsn2 b
|
||||||
|
fi
|
||||||
|
|
||||||
|
declare input="${a}${b}"
|
||||||
|
case "$input" in
|
||||||
|
"${ESC}[A" | "k") echo up ;;
|
||||||
|
"${ESC}[B" | "j") echo down ;;
|
||||||
|
"${ESC}[C" | "l") echo right ;;
|
||||||
|
"${ESC}[D" | "h") echo left ;;
|
||||||
|
'') echo enter ;;
|
||||||
|
' ') echo space ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
_new_line_foreach_item() {
|
||||||
|
count=0
|
||||||
|
while [[ $count -lt $1 ]]; do
|
||||||
|
echo "" >&2
|
||||||
|
((count++))
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
_prompt_text() {
|
||||||
|
echo -en "\033[32m?\033[0m\033[1m ${1}\033[0m " >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
_decrement_selected() {
|
||||||
|
declare selected=$1
|
||||||
|
((selected--))
|
||||||
|
if [[ "${selected}" -lt 0 ]]; then
|
||||||
|
selected=$(($2 - 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n $selected
|
||||||
|
}
|
||||||
|
|
||||||
|
_increment_selected() {
|
||||||
|
declare selected=$1
|
||||||
|
((selected++))
|
||||||
|
|
||||||
|
if [[ "${selected}" -ge "${opts_count}" ]]; then
|
||||||
|
selected=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n $selected
|
||||||
|
}
|
||||||
|
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
input() {
|
||||||
|
_prompt_text "$1"
|
||||||
|
echo -en "\033[36m\c" >&2
|
||||||
|
_read_stdin -r text
|
||||||
|
echo -n "${text}"
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm() {
|
||||||
|
trap "stty echo; exit" EXIT
|
||||||
|
_prompt_text "$1 (y/N)"
|
||||||
|
echo -en "\033[36m\c " >&2
|
||||||
|
|
||||||
|
declare first_row
|
||||||
|
first_row=$(_get_cursor_row)
|
||||||
|
declare current_row
|
||||||
|
current_row=$((first_row - 1))
|
||||||
|
declare result=""
|
||||||
|
echo -n " " >&2
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
echo -e "\033[1D\c " >&2
|
||||||
|
_read_stdin -n1 result
|
||||||
|
|
||||||
|
case "$result" in
|
||||||
|
y | Y)
|
||||||
|
echo -n 1
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
n | N | *)
|
||||||
|
echo -n 0
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -en "\033[0m" >&2
|
||||||
|
echo "" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
list() {
|
||||||
|
_prompt_text "$1 "
|
||||||
|
declare opts=("${@:2}")
|
||||||
|
declare opts_count=$(($# - 1))
|
||||||
|
|
||||||
|
_new_line_foreach_item "${#opts[@]}"
|
||||||
|
|
||||||
|
declare last_row
|
||||||
|
last_row=$(_get_cursor_row)
|
||||||
|
declare first_row
|
||||||
|
first_row=$((last_row - opts_count + 1))
|
||||||
|
|
||||||
|
trap "_cursor_blink_on; stty echo; exit" 2
|
||||||
|
|
||||||
|
_cursor_blink_off
|
||||||
|
|
||||||
|
declare selected=0
|
||||||
|
while true; do
|
||||||
|
declare idx=0
|
||||||
|
for opt in "${opts[@]}"; do
|
||||||
|
_cursor_to $((first_row + idx))
|
||||||
|
|
||||||
|
if [[ $idx -eq "$selected" ]]; then
|
||||||
|
printf "\033[0m\033[36m❯\033[0m \033[36m%s\033[0m" "$opt" >&2
|
||||||
|
else
|
||||||
|
printf " %s" "$opt" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
((idx++))
|
||||||
|
done
|
||||||
|
|
||||||
|
case $(_key_input) in
|
||||||
|
enter) break ;;
|
||||||
|
up) selected=$(_decrement_selected "${selected}" "${opts_count}") ;;
|
||||||
|
down) selected=$(_increment_selected "${selected}" "${opts_count}") ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -en "\n" >&2
|
||||||
|
_cursor_to "${last_row}"
|
||||||
|
_cursor_blink_on
|
||||||
|
echo -n "${selected}"
|
||||||
|
}
|
||||||
|
|
||||||
|
checkbox() {
|
||||||
|
_prompt_text "$1"
|
||||||
|
declare opts
|
||||||
|
opts=("${@:2}")
|
||||||
|
declare opts_count
|
||||||
|
opts_count=$(($# - 1))
|
||||||
|
|
||||||
|
_new_line_foreach_item "${#opts[@]}"
|
||||||
|
|
||||||
|
declare last_row
|
||||||
|
last_row=$(_get_cursor_row)
|
||||||
|
declare first_row
|
||||||
|
first_row=$((last_row - opts_count + 1))
|
||||||
|
|
||||||
|
trap "_cursor_blink_on; stty echo; exit" 2
|
||||||
|
|
||||||
|
_cursor_blink_off
|
||||||
|
|
||||||
|
declare selected=0
|
||||||
|
declare checked=()
|
||||||
|
while true; do
|
||||||
|
declare idx=0
|
||||||
|
for opt in "${opts[@]}"; do
|
||||||
|
_cursor_to $((first_row + idx))
|
||||||
|
declare icon="◯"
|
||||||
|
|
||||||
|
for item in "${checked[@]}"; do
|
||||||
|
if [[ "$item" == "$idx" ]]; then
|
||||||
|
icon="◉"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $idx -eq "$selected" ]]; then
|
||||||
|
printf "%s \e[0m\e[36m❯\e[0m \e[36m%-50s\e[0m" "$icon" "$opt" >&2
|
||||||
|
else
|
||||||
|
printf "%s %-50s" "$icon" "$opt" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
((idx++))
|
||||||
|
done
|
||||||
|
|
||||||
|
case $(_key_input) in
|
||||||
|
enter) break ;;
|
||||||
|
space)
|
||||||
|
declare found=0
|
||||||
|
for item in "${checked[@]}"; do
|
||||||
|
if [[ "$item" == "$selected" ]]; then
|
||||||
|
found=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $found -eq 1 ]; then
|
||||||
|
checked=("${checked[@]/$selected/}")
|
||||||
|
else
|
||||||
|
checked+=("${selected}")
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
up) selected=$(_decrement_selected "${selected}" "${opts_count}") ;;
|
||||||
|
down) selected=$(_increment_selected "${selected}" "${opts_count}") ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
_cursor_to "${last_row}"
|
||||||
|
_cursor_blink_on
|
||||||
|
IFS="" echo -n "${checked[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
password() {
|
||||||
|
_prompt_text "$1"
|
||||||
|
echo -en "\033[36m" >&2
|
||||||
|
declare password=''
|
||||||
|
declare IFS=
|
||||||
|
|
||||||
|
while _read_stdin -r -s -n1 char; do
|
||||||
|
[[ -z "${char}" ]] && {
|
||||||
|
printf '\n' >&2
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${char}" == $'\x7f' ]]; then
|
||||||
|
if [[ "${#password}" -gt 0 ]]; then
|
||||||
|
password="${password%?}"
|
||||||
|
echo -en '\b \b' >&2
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
password+=$char
|
||||||
|
echo -en '*' >&2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -en "\e[0m" >&2
|
||||||
|
echo -n "${password}"
|
||||||
|
}
|
||||||
|
|
||||||
|
editor() {
|
||||||
|
tmpfile=$(mktemp)
|
||||||
|
_prompt_text "$1"
|
||||||
|
echo "" >&2
|
||||||
|
"${EDITOR:-vi}" "${tmpfile}" >/dev/tty
|
||||||
|
echo -en "\033[36m" >&2
|
||||||
|
sed -e 's/^/ /' "${tmpfile}" >&2
|
||||||
|
echo -en "\033[0m" >&2
|
||||||
|
cat "${tmpfile}"
|
||||||
|
}
|
||||||
|
|
||||||
|
with_validate() {
|
||||||
|
while true; do
|
||||||
|
declare val
|
||||||
|
val="$(eval "$1")"
|
||||||
|
|
||||||
|
if ($2 "$val" >/dev/null); then
|
||||||
|
echo "$val"
|
||||||
|
break
|
||||||
|
else
|
||||||
|
show_error "$($2 "$val")"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
range() {
|
||||||
|
declare min="$2"
|
||||||
|
declare current="$3"
|
||||||
|
declare max="$4"
|
||||||
|
declare selected="${current}"
|
||||||
|
declare max_len_current
|
||||||
|
max_len_current=0
|
||||||
|
|
||||||
|
if [[ "${#min}" -gt "${#max}" ]]; then
|
||||||
|
max_len_current="${#min}"
|
||||||
|
else
|
||||||
|
max_len_current="${#max}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
declare padding
|
||||||
|
padding="$(printf "%-${max_len_current}s" "")"
|
||||||
|
declare first_row
|
||||||
|
first_row=$(_get_cursor_row)
|
||||||
|
declare current_row
|
||||||
|
current_row=$((first_row - 1))
|
||||||
|
|
||||||
|
trap "_cursor_blink_on; stty echo; exit" 2
|
||||||
|
|
||||||
|
_cursor_blink_off
|
||||||
|
|
||||||
|
_check_range() {
|
||||||
|
val=$1
|
||||||
|
|
||||||
|
if [[ "$val" -gt "$max" ]]; then
|
||||||
|
val=$min
|
||||||
|
elif [[ "$val" -lt "$min" ]]; then
|
||||||
|
val=$max
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$val"
|
||||||
|
}
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
_prompt_text "$1"
|
||||||
|
printf "\033[37m%s\033[0m \033[1;90m❮\033[0m \033[36m%s%s\033[0m \033[1;90m❯\033[0m \033[37m%s\033[0m\n" "$min" "${padding:${#selected}}" "$selected" "$max" >&2
|
||||||
|
|
||||||
|
case $(_key_input) in
|
||||||
|
enter)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
left)
|
||||||
|
selected="$(_check_range $((selected - 1)))"
|
||||||
|
;;
|
||||||
|
right)
|
||||||
|
selected="$(_check_range $((selected + 1)))"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
_cursor_to "$current_row"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "$selected"
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_present() {
|
||||||
|
if [ "$1" != "" ]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
error "Please specify the value"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
show_error() {
|
||||||
|
echo -e "\033[91;1m✘ $1\033[0m" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
show_success() {
|
||||||
|
echo -e "\033[92;1m✔ $1\033[0m" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_os() {
|
||||||
|
case "$OSTYPE" in
|
||||||
|
solaris*) echo "solaris" ;;
|
||||||
|
darwin*) echo "macos" ;;
|
||||||
|
linux*) echo "linux" ;;
|
||||||
|
bsd*) echo "bsd" ;;
|
||||||
|
msys*) echo "windows" ;;
|
||||||
|
cygwin*) echo "windows" ;;
|
||||||
|
*) echo "unknown" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
get_opener() {
|
||||||
|
declare cmd
|
||||||
|
|
||||||
|
case "$(detect_os)" in
|
||||||
|
macos) cmd="open" ;;
|
||||||
|
linux) cmd="xdg-open" ;;
|
||||||
|
windows) cmd="start" ;;
|
||||||
|
*) cmd="" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "$cmd"
|
||||||
|
}
|
||||||
|
|
||||||
|
open_link() {
|
||||||
|
cmd="$(get_opener)"
|
||||||
|
|
||||||
|
if [[ "$cmd" == "" ]]; then
|
||||||
|
error "Your platform is not supported for opening links."
|
||||||
|
red "Please open the following URL in your preferred browser:"
|
||||||
|
red " ${1}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
$cmd "$1"
|
||||||
|
|
||||||
|
if [[ $? -eq 1 ]]; then
|
||||||
|
error "Failed to open your browser."
|
||||||
|
red "Please open the following URL in your browser:"
|
||||||
|
red "${1}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
guard_operation() {
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
if [[ -z "$AUTO_CONFIRM" && -z "$LLM_AGENT_VAR_AUTO_CONFIRM" ]]; then
|
||||||
|
ans="$(confirm "${1:-Are you sure you want to continue?}")"
|
||||||
|
|
||||||
|
if [[ "$ans" == 0 ]]; then
|
||||||
|
error "Operation aborted!" 2>&1
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Here is an example of a patch block that can be applied to modify the file to request the user's name:
|
||||||
|
# --- a/hello.py
|
||||||
|
# +++ b/hello.py
|
||||||
|
# \@@ ... @@
|
||||||
|
# def hello():
|
||||||
|
# - print("Hello World")
|
||||||
|
# + name = input("What is your name? ")
|
||||||
|
# + print(f"Hello {name}")
|
||||||
|
patch_file() {
|
||||||
|
awk '
|
||||||
|
FNR == NR {
|
||||||
|
lines[FNR] = $0
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
patchLines[FNR] = $0
|
||||||
|
}
|
||||||
|
|
||||||
|
END {
|
||||||
|
totalPatchLines=length(patchLines)
|
||||||
|
totalLines = length(lines)
|
||||||
|
patchLineIndex = 1
|
||||||
|
|
||||||
|
mode = "none"
|
||||||
|
|
||||||
|
while (patchLineIndex <= totalPatchLines) {
|
||||||
|
line = patchLines[patchLineIndex]
|
||||||
|
|
||||||
|
if (line ~ /^--- / || line ~ /^\+\+\+ /) {
|
||||||
|
patchLineIndex++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line ~ /^@@ /) {
|
||||||
|
mode = "hunk"
|
||||||
|
hunkIndex++
|
||||||
|
patchLineIndex++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == "hunk") {
|
||||||
|
while (patchLineIndex <= totalPatchLines && line ~ /^[-+ ]|^\s*$/ && line !~ /^--- /) {
|
||||||
|
sanitizedLine = substr(line, 2)
|
||||||
|
|
||||||
|
if (line !~ /^\+/) {
|
||||||
|
hunkTotalOriginalLines[hunkIndex]++;
|
||||||
|
hunkOriginalLines[hunkIndex,hunkTotalOriginalLines[hunkIndex]] = sanitizedLine
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line !~ /^-/) {
|
||||||
|
hunkTotalUpdatedLines[hunkIndex]++;
|
||||||
|
hunkUpdatedLines[hunkIndex,hunkTotalUpdatedLines[hunkIndex]] = sanitizedLine
|
||||||
|
}
|
||||||
|
|
||||||
|
patchLineIndex++
|
||||||
|
line = patchLines[patchLineIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
mode = "none"
|
||||||
|
} else {
|
||||||
|
patchLineIndex++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hunkIndex == 0) {
|
||||||
|
print "error: no patch" > "/dev/stderr"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
totalHunks = hunkIndex
|
||||||
|
hunkIndex = 1
|
||||||
|
|
||||||
|
for (lineIndex = 1; lineIndex <= totalLines; lineIndex++) {
|
||||||
|
line = lines[lineIndex]
|
||||||
|
nextLineIndex = 0
|
||||||
|
|
||||||
|
if (hunkIndex <= totalHunks && line == hunkOriginalLines[hunkIndex,1]) {
|
||||||
|
nextLineIndex = lineIndex + 1
|
||||||
|
|
||||||
|
for (i = 2; i <= hunkTotalOriginalLines[hunkIndex]; i++) {
|
||||||
|
if (lines[nextLineIndex] != hunkOriginalLines[hunkIndex,i]) {
|
||||||
|
nextLineIndex = 0
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
nextLineIndex++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextLineIndex > 0) {
|
||||||
|
for (i = 1; i <= hunkTotalUpdatedLines[hunkIndex]; i++) {
|
||||||
|
print hunkUpdatedLines[hunkIndex,i]
|
||||||
|
}
|
||||||
|
|
||||||
|
hunkIndex++
|
||||||
|
lineIndex = nextLineIndex - 1;
|
||||||
|
} else {
|
||||||
|
print line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hunkIndex != totalHunks + 1) {
|
||||||
|
print "error: unable to apply patch" > "/dev/stderr"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function inspectHunks() {
|
||||||
|
print "/* Begin inspecting hunks"
|
||||||
|
|
||||||
|
for (i = 1; i <= totalHunks; i++) {
|
||||||
|
print ">>>>>> Original"
|
||||||
|
|
||||||
|
for (j = 1; j <= hunkTotalOriginalLines[i]; j++) {
|
||||||
|
print hunkOriginalLines[i,j]
|
||||||
|
}
|
||||||
|
|
||||||
|
print "======"
|
||||||
|
|
||||||
|
for (j = 1; j <= hunkTotalUpdatedLines[i]; j++) {
|
||||||
|
print hunkUpdatedLines[i,j]
|
||||||
|
}
|
||||||
|
|
||||||
|
print "<<<<<< Updated"
|
||||||
|
}
|
||||||
|
|
||||||
|
print "End inspecting hunks */\n"
|
||||||
|
}' "$1" "$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
guard_path() {
|
||||||
|
if [[ "$#" -ne 2 ]]; then
|
||||||
|
echo "Usage: guard_path <path> <confirmation_prompt>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
path="$(_to_real_path "$1")"
|
||||||
|
confirmation_prompt="$2"
|
||||||
|
|
||||||
|
if [[ ! "$path" == "$(pwd)"* && -z "$AUTO_CONFIRM" && -z "$LLM_AGENT_VAR_AUTO_CONFIRM" ]]; then
|
||||||
|
ans="$(confirm "$confirmation_prompt")"
|
||||||
|
|
||||||
|
if [[ "$ans" == 0 ]]; then
|
||||||
|
error "Operation aborted!" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_to_real_path() {
|
||||||
|
path="$1"
|
||||||
|
|
||||||
|
if [[ $OS == "Windows_NT" ]]; then
|
||||||
|
path="$(cygpath -u "$path")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
awk -v path="$path" -v pwd="$PWD" '
|
||||||
|
BEGIN {
|
||||||
|
if (path !~ /^\//) {
|
||||||
|
path = pwd "/" path
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path ~ /\/\.{1,2}?$/) {
|
||||||
|
isDir = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
split(path, parts, "/")
|
||||||
|
newPartsLength = 0
|
||||||
|
|
||||||
|
for (i = 1; i <= length(parts); i++) {
|
||||||
|
part = parts[i]
|
||||||
|
if (part == "..") {
|
||||||
|
if (newPartsLength > 0) {
|
||||||
|
delete newParts[newPartsLength--]
|
||||||
|
}
|
||||||
|
} else if (part != "." && part != "") {
|
||||||
|
newParts[++newPartsLength] = part
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDir == 1 || newPartsLength == 0) {
|
||||||
|
newParts[++newPartsLength] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
printf "/"
|
||||||
|
|
||||||
|
for (i = 1; i <= newPartsLength; i++) {
|
||||||
|
newPart = newParts[i]
|
||||||
|
printf newPart
|
||||||
|
if (i < newPartsLength) {
|
||||||
|
printf "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
steps:
|
||||||
|
- .file `git diff` -- generate a git commit message
|
||||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
|||||||
|
Output code only without comments or explanations.
|
||||||
|
### INPUT:
|
||||||
|
async sleep in js
|
||||||
|
### OUTPUT:
|
||||||
|
```javascript
|
||||||
|
async function timeout(ms) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
As a professional Prompt Engineer, your role is to create effective and innovative prompts for interacting with AI models.
|
||||||
|
|
||||||
|
Your core skills include:
|
||||||
|
1. **CO-STAR Framework Application**: Utilize the CO-STAR framework to build efficient prompts, ensuring effective communication with large language models.
|
||||||
|
2. **Contextual Awareness**: Construct prompts that adapt to complex conversation contexts, ensuring relevant and coherent responses.
|
||||||
|
3. **Chain-of-Thought Prompting**: Create prompts that elicit AI models to demonstrate their reasoning process, enhancing the transparency and accuracy of answers.
|
||||||
|
4. **Zero-shot Learning**: Design prompts that enable AI models to perform specific tasks without requiring examples, reducing dependence on training data.
|
||||||
|
5. **Few-shot Learning**: Guide AI models to quickly learn and execute new tasks through a few examples.
|
||||||
|
|
||||||
|
Your output format should include:
|
||||||
|
- **Context**: Provide comprehensive background information for the task to ensure the AI understands the specific scenario and offers relevant feedback.
|
||||||
|
- **Objective**: Clearly define the task objective, guiding the AI to focus on achieving specific goals.
|
||||||
|
- **Style**: Specify writing styles according to requirements, such as imitating a particular person or industry expert.
|
||||||
|
- **Tone**: Set an appropriate emotional tone to ensure the AI's response aligns with the expected emotional context.
|
||||||
|
- **Audience**: Tailor AI responses for a specific audience, ensuring content appropriateness and ease of understanding.
|
||||||
|
- **Response**: Specify output formats for easy execution of downstream tasks, such as lists, JSON, or professional reports.
|
||||||
|
- **Workflow**: Instruct the AI on how to step-by-step complete tasks, clarifying inputs, outputs, and specific actions for each step.
|
||||||
|
- **Examples**: Show a case of input and output that fits the scenario.
|
||||||
|
|
||||||
|
Your workflow should be:
|
||||||
|
1. Extract key information from user requests to determine design objectives.
|
||||||
|
2. Based on user needs, create prompts that meet requirements, with each part being professional and detailed.
|
||||||
|
3. Must only output the newly generated and optimized prompts, without explanation, without wrapping it in markdown code block.
|
||||||
|
|
||||||
|
My first request is: __INPUT__
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
Create a concise, 3-6 word title.
|
||||||
|
|
||||||
|
**Notes**:
|
||||||
|
- Avoid quotation marks or emojis
|
||||||
|
- RESPOND ONLY WITH TITLE SLUG TEXT
|
||||||
|
|
||||||
|
**Examples**:
|
||||||
|
stock-market-trends
|
||||||
|
perfect-chocolate-chip-recipe
|
||||||
|
remote-work-productivity-tips
|
||||||
|
video-game-development-insights
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Provide a terse, single sentence description of the given shell command.
|
||||||
|
Describe each argument and option of the command.
|
||||||
|
Provide short responses in about 80 words.
|
||||||
|
APPLY MARKDOWN formatting when possible.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
enabled_tools: all
|
||||||
|
---
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
enabled_mcp_servers: github
|
||||||
|
---
|
||||||
|
You are expert GitHub assistant designed to assist users with GitHub related tasks. You can perform various
|
||||||
|
tasks related to GitHub, such as creating issues, searching for issues, and providing information about repositories.
|
||||||
|
You can also interact with other tools to enhance your capabilities.
|
||||||
|
|
||||||
|
When asked to perform an operation, always try to use the available tools first. When determining which tools are
|
||||||
|
available to complete a user request, you should look at substrings instead of full names to help you determine
|
||||||
|
relevance; Also be sure that for all MCP tools (always named 'mcp_list_*' or 'mcp_invoke_*'), you list the available
|
||||||
|
tools first before selecting one to use.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
enabled_mcp_servers: all
|
||||||
|
---
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
name: repo-analyzer
|
||||||
|
enabled_tools: fs_cat,fs_ls,fs_write,fs_patch,execute_command
|
||||||
|
---
|
||||||
|
**Context:** The task requires the AI to analyze the {{__cwd__}} repository and provide comprehensive insights into
|
||||||
|
various aspects such as directory structure, design patterns, coding conventions, libraries, architecture, module
|
||||||
|
organization, build/test commands, naming conventions, testing practices, commit & pull request guidelines, and
|
||||||
|
security/configuration settings. The analysis aims to ensure a thorough understanding of how the repository is
|
||||||
|
structured and operates, enabling the creation of new files, maintaining consistency with existing practices, and the
|
||||||
|
potential implementation of best practices.
|
||||||
|
|
||||||
|
Should the root directory contain a `LOKI.md` file, this was generated by Loki and should be used as a reference
|
||||||
|
point for all analysis, style questions, etc.
|
||||||
|
|
||||||
|
**Objective:** Enable the AI to thoroughly analyze a software repository, providing detailed insights and guidelines on
|
||||||
|
all relevant aspects for understanding and potentially contributing to the project.
|
||||||
|
|
||||||
|
**Style:** Technical analysis with clarity and precision, ensuring comprehensibility for developers and technical
|
||||||
|
professionals.
|
||||||
|
|
||||||
|
**Tone:** Informative and neutral, focusing on factual presentation and technical details.
|
||||||
|
|
||||||
|
**Audience:** Software developers, technical leads, and quality assurance professionals looking to understand or
|
||||||
|
contribute to the project.
|
||||||
|
|
||||||
|
**Response:** Provide a detailed report format that includes sections for directory structure, coding conventions,
|
||||||
|
libraries, architecture, module organization, development commands, naming conventions, testing practices, commit
|
||||||
|
guidelines, and security considerations.
|
||||||
|
|
||||||
|
**Workflow:**
|
||||||
|
1. Identify and analyze the directory structure to map the organization and purpose of each section. (Should always start with the current directory `.`; i.e. {{__cwd__}})
|
||||||
|
2. Assess design patterns and coding conventions used within the repository.
|
||||||
|
3. Investigate libraries and dependencies to understand the ecosystem being used.
|
||||||
|
4. Examine the architecture to outline system organization and component interaction.
|
||||||
|
5. Review module organization to determine how different parts of the system integrate and communicate.
|
||||||
|
6. Identify build, test, and run commands specific to the repository.
|
||||||
|
7. Analyze naming conventions employed across the project to ensure conformity and clarity.
|
||||||
|
8. Study testing practices, including test frameworks, directory organization, and test naming strategies.
|
||||||
|
9. Review commit and pull request guidelines, focusing on best practices, commit types, and formatting.
|
||||||
|
10. Evaluate security and configuration settings, identifying best practices for data protection and maintaining backward compatibility.
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
- Input: Repository URL where the analysis needs to be conducted.
|
||||||
|
- Output: A structured report detailing each aspect mentioned in the workflow, providing actionable insights and maintaining consistency with established practices.
|
||||||
|
|
||||||
|
**Agent-Specific instructions**: Keep changes minimal and consistent with existing structure. Follow this document’s
|
||||||
|
scope across the repo. Do not rename public binaries or alter license headers. If adding dependencies, justify in the
|
||||||
|
PR description.
|
||||||
|
|
||||||
|
|
||||||
|
You can write files to the filesystem upon request from the user.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Provide only {{__shell__}} commands for {{__os_distro__}} without any description.
|
||||||
|
Ensure the output is a valid {{__shell__}} command.
|
||||||
|
If there is a lack of details, provide most logical solution.
|
||||||
|
If multiple steps are required, try to combine them using '&&' (For PowerShell, use ';' instead).
|
||||||
|
Output only plain text without any markdown formatting or <think> or </think> tokens.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
enabled_mcp_servers: slack
|
||||||
|
temperature: 0.2
|
||||||
|
---
|
||||||
|
You are an expert Slack assistant designed to assist with Slack workspaces via the slack MCP server.
|
||||||
|
You can perform various tasks related to Slack, such as sending messages to channels, searching for messages, and
|
||||||
|
providing information about users. You can also interact with other tools to enhance your capabilities.
|
||||||
|
|
||||||
|
When sending messages to Slack channels, ensure that the messages are clear, concise, and relevant to the channel's
|
||||||
|
purpose. Use appropriate formatting and emojis to enhance the message's readability and engagement.
|
||||||
|
|
||||||
|
If ever a user references communicating with a specific person, you should always try to find that person's Slack
|
||||||
|
username and use that to send the message. If you cannot find that person's Slack username, you should ask the user to
|
||||||
|
provide it.
|
||||||
Binary file not shown.
@@ -0,0 +1,78 @@
|
|||||||
|
# Agent-specific configuration
|
||||||
|
# Location `<loki-config-dir>/agents/<agent-name>/config.yaml`
|
||||||
|
#
|
||||||
|
# Available Environment Variables:
|
||||||
|
# - <agent-name>_MODEL
|
||||||
|
# - <agent-name>_TEMPERATURE
|
||||||
|
# - <agent-name>_TOP_P
|
||||||
|
# - <agent-name>_GLOBAL_TOOLS (as a JSON string array)
|
||||||
|
# - <agent-name>_MCP_SERVERS (as a JSON string array)
|
||||||
|
# - <agent-name>_AGENT_SESSION
|
||||||
|
# - <agent-name>_VARIABLES (as JSON array of key-value pairs; e.g. '[{"name": "username", "value": "alex"}]')
|
||||||
|
|
||||||
|
model: openai:gpt-4o # Specify the LLM to use
|
||||||
|
temperature: null # Set default temperature parameter, range (0, 1)
|
||||||
|
top_p: null # Set default top-p parameter, with a range of (0, 1) or (0, 2) depending on the model
|
||||||
|
agent_session: null # Set a session to use when starting the agent. (e.g. temp, default); defaults to globally set agent_session
|
||||||
|
name: <agent-name> # Name of the agent, used in the UI and logs
|
||||||
|
description: <description> # Description of the agent, used in the UI
|
||||||
|
version: 1 # Version of the agent
|
||||||
|
# Todo System & Auto-Continuation
|
||||||
|
# These settings help smaller models handle multi-step tasks more reliably.
|
||||||
|
# See docs/TODO-SYSTEM.md for detailed documentation.
|
||||||
|
auto_continue: false # Enable automatic continuation when incomplete todos remain
|
||||||
|
max_auto_continues: 10 # Maximum number of automatic continuations before stopping
|
||||||
|
inject_todo_instructions: true # Inject the default todo tool usage instructions into the agent's system prompt
|
||||||
|
continuation_prompt: null # Custom prompt used when auto-continuing (optional; uses default if null)
|
||||||
|
mcp_servers: # Optional list of MCP servers that the agent utilizes
|
||||||
|
- github # Corresponds to the name of an MCP server in the `<loki-config-dir>/functions/mcp.json` file
|
||||||
|
global_tools: # Optional list of additional global tools to enable for the agent; i.e. not tools specific to the agent
|
||||||
|
- web_search
|
||||||
|
- fs
|
||||||
|
- python
|
||||||
|
dynamic_instructions: false # Whether to use dynamic instructions for the agent; if false, static instructions are used
|
||||||
|
instructions: | # Static instructions for the agent; ignored if dynamic instructions are used
|
||||||
|
You are a AI agent designed to demonstrate agent capabilities.
|
||||||
|
|
||||||
|
<tools>
|
||||||
|
{{__tools__}}
|
||||||
|
</tools>
|
||||||
|
|
||||||
|
<system>
|
||||||
|
os: {{__os__}}
|
||||||
|
os_family: {{__os_family__}}
|
||||||
|
arch: {{__arch__}}
|
||||||
|
shell: {{__shell__}}
|
||||||
|
locale: {{__locale__}}
|
||||||
|
now: {{__now__}}
|
||||||
|
cwd: {{__cwd__}}
|
||||||
|
</system>
|
||||||
|
|
||||||
|
<user>
|
||||||
|
username: {{username}}
|
||||||
|
</user>
|
||||||
|
variables: # Optional variables for the agent
|
||||||
|
# The variables defined above like {{__variable_name__}} are automatically available
|
||||||
|
- name: username
|
||||||
|
description: Your user name
|
||||||
|
default: null # A default value for this variable; if null, the variable must be provided when starting the agent
|
||||||
|
conversation_starters: # Optional conversation starters for the agent
|
||||||
|
- What is the meaning of life?
|
||||||
|
- Tell me a joke.
|
||||||
|
- What is the capital of France?
|
||||||
|
- How do I make a cake?
|
||||||
|
- What is the best way to learn programming?
|
||||||
|
- How do I improve my writing skills?
|
||||||
|
- What are some good books to read?
|
||||||
|
- How do I stay motivated?
|
||||||
|
- What is the best way to exercise?
|
||||||
|
- How do I manage my time effectively?
|
||||||
|
documents: # Optional documents to load for the agent
|
||||||
|
- git:/some/repo # Explicitly tell Loki to use the 'git' document loader using an absolute path
|
||||||
|
- pdf:some-pdf-file.pdf # Explicitly tell Loki to use the 'pdf' document loader using a relative path
|
||||||
|
- https://some-website.com/some-page
|
||||||
|
- some-file.pdf # File with relative path to the <loki-config-dir>/agents/<agent-name> directory; i.e. file in the same directory as this config file
|
||||||
|
- ~/some-file.txt # File in the user's home directory
|
||||||
|
- /absolute/path/to/some-file.md # File with absolute path
|
||||||
|
- /absolute/path/**/NAME.txt # Find all NAME.txt files in the specified directory and all its subdirectories
|
||||||
|
- /absolute/path/to/*/README.md # Find all README.md files in all immediate subdirectories of the specified directory (depth=1)
|
||||||
@@ -0,0 +1,367 @@
|
|||||||
|
# ---- LLM ----
|
||||||
|
model: openai:gpt-4o # Specify the LLM to use
|
||||||
|
temperature: null # Set default temperature parameter (0, 1)
|
||||||
|
top_p: null # Set default top-p parameter, with a range of (0, 1) or (0, 2) depending on the model
|
||||||
|
|
||||||
|
# ---- Behavior ----
|
||||||
|
stream: true # Controls whether to use the stream-style APIs when querying for completions from LLM clients.
|
||||||
|
save: true # Indicates whether to persist the conversation to messages.md for posterity
|
||||||
|
keybindings: emacs # Choose keybinding style (emacs, vi)
|
||||||
|
editor: null # Specifies the editor used to edit the input buffer or session. (e.g. vim, emacs, nano, hx). Defaults to $EDITOR
|
||||||
|
wrap: no # Controls text wrapping (no, auto, <max-width>)
|
||||||
|
wrap_code: false # Enables or disables the wrapping of code blocks
|
||||||
|
|
||||||
|
# ---- Prelude ----
|
||||||
|
repl_prelude: null # Set a default session or role for REPL mode to use (e.g. role:<name>, session:<name>, <session>:<role>)
|
||||||
|
cmd_prelude: null # Set a default session or role for CMD mode to use (e.g. role:<name>, session:<name>, <session>:<role>)
|
||||||
|
agent_session: null # Set a session to use when starting an agent (e.g. temp, default)
|
||||||
|
|
||||||
|
# ---- Appearance ----
|
||||||
|
highlight: true # Controls syntax highlighting
|
||||||
|
light_theme: false # Activates a light color theme when true. env: LOKI_LIGHT_THEME
|
||||||
|
|
||||||
|
# ---- Miscellaneous ----
|
||||||
|
user_agent: null # Set User-Agent HTTP header, use `auto` for loki/<current-version>
|
||||||
|
save_shell_history: true # Whether to save shell execution command to the history file
|
||||||
|
sync_models_url: > # URL to sync model changes from
|
||||||
|
https://raw.githubusercontent.com/Dark-Alex-17/loki/refs/heads/main/models.yaml
|
||||||
|
|
||||||
|
# ---- REPL Prompt ----
|
||||||
|
# Custom REPL left/right prompts; see the [REPL Prompt Documentation](./docs/REPL-PROMPT.md) for more information
|
||||||
|
left_prompt:
|
||||||
|
'{color.red}{model}){color.green}{?session {?agent {agent}>}{session}{?role /}}{!session {?agent {agent}>}}{role}{?rag @{rag}}{color.cyan}{?session )}{!session >}{color.reset} '
|
||||||
|
right_prompt:
|
||||||
|
'{color.purple}{?session {?consume_tokens {consume_tokens}({consume_percent}%)}{!consume_tokens {consume_tokens}}}{color.reset}'
|
||||||
|
|
||||||
|
# ---- Vault ----
|
||||||
|
# See the [Vault documentation](./docs/VAULT.md) for more information on the Loki vault
|
||||||
|
vault_password_file: null # Path to a file containing the password for the Loki vault (cannot be a secret template)
|
||||||
|
|
||||||
|
# ---- Function Calling ----
|
||||||
|
# See the [Tools documentation](./docs/function-calling/TOOLS.md) for more details
|
||||||
|
function_calling: true # Enables or disables function calling (Globally).
|
||||||
|
mapping_tools: # Alias for a tool or toolset
|
||||||
|
fs: 'fs_cat,fs_ls,fs_mkdir,fs_rm,fs_write,fs_read,fs_glob,fs_grep'
|
||||||
|
enabled_tools: null # Which tools to enable by default. (e.g. 'fs,web_search_loki')
|
||||||
|
visible_tools: # Which tools are visible to be compiled (and are thus able to be defined in 'enabled_tools')
|
||||||
|
# - demo_py.py
|
||||||
|
# - demo_sh.sh
|
||||||
|
- execute_command.sh
|
||||||
|
# - execute_py_code.py
|
||||||
|
# - execute_sql_code.sh
|
||||||
|
# - fetch_url_via_curl.sh
|
||||||
|
# - fetch_url_via_jina.sh
|
||||||
|
- fs_cat.sh
|
||||||
|
- fs_ls.sh
|
||||||
|
# - fs_read.sh
|
||||||
|
# - fs_glob.sh
|
||||||
|
# - fs_grep.sh
|
||||||
|
# - fs_mkdir.sh
|
||||||
|
# - fs_patch.sh
|
||||||
|
# - fs_write.sh
|
||||||
|
- get_current_time.sh
|
||||||
|
# - get_current_weather.py
|
||||||
|
- get_current_weather.sh
|
||||||
|
- query_jira_issues.sh
|
||||||
|
# - search_arxiv.sh
|
||||||
|
# - search_wikipedia.sh
|
||||||
|
# - search_wolframalpha.sh
|
||||||
|
# - send_mail.sh
|
||||||
|
# - send_twilio.sh
|
||||||
|
# - web_search_loki.sh
|
||||||
|
# - web_search_perplexity.sh
|
||||||
|
# - web_search_tavily.sh
|
||||||
|
|
||||||
|
# ---- MCP Servers ----
|
||||||
|
# See the [MCP Servers documentation](./docs/MCP-SERVERS.md) for more details
|
||||||
|
mcp_server_support: true # Enables or disables MCP servers (globally).
|
||||||
|
mapping_mcp_servers: # Alias for an MCP server or set of servers
|
||||||
|
git: github,gitmcp
|
||||||
|
enabled_mcp_servers: null # Which MCP servers to enable by default (e.g. 'github,slack')
|
||||||
|
|
||||||
|
# ---- Session ----
|
||||||
|
# See the [Session documentation](./docs/SESSIONS.md) for more information
|
||||||
|
save_session: null # Controls the persistence of the session. If true, auto save; if false, don't auto-save save; if null, ask the user what to do
|
||||||
|
compression_threshold: 4000 # Compress the session when the token count reaches or exceeds this threshold
|
||||||
|
summarization_prompt: > # The text prompt used for creating a concise summary of session message
|
||||||
|
'Summarize the discussion briefly in 200 words or less to use as a prompt for future context.'
|
||||||
|
summary_context_prompt: > # The text prompt used for including the summary of the entire session as context to the model
|
||||||
|
'This is a summary of the chat history as a recap: '
|
||||||
|
|
||||||
|
# ---- RAG ----
|
||||||
|
# See the [RAG Docs](./docs/RAG.md) for more details.
|
||||||
|
rag_embedding_model: null # Specifies the embedding model used for context retrieval
|
||||||
|
rag_reranker_model: null # Specifies the reranker model used for sorting retrieved documents; Loki uses Reciprocal Rank Fusion by default
|
||||||
|
rag_top_k: 5 # Specifies the number of documents to retrieve for answering queries
|
||||||
|
rag_chunk_size: null # Defines the size of chunks for document processing in characters
|
||||||
|
rag_chunk_overlap: null # Defines the overlap between chunks
|
||||||
|
# Defines the query structure using variables like __CONTEXT__, __SOURCES__, and __INPUT__ to tailor searches to specific needs
|
||||||
|
rag_template: |
|
||||||
|
Answer the query based on the context while respecting the rules. (user query, some textual context and rules, all inside xml tags)
|
||||||
|
|
||||||
|
<context>
|
||||||
|
__CONTEXT__
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<sources>
|
||||||
|
__SOURCES__
|
||||||
|
</sources>
|
||||||
|
|
||||||
|
<rules>
|
||||||
|
- If you don't know, just say so.
|
||||||
|
- If you are not sure, ask for clarification.
|
||||||
|
- Answer in the same language as the user query.
|
||||||
|
- If the context appears unreadable or of poor quality, tell the user then answer as best as you can.
|
||||||
|
- If the answer is not in the context but you think you know the answer, explain that to the user then answer with your own knowledge.
|
||||||
|
- Answer directly and without using xml tags.
|
||||||
|
- When using information from the context, cite the relevant source from the <sources> section.
|
||||||
|
</rules>
|
||||||
|
|
||||||
|
<user_query>
|
||||||
|
__INPUT__
|
||||||
|
</user_query>
|
||||||
|
# Define document loaders to control how RAG and `.file`/`--file` load files of specific formats.
|
||||||
|
document_loaders:
|
||||||
|
# You can add custom loaders using the following syntax:
|
||||||
|
# <file-extension>: <command-to-load-the-file>
|
||||||
|
# Note: Use `$1` for input file and `$2` for output file. If `$2` is omitted, use stdout as output.
|
||||||
|
pdf: 'pdftotext $1 -' # Use pdftotext to convert a PDF file to text
|
||||||
|
# (see https://poppler.freedesktop.org for details on how to install pdftotext)
|
||||||
|
docx: 'pandoc --to plain $1' # Use pandoc to convert a .docx file to text
|
||||||
|
# (see https://pandoc.org for details on how to install pandoc)
|
||||||
|
jina: 'curl -fsSL https://r.jina.ai/$1 -H "Authorization: Bearer {{JINA_API_KEY}}' # Use Jina to translate a website into text;
|
||||||
|
# Requires a Jina API key to be added to the Loki vault
|
||||||
|
git: > # Use yek to load a git repository into the knowledgebase (https://github.com/bodo-run/yek)
|
||||||
|
sh -c "yek $1 --json | jq 'map({ path: .filename, contents: .content })'"
|
||||||
|
|
||||||
|
# ---- Clients ----
|
||||||
|
# See the [Clients documentation](./docs/clients/CLIENTS.md) for more details
|
||||||
|
clients:
|
||||||
|
# All clients have the following configuration:
|
||||||
|
# - type: xxxx
|
||||||
|
# name: xxxx # Only use it to distinguish clients with the same client type. Optional
|
||||||
|
# models:
|
||||||
|
# - name: xxxx # Chat model
|
||||||
|
# max_input_tokens: 100000
|
||||||
|
# supports_vision: true
|
||||||
|
# supports_function_calling: true
|
||||||
|
# - name: xxxx # Embedding model
|
||||||
|
# type: embedding
|
||||||
|
# default_chunk_size: 1500
|
||||||
|
# max_batch_size: 100
|
||||||
|
# - name: xxxx # Reranker model
|
||||||
|
# type: reranker
|
||||||
|
# patch: # Patch API calls
|
||||||
|
# chat_completions: # API type; Possible values: chat_completions, embeddings, and rerank
|
||||||
|
# <regex>: # The regex to match model names, e.g. '.*' 'gpt-4o' 'gpt-4o|gpt-4-.*'
|
||||||
|
# url: '' # Patch request URL
|
||||||
|
# body: # Patch request body
|
||||||
|
# <json>
|
||||||
|
# headers: # Patch request headers
|
||||||
|
# <key>: <value>
|
||||||
|
# extra:
|
||||||
|
# proxy: socks5://127.0.0.1:1080 # Set proxy
|
||||||
|
# connect_timeout: 10 # Set timeout in seconds for connect to api
|
||||||
|
|
||||||
|
# See https://platform.openai.com/docs/quickstart
|
||||||
|
- type: openai
|
||||||
|
api_base: https://api.openai.com/v1 # Optional
|
||||||
|
api_key: '{{OPENAI_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
organization_id: org-xxx # Optional
|
||||||
|
|
||||||
|
# For any platform compatible with OpenAI's API
|
||||||
|
- type: openai-compatible
|
||||||
|
name: ollama
|
||||||
|
api_base: http://localhost:11434/v1
|
||||||
|
api_key: '{{OLLAMA_API_KEY}}' # Optional; You can either hard-code or inject secrets from the Loki vault
|
||||||
|
models:
|
||||||
|
- name: deepseek-r1
|
||||||
|
max_input_tokens: 131072
|
||||||
|
- name: llama3.1
|
||||||
|
max_input_tokens: 128000
|
||||||
|
supports_function_calling: true
|
||||||
|
- name: llama3.2-vision
|
||||||
|
max_input_tokens: 131072
|
||||||
|
supports_vision: true
|
||||||
|
- name: nomic-embed-text
|
||||||
|
type: embedding
|
||||||
|
default_chunk_size: 1000
|
||||||
|
max_batch_size: 50
|
||||||
|
|
||||||
|
# See https://ai.google.dev/docs
|
||||||
|
- type: gemini
|
||||||
|
api_base: https://generativelanguage.googleapis.com/v1beta
|
||||||
|
api_key: '{{GEMINI_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
patch:
|
||||||
|
chat_completions:
|
||||||
|
'.*':
|
||||||
|
body:
|
||||||
|
safetySettings:
|
||||||
|
- category: HARM_CATEGORY_HARASSMENT
|
||||||
|
threshold: BLOCK_NONE
|
||||||
|
- category: HARM_CATEGORY_HATE_SPEECH
|
||||||
|
threshold: BLOCK_NONE
|
||||||
|
- category: HARM_CATEGORY_SEXUALLY_EXPLICIT
|
||||||
|
threshold: BLOCK_NONE
|
||||||
|
- category: HARM_CATEGORY_DANGEROUS_CONTENT
|
||||||
|
threshold: BLOCK_NONE
|
||||||
|
|
||||||
|
# See https://docs.anthropic.com/claude/reference/getting-started-with-the-api
|
||||||
|
- type: claude
|
||||||
|
api_base: https://api.anthropic.com/v1 # Optional
|
||||||
|
api_key: '{{ANTHROPIC_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://docs.mistral.ai/
|
||||||
|
- type: openai-compatible
|
||||||
|
name: mistral
|
||||||
|
api_base: https://api.mistral.ai/v1
|
||||||
|
api_key: '{{MISTRAL_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://docs.x.ai/docs
|
||||||
|
- type: openai-compatible
|
||||||
|
name: xai
|
||||||
|
api_base: https://api.x.ai/v1
|
||||||
|
api_key: '{{XAI_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://docs.ai21.com/docs/overview
|
||||||
|
- type: openai-compatible
|
||||||
|
name: ai12
|
||||||
|
api_base: https://api.ai21.com/studio/v1
|
||||||
|
api_key: '{{AI21_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://docs.cohere.com/docs/the-cohere-platform
|
||||||
|
- type: cohere
|
||||||
|
api_base: https://api.cohere.ai/v2 # Optional
|
||||||
|
api_key: '{{COHERE_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://docs.perplexity.ai/getting-started/overview
|
||||||
|
- type: openai-compatible
|
||||||
|
name: perplexity
|
||||||
|
api_base: https://api.perplexity.ai
|
||||||
|
api_key: '{{PERPLEXITY_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://console.groq.com/docs/quickstart
|
||||||
|
- type: openai-compatible
|
||||||
|
name: groq
|
||||||
|
api_base: https://api.groq.com/openai/v1
|
||||||
|
api_key: '{{GROQ_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://learn.microsoft.com/en-us/azure/ai-services/openai/chatgpt-quickstart
|
||||||
|
- type: azure-openai
|
||||||
|
api_base: https://{RESOURCE}.openai.azure.com
|
||||||
|
api_key: '{{AZURE_OPENAI_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
models:
|
||||||
|
- name: gpt-4o # Model deployment name
|
||||||
|
max_input_tokens: 128000
|
||||||
|
supports_vision: true
|
||||||
|
supports_function_calling: true
|
||||||
|
|
||||||
|
# See https://cloud.google.com/vertex-ai
|
||||||
|
- type: vertexai
|
||||||
|
project_id: xxx
|
||||||
|
location: xxx
|
||||||
|
# Specifies an application default credentials (adc) file
|
||||||
|
# Run `gcloud auth application-default login` to initialize the ADC file
|
||||||
|
# see https://cloud.google.com/docs/authentication/external/set-up-adc for more information
|
||||||
|
adc_file: <gcloud-config-dir>/application_default_credentials.json # Optional
|
||||||
|
patch:
|
||||||
|
chat_completions:
|
||||||
|
'gemini-.*':
|
||||||
|
body:
|
||||||
|
safetySettings:
|
||||||
|
- category: HARM_CATEGORY_HARASSMENT
|
||||||
|
threshold: BLOCK_ONLY_HIGH
|
||||||
|
- category: HARM_CATEGORY_HATE_SPEECH
|
||||||
|
threshold: BLOCK_ONLY_HIGH
|
||||||
|
- category: HARM_CATEGORY_SEXUALLY_EXPLICIT
|
||||||
|
threshold: BLOCK_ONLY_HIGH
|
||||||
|
- category: HARM_CATEGORY_DANGEROUS_CONTENT
|
||||||
|
threshold: BLOCK_ONLY_HIGH
|
||||||
|
|
||||||
|
# See https://docs.aws.amazon.com/bedrock/latest/userguide/
|
||||||
|
- type: bedrock
|
||||||
|
access_key_id: '{{AWS_ACCESS_KEY_ID}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
secret_access_key: '{{AWS_SECRET_ACCESS_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
region: xxx
|
||||||
|
session_token: xxx # Optional, only needed for temporary credentials
|
||||||
|
|
||||||
|
# See https://developers.cloudflare.com/workers-ai/
|
||||||
|
- type: openai-compatible
|
||||||
|
name: cloudflare
|
||||||
|
api_base: https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/ai/v1
|
||||||
|
api_key: '{{CLOUDFLARE_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html
|
||||||
|
- type: openai-compatible
|
||||||
|
name: ernie
|
||||||
|
api_base: https://qianfan.baidubce.com/v2
|
||||||
|
api_key: '{{BAIDU_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://dashscope.aliyun.com/
|
||||||
|
- type: openai-compatible
|
||||||
|
name: qianwen
|
||||||
|
api_base: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||||
|
api_key: '{{ALIYUN_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://cloud.tencent.com/product/hunyuan
|
||||||
|
- type: openai-compatible
|
||||||
|
name: hunyuan
|
||||||
|
api_base: https://api.hunyuan.cloud.tencent.com/v1
|
||||||
|
api_key: '{{TENCENT_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://platform.moonshot.cn/docs/intro
|
||||||
|
- type: openai-compatible
|
||||||
|
name: moonshot
|
||||||
|
api_base: https://api.moonshot.cn/v1
|
||||||
|
api_key: '{{MOONSHOT_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://platform.deepseek.com/api-docs/
|
||||||
|
- type: openai-compatible
|
||||||
|
name: deepseek
|
||||||
|
api_base: https://api.deepseek.com
|
||||||
|
api_key: '{{DEEPSEEK_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://open.bigmodel.cn/dev/howuse/introduction
|
||||||
|
- type: openai-compatible
|
||||||
|
name: zhipuai
|
||||||
|
api_base: https://open.bigmodel.cn/api/paas/v4
|
||||||
|
api_key: '{{ZHIPUAI_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://platform.minimaxi.com/document/Fast%20access
|
||||||
|
- type: openai-compatible
|
||||||
|
name: minimax
|
||||||
|
api_base: https://api.minimax.chat/v1
|
||||||
|
api_key: '{{MINIMAX_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://openrouter.ai/docs#quick-start
|
||||||
|
- type: openai-compatible
|
||||||
|
name: openrouter
|
||||||
|
api_base: https://openrouter.ai/api/v1
|
||||||
|
api_key: '{{OPENROUTER_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://github.com/marketplace/models
|
||||||
|
- type: openai-compatible
|
||||||
|
name: github
|
||||||
|
api_base: https://models.inference.ai.azure.com
|
||||||
|
api_key: '{{GITHUB_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://deepinfra.com/docs
|
||||||
|
- type: openai-compatible
|
||||||
|
name: deepinfra
|
||||||
|
api_base: https://api.deepinfra.com/v1/openai
|
||||||
|
api_key: '{{DEEPINFRA_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
|
||||||
|
# ----- RAG dedicated -----
|
||||||
|
|
||||||
|
# See https://jina.ai
|
||||||
|
- type: openai-compatible
|
||||||
|
name: jina
|
||||||
|
api_base: https://api.jina.ai/v1
|
||||||
|
api_key: '{{JINA_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
|
|
||||||
|
# See https://docs.voyageai.com/docs/introduction
|
||||||
|
- type: openai-compatible
|
||||||
|
name: voyageai
|
||||||
|
api_base: https://api.voyageai.com/v1
|
||||||
|
api_key: '{{VOYAGEAI_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
variables: # A list of positional variables that the macro uses
|
||||||
|
- name: positional_1 # The name of the positional variable.
|
||||||
|
default: null # Since no default value is provided, this argument is required; 'null' by default
|
||||||
|
rest: false # Do not collect all remaining arguments into this variable; 'false' by default
|
||||||
|
- name: positional_2_with_default_value
|
||||||
|
default: '2' # A default value for the positional argument if no value is provided
|
||||||
|
rest: false
|
||||||
|
- name: collect_remaining_args
|
||||||
|
rest: true # Collect all remaining arguments into this variable
|
||||||
|
# Since no 'default' is defined, at least one additional argument is required
|
||||||
|
steps: # The sequence of REPL commands to execute
|
||||||
|
- .info
|
||||||
|
- .agent {{positional_1}}
|
||||||
|
- What is 2 + {{positional_2_with_default_value}}?
|
||||||
|
- '{{collect_remaining_args}}'
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
# Everything in this section is optional
|
||||||
|
name: <role-name> # The name of the role
|
||||||
|
model: openai:gpt-4o # The model to use for this role
|
||||||
|
temperature: 0.2 # The temperature to use for this role when querying the model
|
||||||
|
top_p: 0 # The top_p to use for this role when querying the model
|
||||||
|
enabled_tools: fs_ls,fs_cat # A comma-separated list of tools to enable for this role
|
||||||
|
enabled_mcp_servers: github,gitmcp # A comma-separated list of MCP servers to enable for this role
|
||||||
|
prompt: null # A custom prompt to use for this role that will immediately query
|
||||||
|
# the model for output instead of using the instructions below
|
||||||
|
---
|
||||||
|
You are an expert at doing things. This is where you write the instructions for the role.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Documentation: https://docs.brew.sh/Formula-Cookbook
|
||||||
|
# https://rubydoc.brew.sh/Formula
|
||||||
|
class Loki < Formula
|
||||||
|
desc "All-in-one, batteries included LLM CLI tool"
|
||||||
|
homepage "https://github.com/Dark-Alex-17/loki"
|
||||||
|
if OS.mac? and Hardware::CPU.arm?
|
||||||
|
url "https://github.com/Dark-Alex-17/loki/releases/download/v$version/loki-aarch64-apple-darwin.tar.gz"
|
||||||
|
sha256 "$hash_mac_arm"
|
||||||
|
elsif OS.mac? and Hardware::CPU.intel?
|
||||||
|
url "https://github.com/Dark-Alex-17/loki/releases/download/v$version/loki-x86_64-apple-darwin.tar.gz"
|
||||||
|
sha256 "$hash_mac"
|
||||||
|
else
|
||||||
|
url "https://github.com/Dark-Alex-17/loki/releases/download/v$version/loki-x86_64-unknown-linux-musl.tar.gz"
|
||||||
|
sha256 "$hash_linux"
|
||||||
|
end
|
||||||
|
version "$version"
|
||||||
|
license "MIT"
|
||||||
|
|
||||||
|
def install
|
||||||
|
bin.install "loki"
|
||||||
|
ohai "You're done! Get started with \"loki --help\""
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import hashlib
|
||||||
|
import sys
|
||||||
|
from string import Template
|
||||||
|
|
||||||
|
args = sys.argv
|
||||||
|
version = args[1]
|
||||||
|
template_file_path = args[2]
|
||||||
|
generated_file_path = args[3]
|
||||||
|
|
||||||
|
# Deployment files
|
||||||
|
hash_mac = args[4].strip()
|
||||||
|
hash_mac_arm = args[5].strip()
|
||||||
|
hash_linux = args[6].strip()
|
||||||
|
|
||||||
|
print("Generating formula")
|
||||||
|
print(" VERSION: %s" % version)
|
||||||
|
print(" TEMPLATE PATH: %s" % template_file_path)
|
||||||
|
print(" SAVING AT: %s" % generated_file_path)
|
||||||
|
print(" MAC HASH: %s" % hash_mac)
|
||||||
|
print(" MAC ARM HASH: %s" % hash_mac_arm)
|
||||||
|
print(" LINUX HASH: %s" % hash_linux)
|
||||||
|
|
||||||
|
with open(template_file_path, "r") as template_file:
|
||||||
|
template = Template(template_file.read())
|
||||||
|
substitute = template.safe_substitute(version=version, hash_mac=hash_mac, hash_mac_arm=hash_mac_arm, hash_linux=hash_linux)
|
||||||
|
print("\n================== Generated package file ==================\n")
|
||||||
|
print(substitute)
|
||||||
|
print("\n============================================================\n")
|
||||||
|
|
||||||
|
with open(generated_file_path, "w") as generated_file:
|
||||||
|
generated_file.write(substitute)
|
||||||
+483
@@ -0,0 +1,483 @@
|
|||||||
|
# Agents
|
||||||
|
|
||||||
|
Agents in Loki follow the same style as OpenAI's GPTs. They consist of 3 parts:
|
||||||
|
|
||||||
|
* [Role](./ROLES.md) - Tell the LLM how to behave
|
||||||
|
* [RAG](./RAG.md) - Pre-built knowledge bases specifically for the agent
|
||||||
|
* [Function Calling](./function-calling/TOOLS.md#tools) ([#2](./function-calling/MCP-SERVERS.md)) - Extends the functionality of the LLM through custom functions it can call
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Agent configuration files are stored in the `agents` subdirectory of your Loki configuration directory. The location of
|
||||||
|
this directory varies between systems so you can use the following command to locate yours:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
loki --info | grep 'agents_dir' | awk '{print $2}'
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're looking for more example agents, refer to the [built-in agents](../assets/agents).
|
||||||
|
|
||||||
|
## Quick Links
|
||||||
|
<!--toc:start-->
|
||||||
|
- [Directory Structure](#directory-structure)
|
||||||
|
- [Metadata](#1-metadata)
|
||||||
|
- [2. Define the Instructions](#2-define-the-instructions)
|
||||||
|
- [Static Instructions](#static-instructions)
|
||||||
|
- [Special Variables](#special-variables)
|
||||||
|
- [User-Defined Variables](#user-defined-variables)
|
||||||
|
- [Dynamic Instructions](#dynamic-instructions)
|
||||||
|
- [Variables](#variables)
|
||||||
|
- [3. Initializing RAG](#3-initializing-rag)
|
||||||
|
- [4. Building Tools for Agents](#4-building-tools-for-agents)
|
||||||
|
- [Limitations](#limitations)
|
||||||
|
- [.env File Support](#env-file-support)
|
||||||
|
- [Python-Based Agent Tools](#python-based-agent-tools)
|
||||||
|
- [Bash-Based Agent Tools](#bash-based-agent-tools)
|
||||||
|
- [5. Conversation Starters](#5-conversation-starters)
|
||||||
|
- [6. Todo System & Auto-Continuation](#6-todo-system--auto-continuation)
|
||||||
|
- [Built-In Agents](#built-in-agents)
|
||||||
|
<!--toc:end-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
Agent configurations often have the following directory structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
<loki-config-dir>/agents
|
||||||
|
└── my-agent
|
||||||
|
├── config.yaml
|
||||||
|
├── tools.sh
|
||||||
|
or
|
||||||
|
├── tools.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This means that agent configurations often are only two files: the agent configuration file (`config.yaml`), and the
|
||||||
|
tool definitions (`agents/my-agent/tools.sh` or `tools.py`).
|
||||||
|
|
||||||
|
To see a full example configuration file, refer to the [example agent config file](../config.agent.example.yaml).
|
||||||
|
|
||||||
|
The best way to understand how an agent is built is to go step by step in the following manner:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Metadata
|
||||||
|
Agent configurations have the following settings available to customize each agent:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Model Configuration
|
||||||
|
model: openai:gpt-4o # Specify the LLM to use
|
||||||
|
temperature: null # Set default temperature parameter, range (0, 1)
|
||||||
|
top_p: null # Set default top-p parameter, with a range of (0, 1) or (0, 2), depending on the model
|
||||||
|
# Agent Metadata Configuration
|
||||||
|
agent_session: null # Set a session to use when starting the agent. (e.g. temp, default); defaults to globally set agent_session
|
||||||
|
# Agent Configuration
|
||||||
|
name: <agent-name> # Name of the agent, used in the UI and logs
|
||||||
|
description: <description> # Description of the agent, used in the UI
|
||||||
|
version: 1 # Version of the agent
|
||||||
|
# Function Calling Configuration
|
||||||
|
mcp_servers: # Optional list of MCP servers that the agent utilizes
|
||||||
|
- github # Corresponds to the name of an MCP server in the `<loki-config-dir>/functions/mcp.json` file
|
||||||
|
global_tools: # Optional list of additional global tools to enable for the agent; i.e. not tools specific to the agent
|
||||||
|
- web_search
|
||||||
|
- fs
|
||||||
|
- python
|
||||||
|
# Todo System & Auto-Continuation (see "Todo System & Auto-Continuation" section below)
|
||||||
|
auto_continue: false # Enable automatic continuation when incomplete todos remain
|
||||||
|
max_auto_continues: 10 # Maximum continuation attempts before stopping
|
||||||
|
inject_todo_instructions: true # Inject todo tool instructions into system prompt
|
||||||
|
continuation_prompt: null # Custom prompt for continuations (optional)
|
||||||
|
```
|
||||||
|
|
||||||
|
As mentioned previously: Agents utilize function calling to extend a model's capabilities. However, agents operate in
|
||||||
|
isolated environment, so in order for an agent to use a tool or MCP server that you have defined globally, you must
|
||||||
|
explicitly state which tools and/or MCP servers the agent uses. Otherwise, it is assumed that the agent doesn't use any
|
||||||
|
tools outside its own custom defined tools.
|
||||||
|
|
||||||
|
And if you don't define a `agents/my-agent/tools.sh` or `agents/my-agent/tools.py`, then the agent is really just a
|
||||||
|
`role`.
|
||||||
|
|
||||||
|
You'll notice there's no settings for agent-specific tooling. This is because they are handled separately and
|
||||||
|
automatically. See the [Building Tools for Agents](#4-building-tools-for-agents) section below for more information.
|
||||||
|
|
||||||
|
To see a full example configuration file, refer to the [example agent config file](../config.agent.example.yaml).
|
||||||
|
|
||||||
|
## 2. Define the Instructions
|
||||||
|
At their heart, agents function similarly to roles in that they tell the model how to behave. Agent configuration files
|
||||||
|
have the following settings for the instruction definitions:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dynamic_instructions: # Whether to use dynamically generated instructions for the agent; if false, static instructions are used. False by default.
|
||||||
|
instructions: # Static instructions for the LLM; These are ignored if dynamic instructions are used
|
||||||
|
variables: # An array of optional variables that the agent expects and uses
|
||||||
|
```
|
||||||
|
|
||||||
|
### Static Instructions
|
||||||
|
By default, Loki agents use statically defined instructions. Think of them as being identical to the instructions for a
|
||||||
|
[role](./ROLES.md#instructions), because they virtually are.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```yaml
|
||||||
|
instructions: |
|
||||||
|
You are an AI agent designed to demonstrate agentic capabilities
|
||||||
|
```
|
||||||
|
|
||||||
|
Just like roles, agents support variable interpolation at runtime. There's two types of variables that can be
|
||||||
|
interpolated into the instructions at runtime: special variables (like roles have), and user-defined variables. Just
|
||||||
|
like roles, variables are interpolated into your instructions anywhere Loki sees the `{{variable}}` syntax.
|
||||||
|
|
||||||
|
#### Special Variables
|
||||||
|
The following special variables are provided by Loki at runtime and can be injected into your agent's instructions:
|
||||||
|
|
||||||
|
| Name | Description | Example |
|
||||||
|
|-----------------|---------------------------------------------------------------------|----------------------------|
|
||||||
|
| `__os__` | Operating system name | `linux` |
|
||||||
|
| `__os_family__` | Operating system family | `unix` |
|
||||||
|
| `__arch__` | System architecture | `x86_64` |
|
||||||
|
| `__shell__` | The current user's default shell | `bash` |
|
||||||
|
| `__locale__` | The current user's preferred language and region settings | `en-US` |
|
||||||
|
| `__now__` | Current timestamp in ISO 8601 format | `2025-11-07T10:15:44.268Z` |
|
||||||
|
| `__cwd__` | The current working directory | `/tmp` |
|
||||||
|
| `__tools__` | A list of the enabled tools (global + mcp servers + agent-specific) | |
|
||||||
|
|
||||||
|
#### User-Defined Variables
|
||||||
|
Agents also support user-defined variables that can be interpolated into the instructions, and are made available to any
|
||||||
|
agent-specific tools you define (see [Building Tools for Agents](#4-building-tools-for-agents) for more details on how to
|
||||||
|
create agent-specific tooling).
|
||||||
|
|
||||||
|
The `variables` setting in an agent's config has the following fields:
|
||||||
|
|
||||||
|
| Field | Required | Description |
|
||||||
|
|---------------|----------|----------------------------------------------------------------------------------------------------|
|
||||||
|
| `name` | * | The name of the variable |
|
||||||
|
| `description` | * | The description of the field |
|
||||||
|
| `default` | | A default value for the field. If left undefined, the user will be prompted for a value at runtime |
|
||||||
|
|
||||||
|
These variables can be referenced in both the agent's instructions, and in the tool definitions via `LLM_AGENT_VAR_<name>`.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```yaml
|
||||||
|
instructions: |
|
||||||
|
You are an agent who answers questions about a user's system.
|
||||||
|
|
||||||
|
<tools>
|
||||||
|
{{__tools__}}
|
||||||
|
</tools>
|
||||||
|
|
||||||
|
<system>
|
||||||
|
os: {{__os__}}
|
||||||
|
os_family: {{__os_family__}}
|
||||||
|
arch: {{__arch__}}
|
||||||
|
shell: {{__shell__}}
|
||||||
|
locale: {{__locale__}}
|
||||||
|
now: {{__now__}}
|
||||||
|
cwd: {{__cwd__}}
|
||||||
|
</system>
|
||||||
|
|
||||||
|
<user>
|
||||||
|
username: {{username}}
|
||||||
|
</user>
|
||||||
|
variables:
|
||||||
|
- name: username # Accessible from the tool definitions via the `LLM_AGENT_VAR_USERNAME` environment variable
|
||||||
|
description: Your user name
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dynamic Instructions
|
||||||
|
Sometimes you may find it useful to dynamically generate instructions on startup. Whether that be via a call to Loki
|
||||||
|
itself to generate them, or by some other means. Loki supports this type of behavior using a special function defined
|
||||||
|
in your `agents/my-agent/tools.py` or `agents/my-agent/tools.sh`.
|
||||||
|
|
||||||
|
**Example: Instructions for a JSON-reader agent that specializes on each JSON input it receives**
|
||||||
|
`agents/json-reader/tools.py`:
|
||||||
|
```python
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from genson import SchemaBuilder
|
||||||
|
|
||||||
|
def _instructions():
|
||||||
|
"""Generates instructions for the agent dynamically"""
|
||||||
|
value = input("Enter a JSON file path OR paste raw JSON: ").strip()
|
||||||
|
if not value:
|
||||||
|
raise SystemExit("A file path or JSON string is required.")
|
||||||
|
|
||||||
|
p = Path(value)
|
||||||
|
if p.exists() and p.is_file():
|
||||||
|
json_file_path = str(p.resolve())
|
||||||
|
json_text = p.read_text(encoding="utf-8")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
json.loads(value)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
raise SystemExit(f"Input is neither a file nor valid JSON.\n{e}")
|
||||||
|
json_file_path = "<provided-inline-json>"
|
||||||
|
json_text = value
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(json_text)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
raise SystemExit(f"Provided content is not valid JSON.\n{e}")
|
||||||
|
|
||||||
|
builder = SchemaBuilder()
|
||||||
|
builder.add_object(data)
|
||||||
|
json_schema = builder.to_schema()
|
||||||
|
return f"""
|
||||||
|
You are an AI agent that can view and filter JSON data with jq.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
json_file_path: {json_file_path}
|
||||||
|
json_schema: {json.dumps(json_schema, indent=2)}
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
`agents/json-reader/tools.sh`:
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# @meta require-tools jq,genson
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
|
||||||
|
# @cmd Generates instructions for the agent dynamically
|
||||||
|
_instructions() {
|
||||||
|
read -r -p "Enter a JSON file path OR paste raw JSON: " value
|
||||||
|
|
||||||
|
if [[ -z "${value}" ]]; then
|
||||||
|
echo "A file path or JSON string is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
json_file_path=""
|
||||||
|
inline_temp=""
|
||||||
|
cleanup() {
|
||||||
|
[[ -n "${inline_temp:-}" && -f "${inline_temp}" ]] && rm -f "${inline_temp}"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
if [[ -f "${value}" ]]; then
|
||||||
|
json_file_path="$(realpath "${value}")"
|
||||||
|
if ! jq empty "${json_file_path}" >/dev/null 2>&1; then
|
||||||
|
echo "Error: File does not contain valid JSON: ${json_file_path}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
inline_temp="$(mktemp)"
|
||||||
|
printf "%s" "${value}" > "${inline_temp}"
|
||||||
|
if ! jq empty "${inline_temp}" >/dev/null 2>&1; then
|
||||||
|
echo "Error: Input is neither a file nor valid JSON." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
json_file_path="<provided-inline-json>"
|
||||||
|
fi
|
||||||
|
|
||||||
|
source_file="${json_file_path}"
|
||||||
|
if [[ "${json_file_path}" == "<provided-inline-json>" ]]; then
|
||||||
|
source_file="${inline_temp}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
json_schema="$(genson < "${source_file}" | jq -c '.')"
|
||||||
|
cat <<EOF >> "$LLM_OUTPUT"
|
||||||
|
You are an AI agent that can view and filter JSON data with jq.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
json_file_path: ${json_file_path}
|
||||||
|
json_schema: ${json_schema}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information on how to create custom tools for your agent and the structure of the `agent/my-agent/tools.sh` or
|
||||||
|
`agent/my-agent/tools.py` files, refer to the [Building Tools for Agents](#4-building-tools-for-agents) section below.
|
||||||
|
|
||||||
|
#### Variables
|
||||||
|
All the same variable interpolations supported by static instructions is also supported by dynamic instructions. For
|
||||||
|
more information on what variables are available and how to use them, refer to the [Special Variables](#special-variables)
|
||||||
|
and [User-Defined Variables](#user-defined-variables) sections above.
|
||||||
|
|
||||||
|
## 3. Initializing RAG
|
||||||
|
Each agent you create also has a dedicated knowledge base that adds additional context to your queries and helps the LLM
|
||||||
|
answer queries effectively. The documents to load into RAG are defined in the `documents` array of your agent
|
||||||
|
configuration file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
documents:
|
||||||
|
- https://www.ohdsi.org/data-standardization/
|
||||||
|
- https://github.com/OHDSI/Vocabulary-v5.0/wiki/**
|
||||||
|
- OMOPCDM_ddl.sql # Relative path to agent (i.e. file lives at '<loki-config-dir>/agents/my-agent/OMOPCDM_ddl.sql')
|
||||||
|
```
|
||||||
|
|
||||||
|
These documents use the same syntax as those you'd define when constructing RAG normally. To see all the available types
|
||||||
|
of documents that Loki supports and how to use custom document loaders, refer to the [RAG documentation](./RAG.md#supported-document-sources).
|
||||||
|
|
||||||
|
Anytime your agent starts up, it will automatically be using the RAG you've defined here.
|
||||||
|
|
||||||
|
## 4. Building Tools for Agents
|
||||||
|
Building tools for agents is virtually identical to building custom tools, with one slight difference: instead of
|
||||||
|
defining a single function that gets executed at runtime (e.g. `main` for bash tools and `run` for Python tools), agent
|
||||||
|
tools define a number of *subcommands*.
|
||||||
|
|
||||||
|
### Limitations
|
||||||
|
You can only utilize either a bash-based `<loki-config-dir>/agents/my-agent/tools.sh` or a Python-based
|
||||||
|
`<loki-config-dir>/agents/my-agent/tools.py`. However, if it's easier to achieve a task in one language vs the other,
|
||||||
|
you're free to define other scripts in your agent's configuration directory and reference them from the main
|
||||||
|
`tools.py/sh` file. **Any scripts *not* named `tools.{py,sh}` will not be picked up by Loki's compiler**, meaning they
|
||||||
|
can be used like any other set of scripts.
|
||||||
|
|
||||||
|
It's important to keep in mind the following:
|
||||||
|
|
||||||
|
* **Do not give agents the same name as an executable**. Loki compiles the tools for each agent into a binary that it
|
||||||
|
temporarily places on your path during execution. If you have a binary with the same name as your agent, then your
|
||||||
|
shell may execute the existing binary instead of your agent's tools
|
||||||
|
* **`LLM_ROOT_DIR` points to the agent's configuration directory**. This is where agents differ slightly from normal
|
||||||
|
tools: The `LLM_ROOT_DIR` environment variable does *not* point to the `functions/tools` directory like it does in
|
||||||
|
global tools. Instead, it points to the agent's configuration directory, making it easier to source scripts and other
|
||||||
|
miscellaneous files
|
||||||
|
|
||||||
|
### .env File Support
|
||||||
|
When Loki loads an agent, it will also search the agent's configuration directory for a `.env` file. If found, all
|
||||||
|
environment variables defined in the file will be made available to the agent's tools.
|
||||||
|
|
||||||
|
### Python-Based Agent Tools
|
||||||
|
Python-based tools are defined exactly the same as they are for custom tool definitions. The only difference is that
|
||||||
|
instead of a single `run` function, you define as many as you like with whatever arguments you like.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
`agents/my-agent/tools.py`
|
||||||
|
```python
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
def get_ip_info():
|
||||||
|
"""
|
||||||
|
Get your IP information
|
||||||
|
"""
|
||||||
|
with urllib.request.urlopen("https://httpbin.org/ip") as response:
|
||||||
|
data = response.read()
|
||||||
|
return data.decode('utf-8')
|
||||||
|
|
||||||
|
def get_ip_address_from_aws():
|
||||||
|
"""
|
||||||
|
Find your public IP address using AWS
|
||||||
|
"""
|
||||||
|
with urllib.request.urlopen("https://checkip.amazonaws.com") as response:
|
||||||
|
data = response.read()
|
||||||
|
return data.decode('utf-8')
|
||||||
|
```
|
||||||
|
|
||||||
|
Loki automatically compiles these as separate functions for the LLM to call. No extra work is needed. Just make sure you
|
||||||
|
follow all the same steps to define each function as you would when creating custom Python tools.
|
||||||
|
|
||||||
|
For more information on how to build tools in Python, refer to the [custom Python tools documentation](./function-calling/CUSTOM-TOOLS.md#custom-python-based-tools)
|
||||||
|
|
||||||
|
### Bash-Based Agent Tools
|
||||||
|
Bash-based agent tools are virtually identical to custom bash tools, with only one difference. Instead of defining a
|
||||||
|
single entrypoint via the `main` function, you actually define as many subcommands as you like.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
`agents/my-agent/tools.sh`
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# @env LLM_OUTPUT=/dev/stdout The output path
|
||||||
|
# @describe Discover network information about your computer and its place in the internet
|
||||||
|
|
||||||
|
# Use the `@cmd` annotation to define subcommands for your script.
|
||||||
|
# @cmd Get your IP information
|
||||||
|
get_ip_info() {
|
||||||
|
curl -fsSL https://httpbin.org/ip >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# @cmd Find your public IP address using AWS
|
||||||
|
get_ip_address_from_aws() {
|
||||||
|
curl -fsSL https://checkip.amazonaws.com >> "$LLM_OUTPUT"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
To compile the script so it's executable and testable:
|
||||||
|
```bash
|
||||||
|
$ loki --build-tools
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can execute your script (assuming your current working directory is `agents/my-agent`):
|
||||||
|
```bash
|
||||||
|
$ ./tools.sh get_ip_info
|
||||||
|
$ ./tools.sh get_ip_address_from_aws
|
||||||
|
```
|
||||||
|
|
||||||
|
All other special annotations (`@env`, `@arg`, `@option` `@flags`) apply to subcommands as well, so be sure to follow
|
||||||
|
the same syntax ad formatting as is used to create custom bash tools globally.
|
||||||
|
|
||||||
|
For more information on how to write, [build and test](function-calling/CUSTOM-BASH-TOOLS.md#execute-and-test-your-bash-tools) tools in bash, refer to the
|
||||||
|
[custom bash tools documentation](function-calling/CUSTOM-BASH-TOOLS.md).
|
||||||
|
|
||||||
|
## 5. Conversation Starters
|
||||||
|
It's often helpful to also have some conversation starters so users know what kinds of things the agent is capable of
|
||||||
|
doing. These are available in the REPL via the `.starter` command and are selectable.
|
||||||
|
|
||||||
|
They are defined using the `conversation_starters` setting in your agent's configuration file:
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
`agents/my-agent/config.yaml`:
|
||||||
|
```yaml
|
||||||
|
conversation_starters:
|
||||||
|
- What is my username?
|
||||||
|
- What is my current shell?
|
||||||
|
- What is my ip?
|
||||||
|
- How much disk space is left on my PC??
|
||||||
|
- How to create an agent?
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 6. Todo System & Auto-Continuation
|
||||||
|
|
||||||
|
Loki includes a built-in task tracking system designed to improve the reliability of agents, especially when using
|
||||||
|
smaller language models. The Todo System helps models:
|
||||||
|
|
||||||
|
- Break complex tasks into manageable steps
|
||||||
|
- Track progress through multi-step workflows
|
||||||
|
- Automatically continue work until all tasks are complete
|
||||||
|
|
||||||
|
### Quick Configuration
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# agents/my-agent/config.yaml
|
||||||
|
auto_continue: true # Enable auto-continuation
|
||||||
|
max_auto_continues: 10 # Max continuation attempts
|
||||||
|
inject_todo_instructions: true # Include the default todo instructions into prompt
|
||||||
|
```
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
1. When `inject_todo_instructions` is enabled, agents receive instructions on using four built-in tools:
|
||||||
|
- `todo__init`: Initialize a todo list with a goal
|
||||||
|
- `todo__add`: Add a task to the list
|
||||||
|
- `todo__done`: Mark a task complete
|
||||||
|
- `todo__list`: View current todo state
|
||||||
|
|
||||||
|
These instructions are a reasonable default that detail how to use Loki's To-Do System. If you wish,
|
||||||
|
you can disable the injection of the default instructions and specify your own instructions for how
|
||||||
|
to use the To-Do System into your main `instructions` for the agent.
|
||||||
|
|
||||||
|
2. When `auto_continue` is enabled and the model stops with incomplete tasks, Loki automatically sends a
|
||||||
|
continuation prompt with the current todo state, nudging the model to continue working.
|
||||||
|
|
||||||
|
3. This continues until all tasks are done or `max_auto_continues` is reached.
|
||||||
|
|
||||||
|
### When to Use
|
||||||
|
|
||||||
|
- Multistep tasks where the model might lose track
|
||||||
|
- Smaller models that need more structure
|
||||||
|
- Workflows requiring guaranteed completion of all steps
|
||||||
|
|
||||||
|
For complete documentation including all configuration options, tool details, and best practices, see the
|
||||||
|
[Todo System Guide](./TODO-SYSTEM.md).
|
||||||
|
|
||||||
|
## Built-In Agents
|
||||||
|
Loki comes packaged with some useful built-in agents:
|
||||||
|
|
||||||
|
* `coder`: An agent to assist you with all your coding tasks
|
||||||
|
* `demo`: An example agent to use for reference when learning to create your own agents
|
||||||
|
* `explore`: An agent designed to help you explore and understand your codebase
|
||||||
|
* `jira-helper`: An agent that assists you with all your Jira-related tasks
|
||||||
|
* `oracle`: An agent for high-level architecture, design decisions, and complex debugging
|
||||||
|
* `sisyphus`: A powerhouse agent for writing complex code and acting as a natural language interface for your codebase (similar to ClaudeCode, Gemini CLI, Codex, or OpenCode)
|
||||||
|
* `sql`: A universal SQL agent that enables you to talk to any relational database in natural language
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
# AIChat to Loki Migration Guide
|
||||||
|
Loki originally started as a fork of AIChat but has since evolved into its own separate project with separate goals.
|
||||||
|
|
||||||
|
As a result, there's some changes you'll need to make to your AIChat configuration to be able to use Loki.
|
||||||
|
|
||||||
|
Be sure you've run `loki` at least once so that the Loki configuration directory and subdirectories exist and is
|
||||||
|
populated with the built-in defaults.
|
||||||
|
|
||||||
|
## Global Configuration File
|
||||||
|
You should be able to copy/paste your AIChat configuration file into your Loki configuration directory. Since the
|
||||||
|
location of the Loki configuration directory varies between systems, you can use the following command to locate your
|
||||||
|
config directory:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
loki --info | grep 'config_dir' | awk '{print $2}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, you'll need to make the following changes:
|
||||||
|
|
||||||
|
* `function_calling` -> `function_calling_support`
|
||||||
|
* `use_tools` -> `enabled_tools`
|
||||||
|
* `agent_prelude` -> `agent_session`
|
||||||
|
* `compress_threshold` -> `compression_threshold`
|
||||||
|
* `summarize_prompt` -> `summarization_prompt`
|
||||||
|
* `summary_prompt` -> `summary_context_prompt`
|
||||||
|
|
||||||
|
## Roles
|
||||||
|
Locate your `roles` directory using the following command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
loki --info | grep 'roles_dir' | awk '{print $2}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Update any roles that have `use_tools` to `enabled_tools`.
|
||||||
|
|
||||||
|
## Sessions
|
||||||
|
Locate your `sessions` directory using the following command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
loki --info | grep 'sessions_dir' | awk '{print $2}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Update the following settings:
|
||||||
|
* `use_tools` -> `enabled_tools`
|
||||||
|
* `compress_threshold` -> `compression_threshold`
|
||||||
|
* `summarize_prompt` -> `summarization_prompt`
|
||||||
|
* `summary_prompt` -> `summary_context_prompt`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# LLM Functions Changes
|
||||||
|
Probably the most significant difference between AIChat and Loki is how tools are handled. So if you cloned the
|
||||||
|
[llm-functions](https://github.com/sigoden/llm-functions) repo, you'll need to make the following changes.
|
||||||
|
|
||||||
|
**Note: JavaScript functions are not supported in Loki.**
|
||||||
|
|
||||||
|
The following guide assumes you're using the `llm-functions` repository as your base for custom functions, and thus
|
||||||
|
follows that directory structure.
|
||||||
|
|
||||||
|
## Agents
|
||||||
|
Agents are now all handled in one place: the `agents` directory (`<loki-config-dir>/agents`):
|
||||||
|
|
||||||
|
```shell
|
||||||
|
loki --info | grep 'agents_dir' | awk '{print $2}'
|
||||||
|
```
|
||||||
|
|
||||||
|
And instead of separate `index.yaml` and `config.yaml` files, they're now both in a single `config.yaml` file.
|
||||||
|
|
||||||
|
So now for all of your agents, copy all the contents of those directories to the corresponding directory in the Loki
|
||||||
|
`agents` directory. Then make the following changes:
|
||||||
|
|
||||||
|
* Copy the contents of your `<aichat-config-dir>/functions/agents` directory into `<loki-config-dir/agents`
|
||||||
|
* Merge `index.yaml` into `config.yaml`
|
||||||
|
* If you never created a custom `config.yaml` file, then simply rename `index.yaml` to `config.yaml`
|
||||||
|
* If you've defined an `agent_prelude`, rename that field to `agent_session`
|
||||||
|
* Convert all JavaScript tools to either Python or Bash
|
||||||
|
* For Bash `tools.sh`: Remove the following line:
|
||||||
|
```bash
|
||||||
|
eval "$(argc --argc-eval "$0" "$@")"
|
||||||
|
```
|
||||||
|
* Any `tools.txt` files you have that define what global functions the agent uses is now replaced by the `global_tools`
|
||||||
|
field in the agent's `config.yaml`. So for example: If your `tools.txt` looks like this:
|
||||||
|
```text
|
||||||
|
fs_mkdir.sh
|
||||||
|
fs_ls.sh
|
||||||
|
fs_patch.sh
|
||||||
|
fs_cat.sh
|
||||||
|
```
|
||||||
|
then you need to add the following to your agent's `config.yaml`:
|
||||||
|
```yaml
|
||||||
|
global_tools:
|
||||||
|
- fs_mkdir.sh
|
||||||
|
- fs_ls.sh
|
||||||
|
- fs_patch.sh
|
||||||
|
- fs_cat.sh
|
||||||
|
```
|
||||||
|
* If you have any bash `tools.sh` that depend on the utility scripts in the `llm-functions` repository, they've been
|
||||||
|
replaced by built-in utility scripts. So use the following to replace any matching lines in your `tools.sh` files:
|
||||||
|
```bash
|
||||||
|
##################
|
||||||
|
## Scripts file ##
|
||||||
|
##################
|
||||||
|
ROOT_DIR="${LLM_ROOT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
||||||
|
# replace with
|
||||||
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
|
||||||
|
#######################
|
||||||
|
## guard_path script ##
|
||||||
|
#######################
|
||||||
|
"$ROOT_DIR/utils/guard_path.sh"
|
||||||
|
# replace with
|
||||||
|
guard_path
|
||||||
|
|
||||||
|
############################
|
||||||
|
## guard_operation script ##
|
||||||
|
############################
|
||||||
|
"$ROOT_DIR/utils/guard_operation.sh"
|
||||||
|
# replace with
|
||||||
|
guard_operation
|
||||||
|
|
||||||
|
######################
|
||||||
|
## patch.awk script ##
|
||||||
|
######################
|
||||||
|
awk -f "$ROOT_DIR/utils/patch.awk"
|
||||||
|
# replace with
|
||||||
|
patch_file
|
||||||
|
```
|
||||||
|
|
||||||
|
When you're done with this migration, you should have the following:
|
||||||
|
|
||||||
|
* No more `functions/agents` directory
|
||||||
|
* No `functions/agents.txt` file (Loki assumes that if the agent directory exists, it is loadable)
|
||||||
|
* No `<loki-config-dir>/agents/<agent-name>/tools.txt`
|
||||||
|
* No `<loki-config-dir>/agents/<agent-name>/index.yaml`
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
Loki consolidates much of the `llm-functions` repo functionality into one binary. So this means
|
||||||
|
|
||||||
|
* There's no need to have `argc` installed anymore
|
||||||
|
* No separate repository to manage
|
||||||
|
* No `tools.txt`
|
||||||
|
* No `functions.json`
|
||||||
|
* No `functions/mcp` directory at all
|
||||||
|
* No `functions/scripts`
|
||||||
|
|
||||||
|
Here's how to migrate your functions over to Loki from the `llm-functions` repository.
|
||||||
|
|
||||||
|
* Copy your AIChat `<aichat-config-dir>/functions` directory into your Loki config directory
|
||||||
|
* Delete the following files and directories from your `<loki-config-dir>/functions` directory:
|
||||||
|
* `scripts/`
|
||||||
|
* `agents.txt`
|
||||||
|
* `functions.json`
|
||||||
|
* `Argcfile.sh`
|
||||||
|
* `README.md` (irrelevant now)
|
||||||
|
* `LICENSE` (irrelevant now)
|
||||||
|
* `utils/guard_operation.sh`
|
||||||
|
* `utils/guard_path.sh`
|
||||||
|
* `utils/patch.awk`
|
||||||
|
* Everything in `tools.txt` now lives in the global config file under the `visible_tools` setting:
|
||||||
|
```text
|
||||||
|
get_current_weather.sh
|
||||||
|
execute_command.sh
|
||||||
|
web_search.sh
|
||||||
|
#execute_py_code.py
|
||||||
|
query_jira_issues.sh
|
||||||
|
```
|
||||||
|
becomes the following in your `<loki-config-dir>/config.yaml`
|
||||||
|
```yaml
|
||||||
|
visible_tools:
|
||||||
|
- get_current_weather.sh
|
||||||
|
- execute_command.sh
|
||||||
|
- web_search.sh
|
||||||
|
# - web_search.sh
|
||||||
|
- query_jira_issues.sh
|
||||||
|
```
|
||||||
|
* If you've defined a `functions/mcp.json` file, you can leave it alone.
|
||||||
|
* Similarly to agents, if you have any bash `tools.sh` that depend on the utility scripts in the `llm-functions`
|
||||||
|
repository, they've been replaced by built-in utility scripts. So use the following to replace any matching lines in
|
||||||
|
your `tools.sh` files:
|
||||||
|
```bash
|
||||||
|
##################
|
||||||
|
## Scripts file ##
|
||||||
|
##################
|
||||||
|
ROOT_DIR="${LLM_ROOT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
||||||
|
# replace with
|
||||||
|
source "$LLM_PROMPT_UTILS_FILE"
|
||||||
|
|
||||||
|
#######################
|
||||||
|
## guard_path script ##
|
||||||
|
#######################
|
||||||
|
"$ROOT_DIR/utils/guard_path.sh"
|
||||||
|
# replace with
|
||||||
|
guard_path
|
||||||
|
|
||||||
|
############################
|
||||||
|
## guard_operation script ##
|
||||||
|
############################
|
||||||
|
"$ROOT_DIR/utils/guard_operation.sh"
|
||||||
|
# replace with
|
||||||
|
guard_operation
|
||||||
|
|
||||||
|
######################
|
||||||
|
## patch.awk script ##
|
||||||
|
######################
|
||||||
|
awk -f "$ROOT_DIR/utils/patch.awk"
|
||||||
|
# replace with
|
||||||
|
patch_file
|
||||||
|
```
|
||||||
|
|
||||||
|
Refer to the [custom bash tools docs](./function-calling/CUSTOM-BASH-TOOLS.md) to learn how to compile and test bash
|
||||||
|
tools in Loki without needing to use `argc`.
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# Environment Variables
|
||||||
|
|
||||||
|
Loki is designed to be highly dynamic and customizable. As a result, Loki utilizes a number of environment variables
|
||||||
|
that can be used to modify its behavior at runtime without needing to modify the existing configuration files.
|
||||||
|
|
||||||
|
Loki also supports defining environment variables via a `.env` file in the Loki configuration directory. This directory
|
||||||
|
varies between systems, so you can find the location of your configuration directory using the following command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
loki --info | grep 'config_dir' | awk '{print $2}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Links
|
||||||
|
<!--toc:start-->
|
||||||
|
- [Global Configuration Related Variables](#global-configuration-related-variables)
|
||||||
|
- [Client Related Variables](#client-related-variables)
|
||||||
|
- [Files and Directory Related Variables](#files-and-directory-related-variables)
|
||||||
|
- [Agent Related Variables](#agent-related-variables)
|
||||||
|
- [Logging Related Variables](#logging-related-variables)
|
||||||
|
- [Miscellaneous Variables](#miscellaneous-variables)
|
||||||
|
<!--toc:end-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Global Configuration Related Variables
|
||||||
|
All configuration items in the global config file have environment variables that can be overridden at runtime. To see
|
||||||
|
all configuration options and more thorough descriptions, refer to the [example config file](../config.example.yaml).
|
||||||
|
|
||||||
|
Below are the most commonly used configuration settings and their corresponding environment variables:
|
||||||
|
|
||||||
|
| Setting | Environment Variable |
|
||||||
|
|----------------------------|---------------------------------|
|
||||||
|
| `model` | `LOKI_MODEL` |
|
||||||
|
| `temperature` | `LOKI_TEMPERATURE` |
|
||||||
|
| `top_p` | `LOKI_TOP_P` |
|
||||||
|
| `stream` | `LOKI_STREAM` |
|
||||||
|
| `save` | `LOKI_SAVE` |
|
||||||
|
| `editor` | `LOKI_EDITOR` |
|
||||||
|
| `wrap` | `LOKI_WRAP` |
|
||||||
|
| `wrap_code` | `LOKI_WRAP_CODE` |
|
||||||
|
| `save_session` | `LOKI_SAVE_SESSION` |
|
||||||
|
| `compression_threshold` | `LOKI_COMPRESSION_THRESHOLD` |
|
||||||
|
| `function_calling_support` | `LOKI_FUNCTION_CALLING_SUPPORT` |
|
||||||
|
| `enabled_tools` | `LOKI_ENABLED_TOOLS` |
|
||||||
|
| `mcp_server_support` | `LOKI_MCP_SERVER_SUPPORT` |
|
||||||
|
| `enabled_mcp_servers` | `LOKI_ENABLED_MCP_SERVERS` |
|
||||||
|
| `rag_embedding_model` | `LOKI_RAG_EMBEDDING_MODEL` |
|
||||||
|
| `rag_reranker_model` | `LOKI_RAG_RERANKER_MODEL` |
|
||||||
|
| `rag_top_k` | `LOKI_RAG_TOP_K` |
|
||||||
|
| `rag_chunk_size` | `LOKI_RAG_CHUNK_SIZE` |
|
||||||
|
| `rag_chunk_overlap` | `LOKI_RAG_CHUNK_OVERLAP` |
|
||||||
|
| `highlight` | `LOKI_HIGHLIGHT` |
|
||||||
|
| `theme` | `LOKI_THEME` |
|
||||||
|
| `serve_addr` | `LOKI_SERVE_ADDR` |
|
||||||
|
| `user_agent` | `LOKI_USER_AGENT` |
|
||||||
|
| `save_shell_history` | `LOKI_SAVE_SHELL_HISTORY` |
|
||||||
|
| `sync_models_url` | `LOKI_SYNC_MODELS_URL` |
|
||||||
|
|
||||||
|
|
||||||
|
## Client Related Variables
|
||||||
|
The following environment variables are available for clients in Loki:
|
||||||
|
|
||||||
|
| Environment Variable | Description |
|
||||||
|
|----------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `{client}_API_KEY` | For clients that require an API key, you can define the keys either through environment variables or <br>using the [vault](./VAULT.md). The variables are named after the client to which they apply; <br>e.g. `OPENAI_API_KEY`, `GEMINI_API_KEY`, etc. |
|
||||||
|
| `LOKI_PLATFORM` | Combine with `{client}_API_KEY` to run Loki without a configuration file. <br>This variable is ignored if a configuration file exists. |
|
||||||
|
| `LOKI_PATCH_{client}_CHAT_COMPLETIONS` | Patch chat completion requests to models on the corresponding client; Can modify the URL, body, <br>or headers. |
|
||||||
|
| `LOKI_SHELL` | Specify the shell that Loki should be using when executing commands |
|
||||||
|
|
||||||
|
## Files and Directory Related Variables
|
||||||
|
You can also customize the files and directories that Loki loads its configuration files from:
|
||||||
|
|
||||||
|
| Environment Variable | Description | Default Value |
|
||||||
|
|----------------------|------------------------------------------------------------------------|---------------------------------|
|
||||||
|
| `LOKI_CONFIG_DIR` | Customize the location of the Loki configuration directory. | `<user-config-dir>/loki` |
|
||||||
|
| `LOKI_ENV_FILE` | Customize the location of the `.env` file to load at startup. | `<loki-config-dir>/.env` |
|
||||||
|
| `LOKI_CONFIG_FILE` | Customize the location of the global `config.yaml` configuration file. | `<loki-config-dir>/config.yaml` |
|
||||||
|
| `LOKI_ROLES_DIR` | Customize the location of the `roles` directory. | `<loki-config-dir>/roles` |
|
||||||
|
| `LOKI_SESSIONS_DIR` | Customize the location of the `sessions` directory. | `<loki-config-dir>/sessions` |
|
||||||
|
| `LOKI_RAGS_DIR` | Customize the location of the `rags` directory. | `<loki-config-dir>/rags` |
|
||||||
|
| `LOKI_FUNCTIONS_DIR` | Customize the location of the `functions` directory. | `<loki-config-dir>/functions` |
|
||||||
|
|
||||||
|
## Agent Related Variables
|
||||||
|
You can also customize the location of full agent configurations using the following environment variables:
|
||||||
|
|
||||||
|
| Environment Variable | Description |
|
||||||
|
|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `<AGENT_NAME>_CONFIG_FILE` | Customize the location of the agent's configuration file; e.g. `SQL_CONFIG_FILE` |
|
||||||
|
| `<AGENT_NAME>_MODEL` | Customize the `model` used for the agent; e.g `SQL_MODEL` |
|
||||||
|
| `<AGENT_NAME>_TEMPERATURE` | Customize the `temperature` used for the agent; e.g. `SQL_TEMPERATURE` |
|
||||||
|
| `<AGENT_NAME>_TOP_P` | Customize the `top_p` used for the agent; e.g. `SQL_TOP_P` |
|
||||||
|
| `<AGENT_NAME>_GLOBAL_TOOLS` | Customize the `global_tools` that are enabled for the agent (a JSON string array); e.g. `SQL_GLOBAL_TOOLS` |
|
||||||
|
| `<AGENT_NAME>_MCP_SERVERS` | Customize the `mcp_servers` that are enabled for the agent (a JSON string array); e.g. `SQL_MCP_SERVERS` |
|
||||||
|
| `<AGENT_NAME>_AGENT_SESSION` | Customize the `agent_session` used with the agent; e.g. `SQL_SESSION` |
|
||||||
|
| `<AGENT_NAME>_INSTRUCTIONS` | Customize the `instructions` for the agent; e.g. `SQL_INSTRUCTIONS` |
|
||||||
|
| `<AGENT_NAME>_VARIABLES` | Customize the `variables` used for the agent (in JSON format of `[{"key1": "value1", "key2": "value2"}]`); <br>e.g. `SQL_VARIABLES` |
|
||||||
|
|
||||||
|
## Logging Related Variables
|
||||||
|
The following variables can be used to change the log level of Loki or the location of the log file:
|
||||||
|
|
||||||
|
| Environment Variable | Description | Default Value |
|
||||||
|
|----------------------|---------------------------------------------|----------------------------------|
|
||||||
|
| `LOKI_LOG_LEVEL` | Customize the log level of Loki | `INFO` |
|
||||||
|
| `LOKI_LOG_FILE` | Customize the location of the Loki log file | `<user-cache-dir>/loki/loki.log` |
|
||||||
|
|
||||||
|
**Pro-Tip:** You can always tail the Loki logs using the `--tail-logs` flag. If you need to disable color output, you
|
||||||
|
can also pass the `--disable-log-colors` flag as well.
|
||||||
|
|
||||||
|
## Miscellaneous Variables
|
||||||
|
| Environment Variable | Description | Default Value |
|
||||||
|
|----------------------|--------------------------------------------------------------------------------------------------|---------------|
|
||||||
|
| `AUTO_CONFIRM` | Bypass all `guard_*` checks in the bash prompt helpers; useful for agent composition and routing | |
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user