zelpi 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -0
- package/bin/pi-os.mjs +37 -0
- package/cli/client.mjs +148 -0
- package/cli/commands.mjs +406 -0
- package/cli/config.mjs +58 -0
- package/cli/daemon.mjs +75 -0
- package/cli/dispatch.mjs +70 -0
- package/cli/embedded.mjs +106 -0
- package/cli/help.mjs +44 -0
- package/cli/hub.mjs +76 -0
- package/cli/kernel.mjs +63 -0
- package/cli/mcp.mjs +144 -0
- package/cli/repl.mjs +100 -0
- package/cli/ui.mjs +109 -0
- package/dist/pios-engine.mjs +948 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# ZelPi — `zos` CLI
|
|
2
|
+
|
|
3
|
+
**ZelPi** (Physical Intelligence Operating System) as an `npm install`-able,
|
|
4
|
+
self-contained command-line OS for autonomous robot fleets.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm install -g pios # installs the `zos` command
|
|
8
|
+
zos # interactive OS shell — boots a backend automatically
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
No repo, no database, no API keys required: the package ships a **bundled
|
|
12
|
+
simulation engine** and an **embedded backend** that auto-starts on first use.
|
|
13
|
+
Point it at a real ZelPi backend later with one env var.
|
|
14
|
+
|
|
15
|
+
It implements the "AI Integration for Embedded" architecture as a top-to-bottom
|
|
16
|
+
command surface:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
CLI / REPL bin + cli/repl ← top layer
|
|
20
|
+
│
|
|
21
|
+
MCP / API Hooks cli/client · cli/mcp ← agents (north) + backend (south)
|
|
22
|
+
│
|
|
23
|
+
Kernel — QoS router cli/kernel + engine ← Robustness | Optimisation | System-Critical
|
|
24
|
+
│
|
|
25
|
+
Model layer (VLM·WM·VLA·LLM) models train/deploy
|
|
26
|
+
│
|
|
27
|
+
Cloud (train/store) hub pull · models train
|
|
28
|
+
│
|
|
29
|
+
Hub / Repo (SOCs · HF) cli/hub ← compatible SOCs + HuggingFace registry
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
zos # OS shell (auto-starts the embedded backend)
|
|
36
|
+
zos status # health · profile · fleet · deployed models
|
|
37
|
+
zos intent "Inventory Aisle 4" # natural-language intent → System-2 plan → dispatch
|
|
38
|
+
zos profile optimisation # switch the kernel QoS profile
|
|
39
|
+
zos hub pull openvla/openvla-7b # load a model from HuggingFace
|
|
40
|
+
zos models deploy M4 # promote a trained model to the fleet
|
|
41
|
+
zos watch # live activity feed
|
|
42
|
+
zos down # stop the embedded backend daemon
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Bare text in the shell is treated as an intent, so `zos` feels like a
|
|
46
|
+
conversational OS console.
|
|
47
|
+
|
|
48
|
+
## How it runs
|
|
49
|
+
|
|
50
|
+
- **Embedded (default).** Any command auto-starts a local backend (`cli/embedded.mjs`)
|
|
51
|
+
running the bundled engine over the same WS+HTTP protocol the full ZelPi server
|
|
52
|
+
speaks. State persists to `~/.pi-os/embedded-state.json`. Intent parsing is the
|
|
53
|
+
deterministic offline parser (no keys). `zos up` runs it in the foreground;
|
|
54
|
+
`zos down` stops the daemon.
|
|
55
|
+
- **Connected.** Point at a full backend (with a live LLM, ROS, MLflow, auth):
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
export PIOS_HOST=http://your-host:8787
|
|
59
|
+
export PIOS_WS_URL=ws://your-host:8787
|
|
60
|
+
zos status
|
|
61
|
+
# or persist: zos config set host your-host && zos config set port 8787
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## The Kernel — QoS profile router
|
|
65
|
+
|
|
66
|
+
Every intent is routed through one execution profile; the engine applies it,
|
|
67
|
+
biasing **Safety-Broker scrutiny** and **execution speed**.
|
|
68
|
+
|
|
69
|
+
| Profile | Speed | Safety gating | Use |
|
|
70
|
+
|---|---|---|---|
|
|
71
|
+
| `balanced` | nominal | nominal | kernel default |
|
|
72
|
+
| `robustness` | −5% | +20% | many SOCs/buses, multi-system spread |
|
|
73
|
+
| `optimisation` | +40% | −50% | fast routine tasks, speed > deliberation |
|
|
74
|
+
| `system-critical` | −18% | +80% | precise, high-integrity work |
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
zos profile # list + active
|
|
78
|
+
zos profile system-critical
|
|
79
|
+
zos intent "Retrieve the green box from Aisle 3" --profile system-critical
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Commands
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
status backend health, profile, fleet, deployed models
|
|
86
|
+
intent "<text>" [--profile p] submit a natural-language intent
|
|
87
|
+
safety [authorize|reroute|abort] show / resolve the Human-in-the-Loop gate
|
|
88
|
+
watch stream the live activity feed
|
|
89
|
+
pause | resume | reset fleet lifecycle
|
|
90
|
+
|
|
91
|
+
profile [name] kernel QoS router
|
|
92
|
+
|
|
93
|
+
fleet list robots
|
|
94
|
+
fleet enroll --chassis "<c>" [--name <n>] [--aisle <n>]
|
|
95
|
+
|
|
96
|
+
models model registry (vla→S2, act→S1, mhal→S0)
|
|
97
|
+
models train --kind vla|act|mhal [--name <n>] [--embodiment <e>]
|
|
98
|
+
models deploy <id>
|
|
99
|
+
|
|
100
|
+
hub [socs|chassis|models] compatible SOCs, embodiments, model registry
|
|
101
|
+
hub pull <key|hf-id> load a model from HF (e.g. pi0, openvla/openvla-7b)
|
|
102
|
+
|
|
103
|
+
up [--ros] [--force] [--embedded] boot a backend
|
|
104
|
+
down stop the embedded backend daemon
|
|
105
|
+
mcp run the MCP server (expose ZelPi to AI agents over stdio)
|
|
106
|
+
login [user] fetch + store a dev JWT
|
|
107
|
+
metrics Prometheus exposition
|
|
108
|
+
config [set <key> <value>] backend host/port/token
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## MCP — drive the fleet from an AI agent
|
|
112
|
+
|
|
113
|
+
`zos mcp` runs a Model-Context-Protocol server over stdio (JSON-RPC 2.0). Tools:
|
|
114
|
+
`pios_status`, `pios_intent`, `pios_set_profile`, `pios_fleet`, `pios_models`,
|
|
115
|
+
`pios_deploy_model`, `pios_resolve_safety`, `pios_hub`.
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"mcpServers": {
|
|
120
|
+
"pi-os": { "command": "zos", "args": ["mcp"], "env": { "PIOS_HOST": "http://127.0.0.1:8787" } }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Environment
|
|
126
|
+
|
|
127
|
+
| Var | Meaning |
|
|
128
|
+
|---|---|
|
|
129
|
+
| `PIOS_HOST` | backend HTTP base (e.g. `http://10.0.0.5:8787`) |
|
|
130
|
+
| `PIOS_WS_URL` | backend WS base |
|
|
131
|
+
| `PIOS_TOKEN` | JWT for an auth-enabled backend (`zos login` fetches a dev token) |
|
|
132
|
+
| `PIOS_NO_AUTOSTART` | disable embedded auto-start |
|
|
133
|
+
| `NO_COLOR` | disable ANSI color |
|
|
134
|
+
|
|
135
|
+
Config persists to `~/.pi-os/config.json`.
|
|
136
|
+
|
|
137
|
+
## Publishing (maintainers)
|
|
138
|
+
|
|
139
|
+
The publishable package is assembled from this repo into `cli-dist/` — lean
|
|
140
|
+
(only `ws`), self-contained (bundled engine), free of the web-app deps.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npm run build:pkg # → cli-dist/ (esbuild bundle + cli/ + bin/ + package.json)
|
|
144
|
+
cd cli-dist
|
|
145
|
+
npm publish --access public # requires `npm login`
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Requires Node ≥ 18 (uses the global WebSocket client on Node 21+, falls back to
|
|
149
|
+
`ws` on older runtimes).
|
package/bin/pi-os.mjs
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// pi-os — Physical Intelligence Operating System · CLI entrypoint.
|
|
3
|
+
// `npm install -g .` (or `npm link`) exposes this as the `pi-os` command.
|
|
4
|
+
// No args → interactive OS shell. Otherwise dispatch a single command.
|
|
5
|
+
|
|
6
|
+
import { run, NO_AUTOSTART } from "../cli/dispatch.mjs";
|
|
7
|
+
import { repl } from "../cli/repl.mjs";
|
|
8
|
+
import { ensureBackend } from "../cli/daemon.mjs";
|
|
9
|
+
import { HELP } from "../cli/help.mjs";
|
|
10
|
+
import { c, sym } from "../cli/ui.mjs";
|
|
11
|
+
|
|
12
|
+
const argv = process.argv.slice(2);
|
|
13
|
+
const first = argv[0];
|
|
14
|
+
|
|
15
|
+
async function main() {
|
|
16
|
+
if (first === "help" || first === "--help" || first === "-h") return console.log(HELP);
|
|
17
|
+
if (first === "version" || first === "--version" || first === "-v") {
|
|
18
|
+
const { createRequire } = await import("node:module");
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
const pkg = require("../package.json");
|
|
21
|
+
return console.log(`zelpi ${pkg.version}`);
|
|
22
|
+
}
|
|
23
|
+
// Interactive OS shell.
|
|
24
|
+
if (!first || first === "shell") {
|
|
25
|
+
await ensureBackend().catch(() => {});
|
|
26
|
+
return repl();
|
|
27
|
+
}
|
|
28
|
+
// For commands that talk to the backend, make sure one is up (auto-start the
|
|
29
|
+
// self-contained embedded backend for local targets — the npm-install path).
|
|
30
|
+
if (!NO_AUTOSTART.has(first)) await ensureBackend().catch(() => {});
|
|
31
|
+
await run(argv);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
main().catch((e) => {
|
|
35
|
+
console.error(`${sym.err} ${c.red(e.message || String(e))}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
});
|
package/cli/client.mjs
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// pi-os CLI — API Hooks layer.
|
|
2
|
+
// The southbound connection from the CLI to the ZelPi backend: HTTP for health /
|
|
3
|
+
// DB views / metrics, and a WebSocket for the live command + snapshot channel
|
|
4
|
+
// (the same protocol the web console speaks). Uses Node's built-in fetch +
|
|
5
|
+
// global WebSocket — zero dependencies.
|
|
6
|
+
|
|
7
|
+
import { httpBase, wsUrl, load } from "./config.mjs";
|
|
8
|
+
|
|
9
|
+
class PiosError extends Error {}
|
|
10
|
+
|
|
11
|
+
// Prefer Node's global WebSocket client (Node 21+/22+); fall back to the `ws`
|
|
12
|
+
// package on older runtimes so the published package works broadly.
|
|
13
|
+
let _WS = globalThis.WebSocket;
|
|
14
|
+
async function WSClass() {
|
|
15
|
+
if (!_WS) _WS = (await import("ws")).WebSocket;
|
|
16
|
+
return _WS;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function getJSON(pathname) {
|
|
20
|
+
const base = httpBase();
|
|
21
|
+
let res;
|
|
22
|
+
try {
|
|
23
|
+
res = await fetch(base + pathname, { signal: AbortSignal.timeout(5000) });
|
|
24
|
+
} catch (e) {
|
|
25
|
+
throw new PiosError(`backend unreachable at ${base} (${e.code || e.name}). Is it running? Try: npx zelpi up`);
|
|
26
|
+
}
|
|
27
|
+
if (!res.ok) throw new PiosError(`${pathname} → HTTP ${res.status}`);
|
|
28
|
+
const txt = await res.text();
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(txt);
|
|
31
|
+
} catch {
|
|
32
|
+
return txt;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const health = () => getJSON("/health");
|
|
37
|
+
export const dbTasks = () => getJSON("/db/tasks");
|
|
38
|
+
export const dbModels = () => getJSON("/db/models");
|
|
39
|
+
export const dbEmbodiments = () => getJSON("/db/embodiments");
|
|
40
|
+
export const metricsText = () => getJSON("/metrics");
|
|
41
|
+
|
|
42
|
+
export async function login(user = "operator") {
|
|
43
|
+
const base = httpBase();
|
|
44
|
+
const res = await fetch(base + "/auth/login", {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: { "content-type": "application/json" },
|
|
47
|
+
body: JSON.stringify({ user }),
|
|
48
|
+
});
|
|
49
|
+
return res.json();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Open a WS, run `fn(ws)` once connected, and resolve when `done()` is called. */
|
|
53
|
+
function withSocket(fn, { timeout = 8000 } = {}) {
|
|
54
|
+
return new Promise(async (resolve, reject) => {
|
|
55
|
+
let ws;
|
|
56
|
+
try {
|
|
57
|
+
const WS = await WSClass();
|
|
58
|
+
ws = new WS(wsUrl());
|
|
59
|
+
} catch (e) {
|
|
60
|
+
return reject(new PiosError(`could not open socket: ${e.message}`));
|
|
61
|
+
}
|
|
62
|
+
const timer = setTimeout(() => {
|
|
63
|
+
try { ws.close(); } catch {}
|
|
64
|
+
reject(new PiosError(`timed out talking to backend (${wsUrl()})`));
|
|
65
|
+
}, timeout);
|
|
66
|
+
let result;
|
|
67
|
+
const done = (value) => {
|
|
68
|
+
result = value;
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
try { ws.close(); } catch {}
|
|
71
|
+
};
|
|
72
|
+
ws.addEventListener("open", () => {
|
|
73
|
+
try { fn(ws, done); } catch (e) { clearTimeout(timer); reject(e); }
|
|
74
|
+
});
|
|
75
|
+
ws.addEventListener("close", (e) => {
|
|
76
|
+
clearTimeout(timer);
|
|
77
|
+
if (e && e.code === 1008) return reject(new PiosError("unauthorized — set a token: npx zelpi login"));
|
|
78
|
+
resolve(result);
|
|
79
|
+
});
|
|
80
|
+
ws.addEventListener("error", () => {
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
reject(new PiosError(`socket error connecting to ${wsUrl()}. Is the backend up?`));
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const parse = (data) => {
|
|
88
|
+
try { return JSON.parse(typeof data === "string" ? data : data.toString()); } catch { return null; }
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** Fetch a single live snapshot over the WS channel. */
|
|
92
|
+
export function snapshot() {
|
|
93
|
+
return withSocket((ws, done) => {
|
|
94
|
+
ws.addEventListener("message", (e) => {
|
|
95
|
+
const m = parse(e.data);
|
|
96
|
+
if (m && m.type === "snapshot") done(m.snap);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Send one command and read back its effect. By default wait `settle` snapshots;
|
|
103
|
+
* if an `until(snap)` predicate is given, resolve on the first post-command
|
|
104
|
+
* snapshot that satisfies it (or after `timeout` ms) — needed for effects that
|
|
105
|
+
* complete asynchronously server-side, e.g. an LLM-planned intent.
|
|
106
|
+
*/
|
|
107
|
+
export function command(msg, { settle = 0, until = null, timeout = 8000 } = {}) {
|
|
108
|
+
return withSocket(
|
|
109
|
+
(ws, done) => {
|
|
110
|
+
let sent = false;
|
|
111
|
+
ws.addEventListener("message", (e) => {
|
|
112
|
+
const m = parse(e.data);
|
|
113
|
+
if (!m || m.type !== "snapshot") return;
|
|
114
|
+
if (!sent) {
|
|
115
|
+
// ignore the immediate pre-command snapshot, then send
|
|
116
|
+
sent = true;
|
|
117
|
+
ws.send(JSON.stringify(msg));
|
|
118
|
+
if (!until && settle === 0) done(m.snap);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (until) { if (until(m.snap)) done(m.snap); }
|
|
122
|
+
else if (--settle <= 0) done(m.snap);
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
{ timeout },
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Stream snapshots to `onSnap` until the returned stop() is called. */
|
|
130
|
+
export function watch(onSnap) {
|
|
131
|
+
let socket;
|
|
132
|
+
let stopped = false;
|
|
133
|
+
const open = async () => {
|
|
134
|
+
const WS = await WSClass();
|
|
135
|
+
if (stopped) return;
|
|
136
|
+
socket = new WS(wsUrl());
|
|
137
|
+
socket.addEventListener("message", (e) => {
|
|
138
|
+
const m = parse(e.data);
|
|
139
|
+
if (m && m.type === "snapshot") onSnap(m.snap);
|
|
140
|
+
});
|
|
141
|
+
socket.addEventListener("close", () => { if (!stopped) setTimeout(open, 1000); });
|
|
142
|
+
socket.addEventListener("error", () => { try { socket.close(); } catch {} });
|
|
143
|
+
};
|
|
144
|
+
open();
|
|
145
|
+
return () => { stopped = true; try { socket.close(); } catch {} };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export { PiosError, load };
|