mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 16:00:58 +00:00
feat: standalone binary support with Bun
- Add build:binary script for Bun compilation - Add paths.ts for cross-platform asset resolution (npm/bun/tsx) - Add GitHub Actions workflow for automated binary releases - Update README with installation options Based on #89 by @steipete
This commit is contained in:
parent
4a60bffe3b
commit
c4a65ad8b9
17 changed files with 626 additions and 65 deletions
|
|
@ -2,6 +2,12 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.12.0] - 2025-12-02
|
||||
|
||||
### Added
|
||||
|
||||
- **Standalone Binary Support**: Build a self-contained binary using Bun with `npm run build:binary`. Pre-built binaries for macOS (arm64/x64), Linux (x64/arm64), and Windows (x64) are available on GitHub Releases. Based on [#89](https://github.com/badlogic/pi-mono/pull/89) by [@steipete](https://github.com/steipete), extended with cross-platform path resolution and GitHub Actions for automated release builds.
|
||||
|
||||
## [0.11.6] - 2025-12-02
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -31,10 +31,51 @@ Works on Linux, macOS, and Windows (barely tested, needs Git Bash running in the
|
|||
|
||||
## Installation
|
||||
|
||||
### npm (recommended)
|
||||
|
||||
```bash
|
||||
npm install -g @mariozechner/pi-coding-agent
|
||||
```
|
||||
|
||||
### Standalone Binary
|
||||
|
||||
Pre-built binaries are available on the [GitHub Releases](https://github.com/badlogic/pi-mono/releases) page. Download the archive for your platform:
|
||||
|
||||
- `pi-darwin-arm64.tar.gz` - macOS Apple Silicon
|
||||
- `pi-darwin-x64.tar.gz` - macOS Intel
|
||||
- `pi-linux-x64.tar.gz` - Linux x64
|
||||
- `pi-linux-arm64.tar.gz` - Linux ARM64
|
||||
- `pi-windows-x64.zip` - Windows x64
|
||||
|
||||
Extract and run:
|
||||
|
||||
```bash
|
||||
# macOS/Linux
|
||||
tar -xzf pi-darwin-arm64.tar.gz
|
||||
./pi-darwin-arm64
|
||||
|
||||
# Windows
|
||||
unzip pi-windows-x64.zip
|
||||
pi-windows-x64.exe
|
||||
```
|
||||
|
||||
The archive includes the binary plus supporting files (README, CHANGELOG, themes). Keep them together in the same directory.
|
||||
|
||||
### Build Binary from Source
|
||||
|
||||
Requires [Bun](https://bun.sh) 1.0+:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/badlogic/pi-mono.git
|
||||
cd pi-mono
|
||||
npm install
|
||||
cd packages/coding-agent
|
||||
npm run build:binary
|
||||
|
||||
# Binary and supporting files are in dist/
|
||||
./dist/pi
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
|
|
@ -1093,6 +1134,23 @@ Things that might happen eventually:
|
|||
- Switch to a model with bigger context (e.g., Gemini) using `/model` and either continue with that model, or let it summarize the session to a .md file to be loaded in a new session
|
||||
- **Better RPC mode docs**: It works, you'll figure it out (see `test/rpc-example.ts`)
|
||||
|
||||
## Development
|
||||
|
||||
### Path Resolution
|
||||
|
||||
The codebase supports three execution modes:
|
||||
- **npm**: Running via `node dist/cli.js` after npm install
|
||||
- **Bun binary**: Standalone compiled binary with files alongside
|
||||
- **tsx**: Running directly from source via `npx tsx src/cli.ts`
|
||||
|
||||
All path resolution for package assets (package.json, README.md, CHANGELOG.md, themes) must go through `src/paths.ts`:
|
||||
|
||||
```typescript
|
||||
import { getPackageDir, getThemeDir, getPackageJsonPath, getReadmePath, getChangelogPath } from "./paths.js";
|
||||
```
|
||||
|
||||
**Never use `__dirname` directly** for resolving package assets. The `paths.ts` module handles the differences between execution modes automatically.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@mariozechner/pi-coding-agent",
|
||||
"version": "0.11.6",
|
||||
"version": "0.12.0",
|
||||
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
|
@ -14,18 +14,19 @@
|
|||
],
|
||||
"scripts": {
|
||||
"clean": "rm -rf dist",
|
||||
"build": "tsgo -p tsconfig.build.json && chmod +x dist/cli.js && npm run copy-theme-assets",
|
||||
"build:binary": "command -v bun >/dev/null 2>&1 || { echo 'Error: Bun is required for building the binary. Install it from https://bun.sh'; exit 1; } && npm run build && bun build --compile --bytecode ./dist/cli.js --outfile dist/pi",
|
||||
"copy-theme-assets": "cp src/theme/*.json dist/theme/",
|
||||
"build": "tsgo -p tsconfig.build.json && chmod +x dist/cli.js && npm run copy-assets",
|
||||
"build:binary": "command -v bun >/dev/null 2>&1 || { echo 'Error: Bun is required for building the binary. Install it from https://bun.sh'; exit 1; } && npm run build && bun build --compile ./dist/cli.js --outfile dist/pi && npm run copy-binary-assets",
|
||||
"copy-assets": "cp src/theme/*.json dist/theme/",
|
||||
"copy-binary-assets": "cp package.json dist/ && cp README.md dist/ && cp CHANGELOG.md dist/",
|
||||
"dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
|
||||
"check": "tsgo --noEmit",
|
||||
"test": "vitest --run",
|
||||
"prepublishOnly": "npm run clean && npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mariozechner/pi-agent-core": "^0.11.6",
|
||||
"@mariozechner/pi-ai": "^0.11.6",
|
||||
"@mariozechner/pi-tui": "^0.11.6",
|
||||
"@mariozechner/pi-agent-core": "^0.12.0",
|
||||
"@mariozechner/pi-ai": "^0.12.0",
|
||||
"@mariozechner/pi-tui": "^0.12.0",
|
||||
"chalk": "^5.5.0",
|
||||
"diff": "^8.0.2",
|
||||
"glob": "^11.0.3"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import { existsSync, readFileSync } from "fs";
|
||||
import { dirname, join } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
export interface ChangelogEntry {
|
||||
major: number;
|
||||
|
|
@ -97,11 +95,5 @@ export function getNewEntries(entries: ChangelogEntry[], lastVersion: string): C
|
|||
return entries.filter((entry) => compareVersions(entry, last) > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the CHANGELOG.md file
|
||||
*/
|
||||
export function getChangelogPath(): string {
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
return join(__dirname, "../CHANGELOG.md");
|
||||
}
|
||||
// Re-export getChangelogPath from paths.ts for convenience
|
||||
export { getChangelogPath } from "./paths.js";
|
||||
|
|
|
|||
|
|
@ -2,14 +2,12 @@ import type { AgentState } from "@mariozechner/pi-agent-core";
|
|||
import type { AssistantMessage, Message, ToolResultMessage, UserMessage } from "@mariozechner/pi-ai";
|
||||
import { existsSync, readFileSync, writeFileSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { basename, dirname, join } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { basename } from "path";
|
||||
import { getPackageJsonPath } from "./paths.js";
|
||||
import type { SessionManager } from "./session-manager.js";
|
||||
|
||||
// Get version from package.json
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
||||
const packageJson = JSON.parse(readFileSync(getPackageJsonPath(), "utf-8"));
|
||||
const VERSION = packageJson.version;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import { ProcessTerminal, TUI } from "@mariozechner/pi-tui";
|
|||
import chalk from "chalk";
|
||||
import { existsSync, readFileSync, statSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { dirname, extname, join, resolve } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { extname, join, resolve } from "path";
|
||||
import { getChangelogPath, getNewEntries, parseChangelog } from "./changelog.js";
|
||||
import { exportFromFile } from "./export-html.js";
|
||||
import { findModel, getApiKeyForModel, getAvailableModels } from "./model-config.js";
|
||||
import { getPackageJsonPath, getReadmePath } from "./paths.js";
|
||||
import { SessionManager } from "./session-manager.js";
|
||||
import { SettingsManager } from "./settings-manager.js";
|
||||
import { expandSlashCommand, loadSlashCommands } from "./slash-commands.js";
|
||||
|
|
@ -19,9 +19,7 @@ import { SessionSelectorComponent } from "./tui/session-selector.js";
|
|||
import { TuiRenderer } from "./tui/tui-renderer.js";
|
||||
|
||||
// Get version from package.json
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
||||
const packageJson = JSON.parse(readFileSync(getPackageJsonPath(), "utf-8"));
|
||||
const VERSION = packageJson.version;
|
||||
|
||||
const defaultModelPerProvider: Record<KnownProvider, string> = {
|
||||
|
|
@ -374,7 +372,7 @@ function buildSystemPrompt(customPrompt?: string, selectedTools?: ToolName[]): s
|
|||
});
|
||||
|
||||
// Get absolute path to README.md
|
||||
const readmePath = resolve(join(__dirname, "../README.md"));
|
||||
const readmePath = getReadmePath();
|
||||
|
||||
// Build tools list based on selected tools
|
||||
const tools = selectedTools || (["read", "bash", "edit", "write"] as ToolName[]);
|
||||
|
|
|
|||
66
packages/coding-agent/src/paths.ts
Normal file
66
packages/coding-agent/src/paths.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { existsSync } from "fs";
|
||||
import { dirname, join, resolve } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
/**
|
||||
* Detect if we're running as a Bun compiled binary.
|
||||
* Bun binaries have import.meta.url starting with "file:///$bunfs/"
|
||||
*/
|
||||
export const isBunBinary = import.meta.url.startsWith("file:///$bunfs/");
|
||||
|
||||
/**
|
||||
* Get the base directory for resolving package assets (themes, package.json, README.md, CHANGELOG.md).
|
||||
* - For Bun binary: returns the directory containing the executable
|
||||
* - For Node.js (dist/): returns __dirname (the dist/ directory)
|
||||
* - For tsx (src/): returns parent directory (the package root)
|
||||
*/
|
||||
export function getPackageDir(): string {
|
||||
if (isBunBinary) {
|
||||
// Bun binary: resolve relative to the executable
|
||||
return dirname(process.execPath);
|
||||
}
|
||||
// Node.js: check if package.json exists in __dirname (dist/) or parent (src/ case)
|
||||
if (existsSync(join(__dirname, "package.json"))) {
|
||||
return __dirname;
|
||||
}
|
||||
// Running from src/ via tsx - go up one level to package root
|
||||
return dirname(__dirname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to the theme directory
|
||||
* - For Bun binary: dist/theme/ next to executable
|
||||
* - For Node.js (dist/): dist/theme/
|
||||
* - For tsx (src/): src/theme/
|
||||
*/
|
||||
export function getThemeDir(): string {
|
||||
if (isBunBinary) {
|
||||
return join(dirname(process.execPath), "theme");
|
||||
}
|
||||
// __dirname is either dist/ or src/ - theme is always a subdirectory
|
||||
return join(__dirname, "theme");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to package.json
|
||||
*/
|
||||
export function getPackageJsonPath(): string {
|
||||
return join(getPackageDir(), "package.json");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to README.md
|
||||
*/
|
||||
export function getReadmePath(): string {
|
||||
return resolve(join(getPackageDir(), "README.md"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to CHANGELOG.md
|
||||
*/
|
||||
export function getChangelogPath(): string {
|
||||
return resolve(join(getPackageDir(), "CHANGELOG.md"));
|
||||
}
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { EditorTheme, MarkdownTheme, SelectListTheme } from "@mariozechner/pi-tui";
|
||||
import { type Static, Type } from "@sinclair/typebox";
|
||||
import { TypeCompiler } from "@sinclair/typebox/compiler";
|
||||
import chalk from "chalk";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
import { getThemeDir } from "../paths.js";
|
||||
|
||||
// ============================================================================
|
||||
// Types & Schema
|
||||
|
|
@ -321,8 +319,9 @@ let BUILTIN_THEMES: Record<string, ThemeJson> | undefined;
|
|||
|
||||
function getBuiltinThemes(): Record<string, ThemeJson> {
|
||||
if (!BUILTIN_THEMES) {
|
||||
const darkPath = path.join(__dirname, "dark.json");
|
||||
const lightPath = path.join(__dirname, "light.json");
|
||||
const themeDir = getThemeDir();
|
||||
const darkPath = path.join(themeDir, "dark.json");
|
||||
const lightPath = path.join(themeDir, "light.json");
|
||||
BUILTIN_THEMES = {
|
||||
dark: JSON.parse(fs.readFileSync(darkPath, "utf-8")) as ThemeJson,
|
||||
light: JSON.parse(fs.readFileSync(lightPath, "utf-8")) as ThemeJson,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue