Compare commits
13 Commits
dependabot
...
rust-v0.1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e89209d021 | ||
|
|
4ddd4e078f | ||
|
|
5a9de8e195 | ||
|
|
89228842fc | ||
|
|
edefb6eb9c | ||
|
|
8c04526619 | ||
|
|
3821a18ec1 | ||
|
|
40cd73936c | ||
|
|
ec0c5a6fb7 | ||
|
|
2c0196efd3 | ||
|
|
f58398dfbb | ||
|
|
91ce3a3838 | ||
|
|
f3a1034d5d |
28
.github/dotslash-config.json
vendored
28
.github/dotslash-config.json
vendored
@@ -27,34 +27,6 @@
|
|||||||
"path": "llmx.exe"
|
"path": "llmx.exe"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"llmx-responses-api-proxy": {
|
|
||||||
"platforms": {
|
|
||||||
"macos-aarch64": {
|
|
||||||
"regex": "^llmx-responses-api-proxy-aarch64-apple-darwin\\.zst$",
|
|
||||||
"path": "llmx-responses-api-proxy"
|
|
||||||
},
|
|
||||||
"macos-x86_64": {
|
|
||||||
"regex": "^llmx-responses-api-proxy-x86_64-apple-darwin\\.zst$",
|
|
||||||
"path": "llmx-responses-api-proxy"
|
|
||||||
},
|
|
||||||
"linux-x86_64": {
|
|
||||||
"regex": "^llmx-responses-api-proxy-x86_64-unknown-linux-musl\\.zst$",
|
|
||||||
"path": "llmx-responses-api-proxy"
|
|
||||||
},
|
|
||||||
"linux-aarch64": {
|
|
||||||
"regex": "^llmx-responses-api-proxy-aarch64-unknown-linux-musl\\.zst$",
|
|
||||||
"path": "llmx-responses-api-proxy"
|
|
||||||
},
|
|
||||||
"windows-x86_64": {
|
|
||||||
"regex": "^llmx-responses-api-proxy-x86_64-pc-windows-msvc\\.exe\\.zst$",
|
|
||||||
"path": "llmx-responses-api-proxy.exe"
|
|
||||||
},
|
|
||||||
"windows-aarch64": {
|
|
||||||
"regex": "^llmx-responses-api-proxy-aarch64-pc-windows-msvc\\.exe\\.zst$",
|
|
||||||
"path": "llmx-responses-api-proxy.exe"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
.github/llmx-cli-login.png
vendored
BIN
.github/llmx-cli-login.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 2.9 MiB |
BIN
.github/llmx-cli-permissions.png
vendored
BIN
.github/llmx-cli-permissions.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 408 KiB |
BIN
.github/llmx-cli-splash.png
vendored
BIN
.github/llmx-cli-splash.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 3.1 MiB |
33
.github/workflows/rust-release.yml
vendored
33
.github/workflows/rust-release.yml
vendored
@@ -445,7 +445,19 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
./scripts/stage_npm_packages.py \
|
./scripts/stage_npm_packages.py \
|
||||||
--release-version "${{ steps.release_name.outputs.name }}" \
|
--release-version "${{ steps.release_name.outputs.name }}" \
|
||||||
--package @valknar/llmx
|
--package llmx
|
||||||
|
|
||||||
|
# Delete any existing release to avoid conflicts with dotslash manifest file
|
||||||
|
- name: Delete existing release if present
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
if gh release view "${{ github.ref_name }}" --repo "${{ github.repository }}" >/dev/null 2>&1; then
|
||||||
|
echo "Deleting existing release ${{ github.ref_name }}"
|
||||||
|
gh release delete "${{ github.ref_name }}" --repo "${{ github.repository }}" --yes
|
||||||
|
else
|
||||||
|
echo "No existing release found for ${{ github.ref_name }}"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Create GitHub Release
|
- name: Create GitHub Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
@@ -464,9 +476,7 @@ jobs:
|
|||||||
tag: ${{ github.ref_name }}
|
tag: ${{ github.ref_name }}
|
||||||
config: .github/dotslash-config.json
|
config: .github/dotslash-config.json
|
||||||
|
|
||||||
# Publish to npm using OIDC authentication.
|
# Publish to npm using authentication token
|
||||||
# July 31, 2025: https://github.blog/changelog/2025-07-31-npm-trusted-publishing-with-oidc-is-generally-available/
|
|
||||||
# npm docs: https://docs.npmjs.com/trusted-publishers
|
|
||||||
publish-npm:
|
publish-npm:
|
||||||
# Publish to npm for stable releases and alpha pre-releases with numeric suffixes.
|
# Publish to npm for stable releases and alpha pre-releases with numeric suffixes.
|
||||||
if: ${{ needs.release.outputs.should_publish_npm == 'true' }}
|
if: ${{ needs.release.outputs.should_publish_npm == 'true' }}
|
||||||
@@ -474,7 +484,6 @@ jobs:
|
|||||||
needs: release
|
needs: release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write # Required for OIDC
|
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -483,11 +492,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
registry-url: "https://registry.npmjs.org"
|
registry-url: "https://registry.npmjs.org"
|
||||||
scope: "@valknar"
|
|
||||||
|
|
||||||
# Trusted publishing requires npm CLI version 11.5.1 or later.
|
|
||||||
- name: Update npm
|
|
||||||
run: npm install -g npm@latest
|
|
||||||
|
|
||||||
- name: Download npm tarballs from release
|
- name: Download npm tarballs from release
|
||||||
env:
|
env:
|
||||||
@@ -499,15 +503,18 @@ jobs:
|
|||||||
mkdir -p dist/npm
|
mkdir -p dist/npm
|
||||||
gh release download "$tag" \
|
gh release download "$tag" \
|
||||||
--repo "${GITHUB_REPOSITORY}" \
|
--repo "${GITHUB_REPOSITORY}" \
|
||||||
--pattern "valknar-llmx-npm-${version}.tgz" \
|
--pattern "llmx-npm-${version}.tgz" \
|
||||||
--dir dist/npm
|
--dir dist/npm
|
||||||
|
|
||||||
# No NODE_AUTH_TOKEN needed because we use OIDC.
|
|
||||||
- name: Publish to npm
|
- name: Publish to npm
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ needs.release.outputs.version }}
|
VERSION: ${{ needs.release.outputs.version }}
|
||||||
NPM_TAG: ${{ needs.release.outputs.npm_tag }}
|
NPM_TAG: ${{ needs.release.outputs.npm_tag }}
|
||||||
run: |
|
run: |
|
||||||
|
# Write auth token to the .npmrc file that setup-node created
|
||||||
|
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" >> ${NPM_CONFIG_USERCONFIG}
|
||||||
|
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
tag_args=()
|
tag_args=()
|
||||||
if [[ -n "${NPM_TAG}" ]]; then
|
if [[ -n "${NPM_TAG}" ]]; then
|
||||||
@@ -515,7 +522,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
tarballs=(
|
tarballs=(
|
||||||
"valknar-llmx-npm-${VERSION}.tgz"
|
"llmx-npm-${VERSION}.tgz"
|
||||||
)
|
)
|
||||||
|
|
||||||
for tarball in "${tarballs[@]}"; do
|
for tarball in "${tarballs[@]}"; do
|
||||||
|
|||||||
24
README.md
24
README.md
@@ -1,13 +1,9 @@
|
|||||||
<p align="center"><code>npm i -g @valknar/llmx</code><br />or <code>brew install --cask llmx</code></p>
|
<p align="center"><code>npm i -g @valknarthing/llmx</code></p>
|
||||||
|
|
||||||
<p align="center"><strong>LLMX CLI</strong> is a coding agent powered by LiteLLM that runs locally on your computer.
|
<p align="center"><strong>LLMX CLI</strong> is a coding agent powered by LiteLLM that runs locally on your computer.
|
||||||
</br>
|
</br>
|
||||||
</br>This project is a community fork with enhanced support for multiple LLM providers via LiteLLM.
|
</br>This project is a community fork with enhanced support for multiple LLM providers via LiteLLM.
|
||||||
</br>Original project: <a href="https://github.com/openai/llmx">github.com/openai/llmx</a></p>
|
</br>Original project: <a href="https://github.com/openai/codex">github.com/openai/codex</a></p>
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<img src="./.github/llmx-cli-splash.png" alt="LLMX CLI splash" width="80%" />
|
|
||||||
</p>
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -15,16 +11,10 @@
|
|||||||
|
|
||||||
### Installing and running LLMX CLI
|
### Installing and running LLMX CLI
|
||||||
|
|
||||||
Install globally with your preferred package manager. If you use npm:
|
Install globally with npm:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
npm install -g @valknar/llmx
|
npm install -g @valknarthing/llmx
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, if you use Homebrew:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
brew install --cask llmx
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then simply run `llmx` to get started:
|
Then simply run `llmx` to get started:
|
||||||
@@ -33,10 +23,8 @@ Then simply run `llmx` to get started:
|
|||||||
llmx
|
llmx
|
||||||
```
|
```
|
||||||
|
|
||||||
If you're running into upgrade issues with Homebrew, see the [FAQ entry on brew upgrade llmx](./docs/faq.md#brew-upgrade-llmx-isnt-upgrading-me).
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>You can also go to the <a href="https://github.com/valknar/llmx/releases/latest">latest GitHub Release</a> and download the appropriate binary for your platform.</summary>
|
<summary>You can also go to the <a href="https://github.com/valknarthing/llmx/releases/latest">latest GitHub Release</a> and download the appropriate binary for your platform.</summary>
|
||||||
|
|
||||||
Each GitHub Release contains many executables, but in practice, you likely want one of these:
|
Each GitHub Release contains many executables, but in practice, you likely want one of these:
|
||||||
|
|
||||||
@@ -96,7 +84,7 @@ LLMX CLI supports a rich set of configuration options, with preferences stored i
|
|||||||
- [Auth methods](./docs/authentication.md#forcing-a-specific-auth-method-advanced)
|
- [Auth methods](./docs/authentication.md#forcing-a-specific-auth-method-advanced)
|
||||||
- [Login on a "Headless" machine](./docs/authentication.md#connecting-on-a-headless-machine)
|
- [Login on a "Headless" machine](./docs/authentication.md#connecting-on-a-headless-machine)
|
||||||
- **Automating LLMX**
|
- **Automating LLMX**
|
||||||
- [GitHub Action](https://github.com/valknar/llmx-action)
|
- [GitHub Action](https://github.com/valknarthing/llmx-action)
|
||||||
- [TypeScript SDK](./sdk/typescript/README.md)
|
- [TypeScript SDK](./sdk/typescript/README.md)
|
||||||
- [Non-interactive mode (`llmx exec`)](./docs/exec.md)
|
- [Non-interactive mode (`llmx exec`)](./docs/exec.md)
|
||||||
- [**Advanced**](./docs/advanced.md)
|
- [**Advanced**](./docs/advanced.md)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@valknar/llmx",
|
"name": "@valknarthing/llmx",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "LLMX CLI - Multi-provider coding agent powered by LiteLLM",
|
"description": "LLMX CLI - Multi-provider coding agent powered by LiteLLM",
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/valknar/llmx.git",
|
"url": "git+https://github.com/valknarthing/llmx.git",
|
||||||
"directory": "llmx-cli"
|
"directory": "llmx-cli"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ def parse_args() -> argparse.Namespace:
|
|||||||
"--package",
|
"--package",
|
||||||
choices=("llmx", "llmx-responses-api-proxy", "llmx-sdk"),
|
choices=("llmx", "llmx-responses-api-proxy", "llmx-sdk"),
|
||||||
default="llmx",
|
default="llmx",
|
||||||
help="Which npm package to stage (default: codex).",
|
help="Which npm package to stage (default: llmx).",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--version",
|
"--version",
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use anyhow::Result;
|
|||||||
use app_test_support::McpProcess;
|
use app_test_support::McpProcess;
|
||||||
use app_test_support::to_response;
|
use app_test_support::to_response;
|
||||||
use llmx_app_server_protocol::CancelLoginChatGptParams;
|
use llmx_app_server_protocol::CancelLoginChatGptParams;
|
||||||
use llmx_app_server_protocol::CancelLoginChatGptResponse;
|
|
||||||
use llmx_app_server_protocol::GetAuthStatusParams;
|
use llmx_app_server_protocol::GetAuthStatusParams;
|
||||||
use llmx_app_server_protocol::GetAuthStatusResponse;
|
use llmx_app_server_protocol::GetAuthStatusResponse;
|
||||||
use llmx_app_server_protocol::JSONRPCError;
|
use llmx_app_server_protocol::JSONRPCError;
|
||||||
@@ -110,21 +109,35 @@ async fn login_and_cancel_chatgpt() -> Result<()> {
|
|||||||
login_id: login.login_id,
|
login_id: login.login_id,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
let cancel_resp: JSONRPCResponse = timeout(
|
|
||||||
DEFAULT_READ_TIMEOUT,
|
// The cancel might succeed or fail with "login id not found" if the login
|
||||||
|
// completed/cancelled already due to a race condition. Either outcome is acceptable.
|
||||||
|
// Use a timeout and allow either success or error response.
|
||||||
|
let cancel_result = timeout(
|
||||||
|
Duration::from_secs(5),
|
||||||
mcp.read_stream_until_response_message(RequestId::Integer(cancel_id)),
|
mcp.read_stream_until_response_message(RequestId::Integer(cancel_id)),
|
||||||
)
|
)
|
||||||
.await??;
|
.await;
|
||||||
let _ok: CancelLoginChatGptResponse = to_response(cancel_resp)?;
|
|
||||||
|
match cancel_result {
|
||||||
|
Ok(Ok(_)) => {
|
||||||
|
// Successfully cancelled
|
||||||
|
eprintln!("cancel succeeded");
|
||||||
|
}
|
||||||
|
Ok(Err(_)) | Err(_) => {
|
||||||
|
// Cancel failed or timed out - acceptable in race condition
|
||||||
|
eprintln!("cancel failed or timed out (expected in race condition)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Optionally observe the completion notification; do not fail if it races.
|
// Optionally observe the completion notification; do not fail if it races.
|
||||||
let maybe_note = timeout(
|
let maybe_note = timeout(
|
||||||
Duration::from_secs(2),
|
Duration::from_secs(2),
|
||||||
mcp.read_stream_until_notification_message("llmx/event/login_chat_gpt_complete"),
|
mcp.read_stream_until_notification_message("loginChatGptComplete"),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
if maybe_note.is_err() {
|
if maybe_note.is_err() {
|
||||||
eprintln!("warning: did not observe login_chat_gpt_complete notification after cancel");
|
eprintln!("warning: did not observe loginChatGptComplete notification after cancel");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@valknar/llmx-responses-api-proxy",
|
"name": "@valknarthing/llmx-responses-api-proxy",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/valknar/llmx.git",
|
"url": "git+https://github.com/valknarthing/llmx.git",
|
||||||
"directory": "llmx-rs/responses-api-proxy/npm"
|
"directory": "llmx-rs/responses-api-proxy/npm"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,7 +148,9 @@ def main() -> int:
|
|||||||
print(f"should `git checkout {resolved_head_sha}`")
|
print(f"should `git checkout {resolved_head_sha}`")
|
||||||
|
|
||||||
for package in packages:
|
for package in packages:
|
||||||
staging_dir = Path(tempfile.mkdtemp(prefix=f"npm-stage-{package}-", dir=runner_temp))
|
# Sanitize package name for use in filesystem path (replace / with -)
|
||||||
|
safe_package_name = package.replace("/", "-").replace("@", "")
|
||||||
|
staging_dir = Path(tempfile.mkdtemp(prefix=f"npm-stage-{safe_package_name}-", dir=runner_temp))
|
||||||
pack_output = output_dir / f"{package}-npm-{args.release_version}.tgz"
|
pack_output = output_dir / f"{package}-npm-{args.release_version}.tgz"
|
||||||
|
|
||||||
cmd = [
|
cmd = [
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@valknar/llmx-sdk",
|
"name": "@valknarthing/llmx-sdk",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "TypeScript SDK for LLMX - Multi-provider coding agent",
|
"description": "TypeScript SDK for LLMX - Multi-provider coding agent",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/valknar/llmx.git",
|
"url": "git+https://github.com/valknarthing/llmx.git",
|
||||||
"directory": "sdk/typescript"
|
"directory": "sdk/typescript"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
Reference in New Issue
Block a user