Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
*.sh eol=lf
*.bash eol=lf
Makefile eol=lf
# The POSIX launcher is a shell script exec'd by the OS; CRLF would cause a
# "bad interpreter" failure on Linux. Explicitly enforce LF even though
# * text=auto eol=lf already covers it, as a defense against editors/CI that
# might override the default.
packages/cli/bin/llxprt eol=lf

# Explicitly declare binary file types to prevent Git from attempting to
# normalize their line endings.
Expand Down
24 changes: 12 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
- 'release/**'
merge_group:
workflow_dispatch:
# Manual trigger - no inputs needed, uses repository variables

Check warning on line 16 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / Lint (YAML)

16:5 [comments-indentation] comment not indented like content

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand Down Expand Up @@ -153,14 +153,14 @@
- name: 'Install shellcheck' # Actionlint uses shellcheck
run: |-
mkdir -p "${RUNNER_TEMP}/shellcheck"
curl -sSLo "${RUNNER_TEMP}/.shellcheck.txz" "https://github.com/koalaman/shellcheck/releases/download/v${SHELLCHECK_VERSION}/shellcheck-v${SHELLCHECK_VERSION}.linux.x86_64.tar.xz"

Check warning on line 156 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / Lint (YAML)

156:121 [line-length] line too long (189 > 120 characters)
tar -xf "${RUNNER_TEMP}/.shellcheck.txz" -C "${RUNNER_TEMP}/shellcheck" --strip-components=1
echo "${RUNNER_TEMP}/shellcheck" >> "${GITHUB_PATH}"

- name: 'Install actionlint'
run: |-
mkdir -p "${RUNNER_TEMP}/actionlint"
curl -sSLo "${RUNNER_TEMP}/.actionlint.tgz" "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"

Check warning on line 163 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / Lint (YAML)

163:121 [line-length] line too long (184 > 120 characters)
tar -xzf "${RUNNER_TEMP}/.actionlint.tgz" -C "${RUNNER_TEMP}/actionlint"
echo "${RUNNER_TEMP}/actionlint" >> "${GITHUB_PATH}"

Expand Down Expand Up @@ -528,13 +528,13 @@
bun scripts/bun-native-modules-smoke.mjs

#
# Node consumer smoke (issue 2243, S6)
# Node consumer smoke (issue 2243, S6 / issue 2603)
#
# Guards npm install/invocation compatibility while keeping the application
# runtime on Bun/TypeScript source: install with npm, then run the checked-in
# Node bin launcher, which re-execs Bun against packages/cli/index.ts. Wired
# into the `lint` aggregator so the expensive test matrix waits for this fast
# install+smoke check first.
# Guards npm install/invocation compatibility: install with npm, then run the
# installed bin directly. The bin is a POSIX sh launcher (issue #2603) that
# execs the package-local Bun against packages/cli/index.ts — no Node process
# is started on the installed command path. Wired into the `lint` aggregator
# so the expensive test matrix waits for this fast install+smoke check first.
node_consumer_smoke:
name: 'Node Consumer Smoke'
runs-on: 'ubuntu-latest'
Expand All @@ -545,8 +545,8 @@
# This job runs npm ci (executing third-party install scripts) and only
# needs to read the repo, so scope permissions to contents:read and drop
# credential persistence (matches bun_native_modules_smoke hardening). It
# validates the published npm entry path by running the checked-in Node
# launcher after install; the launcher starts Bun on TypeScript source.
# validates the published npm entry path by running the installed launcher
# directly after install; the launcher execs Bun on TypeScript source.
permissions:
contents: 'read'
steps:
Expand Down Expand Up @@ -581,8 +581,8 @@
npm init -y
npm install "$RUNNER_TEMP/llxprt-pack"/*.tgz

- name: 'Smoke test installed CLI entry (Node launcher -> Bun -> TypeScript)'
run: 'node "$RUNNER_TEMP/llxprt-consumer/node_modules/.bin/llxprt" --version'
- name: 'Smoke test installed CLI entry (launcher -> Bun, no Node in chain)'
run: '"$RUNNER_TEMP/llxprt-consumer/node_modules/.bin/llxprt" --version'

#
# Bun-backed test orchestration smoke (issue #2463)
Expand Down Expand Up @@ -787,8 +787,8 @@
CI: true
run: npm run test:scripts

- name: 'Smoke test CLI entry (Node launcher -> Bun -> TypeScript)'
run: 'node ./packages/cli/bin/llxprt.cjs --version'
- name: 'Smoke test CLI entry (launcher -> Bun -> TypeScript, no Node)'
run: './packages/cli/bin/llxprt --version'

- name: 'Wait for file system sync'
run: 'sleep 2'
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/interactive-ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ jobs:
set -euo pipefail
node --version
tmux -V
test -x packages/cli/bin/llxprt.cjs
node packages/cli/bin/llxprt.cjs --help >/tmp/llxprt-cli-help.txt
test -x packages/cli/bin/llxprt
./packages/cli/bin/llxprt --help >/tmp/llxprt-cli-help.txt
head -n 5 /tmp/llxprt-cli-help.txt

- name: 'Run interactive UI tests'
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ jobs:
CI: true
run: npm run test:scripts

- name: 'Smoke test CLI entry (Node -> launcher -> Bun)'
run: 'node ./packages/cli/bin/llxprt.cjs --version'
- name: 'Smoke test CLI entry (launcher -> Bun, no Node in chain)'
run: './packages/cli/bin/llxprt --version'
Comment thread
acoliver marked this conversation as resolved.

- name: 'Wait for file system sync'
run: 'sleep 2'
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/smoke-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ jobs:
- name: 'Install Dependencies'
run: 'npm ci'
# No build step: the checked-in launcher resolves packages/cli/index.ts
# and re-execs it with Bun; no dist/ output is consumed.
- name: 'Smoke test CLI entry (Node -> launcher -> Bun)'
run: 'node ./packages/cli/bin/llxprt.cjs --version'
# and execs it with the bundled Bun; no dist/ output is consumed.
- name: 'Smoke test CLI entry (launcher -> Bun, no Node in chain)'
run: './packages/cli/bin/llxprt --version'
Comment thread
acoliver marked this conversation as resolved.
- name: 'Create Issue on Failure'
if: '${{ failure() && github.event.inputs.dry-run == false }}'
env:
Expand Down
99 changes: 99 additions & 0 deletions .github/workflows/windows-installed-command.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: 'Windows Installed Command (issue #2603)'

on:
pull_request:
paths:
- 'packages/cli/bin/**'
- 'packages/cli/package.json'
- 'packages/cli/scripts/install-native-launchers.cjs'
- 'package.json'
- 'package-lock.json'
- 'scripts/postinstall.cjs'
- 'scripts/tests/issue-2603-release-pack.cjs'
- 'scripts/tests/issue-2603-windows-probe.ts'
- 'scripts/tests/issue-2603-startup-benchmark.cjs'
- 'scripts/windows-installed-command-smoke.cjs'
- 'scripts/windows-installed-command-smoke/**'
- '.github/workflows/windows-installed-command.yml'
push:
branches:
- 'main'
paths:
- 'packages/cli/bin/**'
- 'packages/cli/package.json'
- 'packages/cli/scripts/install-native-launchers.cjs'
- 'package.json'
- 'package-lock.json'
- 'scripts/postinstall.cjs'
- 'scripts/tests/issue-2603-release-pack.cjs'
- 'scripts/tests/issue-2603-windows-probe.ts'
- 'scripts/tests/issue-2603-startup-benchmark.cjs'
- 'scripts/windows-installed-command-smoke.cjs'
- 'scripts/windows-installed-command-smoke/**'
- '.github/workflows/windows-installed-command.yml'
workflow_dispatch:

# Cancel superseded runs of this workflow for the same ref so that repeated
# pushes to a PR do not queue redundant Windows install smokes.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
windows-installed-command:
runs-on: 'windows-latest'
# 60 min (root cause D): the smoke does real global/local/npm-exec
# installs plus a benchmark. 30 min was too tight when multiple installs
# ran back-to-back. All install timeouts are env-configurable and stay
# well under this job budget.
timeout-minutes: 60
permissions:
contents: 'read'
steps:
- name: 'Checkout'
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
with:
fetch-depth: 1

- name: 'Set up Node.js'
uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # ratchet:actions/setup-node@v6
with:
node-version-file: '.nvmrc'
cache: 'npm'

- name: 'Setup Bun'
uses: 'oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76' # ratchet:oven-sh/setup-bun@v2
with:
bun-version-file: '.bun-version'

# Publish the absolute path to PowerShell 7 (pwsh.exe) so the smoke
# resolves it robustly under a constrained PATH. windows-latest ships
# pwsh but NOT legacy `powershell` on PATH, which previously caused
# every PowerShell-gated step to fail with ENOENT (run 29850614559).
- name: 'Export PowerShell 7 path'
shell: 'pwsh'
run: |
"PWSH_PATH=$((Get-Command pwsh).Source)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append

- name: 'Install dependencies (npm ci)'
run: 'npm ci'

# The smoke runs the behavioral checks AND the startup benchmark (as a
# child process using the already-installed launcher + platform bun), so
# no separate benchmark step is needed. The benchmark is invoked with
# LLXPRT_BENCH_LAUNCHER/LLXPRT_BENCH_BUN so it never repacks or
# reinstalls — it reuses the install the smoke already proved.
- name: 'Run Windows installed-command behavioral smoke + benchmark'
run: 'node scripts/windows-installed-command-smoke.cjs'
Comment thread
acoliver marked this conversation as resolved.

# Upload the small diagnostic JSON written on failure (root cause I).
# Hosted runner temp vanishes post-job, so this small artifact (no
# node_modules) is how failures are debugged offline. Kept tiny.
- name: 'Upload diagnostic artifact on failure'
if: ${{ failure() }}
uses: 'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02' # ratchet:actions/upload-artifact@v4
with:
name: 'llxprt-win-smoke-diagnostic'
path: '${{ runner.temp }}/llxprt-win-smoke-diagnostic-*.json'
if-no-files-found: 'ignore'
retention-days: 7
Comment thread
acoliver marked this conversation as resolved.
4 changes: 3 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ project-plans/
Thumbs.db

# Generated files
packages/cli/bin/llxprt.cjs
package-lock.json
bun.lock
**/bun.lock
Expand All @@ -56,3 +55,6 @@ junit.xml
# Intentionally-malformed test fixture (must stay unparseable to exercise the
# unparseable-profile dir-scan rejection path in profiles T19a).
packages/agents/src/api/__tests__/fixtures/profile-invalid-malformed.json

# Extensionless shell script (not JS/TS, prettier breaks it)
packages/cli/bin/llxprt
Comment thread
acoliver marked this conversation as resolved.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Changed

- **Installed command launches Bun directly (issue #2603):** The `llxprt` bin entry is now `packages/cli/bin/llxprt`, a POSIX sh launcher with a valid `#!/bin/sh` shebang (directly execve-compatible). On Windows, the CLI workspace `postinstall` (`packages/cli/scripts/install-native-launchers.cjs`) replaces npm's cmd-shim with native `.cmd` and `.ps1` launchers. No Node process is started on the installed command path. The old Node launcher (`packages/cli/bin/llxprt.cjs`) has been removed. The POSIX launcher validates the Bun executable's native binary magic (ELF/Mach-O) before exec, producing an actionable exit 43 for a corrupt or unusable binary without double-starting Bun. The Windows cmd launcher preserves the child exit code exactly (no errorlevel remapping); the PowerShell launcher wraps the invocation in try/catch to surface launch failures as exit 43 while propagating normal nonzero exits via `$LASTEXITCODE`.

### Removed (0.10.0 breaking cleanup)

- Removed provider-neutral Gemini legacy aliases and inherited internal naming as a 0.10.0 breaking cleanup. The `geminiLegacyAliases.ts` singleton alias module (the single legacy re-export location introduced in #2354) is deleted without replacement. External consumers must use the canonical names directly:
Expand Down Expand Up @@ -37,7 +41,7 @@
### Migration

- Direct consumers constructing `AuthPrecedenceResolver` and expecting it to resolve named auth keys must pass `providerKeyStorage` in the constructor options or use core's `createAuthPrecedenceResolver()` factory. The CLI profile flow already resolves named keys to concrete provider API keys before provider construction.
- LLxprt Code has moved to the [Bun](https://bun.sh) runtime. Node-compatible install/run UX is preserved — the npm (`npm install -g @vybestack/llxprt-code`), npx, and Homebrew flows are unchanged from the user's perspective. Bun is now required under the covers to power execution. When Bun is not found on `PATH` (and the bundled `node_modules/.bin/bun` dependency is unavailable), the launcher prints an error instructing the user to reinstall dependencies (`npm install`) or install Bun directly from https://bun.sh. The published npm package ships TypeScript source (`.ts`) and a checked-in Node launcher (`packages/cli/bin/llxprt.cjs`) as its `bin` entry — no compilation or pre-compiled `dist/` artifact is shipped or required. The launcher resolves Bun and executes the `.ts` entry point directly at run time. `tsc --noEmit` is used solely for type-checking during development. The retired `bundle/llxprt.js` esbuild bundle artifact is no longer produced. vitest is retained as the test runner. On Windows, the `node-pty` module has a known terminal resize race condition; the CLI silences this specific error at the process level and uses `@lydell/node-pty` (not the Bun adapter, which is POSIX-only). Users encountering terminal sizing issues should use a compatible terminal emulator; the resize race is in `node-pty` itself, not the Bun runtime.
- LLxprt Code has moved to the [Bun](https://bun.sh) runtime. Node-compatible install/run UX is preserved — the npm (`npm install -g @vybestack/llxprt-code`), npx, and Homebrew flows are unchanged from the user's perspective. Bun is now required under the covers to power execution; the published package bundles Bun as a dependency, so most users never need to install Bun separately. The published npm package ships TypeScript source (`.ts`) and a platform-native launcher (`packages/cli/bin/llxprt`, a POSIX sh script) as its `bin` entry — no compilation or pre-compiled `dist/` artifact is shipped or required. The launcher resolves the package-local Bun and executes the `.ts` entry point directly at run time. `tsc --noEmit` is used solely for type-checking during development. The retired `bundle/llxprt.js` esbuild bundle artifact is no longer produced. vitest is retained as the test runner. On Windows, the `node-pty` module has a known terminal resize race condition; the CLI silences this specific error at the process level and uses `@lydell/node-pty` (not the Bun adapter, which is POSIX-only). Users encountering terminal sizing issues should use a compatible terminal emulator; the resize race is in `node-pty` itself, not the Bun runtime.

### Removed

Expand Down
10 changes: 5 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ To build the entire project (all packages):
bun run build
```

TypeScript source (`.ts`) is shipped directly. The CLI's run path starts from the checked-in Node launcher (`packages/cli/bin/llxprt.cjs`), which resolves Bun and re-execs `packages/cli/index.ts` directly — no pre-compiled CLI `dist/` artifact or `bundle/llxprt.js` artifact is required for the CLI to run. Type checking uses `tsc --noEmit`, and package builds still produce `dist/` artifacts for workspace/library packaging. Refer to `scripts/build.ts`, `scripts/build_package.ts`, and `package.json` scripts for more details on what happens during the build.
TypeScript source (`.ts`) is shipped directly. The CLI's installed command uses platform-native launchers (`packages/cli/bin/llxprt`) that resolve the package-bundled Bun and exec `packages/cli/index.ts` directly — no Node process is started on the installed path, and no pre-compiled CLI `dist/` artifact or `bundle/llxprt.js` artifact is required for the CLI to run. Type checking uses `tsc --noEmit`, and package builds still produce `dist/` artifacts for workspace/library packaging. Refer to `scripts/build.ts`, `scripts/build_package.ts`, and `package.json` scripts for more details on what happens during the build.

### Enabling Sandboxing

Expand All @@ -151,13 +151,13 @@ To start LLxprt Code from the source code, run the following command from the ro
bun run start
```

Alternatively, the dev launcher (`scripts/start.ts`) starts the checked-in launcher under Node; that launcher then resolves Bun and re-execs `packages/cli/index.ts`. In debug mode (`DEBUG=1`), Node starts with the inspector before handing off to the launcher:
Alternatively, the dev launcher (`scripts/start.ts`) spawns Bun directly on the TypeScript source entry (`packages/cli/index.ts`); in debug mode (`DEBUG=1`) Bun starts with the inspector:

```bash
bun scripts/start.ts
```

The production launcher (`packages/cli/bin/llxprt.cjs`) resolves Bun by climbing ancestor directories for `node_modules/.bin/bun`, falling back to `node_modules/bun/bin/bun.exe`, then `PATH` (`which`/`where`). If Bun is absent, the launcher prints guidance to install Bun and exits. See the [Bun Runtime and Install Fallback](./README.md#bun-runtime-and-install-fallback) section in the README.
The production launcher (`packages/cli/bin/llxprt`) is a POSIX sh script with a valid `#!/bin/sh` shebang. It resolves the bundled Bun by checking, in order: the package-local `node_modules/bun/bin/bun.exe`, the enclosing `node_modules/bun/bin/bun.exe` (for hoisted installed packages, stopping at the enclosing `node_modules` boundary), and — for the source workspace only — a verified repository root's `node_modules/bun/bin/bun.exe` (the root manifest must reference this package). It never scans `.bin` symlinks or falls back to a global `bun` on `PATH`. When the package declares an exact Bun pin, a candidate whose `package.json`/version is missing or mismatched is rejected. It validates the executable's native binary magic (ELF/Mach-O) before exec to reject corrupt or unusable binaries with an actionable exit 43. On Windows, the CLI workspace `postinstall` (`packages/cli/scripts/install-native-launchers.cjs`) replaces npm's cmd-shim with native `.cmd` / `.ps1` launchers that invoke the same package-local Bun. The cmd launcher preserves the child exit code exactly (no errorlevel remapping); the PowerShell launcher wraps the invocation in try/catch to surface launch failures as exit 43. (The root `scripts/postinstall.cjs` delegates to this script on Windows after linking internal workspace packages.) If Bun is absent, the launcher prints actionable guidance and exits 43. See the [Bun Runtime and Install Fallback](./README.md#bun-runtime-and-install-fallback) section in the README.

If you'd like to run the source build outside the llxprt-code folder you can utilize `npm link path/to/llxprt-code/packages/cli` (see: [docs](https://docs.npmjs.com/cli/v9/commands/npm-link)) or `alias llxprt="bun path/to/llxprt-code/packages/cli"` to run with `llxprt`

Expand Down Expand Up @@ -308,7 +308,7 @@ For more detailed architecture, see `docs/architecture.md`.
bun run debug
```

This launches the checked-in Node launcher (via `scripts/start.ts`) with the inspector, pausing execution until a debugger attaches before the launcher resolves Bun and executes `packages/cli/index.ts`. You can then open `chrome://inspect` in your Chrome browser to connect to the debugger.
This launches Bun directly on the TypeScript entry (via `scripts/start.ts`) with the inspector, pausing execution until a debugger attaches before Bun runs `packages/cli/index.ts`. You can then open `chrome://inspect` in your Chrome browser to connect to the debugger.

2. In VS Code, use the "Attach" launch configuration (found in `.vscode/launch.json`).

Expand All @@ -322,7 +322,7 @@ DEBUG=1 llxprt

**Note:** If you have `DEBUG=true` in a project's `.env` file, it won't affect llxprt-code due to automatic exclusion. Use `.llxprt/.env` files for llxprt-code specific debug settings.

**Note:** Debugging via the dev launcher (`scripts/start.ts`) still uses the same checked-in launcher as the published `llxprt` binary (`packages/cli/bin/llxprt.cjs`). That launcher resolves Bun and executes the `.ts` source entrypoint (`packages/cli/index.ts`) directly.
**Note:** Debugging via the dev launcher (`scripts/start.ts`) spawns Bun directly on the TypeScript source entrypoint (`packages/cli/index.ts`), equivalent to what the installed `llxprt` binary does via the native launcher (`packages/cli/bin/llxprt`).

### React DevTools

Expand Down
Loading
Loading