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
30 changes: 19 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,13 @@ jobs:
run: bun install

- name: Build
run: bun build --compile --minify --external cpu-features --target=${{ matrix.target }} ./index.ts --outfile ${{ matrix.artifact }}
shell: bash
run: |
VERSION="${GITHUB_REF_NAME:-nightly}"
SHA="${GITHUB_SHA::7}"
bun build --compile --minify --external cpu-features --target=${{ matrix.target }} \
--define "BUILD_VERSION='$VERSION ($SHA)'" \
./index.ts --outfile ${{ matrix.artifact }}

- name: Test binary
if: matrix.can_test
Expand All @@ -84,6 +90,18 @@ jobs:
path: ${{ matrix.artifact }}
retention-days: 90

- name: Upload to R2
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: ryand56/r2-upload-action@latest
with:
r2-account-id: ${{ secrets.CF_ACCOUNT_ID }}
r2-access-key-id: ${{ secrets.CF_ACCESS_KEY_ID }}
r2-secret-access-key: ${{ secrets.CF_SECRET_ACCESS_KEY }}
r2-bucket: vers-releases
source-dir: .
destination-dir: nightly
glob: ${{ matrix.artifact }}

nightly:
needs: build
runs-on: ubuntu-latest
Expand All @@ -107,16 +125,6 @@ jobs:
done
ls -la release/

- name: Upload to R2
uses: ryand56/r2-upload-action@latest
with:
r2-account-id: ${{ secrets.CF_ACCOUNT_ID }}
r2-access-key-id: ${{ secrets.CF_ACCESS_KEY_ID }}
r2-secret-access-key: ${{ secrets.CF_SECRET_ACCESS_KEY }}
r2-bucket: vers-releases
source-dir: release
destination-dir: nightly

- name: Update nightly release
uses: softprops/action-gh-release@v2
with:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ bin
# Nix
result
.direnv/

# Local docs/scratchpads - Claude artifacts can clutter the commit tree
docs/
110 changes: 110 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { runCli } from "./src/cli/cli.js";

// Version injected at build time via --define BUILD_VERSION='...'
// Falls back to "dev" when running via bun (not compiled)
declare const BUILD_VERSION: string;
const VERSION = typeof BUILD_VERSION !== "undefined" ? BUILD_VERSION : "dev";
const RELEASES_BASE_URL = "https://releases.vers.sh";
import { createHttpServer } from "./src/server/http-server";
import { loadConfig, loadMcpServers, getConfig, setConfig } from "./src/utils/config";
import { loadDocsStore } from "./src/utils/docs-store";
Expand Down Expand Up @@ -134,8 +140,93 @@ if (firstArg && !firstArg.startsWith("-") && isSubcommand(firstArg)) {
runFlagMode();
}


// Check if running as standalone binary (not via bun run)
function isStandalone(): boolean {
// process.execPath is the actual binary path
// When compiled: /path/to/vers-agent (our binary)
// When via bun: /path/to/bun (the bun executable)
const execPath = process.execPath;
return !execPath.endsWith("/bun") && !execPath.includes("/bun/") && !execPath.endsWith("/node");
}

// Detect platform for download
function detectPlatform(): string {
const platform = process.platform;
const arch = process.arch;

if (platform === "win32") {
return "vers-agent-windows-x64.exe";
} else if (platform === "linux") {
return arch === "arm64" ? "vers-agent-linux-arm64" : "vers-agent-linux-x64";
} else if (platform === "darwin") {
return arch === "arm64" ? "vers-agent-darwin-arm64" : "vers-agent-darwin-x64";
}
throw new Error(`Unsupported platform: ${platform}-${arch}`);
}

// Self-update the binary
async function selfUpdate(channel: string = "nightly"): Promise<void> {
if (!isStandalone()) {
console.error("--update only works with standalone binary, not when running via bun");
process.exit(1);
}

const asset = detectPlatform();
const url = `${RELEASES_BASE_URL}/${channel}/${asset}`;
const execPath = process.execPath;

console.log(`Updating vers to ${channel}...`);
console.log(` ${url}`);

try {
const response = await fetch(url);
if (!response.ok) {
if (response.status === 404) {
throw new Error(`Channel "${channel}" not found. Try "nightly" or a version tag like "v0.2.0"`);
}
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
}

const tmpPath = `${execPath}.new`;
const data = await response.arrayBuffer();
await Bun.write(tmpPath, data);

// Make executable
await Bun.$`chmod +x ${tmpPath}`.quiet();

// On macOS, ad-hoc sign to satisfy Gatekeeper
if (process.platform === "darwin") {
await Bun.$`codesign --force --sign - ${tmpPath}`.quiet().nothrow();
await Bun.$`xattr -d com.apple.quarantine ${tmpPath}`.quiet().nothrow();
}

// Replace old binary
const backupPath = `${execPath}.old`;
await Bun.$`mv ${execPath} ${backupPath}`.quiet();
await Bun.$`mv ${tmpPath} ${execPath}`.quiet();
await Bun.$`rm -f ${backupPath}`.quiet();

console.log("✓ Updated successfully!");
console.log(` Run 'vers --version' to verify.`);
} catch (err) {
console.error("Update failed:", err);
process.exit(1);
}
}

function runFlagMode() {
const showHelp = args.includes("--help") || args.includes("-h");
const showVersion = args.includes("--version") || args.includes("-v");

// Parse --update [channel] option
const updateIndex = args.indexOf("--update");
const doUpdate = updateIndex !== -1;
const updateArg = args[updateIndex + 1];
const updateChannel = doUpdate && updateArg && !updateArg.startsWith("-")
? updateArg
: "nightly";

const installSkills = args.includes("--install-skills");
const mcpMode = args.includes("--mcp");
const mcpInstall = args.includes("--mcp-install");
Expand All @@ -150,6 +241,23 @@ function runFlagMode() {
const urlIndex = args.indexOf("--url");
const explicitServerUrl = urlIndex !== -1 && args[urlIndex + 1] ? args[urlIndex + 1] : undefined;

// --version: print version and exit
if (showVersion) {
console.log(`vers ${VERSION}`);
process.exit(0);
}

// --update [channel]: self-update the binary (standalone only)
if (doUpdate) {
selfUpdate(updateChannel)
.then(() => process.exit(0))
.catch((err) => {
console.error("Update failed:", err);
process.exit(1);
});
return;
}

// Install embedded skills to ~/.claude/skills/
if (installSkills) {
(async () => {
Expand Down Expand Up @@ -221,6 +329,8 @@ Commands:
help Show detailed command help

Options:
--version, -v Show version
--update [channel] Self-update (default: nightly, or specify version like v0.2.0)
--cli Run interactive CLI (connects to HTTP server)
--mcp Run as MCP server (stdio transport for Claude integration)
--url <url> Connect CLI to remote server (e.g., --url http://192.168.1.100:9999)
Expand Down
15 changes: 10 additions & 5 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@

set -e

REPO="hdresearch/agent"
RELEASE_TAG="nightly"
RELEASE_URL="https://releases.vers.sh/nightly"
INSTALL_DIR="${HOME}/.local/bin"

# Detect OS and architecture
Expand Down Expand Up @@ -38,7 +37,7 @@ detect_platform() {
# Get download URL for the asset
get_download_url() {
local asset_name="$1"
echo "https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${asset_name}"
echo "${RELEASE_URL}/${asset_name}"
}

main() {
Expand All @@ -65,9 +64,9 @@ main() {
echo "Downloading from: ${download_url}"

if command -v curl &> /dev/null; then
curl -fsSL -o "$binary_path" "$download_url"
curl -fL# -o "$binary_path" "$download_url"
elif command -v wget &> /dev/null; then
wget -q -O "$binary_path" "$download_url"
wget --progress=bar:force -O "$binary_path" "$download_url" 2>&1
else
echo "Error: curl or wget required"
exit 1
Expand All @@ -76,6 +75,12 @@ main() {
# Make executable
chmod +x "$binary_path"

# macOS: remove quarantine and ad-hoc sign (Gatekeeper blocks unsigned binaries)
if [ "$(uname -s)" = "Darwin" ]; then
xattr -d com.apple.quarantine "$binary_path" 2>/dev/null || true
codesign --force --sign - "$binary_path" 2>/dev/null || true
fi

echo ""
echo "Installed vers to: ${binary_path}"
echo ""
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"scripts": {
"start": "bun run index.ts",
"dev": "bun --hot run index.ts",
"build": "bun build --compile --minify --external cpu-features ./index.ts --outfile vers-agent",
"build": "bun build --compile --minify --external cpu-features --define \"BUILD_VERSION='dev ($(git rev-parse --short HEAD 2>/dev/null || echo local))'\" ./index.ts --outfile vers-agent",
"build:linux": "bun build --compile --target=bun-linux-x64 --minify --external cpu-features ./index.ts --outfile dist/vers-agent-linux",
"build:bundle": "bun build --target=bun --minify ./index.ts --outdir=dist",
"bundle": "bun run scripts/bundle.ts",
Expand Down