diff --git a/README.md b/README.md index c597bb82..6b4d4a4b 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ The goal: make “agentic work” feel like a product, not a terminal. - `curl -fsSL https://raw.githubusercontent.com/different-ai/openwork/dev/packages/owpenbot/install.sh | bash` - run `owpenbot setup`, then `owpenbot whatsapp login`, then `owpenbot start` - full setup: [packages/owpenbot/README.md](./packages/owpenbot/README.md) -- **Openwrk (CLI host)**: run OpenCode + OpenWork server without the desktop UI. +- **Openwrk (CLI host)**: run OpenCode + OpenWork server without the desktop UI. Install with `npm install -g openwrk`. - docs: [packages/headless/README.md](./packages/headless/README.md) diff --git a/packages/headless/README.md b/packages/headless/README.md index b6b4a57f..d6706849 100644 --- a/packages/headless/README.md +++ b/packages/headless/README.md @@ -9,6 +9,8 @@ npm install -g openwrk openwrk start --workspace /path/to/workspace --approval auto ``` +`openwrk` installs the OpenWork server dependency automatically. No extra install needed. + Or from source: ```bash diff --git a/packages/headless/package.json b/packages/headless/package.json index dafe8d0c..1297e90a 100644 --- a/packages/headless/package.json +++ b/packages/headless/package.json @@ -1,6 +1,6 @@ { "name": "openwrk", - "version": "0.1.0", + "version": "0.1.1", "description": "Headless OpenWork host orchestrator for OpenCode + OpenWork server + Owpenbot", "type": "module", "bin": { @@ -36,7 +36,8 @@ "access": "public" }, "dependencies": { - "@opencode-ai/sdk": "^1.1.31" + "@opencode-ai/sdk": "^1.1.31", + "openwork-server": "^0.1.0" }, "devDependencies": { "@types/node": "^22.10.2", diff --git a/packages/headless/src/cli.ts b/packages/headless/src/cli.ts index a82fd86d..760f11f6 100644 --- a/packages/headless/src/cli.ts +++ b/packages/headless/src/cli.ts @@ -4,7 +4,9 @@ import { randomUUID } from "node:crypto"; import { mkdir, stat, writeFile } from "node:fs/promises"; import { createServer } from "node:net"; import { hostname, networkInterfaces } from "node:os"; -import { join, resolve } from "node:path"; +import { dirname, join, resolve } from "node:path"; +import { access } from "node:fs/promises"; +import { createRequire } from "node:module"; import { once } from "node:events"; import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"; @@ -158,6 +160,15 @@ async function fileExists(path: string): Promise { } } +async function isExecutable(path: string): Promise { + try { + await access(path); + return true; + } catch { + return false; + } +} + async function ensureWorkspace(workspace: string): Promise { const resolved = resolve(workspace); await mkdir(resolved, { recursive: true }); @@ -292,11 +303,20 @@ function prefixStream( }); } +function shouldUseBun(bin: string): boolean { + if (!bin.endsWith(`${join("dist", "cli.js")}`)) return false; + if (bin.includes("openwork-server")) return true; + return bin.includes(`${join("packages", "server")}`); +} + function resolveBinCommand(bin: string): { command: string; prefixArgs: string[] } { if (bin.endsWith(".ts")) { return { command: "bun", prefixArgs: [bin, "--"] }; } if (bin.endsWith(".js")) { + if (shouldUseBun(bin)) { + return { command: "bun", prefixArgs: [bin, "--"] }; + } return { command: "node", prefixArgs: [bin, "--"] }; } return { command: bin, prefixArgs: [] }; @@ -309,6 +329,26 @@ function resolveBinPath(bin: string): string { return bin; } +async function resolveOpenworkServerBin(explicit?: string): Promise { + if (explicit) { + return resolveBinPath(explicit); + } + + const require = createRequire(import.meta.url); + try { + const pkgPath = require.resolve("openwork-server/package.json"); + const pkgDir = dirname(pkgPath); + const cliPath = join(pkgDir, "dist", "cli.js"); + if (await isExecutable(cliPath)) { + return cliPath; + } + } catch { + // ignore + } + + return "openwork-server"; +} + async function waitForHealthy(url: string, timeoutMs = 10_000, pollMs = 250): Promise { const start = Date.now(); let lastError: string | null = null; @@ -760,8 +800,8 @@ async function runStart(args: ParsedArgs) { const corsOrigins = parseList(corsValue); const connectHost = readFlag(args.flags, "connect-host"); - const openworkServerBin = resolveBinPath( - readFlag(args.flags, "openwork-server-bin") ?? process.env.OPENWORK_SERVER_BIN ?? "openwork-server", + const openworkServerBin = await resolveOpenworkServerBin( + readFlag(args.flags, "openwork-server-bin") ?? process.env.OPENWORK_SERVER_BIN, ); const owpenbotBin = resolveBinPath(readFlag(args.flags, "owpenbot-bin") ?? process.env.OWPENBOT_BIN ?? "owpenbot"); const owpenbotEnabled = readBool(args.flags, "owpenbot", true); diff --git a/packages/server/README.md b/packages/server/README.md index a33051b7..f7ef3265 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -5,7 +5,14 @@ Filesystem-backed API for OpenWork remote clients. This package provides the Ope ## Quick start ```bash -pnpm --filter @different-ai/openwork-server dev -- \ +npm install -g openwork-server +openwork-server --workspace /path/to/workspace --approval auto +``` + +Or from source: + +```bash +pnpm --filter openwork-server dev -- \ --workspace /path/to/workspace \ --approval auto ``` diff --git a/packages/server/package.json b/packages/server/package.json index 13a5e245..3994491d 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,7 +1,7 @@ { - "name": "@different-ai/openwork-server", + "name": "openwork-server", "version": "0.1.0", - "private": true, + "description": "Filesystem-backed API for OpenWork remote clients", "type": "module", "bin": { "openwork-server": "dist/cli.js" @@ -14,6 +14,30 @@ "start": "bun dist/cli.js", "typecheck": "tsc -p tsconfig.json --noEmit" }, + "files": [ + "dist", + "README.md" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/different-ai/openwork.git", + "directory": "packages/server" + }, + "homepage": "https://github.com/different-ai/openwork/tree/dev/packages/server", + "bugs": { + "url": "https://github.com/different-ai/openwork/issues" + }, + "keywords": [ + "openwork", + "server", + "opencode", + "headless", + "cli" + ], + "license": "MIT", + "publishConfig": { + "access": "public" + }, "dependencies": { "jsonc-parser": "^3.2.1", "minimatch": "^10.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e77df6a5..44d39669 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,6 +88,9 @@ importers: '@opencode-ai/sdk': specifier: ^1.1.31 version: 1.1.39 + openwork-server: + specifier: ^0.1.0 + version: link:../server devDependencies: '@types/node': specifier: ^22.10.2