From 020ecaea9ef4cc32c41456040fc835cd31b1f5e2 Mon Sep 17 00:00:00 2001 From: aphrody-dev Date: Tue, 13 Oct 2026 22:49:11 +0200 Subject: [PATCH] feat: expose unified product server from bun os CLI --- docs/architecture.md | 19 +++- packages/bun-app-host/src/cli.ts | 6 +- src/cli.ts | 145 ++++++++++++++++--------------- 3 files changed, 97 insertions(+), 73 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index a5d42095..a1290cb6 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -4,6 +4,7 @@ flowchart TD UI[Aphrody shell] --> SDK[Bun Tauri v3 / CEF] UI --> Backend[Bun backend] + Product[Standalone product API] --> Workspace Backend --> Workspace[Native library orchestration] Workspace --> Sources[Pinned library submodules] Backend --> Video[Typed video adapter] @@ -18,11 +19,17 @@ flowchart TD Infra[Aphrody Infra] --> Apps Infra --> Control[Rust deployment controller] Forge[Git forge and runner] --> Factory + Infra --> Forge + Forge --> Portal[source.aphrody.app] + Sources --> Crates[Flat pinned crates / root Cargo workspace] ``` - `apps/desktop`: Aphrody shell, built and served by Bun. -- `packages/workspace`: app identity, config, provenance, source coverage, - native execution, asset manifests and the app transport. +- `packages/bun-workspace`: app identity, config, provenance, source coverage, + native execution, asset manifests and shared process ownership. +- `packages/workspace`: OS CLI and adapters consuming that shared owner. +- `packages/bun-app-host`: product server, common authenticated API transport, + standalone compilation and executable qualification. - `packages/bun-video-project`: exact JSON, bounded native operations and runtime availability. - `packages/bun-artifacts`: target manifests, standalone compilation and verified native payload extraction. @@ -51,4 +58,12 @@ Provider credentials, sessions, datasets and model weights remain isolated. Aphrody Infra owns host, service, route and activation policy. Komodo actuates deployment; Gitea and its runner host Git and execute the same Bun factory. +The root `bun-os` CLI routes `server` commands to the same app-host CLI owner. +Just coordinates setup, shared quality, Cargo, apps, product server and vendor +without implementing another build pipeline. The root owns `node_modules`, +`target`, `build` and `install`. + Source, artifact, CI and public service qualification are recorded separately. +The complete 239-repository Labs inventory and resumable mirror admission live +in Aphrody Infra. The embedded product catalog is a pinned distribution +snapshot, not a claim that every mirror or native application is qualified. diff --git a/packages/bun-app-host/src/cli.ts b/packages/bun-app-host/src/cli.ts index 08a3f772..3773a36d 100644 --- a/packages/bun-app-host/src/cli.ts +++ b/packages/bun-app-host/src/cli.ts @@ -6,8 +6,8 @@ import { productCatalog } from "./build.ts"; import { startProduct } from "./product.ts"; import { verifyProduct } from "./verify.ts"; -if (import.meta.main) { - const [action = "help", root = repositoryRoot, receipt] = Bun.argv.slice(2); +export async function runProductCLI(args: readonly string[]): Promise { + const [action = "help", root = repositoryRoot, receipt] = args; if (action === "help" || action === "--help") console.log("Aphrody / Bun server: cli.ts serve [ROOT] | verify [BINARY] [RECEIPT] | --version"); else if (action === "--version") console.log("Aphrody / Bun server 1.4.4"); @@ -26,3 +26,5 @@ if (import.meta.main) { console.log(JSON.stringify(await verifyProduct(binary, receipt ? resolve(receipt) : binary + ".json"))); } else throw new Error("Use serve, verify or --version."); } + +if (import.meta.main) await runProductCLI(Bun.argv.slice(2)); diff --git a/src/cli.ts b/src/cli.ts index 2176eba1..321113ac 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,6 +1,7 @@ #!/usr/bin/env bun import { resolve } from "node:path"; import { parseArgs } from "node:util"; +import { runProductCLI } from "../packages/bun-app-host/src/cli.ts"; import { assembleSources, buildImage, @@ -14,6 +15,7 @@ import { } from "./distribution.ts"; const usage = `Bun OS factory + server serve [ROOT] | verify [BINARY] [RECEIPT] | --version plan [--profile full|perf] [--target linux-x64|linux-arm64|windows-x64|macos-arm64|wasm] sources --out DIR [--config FILE] stage --out DIR --runtime FILE --receipt FILE --profile full|perf --target TARGET @@ -26,77 +28,82 @@ Sources contain committed, pinned files and every locked submodule; protected st WASM builds libm3's portable API. Native Bun/JSC and the Linux compositor require their native targets.`; try { - const { positionals, values } = parseArgs({ - args: Bun.argv.slice(2), - allowPositionals: true, - options: { - help: { type: "boolean", short: "h" }, - profile: { type: "string", default: "perf" }, - target: { type: "string", default: "linux-x64" }, - out: { type: "string" }, - config: { type: "string" }, - runtime: { type: "string" }, - receipt: { type: "string" }, - material: { type: "string" }, - tag: { type: "string" }, - }, - }); - if (values.help || !positionals.length) { - console.log(usage); - process.exit(0); - } - if (positionals.length !== 1) throw new Error("Expected one factory command"); - if (values.profile !== "full" && values.profile !== "perf") throw new Error("Unknown distribution profile"); - const profile: Profile = values.profile; - if (!["linux-x64", "linux-arm64", "windows-x64", "macos-arm64", "wasm"].includes(values.target!)) - throw new Error("Unknown target"); - const target = values.target as Target; - const required = (name: "out" | "runtime" | "receipt" | "material" | "tag") => { - const value = values[name]; - if (!value) throw new Error(`Missing --${name}`); - return value; - }; - let config: Config = {}; - if (values.config) { - const file = resolve(values.config); - config = await Bun.file(file).json(); - for (const [id, path] of Object.entries(config.checkouts ?? {})) config.checkouts![id] = resolve(file, "..", path); - if (config.sourceExport) { - if (config.sourceExport.cache) config.sourceExport.cache = resolve(file, "..", config.sourceExport.cache); - for (const [id, path] of Object.entries(config.sourceExport.repositories ?? {})) - config.sourceExport.repositories![id] = resolve(file, "..", path); - config.sourceExport.protectedRoots = config.sourceExport.protectedRoots?.map(path => resolve(file, "..", path)); + if (Bun.argv[2] === "server") { + await runProductCLI(Bun.argv.slice(3)); + } else { + const { positionals, values } = parseArgs({ + args: Bun.argv.slice(2), + allowPositionals: true, + options: { + help: { type: "boolean", short: "h" }, + profile: { type: "string", default: "perf" }, + target: { type: "string", default: "linux-x64" }, + out: { type: "string" }, + config: { type: "string" }, + runtime: { type: "string" }, + receipt: { type: "string" }, + material: { type: "string" }, + tag: { type: "string" }, + }, + }); + if (values.help || !positionals.length) { + console.log(usage); + process.exit(0); } + if (positionals.length !== 1) throw new Error("Expected one factory command"); + if (values.profile !== "full" && values.profile !== "perf") throw new Error("Unknown distribution profile"); + const profile: Profile = values.profile; + if (!["linux-x64", "linux-arm64", "windows-x64", "macos-arm64", "wasm"].includes(values.target!)) + throw new Error("Unknown target"); + const target = values.target as Target; + const required = (name: "out" | "runtime" | "receipt" | "material" | "tag") => { + const value = values[name]; + if (!value) throw new Error(`Missing --${name}`); + return value; + }; + let config: Config = {}; + if (values.config) { + const file = resolve(values.config); + config = await Bun.file(file).json(); + for (const [id, path] of Object.entries(config.checkouts ?? {})) + config.checkouts![id] = resolve(file, "..", path); + if (config.sourceExport) { + if (config.sourceExport.cache) config.sourceExport.cache = resolve(file, "..", config.sourceExport.cache); + for (const [id, path] of Object.entries(config.sourceExport.repositories ?? {})) + config.sourceExport.repositories![id] = resolve(file, "..", path); + config.sourceExport.protectedRoots = config.sourceExport.protectedRoots?.map(path => resolve(file, "..", path)); + } + } + let result: unknown; + switch (positionals[0]) { + case "plan": + result = await plan(profile, target, config); + break; + case "sources": + result = await assembleSources(resolve(required("out")), config); + break; + case "stage": + if (target === "wasm") throw new Error("Use the wasm command for the portable library target"); + await stageRuntime( + resolve(required("out")), + resolve(required("runtime")), + resolve(required("receipt")), + profile, + target, + ); + result = { staged: true, profile, target }; + break; + case "wasm": + result = await buildWasm(resolve(required("out")), config); + break; + case "image": + result = await buildImage(resolve(required("out")), profile, target, required("tag")); + break; + default: + throw new Error(`Unknown command ${positionals[0]}; run ${packageRoot}/src/cli.ts --help`); + } + console.log(JSON.stringify(result, null, 2)); } - let result: unknown; - switch (positionals[0]) { - case "plan": - result = await plan(profile, target, config); - break; - case "sources": - result = await assembleSources(resolve(required("out")), config); - break; - case "stage": - if (target === "wasm") throw new Error("Use the wasm command for the portable library target"); - await stageRuntime( - resolve(required("out")), - resolve(required("runtime")), - resolve(required("receipt")), - profile, - target, - ); - result = { staged: true, profile, target }; - break; - case "wasm": - result = await buildWasm(resolve(required("out")), config); - break; - case "image": - result = await buildImage(resolve(required("out")), profile, target, required("tag")); - break; - default: - throw new Error(`Unknown command ${positionals[0]}; run ${packageRoot}/src/cli.ts --help`); - } - console.log(JSON.stringify(result, null, 2)); } catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1;