Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
83581d9d18
|
Generated
+70
-14
@@ -1238,7 +1238,7 @@ dependencies = [
|
|||||||
"encode_unicode",
|
"encode_unicode",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"unicode-width",
|
"unicode-width 0.2.2",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1338,6 +1338,22 @@ version = "0.8.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"libc",
|
||||||
|
"mio 0.8.11",
|
||||||
|
"parking_lot",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossterm"
|
name = "crossterm"
|
||||||
version = "0.28.1"
|
version = "0.28.1"
|
||||||
@@ -1347,7 +1363,7 @@ dependencies = [
|
|||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"crossterm_winapi",
|
"crossterm_winapi",
|
||||||
"filedescriptor",
|
"filedescriptor",
|
||||||
"mio",
|
"mio 1.1.1",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rustix 0.38.44",
|
"rustix 0.38.44",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1366,7 +1382,7 @@ dependencies = [
|
|||||||
"crossterm_winapi",
|
"crossterm_winapi",
|
||||||
"derive_more 2.1.1",
|
"derive_more 2.1.1",
|
||||||
"document-features",
|
"document-features",
|
||||||
"mio",
|
"mio 1.1.1",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rustix 1.1.3",
|
"rustix 1.1.3",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
@@ -2148,7 +2164,7 @@ version = "0.2.24"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
|
checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-width",
|
"unicode-width 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2822,16 +2838,19 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inquire"
|
name = "inquire"
|
||||||
version = "0.9.4"
|
version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6654738b8024300cf062d04a1c13c10c8e2cea598ec1c47dc9b6641159429756"
|
checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"crossterm 0.29.0",
|
"crossterm 0.25.0",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
"fuzzy-matcher",
|
"fuzzy-matcher",
|
||||||
|
"fxhash",
|
||||||
|
"newline-converter",
|
||||||
|
"once_cell",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-width",
|
"unicode-width 0.1.14",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3223,7 +3242,7 @@ dependencies = [
|
|||||||
"tokio-graceful",
|
"tokio-graceful",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-width",
|
"unicode-width 0.2.2",
|
||||||
"url",
|
"url",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
"uuid",
|
"uuid",
|
||||||
@@ -3438,6 +3457,18 @@ dependencies = [
|
|||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@@ -3502,6 +3533,15 @@ version = "1.0.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "newline-converter"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.26.4"
|
version = "0.26.4"
|
||||||
@@ -4515,7 +4555,7 @@ dependencies = [
|
|||||||
"strum_macros 0.26.4",
|
"strum_macros 0.26.4",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-width",
|
"unicode-width 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5337,7 +5377,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
|
checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio 0.8.11",
|
||||||
|
"mio 1.1.1",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5641,7 +5682,7 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"memchr",
|
"memchr",
|
||||||
"mio",
|
"mio 1.1.1",
|
||||||
"terminal-trx",
|
"terminal-trx",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
"xterm-color",
|
"xterm-color",
|
||||||
@@ -5676,7 +5717,7 @@ checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"smawk",
|
"smawk",
|
||||||
"unicode-linebreak",
|
"unicode-linebreak",
|
||||||
"unicode-width",
|
"unicode-width 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5821,7 +5862,7 @@ checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio 1.1.1",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
@@ -6252,6 +6293,12 @@ version = "1.12.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -6919,6 +6966,15 @@ dependencies = [
|
|||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.48.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ bytes = "1.4.0"
|
|||||||
clap = { version = "4.5.40", features = ["cargo", "derive", "wrap_help"] }
|
clap = { version = "4.5.40", features = ["cargo", "derive", "wrap_help"] }
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
futures-util = "0.3.29"
|
futures-util = "0.3.29"
|
||||||
inquire = "0.9.4"
|
inquire = "0.7.0"
|
||||||
is-terminal = "0.4.9"
|
is-terminal = "0.4.9"
|
||||||
reedline = "0.40.0"
|
reedline = "0.40.0"
|
||||||
serde = { version = "1.0.152", features = ["derive"] }
|
serde = { version = "1.0.152", features = ["derive"] }
|
||||||
|
|||||||
@@ -14,28 +14,11 @@ _project_dir() {
|
|||||||
(cd "${dir}" 2>/dev/null && pwd) || echo "${dir}"
|
(cd "${dir}" 2>/dev/null && pwd) || echo "${dir}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Normalize a path to be relative to project root.
|
|
||||||
# Strips the project_dir prefix if the LLM passes an absolute path.
|
|
||||||
# Usage: local rel_path; rel_path=$(_normalize_path "/abs/or/rel/path")
|
|
||||||
_normalize_path() {
|
|
||||||
local input_path="$1"
|
|
||||||
local project_dir
|
|
||||||
project_dir=$(_project_dir)
|
|
||||||
|
|
||||||
if [[ "${input_path}" == /* ]]; then
|
|
||||||
input_path="${input_path#"${project_dir}"/}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
input_path="${input_path#./}"
|
|
||||||
echo "${input_path}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# @cmd Read a file's contents before modifying
|
# @cmd Read a file's contents before modifying
|
||||||
# @option --path! Path to the file (relative to project root)
|
# @option --path! Path to the file (relative to project root)
|
||||||
read_file() {
|
read_file() {
|
||||||
local file_path
|
|
||||||
# shellcheck disable=SC2154
|
# shellcheck disable=SC2154
|
||||||
file_path=$(_normalize_path "${argc_path}")
|
local file_path="${argc_path}"
|
||||||
local project_dir
|
local project_dir
|
||||||
project_dir=$(_project_dir)
|
project_dir=$(_project_dir)
|
||||||
local full_path="${project_dir}/${file_path}"
|
local full_path="${project_dir}/${file_path}"
|
||||||
@@ -56,8 +39,7 @@ read_file() {
|
|||||||
# @option --path! Path for the file (relative to project root)
|
# @option --path! Path for the file (relative to project root)
|
||||||
# @option --content! Complete file contents to write
|
# @option --content! Complete file contents to write
|
||||||
write_file() {
|
write_file() {
|
||||||
local file_path
|
local file_path="${argc_path}"
|
||||||
file_path=$(_normalize_path "${argc_path}")
|
|
||||||
# shellcheck disable=SC2154
|
# shellcheck disable=SC2154
|
||||||
local content="${argc_content}"
|
local content="${argc_content}"
|
||||||
local project_dir
|
local project_dir
|
||||||
@@ -65,7 +47,7 @@ write_file() {
|
|||||||
local full_path="${project_dir}/${file_path}"
|
local full_path="${project_dir}/${file_path}"
|
||||||
|
|
||||||
mkdir -p "$(dirname "${full_path}")"
|
mkdir -p "$(dirname "${full_path}")"
|
||||||
printf '%s' "${content}" > "${full_path}"
|
echo "${content}" > "${full_path}"
|
||||||
|
|
||||||
green "Wrote: ${file_path}" >> "$LLM_OUTPUT"
|
green "Wrote: ${file_path}" >> "$LLM_OUTPUT"
|
||||||
}
|
}
|
||||||
@@ -73,8 +55,7 @@ write_file() {
|
|||||||
# @cmd Find files similar to a given path (for pattern matching)
|
# @cmd Find files similar to a given path (for pattern matching)
|
||||||
# @option --path! Path to find similar files for
|
# @option --path! Path to find similar files for
|
||||||
find_similar_files() {
|
find_similar_files() {
|
||||||
local file_path
|
local file_path="${argc_path}"
|
||||||
file_path=$(_normalize_path "${argc_path}")
|
|
||||||
local project_dir
|
local project_dir
|
||||||
project_dir=$(_project_dir)
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
@@ -90,14 +71,14 @@ find_similar_files() {
|
|||||||
! -name "$(basename "${file_path}")" \
|
! -name "$(basename "${file_path}")" \
|
||||||
! -name "*test*" \
|
! -name "*test*" \
|
||||||
! -name "*spec*" \
|
! -name "*spec*" \
|
||||||
2>/dev/null | sed "s|^${project_dir}/||" | head -3)
|
2>/dev/null | head -3)
|
||||||
|
|
||||||
if [[ -z "${results}" ]]; then
|
if [[ -z "${results}" ]]; then
|
||||||
results=$(find "${project_dir}/src" -type f -name "*.${ext}" \
|
results=$(find "${project_dir}/src" -type f -name "*.${ext}" \
|
||||||
! -name "*test*" \
|
! -name "*test*" \
|
||||||
! -name "*spec*" \
|
! -name "*spec*" \
|
||||||
-not -path '*/target/*' \
|
-not -path '*/target/*' \
|
||||||
2>/dev/null | sed "s|^${project_dir}/||" | head -3)
|
2>/dev/null | head -3)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -n "${results}" ]]; then
|
if [[ -n "${results}" ]]; then
|
||||||
@@ -205,7 +186,6 @@ search_code() {
|
|||||||
grep -v '/target/' | \
|
grep -v '/target/' | \
|
||||||
grep -v '/node_modules/' | \
|
grep -v '/node_modules/' | \
|
||||||
grep -v '/.git/' | \
|
grep -v '/.git/' | \
|
||||||
sed "s|^${project_dir}/||" | \
|
|
||||||
head -20) || true
|
head -20) || true
|
||||||
|
|
||||||
if [[ -n "${results}" ]]; then
|
if [[ -n "${results}" ]]; then
|
||||||
|
|||||||
@@ -14,21 +14,6 @@ _project_dir() {
|
|||||||
(cd "${dir}" 2>/dev/null && pwd) || echo "${dir}"
|
(cd "${dir}" 2>/dev/null && pwd) || echo "${dir}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Normalize a path to be relative to project root.
|
|
||||||
# Strips the project_dir prefix if the LLM passes an absolute path.
|
|
||||||
_normalize_path() {
|
|
||||||
local input_path="$1"
|
|
||||||
local project_dir
|
|
||||||
project_dir=$(_project_dir)
|
|
||||||
|
|
||||||
if [[ "${input_path}" == /* ]]; then
|
|
||||||
input_path="${input_path#"${project_dir}"/}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
input_path="${input_path#./}"
|
|
||||||
echo "${input_path}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# @cmd Get project structure and layout
|
# @cmd Get project structure and layout
|
||||||
get_structure() {
|
get_structure() {
|
||||||
local project_dir
|
local project_dir
|
||||||
@@ -93,7 +78,6 @@ search_content() {
|
|||||||
grep -v '/node_modules/' | \
|
grep -v '/node_modules/' | \
|
||||||
grep -v '/.git/' | \
|
grep -v '/.git/' | \
|
||||||
grep -v '/dist/' | \
|
grep -v '/dist/' | \
|
||||||
sed "s|^${project_dir}/||" | \
|
|
||||||
head -30) || true
|
head -30) || true
|
||||||
|
|
||||||
if [[ -n "${results}" ]]; then
|
if [[ -n "${results}" ]]; then
|
||||||
@@ -107,9 +91,8 @@ search_content() {
|
|||||||
# @option --path! Path to the file (relative to project root)
|
# @option --path! Path to the file (relative to project root)
|
||||||
# @option --lines Maximum lines to read (default: 200)
|
# @option --lines Maximum lines to read (default: 200)
|
||||||
read_file() {
|
read_file() {
|
||||||
local file_path
|
|
||||||
# shellcheck disable=SC2154
|
# shellcheck disable=SC2154
|
||||||
file_path=$(_normalize_path "${argc_path}")
|
local file_path="${argc_path}"
|
||||||
local max_lines="${argc_lines:-200}"
|
local max_lines="${argc_lines:-200}"
|
||||||
local project_dir
|
local project_dir
|
||||||
project_dir=$(_project_dir)
|
project_dir=$(_project_dir)
|
||||||
@@ -139,8 +122,7 @@ read_file() {
|
|||||||
# @cmd Find similar files to a given file (for pattern matching)
|
# @cmd Find similar files to a given file (for pattern matching)
|
||||||
# @option --path! Path to the reference file
|
# @option --path! Path to the reference file
|
||||||
find_similar() {
|
find_similar() {
|
||||||
local file_path
|
local file_path="${argc_path}"
|
||||||
file_path=$(_normalize_path "${argc_path}")
|
|
||||||
local project_dir
|
local project_dir
|
||||||
project_dir=$(_project_dir)
|
project_dir=$(_project_dir)
|
||||||
|
|
||||||
@@ -156,7 +138,7 @@ find_similar() {
|
|||||||
! -name "$(basename "${file_path}")" \
|
! -name "$(basename "${file_path}")" \
|
||||||
! -name "*test*" \
|
! -name "*test*" \
|
||||||
! -name "*spec*" \
|
! -name "*spec*" \
|
||||||
2>/dev/null | sed "s|^${project_dir}/||" | head -5)
|
2>/dev/null | head -5)
|
||||||
|
|
||||||
if [[ -n "${results}" ]]; then
|
if [[ -n "${results}" ]]; then
|
||||||
echo "${results}" >> "$LLM_OUTPUT"
|
echo "${results}" >> "$LLM_OUTPUT"
|
||||||
@@ -165,7 +147,7 @@ find_similar() {
|
|||||||
! -name "$(basename "${file_path}")" \
|
! -name "$(basename "${file_path}")" \
|
||||||
! -name "*test*" \
|
! -name "*test*" \
|
||||||
-not -path '*/target/*' \
|
-not -path '*/target/*' \
|
||||||
2>/dev/null | sed "s|^${project_dir}/||" | head -5)
|
2>/dev/null | head -5)
|
||||||
if [[ -n "${results}" ]]; then
|
if [[ -n "${results}" ]]; then
|
||||||
echo "${results}" >> "$LLM_OUTPUT"
|
echo "${results}" >> "$LLM_OUTPUT"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -14,38 +14,21 @@ _project_dir() {
|
|||||||
(cd "${dir}" 2>/dev/null && pwd) || echo "${dir}"
|
(cd "${dir}" 2>/dev/null && pwd) || echo "${dir}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Normalize a path to be relative to project root.
|
|
||||||
# Strips the project_dir prefix if the LLM passes an absolute path.
|
|
||||||
_normalize_path() {
|
|
||||||
local input_path="$1"
|
|
||||||
local project_dir
|
|
||||||
project_dir=$(_project_dir)
|
|
||||||
|
|
||||||
if [[ "${input_path}" == /* ]]; then
|
|
||||||
input_path="${input_path#"${project_dir}"/}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
input_path="${input_path#./}"
|
|
||||||
echo "${input_path}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# @cmd Read a file for analysis
|
# @cmd Read a file for analysis
|
||||||
# @option --path! Path to the file (relative to project root)
|
# @option --path! Path to the file (relative to project root)
|
||||||
read_file() {
|
read_file() {
|
||||||
local project_dir
|
local project_dir
|
||||||
project_dir=$(_project_dir)
|
project_dir=$(_project_dir)
|
||||||
local file_path
|
|
||||||
# shellcheck disable=SC2154
|
# shellcheck disable=SC2154
|
||||||
file_path=$(_normalize_path "${argc_path}")
|
local full_path="${project_dir}/${argc_path}"
|
||||||
local full_path="${project_dir}/${file_path}"
|
|
||||||
|
|
||||||
if [[ ! -f "${full_path}" ]]; then
|
if [[ ! -f "${full_path}" ]]; then
|
||||||
error "File not found: ${file_path}" >> "$LLM_OUTPUT"
|
error "File not found: ${argc_path}" >> "$LLM_OUTPUT"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
{
|
{
|
||||||
info "Reading: ${file_path}"
|
info "Reading: ${argc_path}"
|
||||||
echo ""
|
echo ""
|
||||||
cat "${full_path}"
|
cat "${full_path}"
|
||||||
} >> "$LLM_OUTPUT"
|
} >> "$LLM_OUTPUT"
|
||||||
@@ -97,7 +80,6 @@ search_code() {
|
|||||||
grep -v '/target/' | \
|
grep -v '/target/' | \
|
||||||
grep -v '/node_modules/' | \
|
grep -v '/node_modules/' | \
|
||||||
grep -v '/.git/' | \
|
grep -v '/.git/' | \
|
||||||
sed "s|^${project_dir}/||" | \
|
|
||||||
head -30) || true
|
head -30) || true
|
||||||
|
|
||||||
if [[ -n "${results}" ]]; then
|
if [[ -n "${results}" ]]; then
|
||||||
@@ -131,8 +113,7 @@ analyze_with_command() {
|
|||||||
# @cmd List directory contents
|
# @cmd List directory contents
|
||||||
# @option --path Path to list (default: project root)
|
# @option --path Path to list (default: project root)
|
||||||
list_directory() {
|
list_directory() {
|
||||||
local dir_path
|
local dir_path="${argc_path:-.}"
|
||||||
dir_path=$(_normalize_path "${argc_path:-.}")
|
|
||||||
local project_dir
|
local project_dir
|
||||||
project_dir=$(_project_dir)
|
project_dir=$(_project_dir)
|
||||||
local full_path="${project_dir}/${dir_path}"
|
local full_path="${project_dir}/${dir_path}"
|
||||||
|
|||||||
@@ -16,15 +16,11 @@
|
|||||||
},
|
},
|
||||||
"atlassian": {
|
"atlassian": {
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": ["-y", "mcp-remote@0.1.13", "https://mcp.atlassian.com/v1/mcp"]
|
"args": ["-y", "mcp-remote@0.1.13", "https://mcp.atlassian.com/v1/sse"]
|
||||||
},
|
},
|
||||||
"docker": {
|
"docker": {
|
||||||
"command": "uvx",
|
"command": "uvx",
|
||||||
"args": ["mcp-server-docker"]
|
"args": ["mcp-server-docker"]
|
||||||
},
|
|
||||||
"ddg-search": {
|
|
||||||
"command": "uvx",
|
|
||||||
"args": ["duckduckgo-mcp-server"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -77,7 +77,7 @@ visible_tools: # Which tools are visible to be compiled (and a
|
|||||||
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
|
||||||
enabled_mcp_servers: null # Which MCP servers to enable by default (e.g. 'github,slack,ddg-search')
|
enabled_mcp_servers: null # Which MCP servers to enable by default (e.g. 'github,slack')
|
||||||
|
|
||||||
# ---- Session ----
|
# ---- Session ----
|
||||||
# See the [Session documentation](./docs/SESSIONS.md) for more information
|
# See the [Session documentation](./docs/SESSIONS.md) for more information
|
||||||
|
|||||||
@@ -714,7 +714,6 @@ Loki comes packaged with some useful built-in agents:
|
|||||||
* `code-reviewer`: A [CodeRabbit](https://coderabbit.ai)-style code reviewer that spawns per-file reviewers using the teammate messaging pattern
|
* `code-reviewer`: A [CodeRabbit](https://coderabbit.ai)-style code reviewer that spawns per-file reviewers using the teammate messaging pattern
|
||||||
* `demo`: An example agent to use for reference when learning to create your own agents
|
* `demo`: An example agent to use for reference when learning to create your own agents
|
||||||
* `explore`: An agent designed to help you explore and understand your codebase
|
* `explore`: An agent designed to help you explore and understand your codebase
|
||||||
* `file-reviewer`: An agent designed to perform code-review on a single file (used by the `code-reviewer` agent)
|
|
||||||
* `jira-helper`: An agent that assists you with all your Jira-related tasks
|
* `jira-helper`: An agent that assists you with all your Jira-related tasks
|
||||||
* `oracle`: An agent for high-level architecture, design decisions, and complex debugging
|
* `oracle`: An agent for high-level architecture, design decisions, and complex debugging
|
||||||
* `sisyphus`: A powerhouse orchestrator agent for writing complex code and acting as a natural language interface for your codebase (similar to ClaudeCode, Gemini CLI, Codex, or OpenCode). Uses sub-agent spawning to delegate to `explore`, `coder`, and `oracle`.
|
* `sisyphus`: A powerhouse orchestrator agent for writing complex code and acting as a natural language interface for your codebase (similar to ClaudeCode, Gemini CLI, Codex, or OpenCode). Uses sub-agent spawning to delegate to `explore`, `coder`, and `oracle`.
|
||||||
|
|||||||
@@ -142,25 +142,6 @@ temporary localhost server to capture the callback automatically (e.g. Gemini) o
|
|||||||
code back into the terminal (e.g. Claude). Loki stores the tokens in `~/.cache/loki/oauth` and automatically refreshes
|
code back into the terminal (e.g. Claude). Loki stores the tokens in `~/.cache/loki/oauth` and automatically refreshes
|
||||||
them when they expire.
|
them when they expire.
|
||||||
|
|
||||||
#### Gemini OAuth Note
|
|
||||||
Loki uses the following scopes for OAuth with Gemini:
|
|
||||||
* https://www.googleapis.com/auth/generative-language.peruserquota
|
|
||||||
* https://www.googleapis.com/auth/userinfo.email
|
|
||||||
* https://www.googleapis.com/auth/generative-language.retriever (Sensitive)
|
|
||||||
|
|
||||||
Since the `generative-language.retriever` scope is a sensitive scope, Google needs to verify Loki, which requires full
|
|
||||||
branding (logo, official website, privacy policy, terms of service, etc.). The Loki app is open-source and is designed
|
|
||||||
to be used as a simple CLI. As such, there's no terms of service or privacy policy associated with it, and thus Google
|
|
||||||
cannot verify Loki.
|
|
||||||
|
|
||||||
So, when you kick off OAuth with Gemini, you may see a page similar to the following:
|
|
||||||

|
|
||||||
|
|
||||||
Simply click the `Advanced` link and click `Go to Loki (unsafe)` to continue the OAuth flow.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
**Step 3: Use normally**
|
**Step 3: Use normally**
|
||||||
|
|
||||||
Once authenticated, the client works like any other. Loki uses the stored OAuth tokens automatically:
|
Once authenticated, the client works like any other. Loki uses the stored OAuth tokens automatically:
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ Loki ships with a `functions/mcp.json` file that includes some useful MCP server
|
|||||||
* [github](https://github.com/github/github-mcp-server) - Interact with GitHub repositories, issues, pull requests, and more.
|
* [github](https://github.com/github/github-mcp-server) - Interact with GitHub repositories, issues, pull requests, and more.
|
||||||
* [docker](https://github.com/ckreiling/mcp-server-docker) - Manage your local Docker containers with natural language
|
* [docker](https://github.com/ckreiling/mcp-server-docker) - Manage your local Docker containers with natural language
|
||||||
* [slack](https://github.com/korotovsky/slack-mcp-server) - Interact with Slack
|
* [slack](https://github.com/korotovsky/slack-mcp-server) - Interact with Slack
|
||||||
* [ddg-search](https://github.com/nickclyde/duckduckgo-mcp-server) - Perform web searches with the DuckDuckGo search engine
|
|
||||||
|
|
||||||
## Loki Configuration
|
## Loki Configuration
|
||||||
MCP servers, like tools, can be used in a handful of contexts:
|
MCP servers, like tools, can be used in a handful of contexts:
|
||||||
|
|||||||
@@ -3,13 +3,6 @@
|
|||||||
# - https://platform.openai.com/docs/api-reference/chat
|
# - https://platform.openai.com/docs/api-reference/chat
|
||||||
- provider: openai
|
- provider: openai
|
||||||
models:
|
models:
|
||||||
- name: gpt-5.2
|
|
||||||
max_input_tokens: 400000
|
|
||||||
max_output_tokens: 128000
|
|
||||||
input_price: 1.75
|
|
||||||
output_price: 14
|
|
||||||
supports_vision: true
|
|
||||||
supports_function_calling: true
|
|
||||||
- name: gpt-5.1
|
- name: gpt-5.1
|
||||||
max_input_tokens: 400000
|
max_input_tokens: 400000
|
||||||
max_output_tokens: 128000
|
max_output_tokens: 128000
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use super::oauth::{OAuthProvider, TokenRequestFormat};
|
|||||||
|
|
||||||
pub struct GeminiOAuthProvider;
|
pub struct GeminiOAuthProvider;
|
||||||
|
|
||||||
|
// TODO: Replace with real credentials after registering Loki with Google Cloud Console
|
||||||
const GEMINI_CLIENT_ID: &str =
|
const GEMINI_CLIENT_ID: &str =
|
||||||
"50826443741-upqcebrs4gctqht1f08ku46qlbirkdsj.apps.googleusercontent.com";
|
"50826443741-upqcebrs4gctqht1f08ku46qlbirkdsj.apps.googleusercontent.com";
|
||||||
const GEMINI_CLIENT_SECRET: &str = "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";
|
const GEMINI_CLIENT_SECRET: &str = "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";
|
||||||
@@ -28,7 +29,7 @@ impl OAuthProvider for GeminiOAuthProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn scopes(&self) -> &str {
|
fn scopes(&self) -> &str {
|
||||||
"https://www.googleapis.com/auth/generative-language.peruserquota https://www.googleapis.com/auth/generative-language.retriever https://www.googleapis.com/auth/userinfo.email"
|
"https://www.googleapis.com/auth/cloud-platform.readonly https://www.googleapis.com/auth/userinfo.email"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn client_secret(&self) -> Option<&str> {
|
fn client_secret(&self) -> Option<&str> {
|
||||||
|
|||||||
+21
-20
@@ -1,12 +1,12 @@
|
|||||||
use super::ClientConfig;
|
use super::ClientConfig;
|
||||||
use super::access_token::{is_valid_access_token, set_access_token};
|
use super::access_token::{is_valid_access_token, set_access_token};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use anyhow::{Result, anyhow, bail};
|
use anyhow::{Result, bail};
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use inquire::Text;
|
use inquire::Text;
|
||||||
use reqwest::{Client as ReqwestClient, RequestBuilder};
|
use reqwest::Client as ReqwestClient;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
@@ -76,6 +76,7 @@ pub async fn run_oauth_flow(provider: &dyn OAuthProvider, client_name: &str) ->
|
|||||||
let listener = TcpListener::bind("127.0.0.1:0")?;
|
let listener = TcpListener::bind("127.0.0.1:0")?;
|
||||||
let port = listener.local_addr()?.port();
|
let port = listener.local_addr()?.port();
|
||||||
let uri = format!("http://127.0.0.1:{port}/callback");
|
let uri = format!("http://127.0.0.1:{port}/callback");
|
||||||
|
// Drop the listener so run_oauth_flow can re-bind below
|
||||||
drop(listener);
|
drop(listener);
|
||||||
uri
|
uri
|
||||||
} else {
|
} else {
|
||||||
@@ -148,15 +149,15 @@ pub async fn run_oauth_flow(provider: &dyn OAuthProvider, client_name: &str) ->
|
|||||||
|
|
||||||
let access_token = response["access_token"]
|
let access_token = response["access_token"]
|
||||||
.as_str()
|
.as_str()
|
||||||
.ok_or_else(|| anyhow!("Missing access_token in response: {response}"))?
|
.ok_or_else(|| anyhow::anyhow!("Missing access_token in response: {response}"))?
|
||||||
.to_string();
|
.to_string();
|
||||||
let refresh_token = response["refresh_token"]
|
let refresh_token = response["refresh_token"]
|
||||||
.as_str()
|
.as_str()
|
||||||
.ok_or_else(|| anyhow!("Missing refresh_token in response: {response}"))?
|
.ok_or_else(|| anyhow::anyhow!("Missing refresh_token in response: {response}"))?
|
||||||
.to_string();
|
.to_string();
|
||||||
let expires_in = response["expires_in"]
|
let expires_in = response["expires_in"]
|
||||||
.as_i64()
|
.as_i64()
|
||||||
.ok_or_else(|| anyhow!("Missing expires_in in response: {response}"))?;
|
.ok_or_else(|| anyhow::anyhow!("Missing expires_in in response: {response}"))?;
|
||||||
|
|
||||||
let expires_at = Utc::now().timestamp() + expires_in;
|
let expires_at = Utc::now().timestamp() + expires_in;
|
||||||
|
|
||||||
@@ -213,7 +214,7 @@ pub async fn refresh_oauth_token(
|
|||||||
|
|
||||||
let access_token = response["access_token"]
|
let access_token = response["access_token"]
|
||||||
.as_str()
|
.as_str()
|
||||||
.ok_or_else(|| anyhow!("Missing access_token in refresh response: {response}"))?
|
.ok_or_else(|| anyhow::anyhow!("Missing access_token in refresh response: {response}"))?
|
||||||
.to_string();
|
.to_string();
|
||||||
let refresh_token = response["refresh_token"]
|
let refresh_token = response["refresh_token"]
|
||||||
.as_str()
|
.as_str()
|
||||||
@@ -221,7 +222,7 @@ pub async fn refresh_oauth_token(
|
|||||||
.unwrap_or_else(|| tokens.refresh_token.clone());
|
.unwrap_or_else(|| tokens.refresh_token.clone());
|
||||||
let expires_in = response["expires_in"]
|
let expires_in = response["expires_in"]
|
||||||
.as_i64()
|
.as_i64()
|
||||||
.ok_or_else(|| anyhow!("Missing expires_in in refresh response: {response}"))?;
|
.ok_or_else(|| anyhow::anyhow!("Missing expires_in in refresh response: {response}"))?;
|
||||||
|
|
||||||
let expires_at = Utc::now().timestamp() + expires_in;
|
let expires_at = Utc::now().timestamp() + expires_in;
|
||||||
|
|
||||||
@@ -265,7 +266,7 @@ fn build_token_request(
|
|||||||
client: &ReqwestClient,
|
client: &ReqwestClient,
|
||||||
provider: &(impl OAuthProvider + ?Sized),
|
provider: &(impl OAuthProvider + ?Sized),
|
||||||
params: &[(&str, &str)],
|
params: &[(&str, &str)],
|
||||||
) -> RequestBuilder {
|
) -> reqwest::RequestBuilder {
|
||||||
let mut request = match provider.token_request_format() {
|
let mut request = match provider.token_request_format() {
|
||||||
TokenRequestFormat::Json => {
|
TokenRequestFormat::Json => {
|
||||||
let body: serde_json::Map<String, Value> = params
|
let body: serde_json::Map<String, Value> = params
|
||||||
@@ -307,7 +308,7 @@ fn listen_for_oauth_callback(redirect_uri: &str) -> Result<(String, String)> {
|
|||||||
let host = url.host_str().unwrap_or("127.0.0.1");
|
let host = url.host_str().unwrap_or("127.0.0.1");
|
||||||
let port = url
|
let port = url
|
||||||
.port()
|
.port()
|
||||||
.ok_or_else(|| anyhow!("No port in redirect URI"))?;
|
.ok_or_else(|| anyhow::anyhow!("No port in redirect URI"))?;
|
||||||
let path = url.path();
|
let path = url.path();
|
||||||
|
|
||||||
println!("Waiting for OAuth callback on {redirect_uri} ...\n");
|
println!("Waiting for OAuth callback on {redirect_uri} ...\n");
|
||||||
@@ -322,11 +323,19 @@ fn listen_for_oauth_callback(redirect_uri: &str) -> Result<(String, String)> {
|
|||||||
let request_path = request_line
|
let request_path = request_line
|
||||||
.split_whitespace()
|
.split_whitespace()
|
||||||
.nth(1)
|
.nth(1)
|
||||||
.ok_or_else(|| anyhow!("Malformed HTTP request from OAuth callback"))?;
|
.ok_or_else(|| anyhow::anyhow!("Malformed HTTP request from OAuth callback"))?;
|
||||||
|
|
||||||
let full_url = format!("http://{host}:{port}{request_path}");
|
let full_url = format!("http://{host}:{port}{request_path}");
|
||||||
let parsed: Url = full_url.parse()?;
|
let parsed: Url = full_url.parse()?;
|
||||||
|
|
||||||
|
let response_body = "<html><body><h2>Authentication successful!</h2><p>You can close this tab and return to your terminal.</p></body></html>";
|
||||||
|
let response = format!(
|
||||||
|
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
|
||||||
|
response_body.len(),
|
||||||
|
response_body
|
||||||
|
);
|
||||||
|
stream.write_all(response.as_bytes())?;
|
||||||
|
|
||||||
if !parsed.path().starts_with(path) {
|
if !parsed.path().starts_with(path) {
|
||||||
bail!("Unexpected callback path: {}", parsed.path());
|
bail!("Unexpected callback path: {}", parsed.path());
|
||||||
}
|
}
|
||||||
@@ -341,22 +350,14 @@ fn listen_for_oauth_callback(redirect_uri: &str) -> Result<(String, String)> {
|
|||||||
.find(|(k, _)| k == "error")
|
.find(|(k, _)| k == "error")
|
||||||
.map(|(_, v)| v.to_string())
|
.map(|(_, v)| v.to_string())
|
||||||
.unwrap_or_else(|| "unknown".to_string());
|
.unwrap_or_else(|| "unknown".to_string());
|
||||||
anyhow!("OAuth callback returned error: {error}")
|
anyhow::anyhow!("OAuth callback returned error: {error}")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let returned_state = parsed
|
let returned_state = parsed
|
||||||
.query_pairs()
|
.query_pairs()
|
||||||
.find(|(k, _)| k == "state")
|
.find(|(k, _)| k == "state")
|
||||||
.map(|(_, v)| v.to_string())
|
.map(|(_, v)| v.to_string())
|
||||||
.ok_or_else(|| anyhow!("Missing state parameter in OAuth callback"))?;
|
.ok_or_else(|| anyhow::anyhow!("Missing state parameter in OAuth callback"))?;
|
||||||
|
|
||||||
let response_body = "<html><body><h2>Authentication successful!</h2><p>You can close this tab and return to your terminal.</p></body></html>";
|
|
||||||
let response = format!(
|
|
||||||
"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
|
|
||||||
response_body.len(),
|
|
||||||
response_body
|
|
||||||
);
|
|
||||||
stream.write_all(response.as_bytes())?;
|
|
||||||
|
|
||||||
Ok((code, returned_state))
|
Ok((code, returned_state))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ use tokio::sync::oneshot;
|
|||||||
pub const USER_FUNCTION_PREFIX: &str = "user__";
|
pub const USER_FUNCTION_PREFIX: &str = "user__";
|
||||||
|
|
||||||
const DEFAULT_ESCALATION_TIMEOUT_SECS: u64 = 300;
|
const DEFAULT_ESCALATION_TIMEOUT_SECS: u64 = 300;
|
||||||
const CUSTOM_MULTI_CHOICE_ANSWER_OPTION: &str = "Other (custom)";
|
|
||||||
|
|
||||||
pub fn user_interaction_function_declarations() -> Vec<FunctionDeclaration> {
|
pub fn user_interaction_function_declarations() -> Vec<FunctionDeclaration> {
|
||||||
vec![
|
vec![
|
||||||
@@ -152,14 +151,9 @@ fn handle_direct_ask(args: &Value) -> Result<Value> {
|
|||||||
.get("question")
|
.get("question")
|
||||||
.and_then(Value::as_str)
|
.and_then(Value::as_str)
|
||||||
.ok_or_else(|| anyhow!("'question' is required"))?;
|
.ok_or_else(|| anyhow!("'question' is required"))?;
|
||||||
let mut options = parse_options(args)?;
|
let options = parse_options(args)?;
|
||||||
options.push(CUSTOM_MULTI_CHOICE_ANSWER_OPTION.to_string());
|
|
||||||
|
|
||||||
let mut answer = Select::new(question, options).prompt()?;
|
let answer = Select::new(question, options).prompt()?;
|
||||||
|
|
||||||
if answer == CUSTOM_MULTI_CHOICE_ANSWER_OPTION {
|
|
||||||
answer = Text::new("Custom response:").prompt()?
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(json!({ "answer": answer }))
|
Ok(json!({ "answer": answer }))
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-10
@@ -23,7 +23,7 @@ use crate::config::{
|
|||||||
TEMP_SESSION_NAME, WorkingMode, ensure_parent_exists, list_agents, load_env_file,
|
TEMP_SESSION_NAME, WorkingMode, ensure_parent_exists, list_agents, load_env_file,
|
||||||
macro_execute,
|
macro_execute,
|
||||||
};
|
};
|
||||||
use crate::render::{prompt_theme, render_error};
|
use crate::render::render_error;
|
||||||
use crate::repl::Repl;
|
use crate::repl::Repl;
|
||||||
use crate::utils::*;
|
use crate::utils::*;
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ use anyhow::{Result, anyhow, bail};
|
|||||||
use clap::{CommandFactory, Parser};
|
use clap::{CommandFactory, Parser};
|
||||||
use clap_complete::CompleteEnv;
|
use clap_complete::CompleteEnv;
|
||||||
use client::ClientConfig;
|
use client::ClientConfig;
|
||||||
use inquire::{Select, Text, set_global_render_config};
|
use inquire::{Select, Text};
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use log4rs::append::console::ConsoleAppender;
|
use log4rs::append::console::ConsoleAppender;
|
||||||
use log4rs::append::file::FileAppender;
|
use log4rs::append::file::FileAppender;
|
||||||
@@ -106,14 +106,6 @@ async fn main() -> Result<()> {
|
|||||||
)
|
)
|
||||||
.await?,
|
.await?,
|
||||||
));
|
));
|
||||||
|
|
||||||
{
|
|
||||||
let cfg = config.read();
|
|
||||||
if cfg.highlight {
|
|
||||||
set_global_render_config(prompt_theme(cfg.render_options()?)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(err) = run(config, cli, text, abort_signal).await {
|
if let Err(err) = run(config, cli, text, abort_signal).await {
|
||||||
render_error(err);
|
render_error(err);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
use crate::render::RenderOptions;
|
|
||||||
use anyhow::Result;
|
|
||||||
use inquire::ui::{Attributes, Color, RenderConfig, StyleSheet};
|
|
||||||
use syntect::highlighting::{Highlighter, Theme};
|
|
||||||
use syntect::parsing::Scope;
|
|
||||||
|
|
||||||
const DEFAULT_INQUIRE_PROMPT_THEME: Color = Color::DarkYellow;
|
|
||||||
|
|
||||||
pub fn prompt_theme<'a>(render_options: RenderOptions) -> Result<RenderConfig<'a>> {
|
|
||||||
let theme = render_options.theme.as_ref();
|
|
||||||
let mut render_config = RenderConfig::default();
|
|
||||||
|
|
||||||
if let Some(theme_ref) = theme {
|
|
||||||
let prompt_color = resolve_foreground(theme_ref, "markup.heading")?
|
|
||||||
.unwrap_or(DEFAULT_INQUIRE_PROMPT_THEME);
|
|
||||||
|
|
||||||
render_config.prompt = StyleSheet::new()
|
|
||||||
.with_fg(prompt_color)
|
|
||||||
.with_attr(Attributes::BOLD);
|
|
||||||
render_config.selected_option = Some(
|
|
||||||
render_config
|
|
||||||
.selected_option
|
|
||||||
.unwrap_or(render_config.option)
|
|
||||||
.with_attr(
|
|
||||||
render_config
|
|
||||||
.selected_option
|
|
||||||
.unwrap_or(render_config.option)
|
|
||||||
.att
|
|
||||||
| Attributes::BOLD,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
render_config.selected_checkbox = render_config
|
|
||||||
.selected_checkbox
|
|
||||||
.with_attr(render_config.selected_checkbox.style.att | Attributes::BOLD);
|
|
||||||
render_config.option = render_config
|
|
||||||
.option
|
|
||||||
.with_attr(render_config.option.att | Attributes::BOLD);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(render_config)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_foreground(theme: &Theme, scope_str: &str) -> Result<Option<Color>> {
|
|
||||||
let scope = Scope::new(scope_str)?;
|
|
||||||
let style_mod = Highlighter::new(theme).style_mod_for_stack(&[scope]);
|
|
||||||
let fg = style_mod.foreground.or(theme.settings.foreground);
|
|
||||||
|
|
||||||
Ok(fg.map(|c| Color::Rgb {
|
|
||||||
r: c.r,
|
|
||||||
g: c.g,
|
|
||||||
b: c.b,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
mod inquire;
|
|
||||||
mod markdown;
|
mod markdown;
|
||||||
mod stream;
|
mod stream;
|
||||||
|
|
||||||
pub use inquire::prompt_theme;
|
|
||||||
|
|
||||||
pub use self::markdown::{MarkdownRender, RenderOptions};
|
pub use self::markdown::{MarkdownRender, RenderOptions};
|
||||||
use self::stream::{markdown_stream, raw_stream};
|
use self::stream::{markdown_stream, raw_stream};
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -428,8 +428,7 @@ pub async fn run_repl_command(
|
|||||||
None => println!("Usage: .model <name>"),
|
None => println!("Usage: .model <name>"),
|
||||||
},
|
},
|
||||||
".authenticate" => {
|
".authenticate" => {
|
||||||
let current_model = config.read().current_model().clone();
|
let client = init_client(config, None)?;
|
||||||
let client = init_client(config, Some(current_model))?;
|
|
||||||
if !client.supports_oauth() {
|
if !client.supports_oauth() {
|
||||||
bail!(
|
bail!(
|
||||||
"Client '{}' doesn't either support OAuth or isn't configured to use it (i.e. uses an API key instead)",
|
"Client '{}' doesn't either support OAuth or isn't configured to use it (i.e. uses an API key instead)",
|
||||||
|
|||||||
Reference in New Issue
Block a user