feat(headless): bundle openwork server for openwrk (#338)

This commit is contained in:
ben
2026-01-30 15:40:18 -08:00
committed by GitHub
parent 654e474493
commit 2059a31f47
7 changed files with 86 additions and 9 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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",

View File

@@ -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<boolean> {
}
}
async function isExecutable(path: string): Promise<boolean> {
try {
await access(path);
return true;
} catch {
return false;
}
}
async function ensureWorkspace(workspace: string): Promise<string> {
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<string> {
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<void> {
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);

View File

@@ -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
```

View File

@@ -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",

3
pnpm-lock.yaml generated
View File

@@ -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