16 Commits

Author SHA1 Message Date
github-actions[bot] b1cd8351fa chore: bump Cargo.toml to 0.5.0 2026-05-27 21:27:54 +00:00
github-actions[bot] ccf5e73341 bump: version 0.4.0 → 0.5.0 [skip ci] 2026-05-27 21:27:49 +00:00
Dark-Alex-17 be5d280c32 fix: bash-based user interactions in agents accidentally regressed in graph implementation 2026-05-27 15:20:19 -06:00
Dark-Alex-17 6633a8c0bf fix: Claude function calling in agent contexts 2026-05-27 14:47:27 -06:00
Dark-Alex-17 097d8936e3 fix: Claude code rate limit error per new Claude changes 2026-05-27 14:06:17 -06:00
Dark-Alex-17 8a53b7934b fmt: apply uniform formatting with name change 2026-05-27 12:57:05 -06:00
Dark-Alex-17 0facb15e32 feat: rename Loki to Coyote 2026-05-27 12:47:32 -06:00
Dark-Alex-17 c172736362 docs: clarified OAuth more 2026-05-22 19:56:00 -06:00
github-actions[bot] 4a2b9fa42a bump: version 0.3.0 → 0.4.0 [skip ci] 2026-05-23 01:53:47 +00:00
Dark-Alex-17 98db37866c docs: Fixed a typo in the README 2026-05-22 19:49:40 -06:00
Dark-Alex-17 ad31fbd169 test: fixed broken cross tests that required home directory access 2026-05-22 19:49:01 -06:00
Dark-Alex-17 d69e28fd39 docs: fixed broken sharing configurations link 2026-05-22 19:48:44 -06:00
Alex Clarke 279eaa5300 Merge pull request #12 from Dark-Alex-17/develop
Release v0.4.0: Graph-based agents, remote asset installation, self-update and god-config refactor
2026-05-22 19:18:13 -06:00
Dark-Alex-17 e687d78931 build: Removed unnecessary Language import for Windows systems 2026-05-22 19:04:46 -06:00
Dark-Alex-17 0c2e4df647 feat: LLM node failures propgate up 2026-05-22 18:27:03 -06:00
Dark-Alex-17 6221875f64 build: upgraded to rust v1.95.0 2026-05-22 18:11:01 -06:00
62 changed files with 963 additions and 3388 deletions
+13 -13
View File
@@ -21,25 +21,25 @@ body:
value: | value: |
I tried this: I tried this:
1. `loki` 1. `coyote`
I expected this to happen: I expected this to happen:
Instead, this happened: Instead, this happened:
- type: textarea - type: textarea
id: loki-log id: coyote-log
attributes: attributes:
label: Loki log label: Coyote log
description: Include the Loki log file to help diagnose the issue. (`loki --info` to see the log_path) description: Include the Coyote log file to help diagnose the issue. (`coyote --info` to see the log_path)
value: | value: |
| OS | Log file location | | OS | Log file location |
| ------- | ----------------------------------------------------- | | ------- | ----------------------------------------------------- |
| Linux | `~/.cache/loki/loki.log` | | Linux | `~/.cache/coyote/coyote.log` |
| Mac | `~/Library/Logs/loki/loki.log` | | Mac | `~/Library/Logs/coyote/coyote.log` |
| Windows | `C:\Users\<User>\AppData\Local\loki\loki.log` | | Windows | `C:\Users\<User>\AppData\Local\coyote\coyote.log` |
``` ```
please provide a copy of your loki log file here if possible; you may need to redact some of the lines please provide a copy of your coyote log file here if possible; you may need to redact some of the lines
``` ```
- type: input - type: input
@@ -57,13 +57,13 @@ body:
validations: validations:
required: true required: true
- type: input - type: input
id: loki-version id: coyote-version
attributes: attributes:
label: Loki Version label: Coyote Version
description: > description: >
Loki version (`loki --version` if using a release, `git describe` if building Coyote version (`coyote --version` if using a release, `git describe` if building
from main). 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** **Make sure that you are using the [latest coyote release](https://github.com/Dark-Alex-17/coyote/releases) or a newer main build**
placeholder: "loki 0.1.0" placeholder: "coyote 0.1.0"
validations: validations:
required: true required: true
+14 -14
View File
@@ -98,9 +98,9 @@ jobs:
# Ignore Act's local artifact dir noise # Ignore Act's local artifact dir noise
echo artifacts/ >> .git/info/exclude || true echo artifacts/ >> .git/info/exclude || true
# Edit the version line right after name="loki" # Edit the version line right after name="coyote"
sed -E -i ' sed -E -i '
/^[[:space:]]*name[[:space:]]*=[[:space:]]*"loki"[[:space:]]*$/ { /^[[:space:]]*name[[:space:]]*=[[:space:]]*"coyote"[[:space:]]*$/ {
n n
s|^[[:space:]]*version[[:space:]]*=[[:space:]]*"[^"]*"|version = "'"$VERSION"'"| s|^[[:space:]]*version[[:space:]]*=[[:space:]]*"[^"]*"|version = "'"$VERSION"'"|
} }
@@ -278,7 +278,7 @@ jobs:
- name: Verify file - name: Verify file
shell: bash shell: bash
run: | run: |
file target/${{ matrix.target }}/release/loki file target/${{ matrix.target }}/release/coyote
- name: Test - name: Test
if: matrix.target != 'aarch64-apple-darwin' && matrix.target != 'aarch64-pc-windows-msvc' if: matrix.target != 'aarch64-apple-darwin' && matrix.target != 'aarch64-pc-windows-msvc'
@@ -382,11 +382,11 @@ jobs:
shell: bash shell: bash
run: | run: |
# Set environment variables # Set environment variables
macos_sha="$(cat ./artifacts/loki-x86_64-apple-darwin.sha256 | awk '{print $1}')" macos_sha="$(cat ./artifacts/coyote-x86_64-apple-darwin.sha256 | awk '{print $1}')"
echo "MACOS_SHA=$macos_sha" >> $GITHUB_ENV echo "MACOS_SHA=$macos_sha" >> $GITHUB_ENV
macos_sha_arm="$(cat ./artifacts/loki-aarch64-apple-darwin.sha256 | awk '{print $1}')" macos_sha_arm="$(cat ./artifacts/coyote-aarch64-apple-darwin.sha256 | awk '{print $1}')"
echo "MACOS_SHA_ARM=$macos_sha_arm" >> $GITHUB_ENV echo "MACOS_SHA_ARM=$macos_sha_arm" >> $GITHUB_ENV
linux_sha="$(cat ./artifacts/loki-x86_64-unknown-linux-musl.sha256 | awk '{print $1}')" linux_sha="$(cat ./artifacts/coyote-x86_64-unknown-linux-musl.sha256 | awk '{print $1}')"
echo "LINUX_SHA=$linux_sha" >> $GITHUB_ENV echo "LINUX_SHA=$linux_sha" >> $GITHUB_ENV
release_version="$(cat ./artifacts/release-version)" release_version="$(cat ./artifacts/release-version)"
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
@@ -402,23 +402,23 @@ jobs:
if: env.ACT != 'true' if: env.ACT != 'true'
run: | run: |
# run packaging script # 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 }} python "./deployment/homebrew/packager.py" ${{ env.RELEASE_VERSION }} "./deployment/homebrew/coyote.rb.template" "./coyote.rb" ${{ env.MACOS_SHA }} ${{ env.MACOS_SHA_ARM }} ${{ env.LINUX_SHA }}
- name: Push changes to Homebrew tap - name: Push changes to Homebrew tap
if: env.ACT != 'true' if: env.ACT != 'true'
env: env:
TOKEN: ${{ secrets.LOKI_GITHUB_TOKEN }} TOKEN: ${{ secrets.COYOTE_GITHUB_TOKEN }}
run: | run: |
# push to Git # push to Git
git config --global user.name "Dark-Alex-17" git config --global user.name "Dark-Alex-17"
git config --global user.email "alex.j.tusa@gmail.com" 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 git clone https://Dark-Alex-17:${{ secrets.COYOTE_GITHUB_TOKEN }}@github.com/Dark-Alex-17/homebrew-coyote.git
rm homebrew-loki/Formula/loki.rb rm homebrew-coyote/Formula/coyote.rb
cp loki.rb homebrew-loki/Formula cp coyote.rb homebrew-coyote/Formula
cd homebrew-loki cd homebrew-coyote
git add . git add .
git diff-index --quiet HEAD || git commit -am "Update formula for Loki release ${{ env.RELEASE_VERSION }}" git diff-index --quiet HEAD || git commit -am "Update formula for Coyote release ${{ env.RELEASE_VERSION }}"
git push https://$TOKEN@github.com/Dark-Alex-17/homebrew-loki.git git push https://$TOKEN@github.com/Dark-Alex-17/homebrew-coyote.git
publish-crate: publish-crate:
needs: publish-github-release needs: publish-github-release
+1 -1
View File
@@ -3,5 +3,5 @@
/.env /.env
!cli/** !cli/**
.idea/ .idea/
/loki.iml /coyote.iml
/.idea/ /.idea/
-1
View File
@@ -1 +0,0 @@
{"type":"rust","build":"cargo build","test":"cargo test","check":"cargo check","_detected_by":"heuristic","_cached_at":"2026-04-13T13:36:33-06:00"}
+115 -4
View File
@@ -1,3 +1,114 @@
## v0.5.0 (2026-05-27)
### Feat
- rename Loki to Coyote
### Fix
- bash-based user interactions in agents accidentally regressed in graph implementation
- Claude function calling in agent contexts
- Claude code rate limit error per new Claude changes
## v0.4.0 (2026-05-23)
### Feat
- LLM node failures propgate up
- Added .install remote tab completions to the REPL
- feature complete install remote with category selection
- Support to interactively add secrets to Coyote that are missing from MCP configs when merging
- Added MCP config merging support for remote asset installations
- install remote now writes files to disk
- Created basic install_remote functions
- Created a more comprehensive and immediately useful default config for first runs
- Created an example graph-based agent called deep-research
- Improved coder agent that is now a graph-based agent
- Removed indicatif spinners. The UX just won't stop clobbering for parallel graph nodes
- Added agent variables support for graph agents and improved script executor to use the same environment variables as normal agent tool calling for further flexibility
- Improved UX with colored spinners for parallel graph agents and no clobbering outputs for sub-agents
- created new graph-based deep-research agent
- improved UX for parallel graph execution
- added branch progress tracker for better visualization of parallel graph super-steps
- Removed the jira-helper agent and replaced it with the atlassian role
- created the RenderMode enum to suppress stdout streaming during parallel graph super-steps
- Full support for map node types
- implemented the frontier-based scheduling for the graph executor with simplified state management (gotta love .clone)
- validation support for parallel graph execution; restricted map nodes to only run for nodes without next targets and not supporting chained map nodes
- created the staging area for state merges per super-step and created the built-in reducers (and their application) for the state merge phase of a super step
- scaffolding work for fan-out nodes for parallel branch execution support and stubbed out Map node types
- Coyote can now update itself via .update and --update commands
- added a .edit command for editing the MCP configuration file
- Created a new .install command to install bundled assets on-demand
- migrated llm node validation to graph loading time instead of graph runtime
- ripped out user input timeout scaffolding for approval and input node types; implementation can't be done cleanly
- added additional support for all RAG-configuration fields in RAG nodes
- initial support for RAG nodes in the graph execution system
- implemented structured logging for graph execution
- merged normal agent config and graph agent configs into one file (either/or)
- added structured-output extraction for llm and agent nodes
- created full llm node runtime implementation
- scaffolded together the initial llm node type and its executor
- wired together graph execution and agent graph dispatch
- implemented support for the graph executor
- created the approval node executor and the input node executor for user interaction
- Added initial support for native Coyote agent nodes in the graph-based agent system
- Added direct script invocation support for graph-based agents
- Added graph validation
- Implemented state management for agent graphs
- initial agent graph scaffolding
- add auto-continue support to all contexts
- dynamic tab completions now show the sessions for a given agent instead of only listing global sessions
- legacy SSE support for MCP server configurations
- support http/sse transport types for MCP server configurations so it fully supports claude desktop-style MCP configs
- 99% complete migration to new state structs to get away from God-Config struct; i.e. AppConfig, AppState, and RequestContext
- Automatic runtime customization using shebangs
- Created a demo TypeScript tool and a get_current_weather function in TypeScript
- Updated the Python demo tool to show all possible parameter types and variations
- Added TypeScript tool support using the refactored common ScriptedLanguage trait
### Fix
- Generified the functions usage of script detection for an executable bit on unix systems
- merge required claude code system prompt into instructions
- updated argc argument passing in run-tool and run-agent scripts
- Added additional graph validation for parallel reads and writes with dependencies between nodes states
- bug in next_single method and improved outcome handling for LLM node execution
- inline RAG bug when globbing files by extension without subdirectory globbing
- update the estimate_token_length function to use the standard word count method
- removed unnecessary regenerate logic for sessions and use the same logic for all contexts; prevents a panic on empty message list
- error when users try to start a session on a graph agent
- added on_other field for approval nodes so users can specify an alternative free-text target when none of the options match what they want
- accidentally added back in full agent tools on LLM nodes
- Improve the coder agent's usage of tools
- make the agent__collect escalation-aware so it doesn't freeze on sub-agent escalations
- check for an existing session before starting up MCP servers when switching to a role
- do not switch to agent if a session is active.
- Do not append todo instructions when function calling is disabled
- a bug in the dynamic completions because the crate name is coyote-ai but the binary is named coyote
- bug found by copilot that would create a lock on the PollSender for sse-based MCP servers
- Accidental shadow of temp_file function for Windows function calling
- upgraded to newer rmcp version to get native-tls support
- RagCache was not being used for agent and sub-agent instantiation
- TypeScript function args were being passed as objects rather than direct parameters
- Added in forgotten wrapper scripts for TypeScript tools
- don't shadow variables in binary path handling for Windows
- Tool call improvements for Windows systems
### Refactor
- migrated llm nodes to use Roles to simplify instructions handling and to function like inline roles
- migrated the next_node and apply_state_updates logic for LLM nodes into the LlmExecutor
- fully complete state re-architecting
- Fully ripped out the god Config struct
- Deprecated old Config struct initialization logic
- migrate functions and MCP servers to AppConfig
- Migrate the vault/bare_init logic
- created a single install_builtins free function to remove from Config::init
- partial migration to init in AppConfig
- Extracted common Python parser logic into a common.rs module
- python tools now use tree-sitter queries instead of AST
## v0.3.0 (2026-04-02) ## v0.3.0 (2026-04-02)
### Feat ### Feat
@@ -21,7 +132,7 @@
- Created a CodeRabbit-style code-reviewer agent - Created a CodeRabbit-style code-reviewer agent
- Added configuration option in agents to indicate the timeout for user input before proceeding (defaults to 5 minutes) - Added configuration option in agents to indicate the timeout for user input before proceeding (defaults to 5 minutes)
- Added support for sub-agents to escalate user interaction requests from any depth to the parent agents for user interactions - Added support for sub-agents to escalate user interaction requests from any depth to the parent agents for user interactions
- built-in user interaction tools to remove the need for the list/confirm/etc prompts in prompt tools and to enhance user interactions in Loki - built-in user interaction tools to remove the need for the list/confirm/etc prompts in prompt tools and to enhance user interactions in Coyote
- Experimental update to sisyphus to use the new parallel agent spawning system - Experimental update to sisyphus to use the new parallel agent spawning system
- Added an agent configuration property that allows auto-injecting sub-agent spawning instructions (when using the built-in sub-agent spawning system) - Added an agent configuration property that allows auto-injecting sub-agent spawning instructions (when using the built-in sub-agent spawning system)
- Auto-dispatch support of sub-agents and support for the teammate pattern between subagents - Auto-dispatch support of sub-agents and support for the teammate pattern between subagents
@@ -75,7 +186,7 @@
- Simplified sisyphus prompt to improve functionality - 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 - 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 Sisyphus agent to make Coyote function like Claude Code, Gemini, Codex, etc.
- Created the Oracle agent to handle high-level architectural decisions and design questions about a given codebase - 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 - 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 - Created the explore agent for exploring codebases to help answer questions
@@ -135,8 +246,8 @@
- Support for secret injection into the global config file (API keys, for example) - Support for secret injection into the global config file (API keys, for example)
- Improved MCP handling toggle handling - Improved MCP handling toggle handling
- Secret injection into the MCP configuration - Secret injection into the MCP configuration
- added REPL support for interacting with the Loki vault - added REPL support for interacting with the Coyote vault
- Integrated gman with Loki to create a vault and added flags to configure the Loki vault - Integrated gman with Coyote to create a vault and added flags to configure the Coyote vault
- Added a default session to the jira helper to make interaction more natural - Added a default session to the jira helper to make interaction more natural
- Created the repo-analyzer role - Created the repo-analyzer role
- Created the coder and sql agents - Created the coder and sql agents
+2 -2
View File
@@ -2,7 +2,7 @@
Contributors are very welcome! **No contribution is too small and all contributions are valued.** Contributors are very welcome! **No contribution is too small and all contributions are valued.**
## Rust ## Rust
You'll need to have the stable Rust toolchain installed in order to develop Loki. You'll need to have the stable Rust toolchain installed in order to develop Coyote.
The Rust toolchain (stable) can be installed via rustup using the following command: The Rust toolchain (stable) can be installed via rustup using the following command:
@@ -84,5 +84,5 @@ Claude, etc.) is not permitted unless explicitly disclosed and approved.
Submissions must certify that the contributor understands and can maintain the code they submit. Submissions must certify that the contributor understands and can maintain the code they submit.
## Questions? Reach out to me! ## Questions? Reach out to me!
If you encounter any questions while developing Loki, please don't hesitate to reach out to me at If you encounter any questions while developing Coyote, 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! alex.j.tusa@gmail.com. I'm happy to help contributors in any way I can, regardless of if they're new or experienced!
+6 -6
View File
@@ -1,19 +1,19 @@
# Credits # Credits
## AIChat ## AIChat
Loki originally started as a fork of the fantastic Coyote originally started as a fork of the fantastic
[AIChat CLI](https://github.com/sigoden/aichat). The initial goal was simply [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 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 servers to be specified per agent. Since then, Coyote has evolved far beyond
its original scope and grown into a passion project with a life of its own. 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 Today, Coyote includes first-class MCP server support (for both local and remote
servers), a built-in vault for interpolating secrets in configuration files, servers), a built-in vault for interpolating secrets in configuration files,
built-in agents and macros, dynamic tab completions, integrated custom built-in agents and macros, dynamic tab completions, integrated custom
functions (no external `argc` dependency), improved documentation, and much functions (no external `argc` dependency), improved documentation, and much
more with many more ideas planned for the future. more with many more ideas planned for the future.
Loki is now developed and maintained as an independent project. Full credit Coyote is now developed and maintained as an independent project. Full credit
for the original foundation goes to the developers of the wonderful for the original foundation goes to the developers of the wonderful
AIChat project. AIChat project.
@@ -21,10 +21,10 @@ This project is not affiliated with or endorsed by the AIChat maintainers.
## AIChat ## AIChat
Loki originally began as a fork of [AIChat CLI](https://github.com/sigoden/aichat), Coyote originally began as a fork of [AIChat CLI](https://github.com/sigoden/aichat),
created and maintained by the AIChat contributors. created and maintained by the AIChat contributors.
While Loki has since diverged significantly and is now developed as an While Coyote has since diverged significantly and is now developed as an
independent project, its early foundation and inspiration came from the independent project, its early foundation and inspiration came from the
AIChat project. AIChat project.
Generated
+316 -196
View File
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -1,16 +1,16 @@
[package] [package]
name = "loki-ai" name = "coyote-ai"
version = "0.3.0" version = "0.5.0"
edition = "2024" edition = "2024"
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"] authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
description = "An all-in-one, batteries included LLM CLI Tool" description = "An all-in-one, batteries included LLM CLI Tool"
keywords = ["chatgpt", "llm", "cli", "ai", "repl"] keywords = ["chatgpt", "llm", "cli", "ai", "repl"]
homepage = "https://github.com/Dark-Alex-17/loki" homepage = "https://github.com/Dark-Alex-17/coyote"
repository = "https://github.com/Dark-Alex-17/loki" repository = "https://github.com/Dark-Alex-17/coyote"
categories = ["command-line-utilities"] categories = ["command-line-utilities"]
readme = "README.md" readme = "README.md"
license = "MIT" license = "MIT"
rust-version = "1.89.0" rust-version = "1.95.0"
exclude = [".github", "CONTRIBUTING.md"] exclude = [".github", "CONTRIBUTING.md"]
[dependencies] [dependencies]
@@ -138,7 +138,7 @@ pretty_assertions = "1.4.0"
serial_test = "3" serial_test = "3"
[[bin]] [[bin]]
name = "loki" name = "coyote"
path = "src/main.rs" path = "src/main.rs"
[profile.release] [profile.release]
+95 -94
View File
@@ -1,54 +1,54 @@
# Loki: All-in-one, batteries-included LLM CLI Tool # Coyote: All-in-one, batteries-included LLM CLI Tool
![Test](https://github.com/Dark-Alex-17/loki/actions/workflows/ci.yaml/badge.svg) ![Test](https://github.com/Dark-Alex-17/coyote/actions/workflows/ci.yaml/badge.svg)
[![crates.io link](https://img.shields.io/crates/v/loki-ai.svg)](https://crates.io/crates/loki-ai) [![crates.io link](https://img.shields.io/crates/v/coyote-ai.svg)](https://crates.io/crates/coyote-ai)
![Release](https://img.shields.io/github/v/release/Dark-Alex-17/loki?color=%23c694ff) ![Release](https://img.shields.io/github/v/release/Dark-Alex-17/coyote?color=%23c694ff)
![Crate.io downloads](https://img.shields.io/crates/d/loki-ai?label=Crate%20downloads) ![Crate.io downloads](https://img.shields.io/crates/d/coyote-ai?label=Crate%20downloads)
[![GitHub Downloads](https://img.shields.io/github/downloads/Dark-Alex-17/loki/total.svg?label=GitHub%20downloads)](https://github.com/Dark-Alex-17/loki/releases) [![GitHub Downloads](https://img.shields.io/github/downloads/Dark-Alex-17/coyote/total.svg?label=GitHub%20downloads)](https://github.com/Dark-Alex-17/coyote/releases)
Loki is an all-in-one, batteries-included, LLM CLI tool featuring Shell Assistant, CLI & REPL Mode, RAG, AI Tools & Coyote is an all-in-one, batteries-included, LLM CLI tool featuring Shell Assistant, CLI & REPL Mode, RAG, AI Tools &
Agents, and More. 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 It is designed to include a number of useful agents, roles, macros, and more so users can get up and running with Coyote
in as little time as possible. You can also install entire bundles of agents, roles, macros, tools, and MCP servers from in as little time as possible. You can also install entire bundles of agents, roles, macros, tools, and MCP servers from
any git repository — see [Sharing Configurations](#sharing-configurations). any git repository. See [Sharing Configurations](https://github.com/Dark-Alex-17/coyote/wiki/Sharing-Configurations) for more information.
![Agent example](https://raw.githubusercontent.com/wiki/Dark-Alex-17/loki/images/agents/sql.gif) ![Agent example](https://raw.githubusercontent.com/wiki/Dark-Alex-17/coyote/images/agents/sql.gif)
Coming from [AIChat](https://github.com/sigoden/aichat)? Follow the [migration guide](https://github.com/Dark-Alex-17/loki/wiki/AIChat-Migration) to get started. Coming from [AIChat](https://github.com/sigoden/aichat)? Follow the [migration guide](https://github.com/Dark-Alex-17/coyote/wiki/AIChat-Migration) to get started.
## Quick Links ## Quick Links
* [AIChat Migration Guide](https://github.com/Dark-Alex-17/loki/wiki/AIChat-Migration): Coming from AIChat? Follow the migration guide to get started. * [AIChat Migration Guide](https://github.com/Dark-Alex-17/coyote/wiki/AIChat-Migration): Coming from AIChat? Follow the migration guide to get started.
* [Installation](#install): Install Loki * [Installation](#install): Install Coyote
* [Getting Started](#getting-started): Get started with Loki by doing first-run setup steps. * [Getting Started](#getting-started): Get started with Coyote by doing first-run setup steps.
* [Sharing Configurations](https://github.com/Dark-Alex-17/loki/wiki/Sharing-Configurations): Install bundles of agents, roles, macros, tools, and MCP servers from any git repo, and share your own. * [Sharing Configurations](https://github.com/Dark-Alex-17/coyote/wiki/Sharing-Configurations): Install bundles of agents, roles, macros, tools, and MCP servers from any git repo, and share your own.
* [REPL](https://github.com/Dark-Alex-17/loki/wiki/REPL): Interactive Read-Eval-Print Loop for conversational interactions with LLMs and Loki. * [REPL](https://github.com/Dark-Alex-17/coyote/wiki/REPL): Interactive Read-Eval-Print Loop for conversational interactions with LLMs and Coyote.
* [Custom REPL Prompt](https://github.com/Dark-Alex-17/loki/wiki/REPL-Prompt): Customize the REPL prompt to provide useful contextual information. * [Custom REPL Prompt](https://github.com/Dark-Alex-17/coyote/wiki/REPL-Prompt): Customize the REPL prompt to provide useful contextual information.
* [Vault](https://github.com/Dark-Alex-17/loki/wiki/Vault): Securely store and manage sensitive information such as API keys and credentials. * [Vault](https://github.com/Dark-Alex-17/coyote/wiki/Vault): Securely store and manage sensitive information such as API keys and credentials.
* [Shell Integrations](https://github.com/Dark-Alex-17/loki/wiki/Shell-Integrations): Seamlessly integrate Loki with your shell environment for enhanced command-line assistance. * [Shell Integrations](https://github.com/Dark-Alex-17/coyote/wiki/Shell-Integrations): Seamlessly integrate Coyote with your shell environment for enhanced command-line assistance.
* [Function Calling](https://github.com/Dark-Alex-17/loki/wiki/Tools): Leverage function calling capabilities to extend Loki's functionality with custom tools * [Function Calling](https://github.com/Dark-Alex-17/coyote/wiki/Tools): Leverage function calling capabilities to extend Coyote's functionality with custom tools
* [Creating Custom Tools](https://github.com/Dark-Alex-17/loki/wiki/Custom-Tools): You can create your own custom tools to enhance Loki's capabilities. * [Creating Custom Tools](https://github.com/Dark-Alex-17/coyote/wiki/Custom-Tools): You can create your own custom tools to enhance Coyote's capabilities.
* [Create Custom Python Tools](https://github.com/Dark-Alex-17/loki/wiki/Custom-Tools#custom-python-based-tools) * [Create Custom Python Tools](https://github.com/Dark-Alex-17/coyote/wiki/Custom-Tools#custom-python-based-tools)
* [Create Custom TypeScript Tools](https://github.com/Dark-Alex-17/loki/wiki/Custom-Tools#custom-typescript-based-tools) * [Create Custom TypeScript Tools](https://github.com/Dark-Alex-17/coyote/wiki/Custom-Tools#custom-typescript-based-tools)
* [Create Custom Bash Tools](https://github.com/Dark-Alex-17/loki/wiki/Custom-Bash-Tools) * [Create Custom Bash Tools](https://github.com/Dark-Alex-17/coyote/wiki/Custom-Bash-Tools)
* [Bash Prompt Utilities](https://github.com/Dark-Alex-17/loki/wiki/Bash-Prompt-Helpers) * [Bash Prompt Utilities](https://github.com/Dark-Alex-17/coyote/wiki/Bash-Prompt-Helpers)
* [First-Class MCP Server Support](https://github.com/Dark-Alex-17/loki/wiki/MCP-Servers): Easily connect and interact with MCP servers for advanced functionality. * [First-Class MCP Server Support](https://github.com/Dark-Alex-17/coyote/wiki/MCP-Servers): Easily connect and interact with MCP servers for advanced functionality.
* [Macros](https://github.com/Dark-Alex-17/loki/wiki/Macros): Automate repetitive tasks and workflows with Loki "scripts" (macros). * [Macros](https://github.com/Dark-Alex-17/coyote/wiki/Macros): Automate repetitive tasks and workflows with Coyote "scripts" (macros).
* [RAG](https://github.com/Dark-Alex-17/loki/wiki/RAG): Retrieval-Augmented Generation for enhanced information retrieval and generation. * [RAG](https://github.com/Dark-Alex-17/coyote/wiki/RAG): Retrieval-Augmented Generation for enhanced information retrieval and generation.
* [Sessions](https://github.com/Dark-Alex-17/loki/wiki/Sessions): Manage and persist conversational contexts and settings across multiple interactions. * [Sessions](https://github.com/Dark-Alex-17/coyote/wiki/Sessions): Manage and persist conversational contexts and settings across multiple interactions.
* [Roles](https://github.com/Dark-Alex-17/loki/wiki/Roles): Customize model behavior for specific tasks or domains. * [Roles](https://github.com/Dark-Alex-17/coyote/wiki/Roles): Customize model behavior for specific tasks or domains.
* [Agents](https://github.com/Dark-Alex-17/loki/wiki/Agents): Leverage AI agents to perform complex tasks and workflows, including sub-agent spawning, teammate messaging, and user interaction tools. * [Agents](https://github.com/Dark-Alex-17/coyote/wiki/Agents): Leverage AI agents to perform complex tasks and workflows, including sub-agent spawning, teammate messaging, and user interaction tools.
* [Graph Agents](https://github.com/Dark-Alex-17/loki/wiki/Graph-Agents): Define an agent as a declarative, YAML-driven workflow. A directed graph of typed nodes (LLM calls, scripts, approvals, user input, RAG retrieval, sub-agent spawns). * [Graph Agents](https://github.com/Dark-Alex-17/coyote/wiki/Graph-Agents): Define an agent as a declarative, YAML-driven workflow. A directed graph of typed nodes (LLM calls, scripts, approvals, user input, RAG retrieval, sub-agent spawns).
* [Todo System](https://github.com/Dark-Alex-17/loki/wiki/TODO-System): Built-in task tracking for improved LLM reliability with smaller models. * [Todo System](https://github.com/Dark-Alex-17/coyote/wiki/TODO-System): Built-in task tracking for improved LLM reliability with smaller models.
* [Environment Variables](https://github.com/Dark-Alex-17/loki/wiki/Environment-Variables): Override and customize your Loki configuration at runtime with environment variables. * [Environment Variables](https://github.com/Dark-Alex-17/coyote/wiki/Environment-Variables): Override and customize your Coyote configuration at runtime with environment variables.
* [Client Configurations](https://github.com/Dark-Alex-17/loki/wiki/Clients): Configuration instructions for various LLM providers. * [Client Configurations](https://github.com/Dark-Alex-17/coyote/wiki/Clients): Configuration instructions for various LLM providers.
* [Authentication (API Key & OAuth)](https://github.com/Dark-Alex-17/loki/wiki/Clients#authentication): Authenticate with API keys or OAuth for subscription-based access. * [Authentication (API Key & OAuth)](https://github.com/Dark-Alex-17/coyote/wiki/Clients#authentication): Authenticate with API keys or OAuth for subscription-based access.
* [Patching API Requests](https://github.com/Dark-Alex-17/loki/wiki/Patches): Learn how to patch API requests for advanced customization. * [Patching API Requests](https://github.com/Dark-Alex-17/coyote/wiki/Patches): Learn how to patch API requests for advanced customization.
* [Custom Themes](https://github.com/Dark-Alex-17/loki/wiki/Themes): Change the look and feel of Loki to your preferences with custom themes. * [Custom Themes](https://github.com/Dark-Alex-17/coyote/wiki/Themes): Change the look and feel of Coyote to your preferences with custom themes.
* [History](#history): A history of how Loki came to be. * [History](#history): A history of how Coyote came to be.
## Prerequisites ## Prerequisites
Loki requires the following tools to be installed on your system: Coyote requires the following tools to be installed on your system:
* [jq](https://github.com/jqlang/jq) * [jq](https://github.com/jqlang/jq)
* `brew install jq` * `brew install jq`
* [usql](https://github.com/xo/usql) (For the `sql` agent) * [usql](https://github.com/xo/usql) (For the `sql` agent)
@@ -57,57 +57,57 @@ Loki requires the following tools to be installed on your system:
* [uv](https://docs.astral.sh/uv/getting-started/installation/) * [uv](https://docs.astral.sh/uv/getting-started/installation/)
* `curl -LsSf https://astral.sh/uv/install.sh | sh` * `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, These tools are used to provide various functionalities within Coyote, such as document processing, JSON manipulation,
etc., and they are used within agents and tools. etc., and they are used within agents and tools.
## Install ## Install
### Cargo ### Cargo
If you have Cargo installed, then you can install `loki` from Crates.io: If you have Cargo installed, then you can install `coyote` from Crates.io:
```shell ```shell
cargo install loki-ai # Binary name is `loki` cargo install coyote-ai # Binary name is `coyote`
# If you encounter issues installing, try installing with '--locked' # If you encounter issues installing, try installing with '--locked'
cargo install --locked loki-ai cargo install --locked coyote-ai
``` ```
### Homebrew (Mac/Linux) ### Homebrew (Mac/Linux)
To install Loki from Homebrew, install the `loki` tap. Then you'll be able to install `loki`: To install Coyote from Homebrew, install the `coyote` tap. Then you'll be able to install `coyote`:
```shell ```shell
brew tap Dark-Alex-17/loki brew tap Dark-Alex-17/coyote
brew install loki brew install coyote
# If you need to be more specific, use: # If you need to be more specific, use:
brew install Dark-Alex-17/loki/loki brew install Dark-Alex-17/coyote/coyote
``` ```
To upgrade `loki` using Homebrew: To upgrade `coyote` using Homebrew:
```shell ```shell
brew upgrade loki brew upgrade coyote
``` ```
### Scripts ### Scripts
#### Linux/MacOS (`bash`) #### 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 You can use the following command to run a bash script that downloads and installs the latest version of `coyote` for your
OS (Linux/MacOS) and architecture (x86_64/arm64): OS (Linux/MacOS) and architecture (x86_64/arm64):
```shell ```shell
curl -fsSL https://raw.githubusercontent.com/Dark-Alex-17/loki/main/install_loki.sh | bash curl -fsSL https://raw.githubusercontent.com/Dark-Alex-17/coyote/main/install_coyote.sh | bash
``` ```
#### Windows/Linux/MacOS (`PowerShell`) #### Windows/Linux/MacOS (`PowerShell`)
You can use the following command to run a PowerShell script that downloads and installs the latest version of `loki` You can use the following command to run a PowerShell script that downloads and installs the latest version of `coyote`
for your OS (Windows/Linux/MacOS) and architecture (x86_64/arm64): for your OS (Windows/Linux/MacOS) and architecture (x86_64/arm64):
```powershell ```powershell
powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/Dark-Alex-17/loki/main/scripts/install_loki.ps1 | iex" powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/Dark-Alex-17/coyote/main/scripts/install_coyote.ps1 | iex"
``` ```
### Manual ### Manual
Binaries are available on the [releases](https://github.com/Dark-Alex-17/loki/releases) page for the following platforms: Binaries are available on the [releases](https://github.com/Dark-Alex-17/coyote/releases) page for the following platforms:
| Platform | Architecture(s) | | Platform | Architecture(s) |
|----------------|-----------------| |----------------|-----------------|
@@ -118,102 +118,103 @@ Binaries are available on the [releases](https://github.com/Dark-Alex-17/loki/re
#### Windows Instructions #### Windows Instructions
To use a binary from the releases page on Windows, do the following: 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. 1. Download the latest [binary](https://github.com/Dark-Alex-17/coyote/releases) for your OS.
2. Use 7-Zip or TarTool to unpack the Tar file. 2. Use 7-Zip or TarTool to unpack the Tar file.
3. Run the executable `loki.exe`! 3. Run the executable `coyote.exe`!
#### Linux/MacOS Instructions #### Linux/MacOS Instructions
To use a binary from the releases page on Linux/MacOS, do the following: 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. 1. Download the latest [binary](https://github.com/Dark-Alex-17/coyote/releases) for your OS.
2. `cd` to the directory where you downloaded the binary. 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`) 3. Extract the binary with `tar -C /usr/local/bin -xzf coyote-<arch>.tar.gz` (Note: This may require `sudo`)
4. Now you can run `loki`! 4. Now you can run `coyote`!
## Updating ## Updating
Loki can update itself in place to the latest GitHub release. Run `loki --update` Coyote can update itself in place to the latest GitHub release. Run `coyote --update`
for the newest release, or `loki --update v0.4.0` for a specific version: for the newest release, or `coyote --update v0.4.0` for a specific version:
```shell ```shell
loki --update coyote --update
loki --update v0.4.0 coyote --update v0.4.0
``` ```
The same is available from within the REPL via `.update` and `.update v0.4.0`. The same is available from within the REPL via `.update` and `.update v0.4.0`.
If Loki was installed with a package manager, prefer that package manager so its If Coyote was installed with a package manager, prefer that package manager so its
records stay in sync with the binary on disk; i.e. `brew upgrade loki` for Homebrew, records stay in sync with the binary on disk; i.e. `brew upgrade coyote` for Homebrew,
or `cargo install --locked loki-ai` for Cargo. or `cargo install --locked coyote-ai` for Cargo.
When Loki detects a package-manager install it prints a warning and asks for When Coyote detects a package-manager install it prints a warning and asks for
confirmation. In a non-interactive shell (no TTY), pass `--force` to update confirmation. In a non-interactive shell (no TTY), pass `--force` to update
anyway: anyway:
```shell ```shell
loki --update --force coyote --update --force
``` ```
## Getting Started ## Getting Started
After installation, you can generate the configuration files and directories by simply running: After installation, you can generate the configuration files and directories by simply running:
```sh ```sh
loki --info coyote --info
``` ```
Then, you need to set up the Loki vault by creating a vault password file. Loki will do this for you automatically and Then, you need to set up the Coyote vault by creating a vault password file. Coyote 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: guide you through the process when you first attempt to access the vault. So, to get started, you can run:
```sh ```sh
loki --list-secrets coyote --list-secrets
``` ```
### Authentication ### Authentication
Each client in your configuration needs authentication (with a few exceptions; e.g. ollama). Most clients use an API key Each client in your configuration needs authentication (with a few exceptions; e.g. ollama). Most clients use an API key
(set via `api_key` in the config or through the [vault](https://github.com/Dark-Alex-17/loki/wiki/Vault)). For providers that support OAuth (e.g. Claude Pro/Max (set via `api_key` in the config or through the [vault](https://github.com/Dark-Alex-17/coyote/wiki/Vault)). For providers that support OAuth (e.g. Claude Pro/Max
subscribers, Google Gemini), you can authenticate with your existing subscription instead: subscribers, Google Gemini), you can authenticate with your existing subscription instead:
```yaml ```yaml
# In your config.yaml # In your config.yaml
clients: clients:
- type: claude - type: claude
name: my-claude-oauth
auth: oauth # Indicate you want to authenticate with OAuth instead of an API key auth: oauth # Indicate you want to authenticate with OAuth instead of an API key
``` ```
```sh ```sh
loki --authenticate claude coyote --authenticate my-claude-oauth
# Or via the REPL: .authenticate # Or via the REPL: .authenticate
``` ```
For full details, see the [authentication documentation](https://github.com/Dark-Alex-17/loki/wiki/Clients#authentication). For full details, see the [authentication documentation](https://github.com/Dark-Alex-17/coyote/wiki/Clients#authentication).
### Tab-Completions ### Tab-Completions
You can also enable tab completions to make using Loki easier. To do so, add the following to your shell profile: You can also enable tab completions to make using Coyote easier. To do so, add the following to your shell profile:
```shell ```shell
# Bash # Bash
# (add to: `~/.bashrc`) # (add to: `~/.bashrc`)
source <(COMPLETE=bash loki) source <(COMPLETE=bash coyote)
# Zsh # Zsh
# (add to: `~/.zshrc`) # (add to: `~/.zshrc`)
source <(COMPLETE=zsh loki) source <(COMPLETE=zsh coyote)
# Fish # Fish
# (add to: `~/.config/fish/config.fish`) # (add to: `~/.config/fish/config.fish`)
source <(COMPLETE=fish loki | psub) source <(COMPLETE=fish coyote | psub)
# Elvish # Elvish
# (add to: `~/.elvish/rc.elv`) # (add to: `~/.elvish/rc.elv`)
eval (E:COMPLETE=elvish loki | slurp) eval (E:COMPLETE=elvish coyote | slurp)
# PowerShell # PowerShell
# (add to: `$PROFILE`) # (add to: `$PROFILE`)
$env:COMPLETE = "powershell" $env:COMPLETE = "powershell"
loki | Out-String | Invoke-Expression coyote | Out-String | Invoke-Expression
``` ```
### Shell Integration ### Shell Integration
You can integrate Loki's Shell Assistant into your shell for enhanced command-line assistance. Add the code in the You can integrate Coyote'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 corresponding [shell integration script](./scripts/shell-integration) to your shell. Then, you can invoke Coyote to convert natural language to
shell commands by pressing `Alt-e`. For example: shell commands by pressing `Alt-e`. For example:
```shell ```shell
@@ -223,18 +224,18 @@ find . -name "*.md"
``` ```
## Configuration ## Configuration
The location of the global Loki configuration varies between systems, so you can use the following command to find your The location of the global Coyote configuration varies between systems, so you can use the following command to find your
`config.yaml` file: `config.yaml` file:
```shell ```shell
loki --info | grep 'config_file' | awk '{print $2}' coyote --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 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). defined, refer to the [example configuration file](./config.example.yaml).
### Default LLM ### Default LLM
The following settings are available to configure the default LLM that is used when you start Loki, and its The following settings are available to configure the default LLM that is used when you start Coyote, and its
hyperparameters: hyperparameters:
| Setting | Description | | Setting | Description |
@@ -244,34 +245,34 @@ hyperparameters:
| `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 | | `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 ### CLI Behavior
You can use the following settings to modify the behavior of Loki: You can use the following settings to modify the behavior of Coyote:
| Setting | Default Value | Description | | Setting | Default Value | Description |
|---------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------| |---------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------|
| `stream` | `true` | Controls whether to use stream-style APIs when querying for completions from LLM providers | | `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 | | `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` | | `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` | | `editor` | `null` | What text editor Coyote 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` | `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 | | `wrap_code` | `false` | Enables or disables the wrapping of code blocks |
### Preludes ### Preludes
Preludes let you define the default behavior for the different operating modes of Loki. The available settings are Preludes let you define the default behavior for the different operating modes of Coyote. The available settings are
shown below: shown below:
| Setting | Description | | Setting | Description |
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `repl_prelude` | This setting lets you specify a default `session` or `role` to use when starting Loki in [REPL](https://github.com/Dark-Alex-17/loki/wiki/REPL) 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> | | `repl_prelude` | This setting lets you specify a default `session` or `role` to use when starting Coyote in [REPL](https://github.com/Dark-Alex-17/coyote/wiki/REPL) 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> | | `cmd_prelude` | This setting lets you specify a default `session` or `role` to use when running one-off queries in Coyote 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`) | | `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 ### Appearance
The appearance of Loki can be modified using the following settings: The appearance of Coyote can be modified using the following settings:
| Setting | Default Value | Description | | Setting | Default Value | Description |
|---------------|---------------|------------------------------------------------------| |---------------|---------------|------------------------------------------------------|
| `highlight` | `true` | This setting enables or disables syntax highlighting | | `highlight` | `true` | This setting enables or disables syntax highlighting |
| `light_theme` | `false` | This setting toggles light mode in Loki | | `light_theme` | `false` | This setting toggles light mode in Coyote |
### Miscellaneous Settings ### Miscellaneous Settings
| Setting | Default Value | Description | | Setting | Default Value | Description |
@@ -283,7 +284,7 @@ The appearance of Loki can be modified using the following settings:
## History ## History
Loki began as a fork of [AIChat CLI](https://github.com/sigoden/aichat) and has since evolved into an independent project. Coyote 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. See [CREDITS.md](./CREDITS.md) for full attribution and background.
+4 -4
View File
@@ -7,14 +7,14 @@ set -euo pipefail
####################### #######################
# Cache file name for detected project info # Cache file name for detected project info
_LOKI_PROJECT_CACHE=".loki-project.json" _COYOTE_PROJECT_CACHE=".coyote-project.json"
# Read cached project detection if valid # Read cached project detection if valid
# Usage: _read_project_cache "/path/to/project" # Usage: _read_project_cache "/path/to/project"
# Returns: cached JSON on stdout (exit 0) or nothing (exit 1) # Returns: cached JSON on stdout (exit 0) or nothing (exit 1)
_read_project_cache() { _read_project_cache() {
local dir="$1" local dir="$1"
local cache_file="${dir}/${_LOKI_PROJECT_CACHE}" local cache_file="${dir}/${_COYOTE_PROJECT_CACHE}"
if [[ -f "${cache_file}" ]]; then if [[ -f "${cache_file}" ]]; then
local cached local cached
@@ -32,7 +32,7 @@ _read_project_cache() {
_write_project_cache() { _write_project_cache() {
local dir="$1" local dir="$1"
local json="$2" local json="$2"
local cache_file="${dir}/${_LOKI_PROJECT_CACHE}" local cache_file="${dir}/${_COYOTE_PROJECT_CACHE}"
echo "${json}" > "${cache_file}" 2>/dev/null || true echo "${json}" > "${cache_file}" 2>/dev/null || true
} }
@@ -238,7 +238,7 @@ _detect_with_llm() {
) )
local llm_response local llm_response
llm_response=$(loki --no-stream "${prompt}" 2>/dev/null) || return 1 llm_response=$(coyote --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}" | sed 's/^```json//;s/^```//;s/```$//' | tr -d '\n' | sed 's/^[[:space:]]*//')
llm_response=$(echo "${llm_response}" | grep -o '{[^}]*}' | head -1) llm_response=$(echo "${llm_response}" | grep -o '{[^}]*}' | head -1)
+3 -3
View File
@@ -4,7 +4,7 @@ A graph-based implementation agent. Plans, implements, and runs build +
tests in a bounded fix-loop until verified. Designed to be delegated to by tests in a bounded fix-loop until verified. Designed to be delegated to by
the **[Sisyphus](../sisyphus/README.md)** agent. the **[Sisyphus](../sisyphus/README.md)** agent.
Coder is a [graph agent](https://github.com/Dark-Alex-17/loki/wiki/Graph-Agents): its workflow is Coder is a [graph agent](https://github.com/Dark-Alex-17/coyote/wiki/Graph-Agents): its workflow is
defined declaratively in `graph.yaml`, with verification and the defined declaratively in `graph.yaml`, with verification and the
implement-fix loop enforced as graph edges rather than prose. implement-fix loop enforced as graph edges rather than prose.
@@ -42,10 +42,10 @@ so it accepts the runtime override flag:
```sh ```sh
# Invoke from inside the project (project_dir defaults to ".") # Invoke from inside the project (project_dir defaults to ".")
cd /path/to/your/project cd /path/to/your/project
loki -a coder "Add a foo() function..." coyote -a coder "Add a foo() function..."
# Or invoke from anywhere with an explicit override # Or invoke from anywhere with an explicit override
loki -a coder --agent-variable project_dir /path/to/your/project "Add..." coyote -a coder --agent-variable project_dir /path/to/your/project "Add..."
``` ```
`graph.yaml` `initial_state` exposes: `graph.yaml` `initial_state` exposes:
+4 -4
View File
@@ -17,8 +17,8 @@ variables:
- name: project_dir - name: project_dir
description: | description: |
Absolute path to the project directory. Defaults to "." which is the Absolute path to the project directory. Defaults to "." which is the
directory you invoked `loki` from. Override at runtime with directory you invoked `coyote` from. Override at runtime with
`loki -a coder --agent-variable project_dir /abs/path "..."`. `coyote -a coder --agent-variable project_dir /abs/path "..."`.
default: "." default: "."
settings: settings:
@@ -70,7 +70,7 @@ nodes:
MUST be absolute. The project root is {{project_dir}}. Prefer paths MUST be absolute. The project root is {{project_dir}}. Prefer paths
like "{{project_dir}}/src/foo.rs" over "src/foo.rs". The implementer like "{{project_dir}}/src/foo.rs" over "src/foo.rs". The implementer
uses these paths directly with fs_write and fs_patch tools, which uses these paths directly with fs_write and fs_patch tools, which
resolve relative paths against the loki invocation directory (NOT resolve relative paths against the coyote invocation directory (NOT
the project dir). Empty arrays are fine if no files in that category. the project dir). Empty arrays are fine if no files in that category.
`risks` is a list of short strings. Anything that could derail the `risks` is a list of short strings. Anything that could derail the
@@ -155,7 +155,7 @@ nodes:
2. Use `fs_write` for new files or full rewrites. 2. Use `fs_write` for new files or full rewrites.
3. NEVER output code to chat. Always use tools. 3. NEVER output code to chat. Always use tools.
4. ALWAYS pass ABSOLUTE paths to fs_write and fs_patch. Relative 4. ALWAYS pass ABSOLUTE paths to fs_write and fs_patch. Relative
paths resolve against the loki invocation directory (not the paths resolve against the coyote invocation directory (not the
project dir), which is rarely what you want. The project root project dir), which is rarely what you want. The project root
is {{project_dir}}. is {{project_dir}}.
+33 -33
View File
@@ -1,6 +1,6 @@
# deep-research # deep-research
A deep web research agent, built as a Loki graph agent. It plans an A deep web research agent, built as a Coyote graph agent. It plans an
investigation, decomposes it into sub-questions researched in investigation, decomposes it into sub-questions researched in
parallel, grounds the work in a local knowledge corpus, vets the parallel, grounds the work in a local knowledge corpus, vets the
credibility of cited sources, runs a reflexion self-critique loop to credibility of cited sources, runs a reflexion self-critique loop to
@@ -13,12 +13,12 @@ this agent runs a fixed graph: every request goes through the same
`plan -> parallel research -> vet -> critique -> synthesize -> verify -> approve` `plan -> parallel research -> vet -> critique -> synthesize -> verify -> approve`
pipeline. pipeline.
This agent is also the **canonical reference for the Loki graph This agent is also the **canonical reference for the Coyote graph
system**: it exercises every node type (`script`, `llm`, `rag`, `map`, system**: it exercises every node type (`script`, `llm`, `rag`, `map`,
`agent`, `input`, `approval`, `end`) and both static fan-out and `agent`, `input`, `approval`, `end`) and both static fan-out and
dynamic `map` fan-out. If you are learning how to build a graph dynamic `map` fan-out. If you are learning how to build a graph
agent, this is the file to read alongside the agent, this is the file to read alongside the
[Graph-Agents wiki](https://github.com/Dark-Alex-17/loki/wiki/Graph-Agents). [Graph-Agents wiki](https://github.com/Dark-Alex-17/coyote/wiki/Graph-Agents).
## Workflow ## Workflow
@@ -48,21 +48,21 @@ incorporate_feedback (script) -> research_each_question (the human-feedbac
### Node-type breakdown ### Node-type breakdown
| Type | Nodes | | Type | Nodes |
|---|---| |-----------------------------|-----------------------------------------------------------------------------------------------------------------------|
| `script` (Python) | `parse_request`, `bootstrap_research`, `combine_findings`, `reflexion_gate`, `verify_sources`, `incorporate_feedback` | | `script` (Python) | `parse_request`, `bootstrap_research`, `combine_findings`, `reflexion_gate`, `verify_sources`, `incorporate_feedback` |
| `llm` (tools: `[]`) | `plan`, `critique` | | `llm` (tools: `[]`) | `plan`, `critique` |
| `llm` (with tool whitelist) | `research_one_question`, `vet_sources` | | `llm` (with tool whitelist) | `research_one_question`, `vet_sources` |
| `rag` | `knowledge_lookup` — local corpus retrieval | | `rag` | `knowledge_lookup` — local corpus retrieval |
| `map` | `research_each_question` — dynamic fan-out per sub-question | | `map` | `research_each_question` — dynamic fan-out per sub-question |
| `agent` | `synthesize` — spawns the `report-writer` sub-agent | | `agent` | `synthesize` — spawns the `report-writer` sub-agent |
| `input` | `ask_topic` | | `input` | `ask_topic` |
| `approval` | `approve` | | `approval` | `approve` |
| `end` | `end_accepted`, `end_rejected` | | `end` | `end_accepted`, `end_rejected` |
## Parallel execution ## Parallel execution
The graph has two parallel super-steps where Loki's BSP scheduler runs The graph has two parallel super-steps where Coyote's BSP scheduler runs
branches concurrently. branches concurrently.
**1. Context loading (`plan` ‖ `knowledge_lookup`)** — after **1. Context loading (`plan` ‖ `knowledge_lookup`)** — after
@@ -96,7 +96,7 @@ PDFs, or text files into `knowledge/` to bias the research toward
your local context. your local context.
The knowledge base is built once, at agent-load time, into The knowledge base is built once, at agent-load time, into
`~/.config/loki/agents/deep-research/knowledge_lookup.yaml`. Because `~/.config/coyote/agents/deep-research/knowledge_lookup.yaml`. Because
the node fully specifies its build config (`embedding_model`, the node fully specifies its build config (`embedding_model`,
`chunk_size`, `chunk_overlap`), the build is non-interactive. Delete `chunk_size`, `chunk_overlap`), the build is non-interactive. Delete
that cached file after adding or changing knowledge to force a that cached file after adding or changing knowledge to force a
@@ -119,13 +119,13 @@ for details.
## Tools and tool scoping ## Tools and tool scoping
This agent demonstrates Loki's three tool sources and how an `llm` This agent demonstrates Coyote's three tool sources and how an `llm`
node's `tools:` whitelist scopes them per node. node's `tools:` whitelist scopes them per node.
The agent's full tool universe, declared in `graph.yaml`: The agent's full tool universe, declared in `graph.yaml`:
- **Global tools** (`global_tools`): `web_search_loki`, - **Global tools** (`global_tools`): `web_search_coyote`,
`fetch_url_via_curl`, `search_arxiv` - Loki's built-in tool scripts. `fetch_url_via_curl`, `search_arxiv` - Coyote's built-in tool scripts.
- **MCP server** (`mcp_servers`): `ddg-search` - a DuckDuckGo web - **MCP server** (`mcp_servers`): `ddg-search` - a DuckDuckGo web
search MCP server. Referenced in a whitelist as `mcp:ddg-search`. search MCP server. Referenced in a whitelist as `mcp:ddg-search`.
- **Custom agent tool** (`tools.sh`): `classify_source` - a - **Custom agent tool** (`tools.sh`): `classify_source` - a
@@ -134,11 +134,11 @@ The agent's full tool universe, declared in `graph.yaml`:
No node receives all of these. Each `llm` node's `tools:` whitelist No node receives all of these. Each `llm` node's `tools:` whitelist
narrows the universe to exactly what that step needs: narrows the universe to exactly what that step needs:
| Node | `tools:` whitelist | Draws from | | Node | `tools:` whitelist | Draws from |
|---|---|---| |-------------------------|-----------------------------------------------------------------------------|--------------------------|
| `plan`, `critique` | `[]` | nothing - pure reasoning | | `plan`, `critique` | `[]` | nothing - pure reasoning |
| `research_one_question` | `web_search_loki`, `fetch_url_via_curl`, `search_arxiv`, `mcp:ddg-search` | global tools + MCP | | `research_one_question` | `web_search_coyote`, `fetch_url_via_curl`, `search_arxiv`, `mcp:ddg-search` | global tools + MCP |
| `vet_sources` | `classify_source` | the custom tool only | | `vet_sources` | `classify_source` | the custom tool only |
`research_one_question` (each parallel branch of the map) can search `research_one_question` (each parallel branch of the map) can search
and fetch but cannot classify sources; `vet_sources` can classify and fetch but cannot classify sources; `vet_sources` can classify
@@ -153,21 +153,21 @@ deterministic - exactly the kind of logic a tool should own rather than
the LLM guessing. the LLM guessing.
Web search may require API-key configuration; see the Web search may require API-key configuration; see the
[Tools](https://github.com/Dark-Alex-17/loki/wiki/Tools) docs. [Tools](https://github.com/Dark-Alex-17/coyote/wiki/Tools) docs.
`fetch_url_via_curl`, `search_arxiv`, and `classify_source` work `fetch_url_via_curl`, `search_arxiv`, and `classify_source` work
without a key. without a key.
## Setup ## Setup
`research_one_question` (each parallel branch of the `map`) uses the `research_one_question` (each parallel branch of the `map`) uses the
`ddg-search` MCP server via `mcp:ddg-search`. It is one of Loki's `ddg-search` MCP server via `mcp:ddg-search`. It is one of Coyote's
default MCP servers; make sure it is registered in default MCP servers; make sure it is registered in
`~/.config/loki/mcp.json` (run `loki --install mcp_config` to restore `~/.config/coyote/mcp.json` (run `coyote --install mcp_config` to restore
the default template if it is missing). If `ddg-search` is unavailable, the default template if it is missing). If `ddg-search` is unavailable,
the branches still have their global web-search tools to fall back on. the branches still have their global web-search tools to fall back on.
The `synthesize` node spawns the `report-writer` sub-agent. Both The `synthesize` node spawns the `report-writer` sub-agent. Both
agents ship with `loki agents install`; if you install one manually, agents ship with `coyote agents install`; if you install one manually,
install both so the agent reference resolves. install both so the agent reference resolves.
## Reflexion ## Reflexion
@@ -205,10 +205,10 @@ backstop: it caps the total visits to any single node.
## Running ## Running
```sh ```sh
loki agents install # ships deep-research coyote agents install # ships deep-research
loki -a deep-research "How does HTTP/3 differ from HTTP/2?" coyote -a deep-research "How does HTTP/3 differ from HTTP/2?"
loki -a deep-research "Recent advances in solid-state batteries" coyote -a deep-research "Recent advances in solid-state batteries"
loki -a deep-research # no prompt -> triggers ask_topic coyote -a deep-research # no prompt -> triggers ask_topic
``` ```
## Anti-hallucination ## Anti-hallucination
@@ -240,7 +240,7 @@ loki -a deep-research # no prompt -> triggers ask_topic
`report-writer` sub-agent. `report-writer` sub-agent.
- **Tool scope.** Narrow the `research_one_question` node's `tools:` - **Tool scope.** Narrow the `research_one_question` node's `tools:`
list to constrain where each branch looks (for example, drop list to constrain where each branch looks (for example, drop
`web_search_loki` and `mcp:ddg-search` to force arXiv-only `web_search_coyote` and `mcp:ddg-search` to force arXiv-only
research). research).
- **Local knowledge.** Drop files into `knowledge/` to bias every - **Local knowledge.** Drop files into `knowledge/` to bias every
research branch toward your local context (see the *Local research branch toward your local context (see the *Local
+3 -3
View File
@@ -9,7 +9,7 @@ description: |
approval. A reviewer's free-form feedback at the approval step feeds approval. A reviewer's free-form feedback at the approval step feeds
back into another research pass. back into another research pass.
This is the canonical Loki graph-agent reference: it exercises every This is the canonical Coyote graph-agent reference: it exercises every
node type (script, llm, rag, map, agent, input, approval, end) and node type (script, llm, rag, map, agent, input, approval, end) and
both static fan-out and dynamic map fan-out. both static fan-out and dynamic map fan-out.
@@ -18,7 +18,7 @@ version: "1.0"
temperature: 0.0 temperature: 0.0
global_tools: global_tools:
- web_search_loki.sh - web_search_coyote.sh
- fetch_url_via_curl.sh - fetch_url_via_curl.sh
- search_arxiv.sh - search_arxiv.sh
@@ -147,7 +147,7 @@ nodes:
{{research_feedback}} {{research_feedback}}
tools: tools:
- web_search_loki - web_search_coyote
- fetch_url_via_curl - fetch_url_via_curl
- search_arxiv - search_arxiv
- mcp:ddg-search - mcp:ddg-search
@@ -5,7 +5,7 @@ hybrid (vector + keyword) retrieval over every file in this directory.
Drop your own notes, papers (PDFs), Markdown docs, or text files here Drop your own notes, papers (PDFs), Markdown docs, or text files here
and they will be indexed into a per-agent knowledge base on first run. and they will be indexed into a per-agent knowledge base on first run.
Loki supports common file types out of the box: `.md`, `.txt`, `.pdf`, Coyote supports common file types out of the box: `.md`, `.txt`, `.pdf`,
`.html`, and others. Subdirectories are walked recursively. `.html`, and others. Subdirectories are walked recursively.
A small starter file (`research-style-notes.md`) ships so the RAG A small starter file (`research-style-notes.md`) ships so the RAG
@@ -17,7 +17,7 @@ To force the knowledge base to rebuild after you add or change files,
delete the cached index: delete the cached index:
```sh ```sh
rm ~/.config/loki/agents/deep-research/knowledge_lookup.yaml rm ~/.config/coyote/agents/deep-research/knowledge_lookup.yaml
``` ```
The next run will rebuild from the current contents of this directory. The next run will rebuild from the current contents of this directory.
+1 -1
View File
@@ -2,6 +2,6 @@
This agent serves as a demo to guide agent development and showcase various agent capabilities. 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. To enable tools, Coyote 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`. The base configuration using `tools.py`. To switch to using `tools.sh`, rename or remove `tools.py`.
+2 -2
View File
@@ -17,7 +17,7 @@ It can also be used as a standalone tool for understanding codebases and finding
## Pro-Tip: Use an IDE MCP Server for Improved Performance ## Pro-Tip: Use an IDE MCP Server for Improved Performance
Many modern IDEs now include MCP servers that let LLMs perform operations within the IDE itself and use IDE tools. Using Many modern IDEs now include MCP servers that let LLMs perform operations within the IDE itself and use IDE tools. Using
an IDE's MCP server dramatically improves the performance of coding agents. So if you have an IDE, try adding that MCP an IDE's MCP server dramatically improves the performance of coding agents. So if you have an IDE, try adding that MCP
server to your config (see the [MCP Server docs](../../../docs/function-calling/MCP-SERVERS.md) to see how to configure server to your config (see the [MCP Server docs](https://github.com/Dark-Alex-17/loki/wiki/MCP-Servers) to see how to configure
them), and modify the agent definition to look like this: them), and modify the agent definition to look like this:
```yaml ```yaml
@@ -31,7 +31,7 @@ global_tools:
- fs_grep.sh - fs_grep.sh
- fs_glob.sh - fs_glob.sh
- fs_ls.sh - fs_ls.sh
- web_search_loki.sh - web_search_coyote.sh
# ... # ...
``` ```
+2 -2
View File
@@ -19,7 +19,7 @@ It can also be used as a standalone tool for design reviews and solving difficul
## Pro-Tip: Use an IDE MCP Server for Improved Performance ## Pro-Tip: Use an IDE MCP Server for Improved Performance
Many modern IDEs now include MCP servers that let LLMs perform operations within the IDE itself and use IDE tools. Using Many modern IDEs now include MCP servers that let LLMs perform operations within the IDE itself and use IDE tools. Using
an IDE's MCP server dramatically improves the performance of coding agents. So if you have an IDE, try adding that MCP an IDE's MCP server dramatically improves the performance of coding agents. So if you have an IDE, try adding that MCP
server to your config (see the [MCP Server docs](../../../docs/function-calling/MCP-SERVERS.md) to see how to configure server to your config (see the [MCP Server docs](https://github.com/Dark-Alex-17/loki/wiki/MCP-Servers) to see how to configure
them), and modify the agent definition to look like this: them), and modify the agent definition to look like this:
```yaml ```yaml
@@ -33,7 +33,7 @@ global_tools:
- fs_grep.sh - fs_grep.sh
- fs_glob.sh - fs_glob.sh
- fs_ls.sh - fs_ls.sh
- web_search_loki.sh - web_search_coyote.sh
# ... # ...
``` ```
+1 -1
View File
@@ -27,7 +27,7 @@ You can also use this agent directly if you have a set of findings you
want polished: want polished:
```sh ```sh
loki -a report-writer "Topic: X. Findings: <paste findings here>" coyote -a report-writer "Topic: X. Findings: <paste findings here>"
``` ```
It will produce a single Markdown report following the rules in its It will produce a single Markdown report following the rules in its
+4 -4
View File
@@ -1,6 +1,6 @@
# Sisyphus # Sisyphus
The main coordinator agent for the Loki coding ecosystem, providing a powerful CLI interface for code generation and The main coordinator agent for the Coyote coding ecosystem, providing a powerful CLI interface for code generation and
project management similar to OpenCode, ClaudeCode, Codex, or Gemini CLI. project management similar to OpenCode, ClaudeCode, Codex, or Gemini CLI.
_Inspired by the Sisyphus and Oracle agents of OpenCode._ _Inspired by the Sisyphus and Oracle agents of OpenCode._
@@ -19,8 +19,8 @@ Sisyphus acts as the primary entry point, capable of handling complex tasks by c
## Pro-Tip: Use an IDE MCP Server for Improved Performance ## Pro-Tip: Use an IDE MCP Server for Improved Performance
Many modern IDEs (JetBrains, VS Code, Cursor, Zed, etc.) expose MCP servers that let LLMs use IDE tools directly. Using Many modern IDEs (JetBrains, VS Code, Cursor, Zed, etc.) expose MCP servers that let LLMs use IDE tools directly. Using
one dramatically improves the performance of coding agents. If you have one, add it to your loki config (see the one dramatically improves the performance of coding agents. If you have one, add it to your coyote config (see the
[MCP Server docs](../../../docs/function-calling/MCP-SERVERS.md)) and reference it in this agent's `mcp_servers:` list: [MCP Server docs](https://github.com/Dark-Alex-17/loki/wiki/MCP-Servers)) and reference it in this agent's `mcp_servers:` list:
```yaml ```yaml
# ... # ...
@@ -33,7 +33,7 @@ global_tools:
- fs_grep.sh - fs_grep.sh
- fs_glob.sh - fs_glob.sh
- fs_ls.sh - fs_ls.sh
- web_search_loki.sh - web_search_coyote.sh
- execute_command.sh - execute_command.sh
# ... # ...
-1106
View File
File diff suppressed because it is too large Load Diff
@@ -6,11 +6,11 @@ set -e
# @option --query! The search query. # @option --query! The search query.
# @meta require-tools loki # @meta require-tools coyote
# @env WEB_SEARCH_MODEL=gemini:gemini-2.5-flash The model for web-searching. # @env WEB_SEARCH_MODEL=gemini:gemini-2.5-flash The model for web-searching.
# #
# supported loki models: # supported coyote models:
# - gemini:gemini-2.0-* # - gemini:gemini-2.0-*
# - vertexai:gemini-* # - vertexai:gemini-*
# - perplexity:* # - perplexity:*
@@ -22,15 +22,15 @@ main() {
client="${WEB_SEARCH_MODEL%%:*}" client="${WEB_SEARCH_MODEL%%:*}"
if [[ "$client" == "gemini" ]]; then if [[ "$client" == "gemini" ]]; then
export LOKI_PATCH_GEMINI_CHAT_COMPLETIONS='{".*":{"body":{"tools":[{"google_search":{}}]}}}' export COYOTE_PATCH_GEMINI_CHAT_COMPLETIONS='{".*":{"body":{"tools":[{"google_search":{}}]}}}'
elif [[ "$client" == "vertexai" ]]; then elif [[ "$client" == "vertexai" ]]; then
export LOKI_PATCH_VERTEXAI_CHAT_COMPLETIONS='{ export COYOTE_PATCH_VERTEXAI_CHAT_COMPLETIONS='{
"gemini-1.5-.*":{"body":{"tools":[{"googleSearchRetrieval":{}}]}}, "gemini-1.5-.*":{"body":{"tools":[{"googleSearchRetrieval":{}}]}},
"gemini-2.0-.*":{"body":{"tools":[{"google_search":{}}]}} "gemini-2.0-.*":{"body":{"tools":[{"google_search":{}}]}}
}' }'
elif [[ "$client" == "ernie" ]]; then elif [[ "$client" == "ernie" ]]; then
export LOKI_PATCH_ERNIE_CHAT_COMPLETIONS='{".*":{"body":{"web_search":{"enable":true}}}}' export COYOTE_PATCH_ERNIE_CHAT_COMPLETIONS='{".*":{"body":{"web_search":{"enable":true}}}}'
fi fi
loki -m "$WEB_SEARCH_MODEL" "$argc_query" >> "$LLM_OUTPUT" coyote -m "$WEB_SEARCH_MODEL" "$argc_query" >> "$LLM_OUTPUT"
} }
+15 -19
View File
@@ -506,16 +506,14 @@ open_link() {
} }
guard_operation() { guard_operation() {
if [[ -t 1 ]]; then if [[ -z "$AUTO_CONFIRM" && -z "$LLM_AGENT_VAR_AUTO_CONFIRM" ]]; then
if [[ -z "$AUTO_CONFIRM" && -z "$LLM_AGENT_VAR_AUTO_CONFIRM" ]]; then ans="$(confirm "${1:-Are you sure you want to continue?}")"
ans="$(confirm "${1:-Are you sure you want to continue?}")"
if [[ "$ans" == 0 ]]; then if [[ "$ans" == 0 ]]; then
error "Operation aborted!" 2>&1 error "Operation aborted!" 2>&1
exit 1 exit 1
fi
fi 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: # Here is an example of a patch block that can be applied to modify the file to request the user's name:
@@ -655,19 +653,17 @@ guard_path() {
exit 1 exit 1
fi fi
if [[ -t 1 ]]; then path="$(_to_real_path "$1")"
path="$(_to_real_path "$1")" confirmation_prompt="$2"
confirmation_prompt="$2"
if [[ ! "$path" == "$(pwd)"* && -z "$AUTO_CONFIRM" && -z "$LLM_AGENT_VAR_AUTO_CONFIRM" ]]; then if [[ ! "$path" == "$(pwd)"* && -z "$AUTO_CONFIRM" && -z "$LLM_AGENT_VAR_AUTO_CONFIRM" ]]; then
ans="$(confirm "$confirmation_prompt")" ans="$(confirm "$confirmation_prompt")"
if [[ "$ans" == 0 ]]; then if [[ "$ans" == 0 ]]; then
error "Operation aborted!" >&2 error "Operation aborted!" >&2
exit 1 exit 1
fi fi
fi fi
fi
} }
_to_real_path() { _to_real_path() {
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -9,7 +9,7 @@ security/configuration settings. The analysis aims to ensure a thorough understa
structured and operates, enabling the creation of new files, maintaining consistency with existing practices, and the structured and operates, enabling the creation of new files, maintaining consistency with existing practices, and the
potential implementation of best practices. 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 Should the root directory contain a `COYOTE.md` file, this was generated by Coyote and should be used as a reference
point for all analysis, style questions, etc. point for all analysis, style questions, etc.
**Objective:** Enable the AI to thoroughly analyze a software repository, providing detailed insights and guidelines on **Objective:** Enable the AI to thoroughly analyze a software repository, providing detailed insights and guidelines on
+7 -7
View File
@@ -1,5 +1,5 @@
# Agent-specific configuration # Agent-specific configuration
# Location `<loki-config-dir>/agents/<agent-name>/config.yaml` # Location `<coyote-config-dir>/agents/<agent-name>/config.yaml`
# #
# Available Environment Variables: # Available Environment Variables:
# - <agent-name>_MODEL # - <agent-name>_MODEL
@@ -21,14 +21,14 @@ version: 1 # Version of the agent
# The auto-continue system provides built-in task tracking for improved reliability. # The auto-continue system provides built-in task tracking for improved reliability.
# When enabled, the model can create todo lists and the system will automatically # When enabled, the model can create todo lists and the system will automatically
# prompt it to continue when incomplete tasks remain. # prompt it to continue when incomplete tasks remain.
# See the [Todo System documentation](https://github.com/Dark-Alex-17/loki/wiki/TODO-System) for more information # See the [Todo System documentation](https://github.com/Dark-Alex-17/coyote/wiki/TODO-System) for more information
auto_continue: false # Enable automatic continuation when incomplete todos remain auto_continue: false # Enable automatic continuation when incomplete todos remain
max_auto_continues: 10 # Maximum number of automatic continuations before stopping 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 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) continuation_prompt: null # Custom prompt used when auto-continuing (optional; uses default if null)
# Sub-Agent Spawning System # Sub-Agent Spawning System
# Enable this agent to spawn and manage child agents in parallel. # Enable this agent to spawn and manage child agents in parallel.
# See https://github.com/Dark-Alex-17/loki/wiki/Agents for detailed documentation. # See https://github.com/Dark-Alex-17/coyote/wiki/Agents for detailed documentation.
can_spawn_agents: false # Enable the agent to spawn child agents can_spawn_agents: false # Enable the agent to spawn child agents
max_concurrent_agents: 4 # Maximum number of agents that can run simultaneously max_concurrent_agents: 4 # Maximum number of agents that can run simultaneously
max_agent_depth: 3 # Maximum nesting depth for sub-agents (prevents runaway spawning) max_agent_depth: 3 # Maximum nesting depth for sub-agents (prevents runaway spawning)
@@ -37,7 +37,7 @@ summarization_model: null # Model to use for summarizing sub-agent output
summarization_threshold: 4000 # Character threshold above which sub-agent output is summarized before returning to parent summarization_threshold: 4000 # Character threshold above which sub-agent output is summarized before returning to parent
escalation_timeout: 300 # Seconds a sub-agent waits for a user interaction response before timing out (default: 5 minutes) escalation_timeout: 300 # Seconds a sub-agent waits for a user interaction response before timing out (default: 5 minutes)
mcp_servers: # Optional list of MCP servers that the agent utilizes 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 - github # Corresponds to the name of an MCP server in the `<coyote-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 global_tools: # Optional list of additional global tools to enable for the agent; i.e. not tools specific to the agent
- web_search - web_search
- fs - fs
@@ -80,10 +80,10 @@ conversation_starters: # Optional conversation starters for the agent
- What is the best way to exercise? - What is the best way to exercise?
- How do I manage my time effectively? - How do I manage my time effectively?
documents: # Optional documents to load for the agent documents: # Optional documents to load for the agent
- git:/some/repo # Explicitly tell Loki to use the 'git' document loader using an absolute path - git:/some/repo # Explicitly tell Coyote 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 - pdf:some-pdf-file.pdf # Explicitly tell Coyote to use the 'pdf' document loader using a relative path
- https://some-website.com/some-page - 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.pdf # File with relative path to the <coyote-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 - ~/some-file.txt # File in the user's home directory
- /absolute/path/to/some-file.md # File with absolute path - /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/**/NAME.txt # Find all NAME.txt files in the specified directory and all its subdirectories
+46 -46
View File
@@ -18,31 +18,31 @@ agent_session: null # Set a session to use when starting an agent (
# ---- Appearance ---- # ---- Appearance ----
highlight: true # Controls syntax highlighting highlight: true # Controls syntax highlighting
light_theme: false # Activates a light color theme when true. env: LOKI_LIGHT_THEME light_theme: false # Activates a light color theme when true. env: COYOTE_LIGHT_THEME
# ---- Miscellaneous ---- # ---- Miscellaneous ----
user_agent: null # Set User-Agent HTTP header, use `auto` for loki/<current-version> user_agent: null # Set User-Agent HTTP header, use `auto` for coyote/<current-version>
save_shell_history: true # Whether to save shell execution command to the history file save_shell_history: true # Whether to save shell execution command to the history file
sync_models_url: > # URL to sync model changes from sync_models_url: > # URL to sync model changes from
https://raw.githubusercontent.com/Dark-Alex-17/loki/refs/heads/main/models.yaml https://raw.githubusercontent.com/Dark-Alex-17/coyote/refs/heads/main/models.yaml
# ---- REPL Prompt ---- # ---- REPL Prompt ----
# Custom REPL left/right prompts; see the [REPL Prompt Documentation](https://github.com/Dark-Alex-17/loki/wiki/REPL-Prompt) for more information # Custom REPL left/right prompts; see the [REPL Prompt Documentation](https://github.com/Dark-Alex-17/coyote/wiki/REPL-Prompt) for more information
left_prompt: left_prompt:
'{color.red}{model}){color.green}{?session {?agent {agent}>}{session}{?role /}}{!session {?agent {agent}>}}{role}{?rag @{rag}}{color.cyan}{?session )}{!session >}{color.reset} ' '{color.red}{model}){color.green}{?session {?agent {agent}>}{session}{?role /}}{!session {?agent {agent}>}}{role}{?rag @{rag}}{color.cyan}{?session )}{!session >}{color.reset} '
right_prompt: right_prompt:
'{color.purple}{?session {?consume_tokens {consume_tokens}({consume_percent}%)}{!consume_tokens {consume_tokens}}}{color.reset}' '{color.purple}{?session {?consume_tokens {consume_tokens}({consume_percent}%)}{!consume_tokens {consume_tokens}}}{color.reset}'
# ---- Vault ---- # ---- Vault ----
# See the [Vault documentation](https://github.com/Dark-Alex-17/loki/wiki/Vault) for more information on the Loki vault # See the [Vault documentation](https://github.com/Dark-Alex-17/coyote/wiki/Vault) for more information on the Coyote vault
vault_password_file: null # Path to a file containing the password for the Loki vault (cannot be a secret template) vault_password_file: null # Path to a file containing the password for the Coyote vault (cannot be a secret template)
# ---- Function Calling ---- # ---- Function Calling ----
# See the [Tools documentation](https://github.com/Dark-Alex-17/loki/wiki/Tools) for more details # See the [Tools documentation](https://github.com/Dark-Alex-17/coyote/wiki/Tools) for more details
function_calling: true # Enables or disables function calling (Globally). function_calling: true # Enables or disables function calling (Globally).
mapping_tools: # Alias for a tool or toolset 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' 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') enabled_tools: null # Which tools to enable by default. (e.g. 'fs,web_search_coyote')
visible_tools: # Which tools are visible to be compiled (and are thus able to be defined in 'enabled_tools') visible_tools: # Which tools are visible to be compiled (and are thus able to be defined in 'enabled_tools')
# - demo_py.py # - demo_py.py
# - demo_sh.sh # - demo_sh.sh
@@ -69,12 +69,12 @@ visible_tools: # Which tools are visible to be compiled (and a
# - search_wolframalpha.sh # - search_wolframalpha.sh
# - send_mail.sh # - send_mail.sh
# - send_twilio.sh # - send_twilio.sh
# - web_search_loki.sh # - web_search_coyote.sh
# - web_search_perplexity.sh # - web_search_perplexity.sh
# - web_search_tavily.sh # - web_search_tavily.sh
# ---- MCP Servers ---- # ---- MCP Servers ----
# See the [MCP Servers documentation](https://github.com/Dark-Alex-17/loki/wiki/MCP-Servers) for more details # See the [MCP Servers documentation](https://github.com/Dark-Alex-17/coyote/wiki/MCP-Servers) for more details
mcp_server_support: true # Enables or disables MCP servers (globally). mcp_server_support: true # Enables or disables MCP servers (globally).
mapping_mcp_servers: # Alias for an MCP server or set of servers mapping_mcp_servers: # Alias for an MCP server or set of servers
git: github,gitmcp git: github,gitmcp
@@ -84,14 +84,14 @@ enabled_mcp_servers: null # Which MCP servers to enable by default (e.g.
# The auto-continue system provides built-in task tracking for improved reliability. # The auto-continue system provides built-in task tracking for improved reliability.
# When enabled, the model can create todo lists and the system will automatically # When enabled, the model can create todo lists and the system will automatically
# prompt it to continue when incomplete tasks remain. # prompt it to continue when incomplete tasks remain.
# See the [Todo System documentation](https://github.com/Dark-Alex-17/loki/wiki/TODO-System) for more information # See the [Todo System documentation](https://github.com/Dark-Alex-17/coyote/wiki/TODO-System) for more information
auto_continue: false # Enable automatic continuation when incomplete todos remain (default: false) auto_continue: false # Enable automatic continuation when incomplete todos remain (default: false)
max_auto_continues: 10 # Maximum number of automatic continuations before stopping (default: 10) max_auto_continues: 10 # Maximum number of automatic continuations before stopping (default: 10)
inject_todo_instructions: true # Inject default todo usage instructions into the system prompt (default: true) inject_todo_instructions: true # Inject default todo usage instructions into the system prompt (default: true)
continuation_prompt: null # Custom prompt used when auto-continuing. If null, uses built-in default continuation_prompt: null # Custom prompt used when auto-continuing. If null, uses built-in default
# ---- Session ---- # ---- Session ----
# See the [Session documentation](https://github.com/Dark-Alex-17/loki/wiki/Sessions) for more information # See the [Session documentation](https://github.com/Dark-Alex-17/coyote/wiki/Sessions) 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 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 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 summarization_prompt: > # The text prompt used for creating a concise summary of session message
@@ -100,9 +100,9 @@ summary_context_prompt: > # The text prompt used for including the summar
'This is a summary of the chat history as a recap: ' 'This is a summary of the chat history as a recap: '
# ---- RAG ---- # ---- RAG ----
# See the [RAG Docs](https://github.com/Dark-Alex-17/loki/wiki/RAG) for more details. # See the [RAG Docs](https://github.com/Dark-Alex-17/coyote/wiki/RAG) for more details.
rag_embedding_model: null # Specifies the embedding model used for context retrieval 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_reranker_model: null # Specifies the reranker model used for sorting retrieved documents; Coyote uses Reciprocal Rank Fusion by default
rag_top_k: 5 # Specifies the number of documents to retrieve for answering queries 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_size: null # Defines the size of chunks for document processing in characters
rag_chunk_overlap: null # Defines the overlap between chunks rag_chunk_overlap: null # Defines the overlap between chunks
@@ -141,12 +141,12 @@ document_loaders:
docx: 'pandoc --to plain $1' # Use pandoc to convert a .docx file to text 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) # (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; 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 # Requires a Jina API key to be added to the Coyote vault
git: > # Use yek to load a git repository into the knowledgebase (https://github.com/bodo-run/yek) 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 })'" sh -c "yek $1 --json | jq 'map({ path: .filename, contents: .content })'"
# ---- Clients ---- # ---- Clients ----
# See the [Clients documentation](https://github.com/Dark-Alex-17/loki/wiki/Clients) for more details # See the [Clients documentation](https://github.com/Dark-Alex-17/coyote/wiki/Clients) for more details
clients: clients:
# All clients have the following configuration: # All clients have the following configuration:
# - type: xxxx # - type: xxxx
@@ -177,14 +177,14 @@ clients:
# See https://platform.openai.com/docs/quickstart # See https://platform.openai.com/docs/quickstart
- type: openai - type: openai
api_base: https://api.openai.com/v1 # Optional 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 api_key: '{{OPENAI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
organization_id: org-xxx # Optional organization_id: org-xxx # Optional
# For any platform compatible with OpenAI's API # For any platform compatible with OpenAI's API
- type: openai-compatible - type: openai-compatible
name: ollama name: ollama
api_base: http://localhost:11434/v1 api_base: http://localhost:11434/v1
api_key: '{{OLLAMA_API_KEY}}' # Optional; You can either hard-code or inject secrets from the Loki vault api_key: '{{OLLAMA_API_KEY}}' # Optional; You can either hard-code or inject secrets from the Coyote vault
models: models:
- name: deepseek-r1 - name: deepseek-r1
max_input_tokens: 131072 max_input_tokens: 131072
@@ -202,9 +202,9 @@ clients:
# See https://ai.google.dev/docs # See https://ai.google.dev/docs
- type: gemini - type: gemini
api_base: https://generativelanguage.googleapis.com/v1beta api_base: https://generativelanguage.googleapis.com/v1beta
api_key: '{{GEMINI_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault api_key: '{{GEMINI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
auth: null # When set to 'oauth', Loki will use OAuth instead of an API key auth: null # When set to 'oauth', Coyote will use OAuth instead of an API key
# Authenticate with `loki --authenticate` or `.authenticate` in the REPL # Authenticate with `coyote --authenticate` or `.authenticate` in the REPL
patch: patch:
chat_completions: chat_completions:
'.*': '.*':
@@ -222,49 +222,49 @@ clients:
# See https://docs.anthropic.com/claude/reference/getting-started-with-the-api # See https://docs.anthropic.com/claude/reference/getting-started-with-the-api
- type: claude - type: claude
api_base: https://api.anthropic.com/v1 # Optional 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 api_key: '{{ANTHROPIC_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
auth: null # When set to 'oauth', Loki will use OAuth instead of an API key auth: null # When set to 'oauth', Coyote will use OAuth instead of an API key
# Authenticate with `loki --authenticate` or `.authenticate` in the REPL # Authenticate with `coyote --authenticate` or `.authenticate` in the REPL
# See https://docs.mistral.ai/ # See https://docs.mistral.ai/
- type: openai-compatible - type: openai-compatible
name: mistral name: mistral
api_base: https://api.mistral.ai/v1 api_base: https://api.mistral.ai/v1
api_key: '{{MISTRAL_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault api_key: '{{MISTRAL_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# See https://docs.x.ai/docs # See https://docs.x.ai/docs
- type: openai-compatible - type: openai-compatible
name: xai name: xai
api_base: https://api.x.ai/v1 api_base: https://api.x.ai/v1
api_key: '{{XAI_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault api_key: '{{XAI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# See https://docs.ai21.com/docs/overview # See https://docs.ai21.com/docs/overview
- type: openai-compatible - type: openai-compatible
name: ai12 name: ai12
api_base: https://api.ai21.com/studio/v1 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 api_key: '{{AI21_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# See https://docs.cohere.com/docs/the-cohere-platform # See https://docs.cohere.com/docs/the-cohere-platform
- type: cohere - type: cohere
api_base: https://api.cohere.ai/v2 # Optional 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 api_key: '{{COHERE_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# See https://docs.perplexity.ai/getting-started/overview # See https://docs.perplexity.ai/getting-started/overview
- type: openai-compatible - type: openai-compatible
name: perplexity name: perplexity
api_base: https://api.perplexity.ai api_base: https://api.perplexity.ai
api_key: '{{PERPLEXITY_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault api_key: '{{PERPLEXITY_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# See https://console.groq.com/docs/quickstart # See https://console.groq.com/docs/quickstart
- type: openai-compatible - type: openai-compatible
name: groq name: groq
api_base: https://api.groq.com/openai/v1 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 api_key: '{{GROQ_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# See https://learn.microsoft.com/en-us/azure/ai-services/openai/chatgpt-quickstart # See https://learn.microsoft.com/en-us/azure/ai-services/openai/chatgpt-quickstart
- type: azure-openai - type: azure-openai
api_base: https://{RESOURCE}.openai.azure.com 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 api_key: '{{AZURE_OPENAI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
models: models:
- name: gpt-4o # Model deployment name - name: gpt-4o # Model deployment name
max_input_tokens: 128000 max_input_tokens: 128000
@@ -295,8 +295,8 @@ clients:
# See https://docs.aws.amazon.com/bedrock/latest/userguide/ # See https://docs.aws.amazon.com/bedrock/latest/userguide/
- type: bedrock - type: bedrock
access_key_id: '{{AWS_ACCESS_KEY_ID}}' # You can either hard-code or inject secrets from the Loki vault access_key_id: '{{AWS_ACCESS_KEY_ID}}' # You can either hard-code or inject secrets from the Coyote vault
secret_access_key: '{{AWS_SECRET_ACCESS_KEY}}' # 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 Coyote vault
region: xxx region: xxx
session_token: xxx # Optional, only needed for temporary credentials session_token: xxx # Optional, only needed for temporary credentials
@@ -304,67 +304,67 @@ clients:
- type: openai-compatible - type: openai-compatible
name: cloudflare name: cloudflare
api_base: https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/ai/v1 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 api_key: '{{CLOUDFLARE_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# See https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html # See https://cloud.baidu.com/doc/WENXINWORKSHOP/index.html
- type: openai-compatible - type: openai-compatible
name: ernie name: ernie
api_base: https://qianfan.baidubce.com/v2 api_base: https://qianfan.baidubce.com/v2
api_key: '{{BAIDU_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault api_key: '{{BAIDU_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# See https://dashscope.aliyun.com/ # See https://dashscope.aliyun.com/
- type: openai-compatible - type: openai-compatible
name: qianwen name: qianwen
api_base: https://dashscope.aliyuncs.com/compatible-mode/v1 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 api_key: '{{ALIYUN_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# See https://cloud.tencent.com/product/hunyuan # See https://cloud.tencent.com/product/hunyuan
- type: openai-compatible - type: openai-compatible
name: hunyuan name: hunyuan
api_base: https://api.hunyuan.cloud.tencent.com/v1 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 api_key: '{{TENCENT_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# See https://platform.moonshot.cn/docs/intro # See https://platform.moonshot.cn/docs/intro
- type: openai-compatible - type: openai-compatible
name: moonshot name: moonshot
api_base: https://api.moonshot.cn/v1 api_base: https://api.moonshot.cn/v1
api_key: '{{MOONSHOT_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault api_key: '{{MOONSHOT_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# See https://platform.deepseek.com/api-docs/ # See https://platform.deepseek.com/api-docs/
- type: openai-compatible - type: openai-compatible
name: deepseek name: deepseek
api_base: https://api.deepseek.com api_base: https://api.deepseek.com
api_key: '{{DEEPSEEK_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault api_key: '{{DEEPSEEK_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# See https://open.bigmodel.cn/dev/howuse/introduction # See https://open.bigmodel.cn/dev/howuse/introduction
- type: openai-compatible - type: openai-compatible
name: zhipuai name: zhipuai
api_base: https://open.bigmodel.cn/api/paas/v4 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 api_key: '{{ZHIPUAI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# See https://platform.minimaxi.com/document/Fast%20access # See https://platform.minimaxi.com/document/Fast%20access
- type: openai-compatible - type: openai-compatible
name: minimax name: minimax
api_base: https://api.minimax.chat/v1 api_base: https://api.minimax.chat/v1
api_key: '{{MINIMAX_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault api_key: '{{MINIMAX_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# See https://openrouter.ai/docs#quick-start # See https://openrouter.ai/docs#quick-start
- type: openai-compatible - type: openai-compatible
name: openrouter name: openrouter
api_base: https://openrouter.ai/api/v1 api_base: https://openrouter.ai/api/v1
api_key: '{{OPENROUTER_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault api_key: '{{OPENROUTER_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# See https://github.com/marketplace/models # See https://github.com/marketplace/models
- type: openai-compatible - type: openai-compatible
name: github name: github
api_base: https://models.inference.ai.azure.com 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 api_key: '{{GITHUB_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# See https://deepinfra.com/docs # See https://deepinfra.com/docs
- type: openai-compatible - type: openai-compatible
name: deepinfra name: deepinfra
api_base: https://api.deepinfra.com/v1/openai 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 api_key: '{{DEEPINFRA_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# ----- RAG dedicated ----- # ----- RAG dedicated -----
@@ -373,10 +373,10 @@ clients:
- type: openai-compatible - type: openai-compatible
name: jina name: jina
api_base: https://api.jina.ai/v1 api_base: https://api.jina.ai/v1
api_key: '{{JINA_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault api_key: '{{JINA_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
# See https://docs.voyageai.com/docs/introduction # See https://docs.voyageai.com/docs/introduction
- type: openai-compatible - type: openai-compatible
name: voyageai name: voyageai
api_base: https://api.voyageai.com/v1 api_base: https://api.voyageai.com/v1
api_key: '{{VOYAGEAI_API_KEY}}' # You can either hard-code or inject secrets from the Loki vault api_key: '{{VOYAGEAI_API_KEY}}' # You can either hard-code or inject secrets from the Coyote vault
+1 -1
View File
@@ -16,7 +16,7 @@ prompt: null # A custom prompt to use for this role tha
# The auto-continue system provides built-in task tracking for improved reliability. # The auto-continue system provides built-in task tracking for improved reliability.
# When enabled, the model can create todo lists and the system will automatically # When enabled, the model can create todo lists and the system will automatically
# prompt it to continue when incomplete tasks remain. # prompt it to continue when incomplete tasks remain.
# See the [Todo System documentation](https://github.com/Dark-Alex-17/loki/wiki/TODO-System) for more information # See the [Todo System documentation](https://github.com/Dark-Alex-17/coyote/wiki/TODO-System) for more information
auto_continue: false # Enable automatic continuation when incomplete todos remain (default: false) auto_continue: false # Enable automatic continuation when incomplete todos remain (default: false)
max_auto_continues: 10 # Maximum number of automatic continuations before stopping (default: 10) max_auto_continues: 10 # Maximum number of automatic continuations before stopping (default: 10)
inject_todo_instructions: true # Inject default todo tool usage instructions into the system prompt (default: true) inject_todo_instructions: true # Inject default todo tool usage instructions into the system prompt (default: true)
+23
View File
@@ -0,0 +1,23 @@
# Documentation: https://docs.brew.sh/Formula-Cookbook
# https://rubydoc.brew.sh/Formula
class Coyote < Formula
desc "All-in-one, batteries included LLM CLI tool"
homepage "https://github.com/Dark-Alex-17/coyote"
if OS.mac? and Hardware::CPU.arm?
url "https://github.com/Dark-Alex-17/coyote/releases/download/v$version/coyote-aarch64-apple-darwin.tar.gz"
sha256 "$hash_mac_arm"
elsif OS.mac? and Hardware::CPU.intel?
url "https://github.com/Dark-Alex-17/coyote/releases/download/v$version/coyote-x86_64-apple-darwin.tar.gz"
sha256 "$hash_mac"
else
url "https://github.com/Dark-Alex-17/coyote/releases/download/v$version/coyote-x86_64-unknown-linux-musl.tar.gz"
sha256 "$hash_linux"
end
version "$version"
license "MIT"
def install
bin.install "coyote"
ohai "You're done! Get started with \"coyote --help\""
end
end
-23
View File
@@ -1,23 +0,0 @@
# 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
+14 -14
View File
@@ -1,5 +1,5 @@
# Graph-based agent definition (full-featured reference) # Graph-based agent definition (full-featured reference)
# Location: <loki-config-dir>/agents/<agent-name>/graph.yaml # Location: <coyote-config-dir>/agents/<agent-name>/graph.yaml
# #
# A graph agent is defined by this file alone. An agent directory contains # A graph agent is defined by this file alone. An agent directory contains
# either a config.yaml (a normal LLM-loop agent) or a graph.yaml (a graph # either a config.yaml (a normal LLM-loop agent) or a graph.yaml (a graph
@@ -13,7 +13,7 @@
# runnable deep-research graph agent, see assets/agents/deep-research/. # runnable deep-research graph agent, see assets/agents/deep-research/.
# #
# Full documentation: # Full documentation:
# https://github.com/Dark-Alex-17/loki/wiki/Graph-Agents # https://github.com/Dark-Alex-17/coyote/wiki/Graph-Agents
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Identity # Identity
@@ -35,7 +35,7 @@ temperature: 0.0 # Default sampling temperature for `llm` node
top_p: null # Default sampling top-p for `llm` nodes top_p: null # Default sampling top-p for `llm` nodes
global_tools: # Tool universe an `llm` node's `tools:` whitelist draws from global_tools: # Tool universe an `llm` node's `tools:` whitelist draws from
- web_search_loki.sh - web_search_coyote.sh
- fetch_url_via_curl.sh - fetch_url_via_curl.sh
mcp_servers: # MCP servers an `llm` node may reference via `mcp:<server>` mcp_servers: # MCP servers an `llm` node may reference via `mcp:<server>`
@@ -52,7 +52,7 @@ conversation_starters: # Suggested prompts surfaced in the UI
# (see initial_state below). # (see initial_state below).
# - Script nodes via the env var `LLM_AGENT_VAR_<UPPER_NAME>`. # - Script nodes via the env var `LLM_AGENT_VAR_<UPPER_NAME>`.
# Values may be overridden at runtime with # Values may be overridden at runtime with
# `loki -a <agent> --agent-variable <name> <value> "..."`. # `coyote -a <agent> --agent-variable <name> <value> "..."`.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
variables: variables:
- name: project_dir - name: project_dir
@@ -103,7 +103,7 @@ reducers:
# Values placed into graph state before any node runs; reference anywhere via # Values placed into graph state before any node runs; reference anywhere via
# {{key}}. # {{key}}.
# #
# Note: `initial_prompt` is seeded automatically by Loki with the # Note: `initial_prompt` is seeded automatically by Coyote with the
# caller's prompt. So there's no need to set it here. # caller's prompt. So there's no need to set it here.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
initial_state: initial_state:
@@ -123,7 +123,7 @@ start: triage # ID of the first node to run (must exist in `nodes
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Nodes # Nodes
# Each node is keyed by its id. The `id:` inside a node must match its key # Each node is keyed by its id. The `id:` inside a node must match its key
# (it may also be omitted and thus Loki fills it in from the key). # (it may also be omitted and thus Coyote fills it in from the key).
# #
# Node types: agent | script | approval | input | llm | rag | map | end # Node types: agent | script | approval | input | llm | rag | map | end
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -202,7 +202,7 @@ nodes:
instructions: "You are a web researcher. Cite every claim." instructions: "You are a web researcher. Cite every claim."
prompt: "Web research: {{topic}}. Return findings and sources." prompt: "Web research: {{topic}}. Return findings and sources."
tools: tools:
- web_search_loki - web_search_coyote
- mcp:ddg-search - mcp:ddg-search
output_schema: output_schema:
type: object type: object
@@ -226,13 +226,13 @@ nodes:
# The script also receives these env vars (parity with bash tools called # The script also receives these env vars (parity with bash tools called
# from normal agents): # from normal agents):
# GRAPH_STATE / GRAPH_STATE_FILE state payload (one of the two is set) # GRAPH_STATE / GRAPH_STATE_FILE state payload (one of the two is set)
# LLM_ROOT_DIR loki config dir # LLM_ROOT_DIR coyote config dir
# LLM_PROMPT_UTILS_FILE path to .shared/prompt-utils.sh # LLM_PROMPT_UTILS_FILE path to .shared/prompt-utils.sh
# LLM_AGENT_DATA_DIR this agent's data directory # LLM_AGENT_DATA_DIR this agent's data directory
# LLM_AGENT_VAR_<NAME> one per declared `variables:` entry # LLM_AGENT_VAR_<NAME> one per declared `variables:` entry
# PATH with loki's functions bin dir prepended # PATH with coyote's functions bin dir prepended
# CLICOLOR_FORCE / FORCE_COLOR so child tools emit ANSI colors # CLICOLOR_FORCE / FORCE_COLOR so child tools emit ANSI colors
# The script's working directory is loki's invocation CWD (not the agent # The script's working directory is coyote's invocation CWD (not the agent
# directory), matching the behavior of bash tools. # directory), matching the behavior of bash tools.
# #
# This node fires once: after both `retrieve` and `web_search` finish. # This node fires once: after both `retrieve` and `web_search` finish.
@@ -256,13 +256,13 @@ nodes:
# targets. # targets.
# --- agent node --------------------------------------------------------- # --- agent node ---------------------------------------------------------
# Spawns a full Loki sub-agent and waits for it. The child uses its own # Spawns a full Coyote sub-agent and waits for it. The child uses its own
# tool stack. Agent nodes have no `tools:` field. No schema hint is # tool stack. Agent nodes have no `tools:` field. No schema hint is
# injected even when `output_schema` is set (unlike llm nodes). # injected even when `output_schema` is set (unlike llm nodes).
deep_dive: deep_dive:
id: deep_dive id: deep_dive
type: agent type: agent
agent: deep-research # Name of an existing Loki agent to spawn agent: deep-research # Name of an existing Coyote agent to spawn
prompt: | # User message sent to the child (templated) prompt: | # User message sent to the child (templated)
Research {{topic}} in depth. Existing context: Research {{topic}} in depth. Existing context:
{{context}} {{context}}
@@ -325,7 +325,7 @@ nodes:
instructions: "Research one subject deeply for a {{audience}} audience." instructions: "Research one subject deeply for a {{audience}} audience."
prompt: "Research {{subject}}: pull the key facts and one citation." prompt: "Research {{subject}}: pull the key facts and one citation."
tools: tools:
- web_search_loki - web_search_coyote
# No `next:`, `state_updates:`, or `output_schema:` here. Map branches # No `next:`, `state_updates:`, or `output_schema:` here. Map branches
# have a strict contract (see `subjects_map.branch` comment). # have a strict contract (see `subjects_map.branch` comment).
@@ -348,7 +348,7 @@ nodes:
instructions: "You write concise research summaries for a {{audience}} audience." instructions: "You write concise research summaries for a {{audience}} audience."
prompt: "Summarize the topic {{topic}}, using your tools as needed." prompt: "Summarize the topic {{topic}}, using your tools as needed."
tools: # Narrow whitelist: exactly these entries, nothing else tools: # Narrow whitelist: exactly these entries, nothing else
- web_search_loki # an exact global-tool / custom-tool name - web_search_coyote # an exact global-tool / custom-tool name
- mcp:ddg-search # `mcp:<server>` includes that server's functions - mcp:ddg-search # `mcp:<server>` includes that server's functions
model: claude:claude-haiku-4-5 # Optional per-node model override model: claude:claude-haiku-4-5 # Optional per-node model override
temperature: 0.3 # Optional per-node sampling override temperature: 0.3 # Optional per-node sampling override
+1 -1
View File
@@ -18,7 +18,7 @@ fmt:
cargo fmt --all cargo fmt --all
# Build the project for the current system architecture # Build the project for the current system architecture
# (Gets stored at ./target/[debug|release]/loki) # (Gets stored at ./target/[debug|release]/coyote)
[group: 'build'] [group: 'build']
[arg('build_type', pattern="debug|release")] [arg('build_type', pattern="debug|release")]
build build_type='debug': build build_type='debug':
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"name": "loki", "name": "coyote",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": {} "packages": {}
@@ -1,24 +1,24 @@
<# <#
loki installer (Windows/PowerShell 5+ and PowerShell 7) coyote installer (Windows/PowerShell 5+ and PowerShell 7)
Examples: Examples:
powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/Dark-Alex-17/loki/main/scripts/install_loki.ps1 | iex" powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://raw.githubusercontent.com/Dark-Alex-17/coyote/main/scripts/install_coyote.ps1 | iex"
pwsh -c "irm https://raw.githubusercontent.com/Dark-Alex-17/loki/main/scripts/install_loki.ps1 | iex -Version vX.Y.Z" pwsh -c "irm https://raw.githubusercontent.com/Dark-Alex-17/coyote/main/scripts/install_coyote.ps1 | iex -Version vX.Y.Z"
Parameters: Parameters:
-Version <tag> (default: latest) -Version <tag> (default: latest)
-BinDir <path> (default: %LOCALAPPDATA%\loki\bin on Windows; ~/.local/bin on *nix PowerShell) -BinDir <path> (default: %LOCALAPPDATA%\coyote\bin on Windows; ~/.local/bin on *nix PowerShell)
#> #>
[CmdletBinding()] [CmdletBinding()]
param( param(
[string]$Version = $env:LOKI_VERSION, [string]$Version = $env:COYOTE_VERSION,
[string]$BinDir = $env:BIN_DIR [string]$BinDir = $env:BIN_DIR
) )
$Repo = 'Dark-Alex-17/loki' $Repo = 'Dark-Alex-17/coyote'
function Write-Info($msg) { Write-Host "[loki-install] $msg" } function Write-Info($msg) { Write-Host "[coyote-install] $msg" }
function Fail($msg) { Write-Error $msg; exit 1 } function Fail($msg) { Write-Error $msg; exit 1 }
Add-Type -AssemblyName System.Runtime Add-Type -AssemblyName System.Runtime
@@ -38,7 +38,7 @@ switch ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture) {
} }
if (-not $BinDir) { if (-not $BinDir) {
if ($isWin) { $BinDir = Join-Path $env:LOCALAPPDATA 'loki\bin' } if ($isWin) { $BinDir = Join-Path $env:LOCALAPPDATA 'coyote\bin' }
else { $home = $env:HOME; if (-not $home) { $home = (Get-Item -Path ~).FullName }; $BinDir = Join-Path $home '.local/bin' } else { $home = $env:HOME; if (-not $home) { $home = (Get-Item -Path ~).FullName }; $BinDir = Join-Path $home '.local/bin' }
} }
New-Item -ItemType Directory -Force -Path $BinDir | Out-Null New-Item -ItemType Directory -Force -Path $BinDir | Out-Null
@@ -49,23 +49,23 @@ $apiBase = "https://api.github.com/repos/$Repo/releases"
$relUrl = if ($Version) { "$apiBase/tags/$Version" } else { "$apiBase/latest" } $relUrl = if ($Version) { "$apiBase/tags/$Version" } else { "$apiBase/latest" }
Write-Info "Fetching release: $relUrl" Write-Info "Fetching release: $relUrl"
try { try {
$release = Invoke-RestMethod -UseBasicParsing -Headers @{ 'User-Agent' = 'loki-installer' } -Uri $relUrl -Method GET $release = Invoke-RestMethod -UseBasicParsing -Headers @{ 'User-Agent' = 'coyote-installer' } -Uri $relUrl -Method GET
} catch { Fail "Failed to fetch release metadata. $_" } } catch { Fail "Failed to fetch release metadata. $_" }
if (-not $release.assets) { Fail "No assets found in the release." } if (-not $release.assets) { Fail "No assets found in the release." }
$candidates = @() $candidates = @()
if ($os -eq 'windows') { if ($os -eq 'windows') {
if ($arch -eq 'x86_64') { $candidates += 'loki-x86_64-pc-windows-msvc.zip' } if ($arch -eq 'x86_64') { $candidates += 'coyote-x86_64-pc-windows-msvc.zip' }
else { $candidates += 'loki-aarch64-pc-windows-msvc.zip' } else { $candidates += 'coyote-aarch64-pc-windows-msvc.zip' }
} elseif ($os -eq 'darwin') { } elseif ($os -eq 'darwin') {
if ($arch -eq 'x86_64') { $candidates += 'loki-x86_64-apple-darwin.tar.gz' } if ($arch -eq 'x86_64') { $candidates += 'coyote-x86_64-apple-darwin.tar.gz' }
else { $candidates += 'loki-aarch64-apple-darwin.tar.gz' } else { $candidates += 'coyote-aarch64-apple-darwin.tar.gz' }
} elseif ($os -eq 'linux') { } elseif ($os -eq 'linux') {
if ($arch -eq 'x86_64') { if ($arch -eq 'x86_64') {
$candidates += 'loki-x86_64-unknown-linux-gnu.tar.gz' $candidates += 'coyote-x86_64-unknown-linux-gnu.tar.gz'
$candidates += 'loki-x86_64-unknown-linux-musl.tar.gz' $candidates += 'coyote-x86_64-unknown-linux-musl.tar.gz'
} else { } else {
$candidates += 'loki-aarch64-unknown-linux-musl.tar.gz' $candidates += 'coyote-aarch64-unknown-linux-musl.tar.gz'
} }
} else { } else {
Fail "Unsupported OS for this installer: $os" Fail "Unsupported OS for this installer: $os"
@@ -84,9 +84,9 @@ if (-not $asset) {
Write-Info "Selected asset: $($asset.name)" Write-Info "Selected asset: $($asset.name)"
Write-Info "Download URL: $($asset.browser_download_url)" Write-Info "Download URL: $($asset.browser_download_url)"
$tmp = New-Item -ItemType Directory -Force -Path ([IO.Path]::Combine([IO.Path]::GetTempPath(), "loki-$(Get-Random)")) $tmp = New-Item -ItemType Directory -Force -Path ([IO.Path]::Combine([IO.Path]::GetTempPath(), "coyote-$(Get-Random)"))
$archive = Join-Path $tmp.FullName 'asset' $archive = Join-Path $tmp.FullName 'asset'
try { Invoke-WebRequest -UseBasicParsing -Headers @{ 'User-Agent' = 'loki-installer' } -Uri $asset.browser_download_url -OutFile $archive } catch { Fail "Failed to download asset. $_" } try { Invoke-WebRequest -UseBasicParsing -Headers @{ 'User-Agent' = 'coyote-installer' } -Uri $asset.browser_download_url -OutFile $archive } catch { Fail "Failed to download asset. $_" }
$extractDir = Join-Path $tmp.FullName 'extract'; New-Item -ItemType Directory -Force -Path $extractDir | Out-Null $extractDir = Join-Path $tmp.FullName 'extract'; New-Item -ItemType Directory -Force -Path $extractDir | Out-Null
@@ -107,14 +107,14 @@ if ($asset.name -match '\.zip$') {
$bin = $null $bin = $null
Get-ChildItem -Recurse -File $extractDir | ForEach-Object { Get-ChildItem -Recurse -File $extractDir | ForEach-Object {
if ($isWin) { if ($_.Name -ieq 'loki.exe') { $bin = $_.FullName } } if ($isWin) { if ($_.Name -ieq 'coyote.exe') { $bin = $_.FullName } }
else { if ($_.Name -ieq 'loki') { $bin = $_.FullName } } else { if ($_.Name -ieq 'coyote') { $bin = $_.FullName } }
} }
if (-not $bin) { Fail "Could not find loki binary inside the archive." } if (-not $bin) { Fail "Could not find coyote binary inside the archive." }
if (-not $isWin) { try { & chmod +x -- $bin } catch {} } if (-not $isWin) { try { & chmod +x -- $bin } catch {} }
$exec = if ($isWin) { 'loki.exe'} else { 'loki' } $exec = if ($isWin) { 'coyote.exe'} else { 'coyote' }
$dest = Join-Path $BinDir $exec $dest = Join-Path $BinDir $exec
Copy-Item -Force $bin $dest Copy-Item -Force $bin $dest
Write-Info "Installed: $dest" Write-Info "Installed: $dest"
@@ -135,5 +135,5 @@ if ($isWin) {
} }
} }
Write-Info "Done. Try: loki --help" Write-Info "Done. Try: coyote --help"
@@ -1,23 +1,23 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
# loki installer (Linux/macOS) # coyote installer (Linux/macOS)
# #
# Usage examples: # Usage examples:
# curl -fsSL https://raw.githubusercontent.com/Dark-Alex-17/loki/main/scripts/install_loki.sh | bash # curl -fsSL https://raw.githubusercontent.com/Dark-Alex-17/coyote/main/scripts/install_coyote.sh | bash
# curl -fsSL https://raw.githubusercontent.com/Dark-Alex-17/loki/main/scripts/install_loki.sh | bash -s -- --version vX.Y.Z # curl -fsSL https://raw.githubusercontent.com/Dark-Alex-17/coyote/main/scripts/install_coyote.sh | bash -s -- --version vX.Y.Z
# BIN_DIR="$HOME/.local/bin" bash scripts/install_loki.sh # BIN_DIR="$HOME/.local/bin" bash scripts/install_coyote.sh
# #
# Flags / Env: # Flags / Env:
# --version <tag> Release tag (default: latest). Or set LOKI_VERSION. # --version <tag> Release tag (default: latest). Or set COYOTE_VERSION.
# --bin-dir <dir> Install directory (default: /usr/local/bin or ~/.local/bin). Or set BIN_DIR. # --bin-dir <dir> Install directory (default: /usr/local/bin or ~/.local/bin). Or set BIN_DIR.
REPO="Dark-Alex-17/loki" REPO="Dark-Alex-17/coyote"
VERSION="${LOKI_VERSION:-}" VERSION="${COYOTE_VERSION:-}"
BIN_DIR="${BIN_DIR:-}" BIN_DIR="${BIN_DIR:-}"
usage() { usage() {
echo "loki installer (Linux/macOS)" echo "coyote installer (Linux/macOS)"
echo echo
echo "Options:" echo "Options:"
echo " --version <tag> Release tag (default: latest)" echo " --version <tag> Release tag (default: latest)"
@@ -44,7 +44,7 @@ fi
mkdir -p "${BIN_DIR}" mkdir -p "${BIN_DIR}"
log() { log() {
echo "[loki-install] $*" echo "[coyote-install] $*"
} }
need_cmd() { need_cmd() {
@@ -92,9 +92,9 @@ fi
http_get() { http_get() {
if [[ "$DL" == "curl" ]]; then if [[ "$DL" == "curl" ]]; then
curl -fsSL -H 'User-Agent: loki-installer' "$1" curl -fsSL -H 'User-Agent: coyote-installer' "$1"
else else
wget -qO- --header='User-Agent: loki-installer' "$1" wget -qO- --header='User-Agent: coyote-installer' "$1"
fi fi
} }
@@ -111,9 +111,9 @@ fi
ASSET_CANDIDATES=() ASSET_CANDIDATES=()
if [[ "$OS" == "darwin" ]]; then if [[ "$OS" == "darwin" ]]; then
if [[ "$ARCH" == "x86_64" ]]; then if [[ "$ARCH" == "x86_64" ]]; then
ASSET_CANDIDATES+=("loki-x86_64-apple-darwin.tar.gz") ASSET_CANDIDATES+=("coyote-x86_64-apple-darwin.tar.gz")
else else
ASSET_CANDIDATES+=("loki-aarch64-apple-darwin.tar.gz") ASSET_CANDIDATES+=("coyote-aarch64-apple-darwin.tar.gz")
fi fi
elif [[ "$OS" == "linux" ]]; then elif [[ "$OS" == "linux" ]]; then
if [[ "$ARCH" == "x86_64" ]]; then if [[ "$ARCH" == "x86_64" ]]; then
@@ -122,12 +122,12 @@ elif [[ "$OS" == "linux" ]]; then
if ldd --version 2>&1 | grep -qi glibc; then LIBC="gnu"; fi if ldd --version 2>&1 | grep -qi glibc; then LIBC="gnu"; fi
if [[ "$LIBC" == "gnu" ]]; then if [[ "$LIBC" == "gnu" ]]; then
ASSET_CANDIDATES+=("loki-x86_64-unknown-linux-gnu.tar.gz") ASSET_CANDIDATES+=("coyote-x86_64-unknown-linux-gnu.tar.gz")
fi fi
ASSET_CANDIDATES+=("loki-x86_64-unknown-linux-musl.tar.gz") ASSET_CANDIDATES+=("coyote-x86_64-unknown-linux-musl.tar.gz")
else else
ASSET_CANDIDATES+=("loki-aarch64-unknown-linux-musl.tar.gz") ASSET_CANDIDATES+=("coyote-aarch64-unknown-linux-musl.tar.gz")
fi fi
else else
echo "Error: unsupported OS for this installer: $OS" >&2; exit 1 echo "Error: unsupported OS for this installer: $OS" >&2; exit 1
@@ -170,9 +170,9 @@ log "Download URL: $ASSET_URL"
ARCHIVE="$TMPDIR/asset" ARCHIVE="$TMPDIR/asset"
if [[ "$DL" == "curl" ]]; then if [[ "$DL" == "curl" ]]; then
curl -fL -H 'User-Agent: loki-installer' "$ASSET_URL" -o "$ARCHIVE" curl -fL -H 'User-Agent: coyote-installer' "$ASSET_URL" -o "$ARCHIVE"
else else
wget -q --header='User-Agent: loki-installer' "$ASSET_URL" -O "$ARCHIVE" wget -q --header='User-Agent: coyote-installer' "$ASSET_URL" -O "$ARCHIVE"
fi fi
WORK="$TMPDIR/work"; mkdir -p "$WORK" WORK="$TMPDIR/work"; mkdir -p "$WORK"
@@ -192,21 +192,21 @@ fi
BIN_PATH="" BIN_PATH=""
while IFS= read -r -d '' f; do while IFS= read -r -d '' f; do
base=$(basename "$f") base=$(basename "$f")
if [[ "$base" == "loki" ]]; then if [[ "$base" == "coyote" ]]; then
BIN_PATH="$f" BIN_PATH="$f"
break break
fi fi
done < <(find "$EXTRACTED_DIR" -type f -print0) done < <(find "$EXTRACTED_DIR" -type f -print0)
if [[ -z "$BIN_PATH" ]]; then if [[ -z "$BIN_PATH" ]]; then
echo "Error: could not find 'loki' binary in the archive" >&2 echo "Error: could not find 'coyote' binary in the archive" >&2
exit 1 exit 1
fi fi
chmod +x "$BIN_PATH" chmod +x "$BIN_PATH"
install -m 0755 "$BIN_PATH" "${BIN_DIR}/loki" install -m 0755 "$BIN_PATH" "${BIN_DIR}/coyote"
log "Installed: ${BIN_DIR}/loki" log "Installed: ${BIN_DIR}/coyote"
case ":$PATH:" in case ":$PATH:" in
*":${BIN_DIR}:"*) ;; *":${BIN_DIR}:"*) ;;
@@ -216,5 +216,5 @@ case ":$PATH:" in
;; ;;
esac esac
log "Done. Try: loki --help" log "Done. Try: coyote --help"
+3 -3
View File
@@ -1,7 +1,7 @@
_loki_bash() { _coyote_bash() {
if [[ -n "$READLINE_LINE" ]]; then if [[ -n "$READLINE_LINE" ]]; then
READLINE_LINE=$(loki -e "$READLINE_LINE") READLINE_LINE=$(coyote -e "$READLINE_LINE")
READLINE_POINT=${#READLINE_LINE} READLINE_POINT=${#READLINE_LINE}
fi fi
} }
bind -x '"\ee": _loki_bash' bind -x '"\ee": _coyote_bash'
+3 -3
View File
@@ -1,7 +1,7 @@
fn _loki_elvish { fn _coyote_elvish {
var line = (edit:current-command) var line = (edit:current-command)
var new-line = (loki -e $line) var new-line = (coyote -e $line)
edit:replace-input $new-line edit:replace-input $new-line
} }
edit:insert:binding[Alt-e] = $_loki_elvish edit:insert:binding[Alt-e] = $_coyote_elvish
+3 -3
View File
@@ -1,9 +1,9 @@
function _loki_fish function _coyote_fish
set -l _old (commandline) set -l _old (commandline)
if test -n $_old if test -n $_old
echo -n "⌛" echo -n "⌛"
commandline -f repaint commandline -f repaint
commandline (loki -e $_old) commandline (coyote -e $_old)
end end
end end
bind \ee _loki_fish bind \ee _coyote_fish
+4 -4
View File
@@ -1,20 +1,20 @@
def _loki_nushell [] { def _coyote_nushell [] {
let _prev = (commandline) let _prev = (commandline)
if ($_prev != "") { if ($_prev != "") {
print '⌛' print '⌛'
commandline edit -r (loki -e $_prev) commandline edit -r (coyote -e $_prev)
} }
} }
$env.config.keybindings = ($env.config.keybindings | append { $env.config.keybindings = ($env.config.keybindings | append {
name: loki_integration name: coyote_integration
modifier: alt modifier: alt
keycode: char_e keycode: char_e
mode: [emacs, vi_insert] mode: [emacs, vi_insert]
event:[ event:[
{ {
send: executehostcommand, send: executehostcommand,
cmd: "_loki_nushell" cmd: "_coyote_nushell"
} }
] ]
} }
+1 -1
View File
@@ -3,7 +3,7 @@ Set-PSReadLineKeyHandler -Chord "alt+e" -ScriptBlock {
[Microsoft.PowerShell.PSConsoleReadline]::GetBufferState([ref]$_old, [ref]$null) [Microsoft.PowerShell.PSConsoleReadline]::GetBufferState([ref]$_old, [ref]$null)
if ($_old) { if ($_old) {
[Microsoft.PowerShell.PSConsoleReadLine]::Insert('⌛') [Microsoft.PowerShell.PSConsoleReadLine]::Insert('⌛')
$_new = (loki -e $_old) $_new = (coyote -e $_old)
[Microsoft.PowerShell.PSConsoleReadLine]::DeleteLine() [Microsoft.PowerShell.PSConsoleReadLine]::DeleteLine()
[Microsoft.PowerShell.PSConsoleReadline]::Insert($_new) [Microsoft.PowerShell.PSConsoleReadline]::Insert($_new)
} }
+4 -4
View File
@@ -1,11 +1,11 @@
_loki_zsh() { _coyote_zsh() {
if [[ -n "$BUFFER" ]]; then if [[ -n "$BUFFER" ]]; then
local _old=$BUFFER local _old=$BUFFER
BUFFER+="⌛" BUFFER+="⌛"
zle -I && zle redisplay zle -I && zle redisplay
BUFFER=$(loki -e "$_old") BUFFER=$(coyote -e "$_old")
zle end-of-line zle end-of-line
fi fi
} }
zle -N _loki_zsh zle -N _coyote_zsh
bindkey '\ee' _loki_zsh bindkey '\ee' _coyote_zsh
+9 -7
View File
@@ -9,7 +9,7 @@ use std::env;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::io; use std::io;
const LOKI_CLI_NAME: &str = "loki"; const COYOTE_CLI_NAME: &str = "coyote";
#[derive(Clone, Copy, Debug, clap::ValueEnum)] #[derive(Clone, Copy, Debug, clap::ValueEnum)]
pub enum ShellCompletion { pub enum ShellCompletion {
@@ -24,12 +24,14 @@ pub enum ShellCompletion {
impl ShellCompletion { impl ShellCompletion {
pub fn generate_completions(self, cmd: &mut clap::Command) { pub fn generate_completions(self, cmd: &mut clap::Command) {
match self { match self {
Self::Bash => generate(Shell::Bash, cmd, LOKI_CLI_NAME, &mut io::stdout()), Self::Bash => generate(Shell::Bash, cmd, COYOTE_CLI_NAME, &mut io::stdout()),
Self::Elvish => generate(Shell::Elvish, cmd, LOKI_CLI_NAME, &mut io::stdout()), Self::Elvish => generate(Shell::Elvish, cmd, COYOTE_CLI_NAME, &mut io::stdout()),
Self::Fish => generate(Shell::Fish, cmd, LOKI_CLI_NAME, &mut io::stdout()), Self::Fish => generate(Shell::Fish, cmd, COYOTE_CLI_NAME, &mut io::stdout()),
Self::PowerShell => generate(Shell::PowerShell, cmd, LOKI_CLI_NAME, &mut io::stdout()), Self::PowerShell => {
Self::Zsh => generate(Shell::Zsh, cmd, LOKI_CLI_NAME, &mut io::stdout()), generate(Shell::PowerShell, cmd, COYOTE_CLI_NAME, &mut io::stdout())
Self::Nushell => generate(Nushell, cmd, LOKI_CLI_NAME, &mut io::stdout()), }
Self::Zsh => generate(Shell::Zsh, cmd, COYOTE_CLI_NAME, &mut io::stdout()),
Self::Nushell => generate(Nushell, cmd, COYOTE_CLI_NAME, &mut io::stdout()),
} }
} }
} }
+10 -10
View File
@@ -15,7 +15,7 @@ use std::io::{Read, stdin};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
#[command( #[command(
name = "loki", name = "coyote",
author = crate_authors!(), author = crate_authors!(),
version = crate_version!(), version = crate_version!(),
about = crate_description!(), about = crate_description!(),
@@ -125,19 +125,19 @@ pub struct Cli {
/// Disable colored log output /// Disable colored log output
#[arg(long, requires = "tail_logs")] #[arg(long, requires = "tail_logs")]
pub disable_log_colors: bool, pub disable_log_colors: bool,
/// Add a secret to the Loki vault /// Add a secret to the Coyote vault
#[arg(long, value_name = "SECRET_NAME", exclusive = true)] #[arg(long, value_name = "SECRET_NAME", exclusive = true)]
pub add_secret: Option<String>, pub add_secret: Option<String>,
/// Decrypt a secret from the Loki vault and print the plaintext /// Decrypt a secret from the Coyote vault and print the plaintext
#[arg(long, value_name = "SECRET_NAME", exclusive = true, add = ArgValueCompleter::new(secrets_completer))] #[arg(long, value_name = "SECRET_NAME", exclusive = true, add = ArgValueCompleter::new(secrets_completer))]
pub get_secret: Option<String>, pub get_secret: Option<String>,
/// Update an existing secret in the Loki vault /// Update an existing secret in the Coyote vault
#[arg(long, value_name = "SECRET_NAME", exclusive = true, add = ArgValueCompleter::new(secrets_completer))] #[arg(long, value_name = "SECRET_NAME", exclusive = true, add = ArgValueCompleter::new(secrets_completer))]
pub update_secret: Option<String>, pub update_secret: Option<String>,
/// Delete a secret from the Loki vault /// Delete a secret from the Coyote vault
#[arg(long, value_name = "SECRET_NAME", exclusive = true, add = ArgValueCompleter::new(secrets_completer))] #[arg(long, value_name = "SECRET_NAME", exclusive = true, add = ArgValueCompleter::new(secrets_completer))]
pub delete_secret: Option<String>, pub delete_secret: Option<String>,
/// List all secrets stored in the Loki vault /// List all secrets stored in the Coyote vault
#[arg(long, exclusive = true)] #[arg(long, exclusive = true)]
pub list_secrets: bool, pub list_secrets: bool,
/// Authenticate with an LLM provider using OAuth (e.g., --authenticate client_name) /// Authenticate with an LLM provider using OAuth (e.g., --authenticate client_name)
@@ -146,10 +146,10 @@ pub struct Cli {
/// Generate static shell completion scripts /// Generate static shell completion scripts
#[arg(long, value_name = "SHELL", value_enum)] #[arg(long, value_name = "SHELL", value_enum)]
pub completions: Option<ShellCompletion>, pub completions: Option<ShellCompletion>,
/// Update Loki to the latest release, or to a specific version /// Update Coyote to the latest release, or to a specific version
#[arg(long, value_name = "VERSION")] #[arg(long, value_name = "VERSION")]
pub update: Option<Option<String>>, pub update: Option<Option<String>>,
/// With --update, update even if Loki was installed via a package manager /// With --update, update even if Coyote was installed via a package manager
#[arg(long, requires = "update")] #[arg(long, requires = "update")]
pub force: bool, pub force: bool,
} }
@@ -202,7 +202,7 @@ mod tests {
use clap::Parser; use clap::Parser;
fn parse(args: &[&str]) -> Cli { fn parse(args: &[&str]) -> Cli {
let mut full_args = vec!["loki"]; let mut full_args = vec!["coyote"];
full_args.extend_from_slice(args); full_args.extend_from_slice(args);
Cli::try_parse_from(full_args).unwrap() Cli::try_parse_from(full_args).unwrap()
} }
@@ -436,6 +436,6 @@ mod tests {
#[test] #[test]
fn parse_force_without_update_fails() { fn parse_force_without_update_fails() {
assert!(Cli::try_parse_from(["loki", "--force"]).is_err()); assert!(Cli::try_parse_from(["coyote", "--force"]).is_err());
} }
} }
+24 -30
View File
@@ -85,7 +85,7 @@ async fn prepare_chat_completions(
let ready = oauth::prepare_oauth_access_token(client, &provider, self_.name()).await?; let ready = oauth::prepare_oauth_access_token(client, &provider, self_.name()).await?;
if !ready { if !ready {
bail!( bail!(
"OAuth configured but no tokens found for '{}'. Run: 'loki --authenticate {}' or '.authenticate' in the REPL", "OAuth configured but no tokens found for '{}'. Run: 'coyote --authenticate {}' or '.authenticate' in the REPL",
self_.name(), self_.name(),
self_.name() self_.name()
); );
@@ -100,7 +100,7 @@ async fn prepare_chat_completions(
request_data.header("x-api-key", api_key); request_data.header("x-api-key", api_key);
} else { } else {
bail!( bail!(
"No authentication configured for '{}'. Set `api_key` or use `auth: oauth` with `loki --authenticate {}`.", "No authentication configured for '{}'. Set `api_key` or use `auth: oauth` with `coyote --authenticate {}`.",
self_.name(), self_.name(),
self_.name() self_.name()
); );
@@ -114,41 +114,35 @@ async fn prepare_chat_completions(
/// ///
/// This behavior was discovered 2026-03-17. /// This behavior was discovered 2026-03-17.
/// ///
/// So this function injects the Claude Code system prompt into the request /// The prefix must be in its **own** top-level system block. Concatenating it
/// body to make it a valid request. /// with role / session content into a single block causes Anthropic to reject
/// the request with `rate_limit_error`. Any pre-existing system content is
/// preserved as additional blocks after the prefix.
fn inject_oauth_system_prompt(body: &mut Value) { fn inject_oauth_system_prompt(body: &mut Value) {
let existing_text = match body.get("system") { let existing_blocks: Vec<Value> = match body.get("system") {
Some(Value::String(s)) => { Some(Value::String(s)) => {
if s.starts_with(CLAUDE_CODE_PREFIX) { if s.is_empty() {
return; Vec::new()
} else {
vec![json!({ "type": "text", "text": s })]
} }
(!s.is_empty()).then(|| s.clone())
} }
Some(Value::Array(blocks)) => { Some(Value::Array(blocks)) => blocks.clone(),
let already_injected = blocks.iter().any(|b| { _ => Vec::new(),
b.get("text")
.and_then(|t| t.as_str())
.map(|t| t.starts_with(CLAUDE_CODE_PREFIX))
.unwrap_or(false)
});
if already_injected {
return;
}
let joined: Vec<String> = blocks
.iter()
.filter_map(|b| b.get("text").and_then(|t| t.as_str()).map(String::from))
.collect();
(!joined.is_empty()).then(|| joined.join("\n\n"))
}
_ => None,
}; };
let merged = match existing_text { let already_injected = existing_blocks
Some(rest) => format!("{}\n\n{}", CLAUDE_CODE_PREFIX, rest), .first()
None => CLAUDE_CODE_PREFIX.to_string(), .and_then(|b| b.get("text").and_then(|t| t.as_str()))
}; .map(|t| t == CLAUDE_CODE_PREFIX)
.unwrap_or(false);
if already_injected {
return;
}
body["system"] = json!([{ "type": "text", "text": merged }]); let mut system = vec![json!({ "type": "text", "text": CLAUDE_CODE_PREFIX })];
system.extend(existing_blocks);
body["system"] = Value::Array(system);
} }
pub async fn claude_chat_completions( pub async fn claude_chat_completions(
+3 -3
View File
@@ -111,7 +111,7 @@ async fn prepare_chat_completions(
let ready = oauth::prepare_oauth_access_token(client, &provider, self_.name()).await?; let ready = oauth::prepare_oauth_access_token(client, &provider, self_.name()).await?;
if !ready { if !ready {
bail!( bail!(
"OAuth configured but no tokens found for '{}'. Run: 'loki --authenticate {}' or '.authenticate' in the REPL", "OAuth configured but no tokens found for '{}'. Run: 'coyote --authenticate {}' or '.authenticate' in the REPL",
self_.name(), self_.name(),
self_.name() self_.name()
); );
@@ -122,7 +122,7 @@ async fn prepare_chat_completions(
request_data.header("x-goog-api-key", api_key); request_data.header("x-goog-api-key", api_key);
} else { } else {
bail!( bail!(
"No authentication configured for '{}'. Set `api_key` or use `auth: oauth` with `loki --authenticate {}`.", "No authentication configured for '{}'. Set `api_key` or use `auth: oauth` with `coyote --authenticate {}`.",
self_.name(), self_.name(),
self_.name() self_.name()
); );
@@ -181,7 +181,7 @@ async fn prepare_embeddings(
let ready = oauth::prepare_oauth_access_token(client, &provider, self_.name()).await?; let ready = oauth::prepare_oauth_access_token(client, &provider, self_.name()).await?;
if !ready { if !ready {
bail!( bail!(
"OAuth configured but no tokens found for '{}'. Run: 'loki --authenticate {}' or '.authenticate' in the REPL", "OAuth configured but no tokens found for '{}'. Run: 'coyote --authenticate {}' or '.authenticate' in the REPL",
self_.name(), self_.name(),
self_.name() self_.name()
); );
+1 -1
View File
@@ -526,7 +526,7 @@ impl RoleLike for Agent {
} }
fn enabled_tools(&self) -> Option<String> { fn enabled_tools(&self) -> Option<String> {
self.config.global_tools.clone().join(",").into() None
} }
fn enabled_mcp_servers(&self) -> Option<String> { fn enabled_mcp_servers(&self) -> Option<String> {
+2 -2
View File
@@ -879,7 +879,7 @@ mod tests {
#[test] #[test]
fn from_files_loads_single_text_file() { fn from_files_loads_single_text_file() {
let dir = env::temp_dir().join(format!( let dir = env::temp_dir().join(format!(
"loki-input-test-{}", "coyote-input-test-{}",
SystemTime::now() SystemTime::now()
.duration_since(std::time::UNIX_EPOCH) .duration_since(std::time::UNIX_EPOCH)
.unwrap() .unwrap()
@@ -906,7 +906,7 @@ mod tests {
#[test] #[test]
fn from_files_loads_multiple_files() { fn from_files_loads_multiple_files() {
let dir = env::temp_dir().join(format!( let dir = env::temp_dir().join(format!(
"loki-input-test-multi-{}", "coyote-input-test-multi-{}",
SystemTime::now() SystemTime::now()
.duration_since(std::time::UNIX_EPOCH) .duration_since(std::time::UNIX_EPOCH)
.unwrap() .unwrap()
+7 -6
View File
@@ -6,6 +6,7 @@ use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use crate::config::{InstallFilter, paths}; use crate::config::{InstallFilter, paths};
#[cfg(not(windows))]
use crate::function::Language; use crate::function::Language;
use crate::mcp::{McpServer, McpServersConfig}; use crate::mcp::{McpServer, McpServersConfig};
use crate::utils; use crate::utils;
@@ -135,7 +136,7 @@ impl Drop for TempRepoDir {
} }
fn clone_to_temp(url: &str, reference: Option<&str>) -> Result<TempRepoDir> { fn clone_to_temp(url: &str, reference: Option<&str>) -> Result<TempRepoDir> {
let dest = utils::temp_file("loki-remote-install-", ""); let dest = utils::temp_file("coyote-remote-install-", "");
let dest_arg: OsString = dest.as_os_str().into(); let dest_arg: OsString = dest.as_os_str().into();
let is_sha = reference let is_sha = reference
@@ -874,7 +875,7 @@ fn print_secret_summary(added: &[String], deferred: &[String]) {
if !deferred.is_empty() { if !deferred.is_empty() {
println!( println!(
"\nThe following secrets are still required by your MCP servers. \ "\nThe following secrets are still required by your MCP servers. \
Add them with `loki --add-secret <NAME>` or `.vault add <NAME>` in the REPL:" Add them with `coyote --add-secret <NAME>` or `.vault add <NAME>` in the REPL:"
); );
for name in deferred { for name in deferred {
println!(" {{{{ {name} }}}}"); println!(" {{{{ {name} }}}}");
@@ -1264,12 +1265,12 @@ mod tests {
let target = dir.join("target.json"); let target = dir.join("target.json");
write_mcp( write_mcp(
&remote, &remote,
r#"{"mcpServers": {"x": {"type":"stdio","command":"echo","env":{"K":"{{LOKI_TEST_MERGE_SECRET}}"}}}}"#, r#"{"mcpServers": {"x": {"type":"stdio","command":"echo","env":{"K":"{{COYOTE_TEST_MERGE_SECRET}}"}}}}"#,
); );
let report = merge_mcp_json(None, &remote, &target, false).unwrap(); let report = merge_mcp_json(None, &remote, &target, false).unwrap();
assert_eq!(report.missing_secrets, vec!["LOKI_TEST_MERGE_SECRET"]); assert_eq!(report.missing_secrets, vec!["COYOTE_TEST_MERGE_SECRET"]);
let _ = fs::remove_dir_all(&dir); let _ = fs::remove_dir_all(&dir);
} }
@@ -1299,8 +1300,8 @@ mod tests {
#[test] #[test]
fn handle_missing_secrets_defers_all_in_non_tty() { fn handle_missing_secrets_defers_all_in_non_tty() {
let missing = vec![ let missing = vec![
"LOKI_TEST_STEP4_A".to_string(), "COYOTE_TEST_STEP4_A".to_string(),
"LOKI_TEST_STEP4_B".to_string(), "COYOTE_TEST_STEP4_B".to_string(),
]; ];
assert!(handle_missing_secrets(&missing).is_ok()); assert!(handle_missing_secrets(&missing).is_ok());
+3 -3
View File
@@ -104,13 +104,13 @@ const DEFAULT_VISIBLE_TOOLS: [&str; 18] = [
"get_current_weather.sh", "get_current_weather.sh",
"search_wikipedia.sh", "search_wikipedia.sh",
"search_arxiv.sh", "search_arxiv.sh",
"web_search_loki.sh", "web_search_coyote.sh",
]; ];
const CLIENTS_FIELD: &str = "clients"; const CLIENTS_FIELD: &str = "clients";
const SYNC_MODELS_URL: &str = const SYNC_MODELS_URL: &str =
"https://raw.githubusercontent.com/Dark-Alex-17/loki/refs/heads/main/models.yaml"; "https://raw.githubusercontent.com/Dark-Alex-17/coyote/refs/heads/main/models.yaml";
const SUMMARIZATION_PROMPT: &str = const SUMMARIZATION_PROMPT: &str =
"Summarize the discussion briefly in 200 words or less to use as a prompt for future context."; "Summarize the discussion briefly in 200 words or less to use as a prompt for future context.";
@@ -625,7 +625,7 @@ pub async fn create_config_file(config_path: &Path) -> Result<()> {
let config_data = serde_yaml::to_string(&config).with_context(|| "Failed to create config")?; let config_data = serde_yaml::to_string(&config).with_context(|| "Failed to create config")?;
let config_data = format!( let config_data = format!(
"# see https://github.com/Dark-Alex-17/loki/blob/main/config.example.yaml\n\n{config_data}" "# see https://github.com/Dark-Alex-17/coyote/blob/main/config.example.yaml\n\n{config_data}"
); );
ensure_parent_exists(config_path)?; ensure_parent_exists(config_path)?;
+8 -2
View File
@@ -1498,7 +1498,7 @@ impl RequestContext {
if !target_path.exists() { if !target_path.exists() {
fs::write( fs::write(
&target_path, &target_path,
"# see https://github.com/Dark-Alex-17/loki/blob/main/config.agent.example.yaml\n", "# see https://github.com/Dark-Alex-17/coyote/blob/main/config.agent.example.yaml\n",
) )
.with_context(|| format!("Failed to write to '{}'", target_path.display()))?; .with_context(|| format!("Failed to write to '{}'", target_path.display()))?;
} }
@@ -2706,7 +2706,7 @@ mod tests {
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.unwrap() .unwrap()
.as_nanos(); .as_nanos();
let path = env::temp_dir().join(format!("loki-request-context-tests-{unique}")); let path = env::temp_dir().join(format!("coyote-request-context-tests-{unique}"));
create_dir_all(&path).unwrap(); create_dir_all(&path).unwrap();
unsafe { unsafe {
env::set_var(&key, &path); env::set_var(&key, &path);
@@ -2969,6 +2969,7 @@ mod tests {
#[test] #[test]
#[serial] #[serial]
fn rebuild_tool_scope_mcp_disabled_skips_servers() { fn rebuild_tool_scope_mcp_disabled_skips_servers() {
let _guard = TestConfigDirGuard::new();
let app_state = app_state_with_mcp_config(false, &["github", "slack"]); let app_state = app_state_with_mcp_config(false, &["github", "slack"]);
let mut ctx = RequestContext::new(app_state, WorkingMode::Cmd); let mut ctx = RequestContext::new(app_state, WorkingMode::Cmd);
let app = ctx.app.config.clone(); let app = ctx.app.config.clone();
@@ -2982,6 +2983,7 @@ mod tests {
#[test] #[test]
#[serial] #[serial]
fn rebuild_tool_scope_no_enabled_servers_yields_empty_runtime() { fn rebuild_tool_scope_no_enabled_servers_yields_empty_runtime() {
let _guard = TestConfigDirGuard::new();
let app_state = app_state_with_mcp_config(true, &["github"]); let app_state = app_state_with_mcp_config(true, &["github"]);
let mut ctx = RequestContext::new(app_state, WorkingMode::Cmd); let mut ctx = RequestContext::new(app_state, WorkingMode::Cmd);
let app = ctx.app.config.clone(); let app = ctx.app.config.clone();
@@ -2995,6 +2997,7 @@ mod tests {
#[test] #[test]
#[serial] #[serial]
fn rebuild_tool_scope_no_mcp_config_yields_empty_runtime() { fn rebuild_tool_scope_no_mcp_config_yields_empty_runtime() {
let _guard = TestConfigDirGuard::new();
let app_state = app_state_with_mcp_config(true, &[]); let app_state = app_state_with_mcp_config(true, &[]);
let mut ctx = RequestContext::new(app_state, WorkingMode::Cmd); let mut ctx = RequestContext::new(app_state, WorkingMode::Cmd);
let app = ctx.app.config.clone(); let app = ctx.app.config.clone();
@@ -3008,6 +3011,7 @@ mod tests {
#[test] #[test]
#[serial] #[serial]
fn rebuild_tool_scope_preserves_tool_tracker() { fn rebuild_tool_scope_preserves_tool_tracker() {
let _guard = TestConfigDirGuard::new();
let app_state = app_state_with_mcp_config(false, &[]); let app_state = app_state_with_mcp_config(false, &[]);
let mut ctx = RequestContext::new(app_state, WorkingMode::Cmd); let mut ctx = RequestContext::new(app_state, WorkingMode::Cmd);
let dummy = ToolCall { let dummy = ToolCall {
@@ -3035,6 +3039,7 @@ mod tests {
#[test] #[test]
#[serial] #[serial]
fn rebuild_tool_scope_repl_mode_appends_user_interaction_functions() { fn rebuild_tool_scope_repl_mode_appends_user_interaction_functions() {
let _guard = TestConfigDirGuard::new();
let app_state = app_state_with_mcp_config(false, &[]); let app_state = app_state_with_mcp_config(false, &[]);
let mut ctx = RequestContext::new(app_state, WorkingMode::Repl); let mut ctx = RequestContext::new(app_state, WorkingMode::Repl);
let app = ctx.app.config.clone(); let app = ctx.app.config.clone();
@@ -3058,6 +3063,7 @@ mod tests {
#[test] #[test]
#[serial] #[serial]
fn rebuild_tool_scope_cmd_mode_no_user_interaction_functions() { fn rebuild_tool_scope_cmd_mode_no_user_interaction_functions() {
let _guard = TestConfigDirGuard::new();
let app_state = app_state_with_mcp_config(false, &[]); let app_state = app_state_with_mcp_config(false, &[]);
let mut ctx = RequestContext::new(app_state, WorkingMode::Cmd); let mut ctx = RequestContext::new(app_state, WorkingMode::Cmd);
let app = ctx.app.config.clone(); let app = ctx.app.config.clone();
+17 -17
View File
@@ -69,7 +69,7 @@ fn normalize_version(requested: Option<String>) -> Option<String> {
} }
fn is_dir_writable(dir: &Path) -> bool { fn is_dir_writable(dir: &Path) -> bool {
let probe = dir.join(format!(".loki-update-write-test-{}", process::id())); let probe = dir.join(format!(".coyote-update-write-test-{}", process::id()));
match OpenOptions::new().write(true).create_new(true).open(&probe) { match OpenOptions::new().write(true).create_new(true).open(&probe) {
Ok(_) => { Ok(_) => {
let _ = fs::remove_file(&probe); let _ = fs::remove_file(&probe);
@@ -83,24 +83,24 @@ pub fn run_self_update(requested: Option<String>, force: bool) -> Result<()> {
let target_tag = normalize_version(requested); let target_tag = normalize_version(requested);
let exe_path = env::current_exe() let exe_path = env::current_exe()
.context("Could not determine the path of the running loki executable")?; .context("Could not determine the path of the running coyote executable")?;
let resolved = canonicalize(&exe_path).unwrap_or_else(|_| exe_path.clone()); let resolved = canonicalize(&exe_path).unwrap_or_else(|_| exe_path.clone());
let source = classify_install_path(&resolved); let source = classify_install_path(&resolved);
if source.is_package_managed() { if source.is_package_managed() {
let body = match source { let body = match source {
InstallSource::Homebrew => format!( InstallSource::Homebrew => format!(
"Loki appears to be installed via Homebrew ({}).\n\ "Coyote appears to be installed via Homebrew ({}).\n\
Updating in place replaces the binary inside Homebrew's Cellar; `brew` will\n\ Updating in place replaces the binary inside Homebrew's Cellar; `brew` will\n\
then report a version that no longer matches the file on disk, and a later\n\ then report a version that no longer matches the file on disk, and a later\n\
`brew upgrade`/`brew reinstall` may overwrite it or fail.\n\ `brew upgrade`/`brew reinstall` may overwrite it or fail.\n\
The clean way to update is: brew upgrade loki", The clean way to update is: brew upgrade coyote",
exe_path.display() exe_path.display()
), ),
InstallSource::Cargo => format!( InstallSource::Cargo => format!(
"Loki appears to be installed via `cargo install` ({}).\n\ "Coyote appears to be installed via `cargo install` ({}).\n\
Updating in place leaves Cargo's records out of sync with the binary on disk.\n\ Updating in place leaves Cargo's records out of sync with the binary on disk.\n\
The clean way to update is: cargo install --locked loki-ai", The clean way to update is: cargo install --locked coyote-ai",
exe_path.display() exe_path.display()
), ),
InstallSource::Manual => unreachable!("Manual installs are not package-managed"), InstallSource::Manual => unreachable!("Manual installs are not package-managed"),
@@ -130,7 +130,7 @@ pub fn run_self_update(requested: Option<String>, force: bool) -> Result<()> {
{ {
bail!( bail!(
"No write permission for '{}'. Re-run with elevated permissions (e.g. sudo), \ "No write permission for '{}'. Re-run with elevated permissions (e.g. sudo), \
or update Loki through your package manager.", or update Coyote through your package manager.",
parent.display() parent.display()
); );
} }
@@ -139,8 +139,8 @@ pub fn run_self_update(requested: Option<String>, force: bool) -> Result<()> {
let mut builder = Update::configure(); let mut builder = Update::configure();
builder builder
.repo_owner("Dark-Alex-17") .repo_owner("Dark-Alex-17")
.repo_name("loki") .repo_name("coyote")
.bin_name("loki") .bin_name("coyote")
.current_version(env!("CARGO_PKG_VERSION")) .current_version(env!("CARGO_PKG_VERSION"))
.no_confirm(true) .no_confirm(true)
.show_download_progress(interactive); .show_download_progress(interactive);
@@ -155,10 +155,10 @@ pub fn run_self_update(requested: Option<String>, force: bool) -> Result<()> {
match status { match status {
Status::UpToDate(version) => { Status::UpToDate(version) => {
println!("Loki is already up to date (v{version})."); println!("Coyote is already up to date (v{version}).");
} }
Status::Updated(version) => { Status::Updated(version) => {
println!("Loki updated to v{version}. Restart loki to use the new version."); println!("Coyote updated to v{version}. Restart coyote to use the new version.");
} }
} }
Ok(()) Ok(())
@@ -172,7 +172,7 @@ mod tests {
#[test] #[test]
fn classify_cargo_install() { fn classify_cargo_install() {
assert_eq!( assert_eq!(
classify_install_path(&PathBuf::from("/home/u/.cargo/bin/loki")), classify_install_path(&PathBuf::from("/home/u/.cargo/bin/coyote")),
InstallSource::Cargo InstallSource::Cargo
); );
} }
@@ -180,7 +180,7 @@ mod tests {
#[test] #[test]
fn classify_homebrew_opt_prefix() { fn classify_homebrew_opt_prefix() {
assert_eq!( assert_eq!(
classify_install_path(&PathBuf::from("/opt/homebrew/bin/loki")), classify_install_path(&PathBuf::from("/opt/homebrew/bin/coyote")),
InstallSource::Homebrew InstallSource::Homebrew
); );
} }
@@ -188,7 +188,7 @@ mod tests {
#[test] #[test]
fn classify_homebrew_cellar() { fn classify_homebrew_cellar() {
assert_eq!( assert_eq!(
classify_install_path(&PathBuf::from("/usr/local/Cellar/loki/0.3.0/bin/loki")), classify_install_path(&PathBuf::from("/usr/local/Cellar/coyote/0.3.0/bin/coyote")),
InstallSource::Homebrew InstallSource::Homebrew
); );
} }
@@ -196,7 +196,7 @@ mod tests {
#[test] #[test]
fn classify_homebrew_linuxbrew() { fn classify_homebrew_linuxbrew() {
assert_eq!( assert_eq!(
classify_install_path(&PathBuf::from("/home/linuxbrew/.linuxbrew/bin/loki")), classify_install_path(&PathBuf::from("/home/linuxbrew/.linuxbrew/bin/coyote")),
InstallSource::Homebrew InstallSource::Homebrew
); );
} }
@@ -204,7 +204,7 @@ mod tests {
#[test] #[test]
fn classify_manual_usr_local_bin() { fn classify_manual_usr_local_bin() {
assert_eq!( assert_eq!(
classify_install_path(&PathBuf::from("/usr/local/bin/loki")), classify_install_path(&PathBuf::from("/usr/local/bin/coyote")),
InstallSource::Manual InstallSource::Manual
); );
} }
@@ -212,7 +212,7 @@ mod tests {
#[test] #[test]
fn classify_manual_local_bin() { fn classify_manual_local_bin() {
assert_eq!( assert_eq!(
classify_install_path(&PathBuf::from("/home/u/.local/bin/loki")), classify_install_path(&PathBuf::from("/home/u/.local/bin/coyote")),
InstallSource::Manual InstallSource::Manual
); );
} }
+27 -5
View File
@@ -4,6 +4,7 @@ pub(crate) mod user_interaction;
use crate::{ use crate::{
config::{Agent, RequestContext}, config::{Agent, RequestContext},
graph,
utils::*, utils::*,
}; };
@@ -1199,6 +1200,9 @@ pub fn run_llm_function(
if dir.exists() { if dir.exists() {
bin_dirs.push(dir); bin_dirs.push(dir);
} }
if graph::agent_has_graph(&agent_name) {
envs.insert("AUTO_CONFIRM".into(), "true".into());
}
} else { } else {
bin_dirs.push(paths::functions_bin_dir()); bin_dirs.push(paths::functions_bin_dir());
} }
@@ -1242,7 +1246,7 @@ pub fn run_llm_function(
.map_err(|err| anyhow!("Unable to run {command_name}, {err}"))?; .map_err(|err| anyhow!("Unable to run {command_name}, {err}"))?;
let stdout = child.stdout.take().expect("Failed to capture stdout"); let stdout = child.stdout.take().expect("Failed to capture stdout");
let mut stderr = child.stderr.take().expect("Failed to capture stderr"); let stderr = child.stderr.take().expect("Failed to capture stderr");
let stdout_thread = std::thread::spawn(move || { let stdout_thread = std::thread::spawn(move || {
let mut buffer = [0; 1024]; let mut buffer = [0; 1024];
@@ -1269,8 +1273,29 @@ pub fn run_llm_function(
}); });
let stderr_thread = std::thread::spawn(move || { let stderr_thread = std::thread::spawn(move || {
let mut buffer = [0; 1024];
let mut reader = stderr;
let mut err = io::stderr();
let mut buf = Vec::new(); let mut buf = Vec::new();
let _ = stderr.read_to_end(&mut buf); while let Ok(n) = reader.read(&mut buffer) {
if n == 0 {
break;
}
let chunk = &buffer[0..n];
buf.extend_from_slice(chunk);
let mut last_pos = 0;
for (i, &byte) in chunk.iter().enumerate() {
if byte == b'\n' {
let _ = err.write_all(&chunk[last_pos..i]);
let _ = err.write_all(b"\r\n");
last_pos = i + 1;
}
}
if last_pos < n {
let _ = err.write_all(&chunk[last_pos..n]);
}
let _ = err.flush();
}
buf buf
}); });
@@ -1283,9 +1308,6 @@ pub fn run_llm_function(
let exit_code = status.code().unwrap_or_default(); let exit_code = status.code().unwrap_or_default();
if exit_code != 0 { if exit_code != 0 {
let stderr = String::from_utf8_lossy(&stderr_bytes).trim().to_string(); let stderr = String::from_utf8_lossy(&stderr_bytes).trim().to_string();
if !stderr.is_empty() {
eprintln!("{stderr}");
}
let tool_error_message = format!("Tool call '{command_name}' exited with code {exit_code}"); let tool_error_message = format!("Tool call '{command_name}' exited with code {exit_code}");
eprintln!("{}", warning_text(&format!("⚠️ {tool_error_message} ⚠️"))); eprintln!("{}", warning_text(&format!("⚠️ {tool_error_message} ⚠️")));
let mut error_json = json!({"tool_call_error": tool_error_message}); let mut error_json = json!({"tool_call_error": tool_error_message});
+34 -18
View File
@@ -28,36 +28,46 @@ impl LlmNodeExecutor {
parent_ctx: &mut RequestContext, parent_ctx: &mut RequestContext,
) -> Result<LlmExecutionOutcome> { ) -> Result<LlmExecutionOutcome> {
let result = run(node, state_manager, parent_ctx).await; let result = run(node, state_manager, parent_ctx).await;
let (output, failed) = match result { let (output, failure_reason) = match result {
Ok(raw) => match &node.output_schema { Ok(raw) => match &node.output_schema {
Some(schema) => match structured::extract(&raw, schema, parent_ctx).await { Some(schema) => match structured::extract(&raw, schema, parent_ctx).await {
Ok(value) => (value, false), Ok(value) => (value, None),
Err(e) => { Err(e) => {
warn!("llm node structured extraction failed: {e}"); warn!("llm node structured extraction failed: {e}");
( (
Value::String(format!("LLM node structured-extraction failed: {e}")), Value::String(format!("LLM node structured-extraction failed: {e}")),
true, Some(format!("structured-extraction failed: {e}")),
) )
} }
}, },
None => (Value::String(raw), false), None => (Value::String(raw), None),
}, },
Err(e) => { Err(e) => {
warn!("llm node failed: {e}"); warn!("llm node failed: {e}");
(Value::String(format!("LLM node failed: {e}")), true) (
Value::String(format!("LLM node failed: {e}")),
Some(format!("LLM call failed: {e:#}")),
)
} }
}; };
apply_state_updates_with_output(node, state_manager, &output); apply_state_updates_with_output(node, state_manager, &output);
Ok(outcome_from(failed, node.fallback.as_deref())) outcome_from(failure_reason.as_deref(), node.fallback.as_deref())
} }
} }
fn outcome_from(failed: bool, fallback: Option<&str>) -> LlmExecutionOutcome { fn outcome_from(
if failed && let Some(fb) = fallback { failure_reason: Option<&str>,
LlmExecutionOutcome::FellBack(fb.to_string()) fallback: Option<&str>,
} else { ) -> Result<LlmExecutionOutcome> {
LlmExecutionOutcome::Continue match (failure_reason, fallback) {
(None, _) => Ok(LlmExecutionOutcome::Continue),
(Some(_), Some(fb)) => Ok(LlmExecutionOutcome::FellBack(fb.to_string())),
(Some(reason), None) => bail!(
"LLM node failed and no fallback declared: {reason}. \
Add a `fallback:` route on the node to route on failure, \
or fix the underlying error."
),
} }
} }
@@ -445,23 +455,29 @@ mod tests {
#[test] #[test]
fn outcome_from_success_is_continue() { fn outcome_from_success_is_continue() {
assert_eq!( assert_eq!(
outcome_from(false, Some("fb")), outcome_from(None, Some("fb")).unwrap(),
LlmExecutionOutcome::Continue
);
assert_eq!(
outcome_from(None, None).unwrap(),
LlmExecutionOutcome::Continue LlmExecutionOutcome::Continue
); );
assert_eq!(outcome_from(false, None), LlmExecutionOutcome::Continue);
} }
#[test] #[test]
fn outcome_from_failure_with_fallback_is_fell_back() { fn outcome_from_failure_with_fallback_is_fell_back() {
assert_eq!( assert_eq!(
outcome_from(true, Some("fb")), outcome_from(Some("HTTP 404"), Some("fb")).unwrap(),
LlmExecutionOutcome::FellBack("fb".to_string()) LlmExecutionOutcome::FellBack("fb".to_string())
); );
} }
#[test] #[test]
fn outcome_from_failure_without_fallback_is_continue() { fn outcome_from_failure_without_fallback_propagates_error() {
assert_eq!(outcome_from(true, None), LlmExecutionOutcome::Continue); let err = outcome_from(Some("HTTP 404"), None).unwrap_err();
let msg = format!("{err:#}");
assert!(msg.contains("no fallback declared"), "got: {msg}");
assert!(msg.contains("HTTP 404"), "got: {msg}");
} }
fn node_with_schema(updates: Option<HashMap<String, String>>, schema: Value) -> LlmNode { fn node_with_schema(updates: Option<HashMap<String, String>>, schema: Value) -> LlmNode {
@@ -552,13 +568,13 @@ mod tests {
let entries = vec![ let entries = vec![
"read_query".to_string(), "read_query".to_string(),
"mcp:pubmed-search".to_string(), "mcp:pubmed-search".to_string(),
"web_search_loki".to_string(), "web_search_coyote".to_string(),
"mcp:github".to_string(), "mcp:github".to_string(),
]; ];
let (regular, mcp) = categorize_tools(Some(&entries)); let (regular, mcp) = categorize_tools(Some(&entries));
assert_eq!(regular, vec!["read_query", "web_search_loki"]); assert_eq!(regular, vec!["read_query", "web_search_coyote"]);
assert_eq!(mcp, vec!["pubmed-search", "github"]); assert_eq!(mcp, vec!["pubmed-search", "github"]);
} }
+1 -1
View File
@@ -423,7 +423,7 @@ mod tests {
#[test] #[test]
fn load_from_file_reads_disk() { fn load_from_file_reads_disk() {
let dir = env::temp_dir(); let dir = env::temp_dir();
let path = dir.join(format!("loki_graph_parser_test_{}.yaml", process::id())); let path = dir.join(format!("coyote_graph_parser_test_{}.yaml", process::id()));
let yaml = formatdoc! {r#" let yaml = formatdoc! {r#"
name: disk_graph name: disk_graph
version: "1.0" version: "1.0"
+1
View File
@@ -55,6 +55,7 @@ impl ScriptExecutor {
cmd.stdout(Stdio::piped()); cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped()); cmd.stderr(Stdio::piped());
cmd.envs(&self.extra_envs); cmd.envs(&self.extra_envs);
cmd.env("AUTO_CONFIRM", "true");
match &state_repr { match &state_repr {
StateRepresentation::Inline(json) => { StateRepresentation::Inline(json) => {
cmd.env("GRAPH_STATE", json); cmd.env("GRAPH_STATE", json);
+2 -2
View File
@@ -812,7 +812,7 @@ model: anthropic:claude-sonnet-4-6
temperature: 0.2 temperature: 0.2
top_p: 0.9 top_p: 0.9
global_tools: global_tools:
- web_search_loki.sh - web_search_coyote.sh
mcp_servers: mcp_servers:
- pubmed-search - pubmed-search
conversation_starters: conversation_starters:
@@ -827,7 +827,7 @@ nodes:
assert_eq!(graph.model.as_deref(), Some("anthropic:claude-sonnet-4-6")); assert_eq!(graph.model.as_deref(), Some("anthropic:claude-sonnet-4-6"));
assert_eq!(graph.temperature, Some(0.2)); assert_eq!(graph.temperature, Some(0.2));
assert_eq!(graph.top_p, Some(0.9)); assert_eq!(graph.top_p, Some(0.9));
assert_eq!(graph.global_tools, vec!["web_search_loki.sh"]); assert_eq!(graph.global_tools, vec!["web_search_coyote.sh"]);
assert_eq!(graph.mcp_servers, vec!["pubmed-search"]); assert_eq!(graph.mcp_servers, vec!["pubmed-search"]);
assert_eq!(graph.conversation_starters, vec!["Look up 2160-0"]); assert_eq!(graph.conversation_starters, vec!["Look up 2160-0"]);
} }
+2 -1
View File
@@ -369,7 +369,8 @@ mod tests {
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.expect("time went backwards") .expect("time went backwards")
.as_nanos(); .as_nanos();
let path = std::env::temp_dir().join(format!("loki_python_parser_{file_name}_{unique}.py")); let path =
std::env::temp_dir().join(format!("coyote_python_parser_{file_name}_{unique}.py"));
fs::write(&path, source).expect("failed to write temp python source"); fs::write(&path, source).expect("failed to write temp python source");
let file = File::open(&path).expect("failed to open temp python source"); let file = File::open(&path).expect("failed to open temp python source");
let result = generate_python_declarations(file, file_name, Some(parent)); let result = generate_python_declarations(file, file_name, Some(parent));
+1 -1
View File
@@ -425,7 +425,7 @@ mod tests {
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.expect("time") .expect("time")
.as_nanos(); .as_nanos();
let path = std::env::temp_dir().join(format!("loki_ts_parser_{file_name}_{unique}.ts")); let path = std::env::temp_dir().join(format!("coyote_ts_parser_{file_name}_{unique}.ts"));
fs::write(&path, source).expect("write"); fs::write(&path, source).expect("write");
let file = File::open(&path).expect("open"); let file = File::open(&path).expect("open");
let result = generate_typescript_declarations(file, file_name, Some(parent)); let result = generate_typescript_declarations(file, file_name, Some(parent));
+2 -2
View File
@@ -215,7 +215,7 @@ static REPL_COMMANDS: LazyLock<[ReplCommand; 42]> = LazyLock::new(|| {
), ),
ReplCommand::new( ReplCommand::new(
".vault", ".vault",
"View or modify the Loki vault", "View or modify the Coyote vault",
AssertState::pass(), AssertState::pass(),
), ),
ReplCommand::new( ReplCommand::new(
@@ -225,7 +225,7 @@ static REPL_COMMANDS: LazyLock<[ReplCommand; 42]> = LazyLock::new(|| {
), ),
ReplCommand::new( ReplCommand::new(
".update", ".update",
"Update Loki to the latest release (or a specified version)", "Update Coyote to the latest release (or a specified version)",
AssertState::pass(), AssertState::pass(),
), ),
ReplCommand::new(".exit", "Exit REPL", AssertState::pass()), ReplCommand::new(".exit", "Exit REPL", AssertState::pass()),
+1 -1
View File
@@ -366,7 +366,7 @@ mod tests {
assert!(is_valid_extension(Some(&md_ext), Path::new("Agents.md"))); assert!(is_valid_extension(Some(&md_ext), Path::new("Agents.md")));
assert!(is_valid_extension( assert!(is_valid_extension(
Some(&md_ext), Some(&md_ext),
Path::new("/home/atusa/code/loki.wiki/Agents.md") Path::new("/home/atusa/code/coyote.wiki/Agents.md")
)); ));
assert!(!is_valid_extension(Some(&md_ext), Path::new("notes.txt"))); assert!(!is_valid_extension(Some(&md_ext), Path::new("notes.txt")));
assert!(!is_valid_extension(Some(&md_ext), Path::new("README"))); assert!(!is_valid_extension(Some(&md_ext), Path::new("README")));
+2 -2
View File
@@ -28,7 +28,7 @@ pub fn ensure_password_file_initialized(local_provider: &mut LocalProvider) -> R
} }
} else { } else {
Err(anyhow!( Err(anyhow!(
"A password file is required to utilize the Loki vault. Please configure a password file in your config file and try again." "A password file is required to utilize the Coyote vault. Please configure a password file in your config file and try again."
)) ))
} }
} }
@@ -95,7 +95,7 @@ pub fn create_vault_password_file(vault: &mut Vault) -> Result<()> {
if !ans { if !ans {
return Err(anyhow!( return Err(anyhow!(
"A password file is required to utilize the Loki vault. Please configure a password file in your config file and try again." "A password file is required to utilize the Coyote vault. Please configure a password file in your config file and try again."
)); ));
} }