my-durable 0.0.2 → 0.0.4
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 +18 -0
- package/bin/dpi.mjs +150 -0
- package/package.json +2 -4
- package/bin/device.mjs +0 -5
- package/bin/hub.mjs +0 -5
- package/bin/tui.mjs +0 -5
package/README.md
CHANGED
|
@@ -8,6 +8,24 @@
|
|
|
8
8
|
|
|
9
9
|
📺 Watch this video to get the full breakdown of this codebase: **[Pi Durable PROACTIVE Agents](https://youtu.be/DNDYYHsypZg)**
|
|
10
10
|
|
|
11
|
+
## Quick start (`dpi`)
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install -g my-durable
|
|
15
|
+
|
|
16
|
+
dpi hub # web UI + device tunnel server → http://127.0.0.1:4781
|
|
17
|
+
dpi device # device daemon (pairs to a hub, serves this machine)
|
|
18
|
+
dpi tui # terminal admin client
|
|
19
|
+
dpi --help # commands, flags and environment variables
|
|
20
|
+
dpi update --check # is a newer published version available?
|
|
21
|
+
dpi update # install it (npm i -g my-durable@latest)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`dpi hub` is the only component that other machines talk to, so it is also the one
|
|
25
|
+
to put behind a TLS proxy or tunnel (it refuses anything that did not arrive over
|
|
26
|
+
HTTPS). Pair a device by opening the hub, pressing **Pair a device**, and entering
|
|
27
|
+
the code — or open the pairing link directly on the machine being added.
|
|
28
|
+
|
|
11
29
|
<p align="center">
|
|
12
30
|
<img src="images/01_durable_agent_network.svg" alt="Three clients (web UI, dpi-tui, curl) reach durable agents on four devices; the clients disconnect and every device keeps checkpointing its run to SQLite" width="850">
|
|
13
31
|
</p>
|
package/bin/dpi.mjs
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `dpi` — one command for the Durable Pi stack.
|
|
3
|
+
// dpi hub [--port N] [--host H] [--public-host H] web UI + device tunnel server
|
|
4
|
+
// dpi device device-side daemon
|
|
5
|
+
// dpi tui terminal admin client
|
|
6
|
+
// Every subcommand runs the TypeScript entry through tsx, so no build step is needed.
|
|
7
|
+
import { access } from "node:fs/promises";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
/** Subcommand → TypeScript entry, relative to this file. */
|
|
11
|
+
const COMMANDS = {
|
|
12
|
+
hub: "../apps/dpi-playground/apps/dpi-playground-hub/src/main.ts",
|
|
13
|
+
device: "../apps/dpi-backend/src/main.ts",
|
|
14
|
+
tui: "../apps/dpi-tui/src/main.ts",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const HELP = `dpi — Durable Pi
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
dpi hub [--port 4781] [--host 127.0.0.1] [--public-host <host>]
|
|
21
|
+
Web UI + device tunnel server (devices dial this).
|
|
22
|
+
dpi device
|
|
23
|
+
Device-side daemon (pairs to a hub, serves one machine's agents).
|
|
24
|
+
dpi tui
|
|
25
|
+
Terminal admin client.
|
|
26
|
+
dpi help | dpi --help | dpi -h
|
|
27
|
+
dpi version | dpi --version | dpi -v
|
|
28
|
+
dpi update [--check]
|
|
29
|
+
--check compare the installed version with the registry, change nothing
|
|
30
|
+
(no flag) install the latest published version globally
|
|
31
|
+
|
|
32
|
+
Environment (hub): DPI_HOME, DPI_HUB_PORT, DPI_HUB_HOST, DPI_HUB_PUBLIC_HOST,
|
|
33
|
+
DPI_HUB_ORIGIN, DPI_HUB_REQUIRE_TLS
|
|
34
|
+
Environment (device): DPI_HUB_URL, DPI_HOME
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* tsx is a dependency of the published package, but a bare repository checkout has it only
|
|
39
|
+
* inside the per-app node_modules. Try the normal resolution first, then the known local
|
|
40
|
+
* copies, so `dpi` works in both places without a root install.
|
|
41
|
+
*/
|
|
42
|
+
async function loadTsx() {
|
|
43
|
+
try {
|
|
44
|
+
return await import("tsx/esm/api");
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error?.code !== "ERR_MODULE_NOT_FOUND") throw error;
|
|
47
|
+
}
|
|
48
|
+
const candidates = [
|
|
49
|
+
"../apps/dpi-playground/node_modules/tsx/dist/esm/api/index.mjs",
|
|
50
|
+
"../apps/dpi-backend/node_modules/tsx/dist/esm/api/index.mjs",
|
|
51
|
+
"../apps/dpi-sdk/node_modules/tsx/dist/esm/api/index.mjs",
|
|
52
|
+
"../apps/dpi-tui/node_modules/tsx/dist/esm/api/index.mjs",
|
|
53
|
+
];
|
|
54
|
+
for (const relative of candidates) {
|
|
55
|
+
const url = new URL(relative, import.meta.url);
|
|
56
|
+
try {
|
|
57
|
+
await access(fileURLToPath(url));
|
|
58
|
+
return await import(url.href);
|
|
59
|
+
} catch {
|
|
60
|
+
// try the next one
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
throw new Error(
|
|
64
|
+
"dpi: cannot find tsx. Install the package (npm install -g my-durable) or run npm install in an app directory.",
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const [first, ...rest] = process.argv.slice(2);
|
|
69
|
+
|
|
70
|
+
if (!first || first === "help" || first === "--help" || first === "-h") {
|
|
71
|
+
process.stdout.write(HELP);
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (first === "version" || first === "--version" || first === "-v") {
|
|
76
|
+
const { readFileSync } = await import("node:fs");
|
|
77
|
+
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
78
|
+
process.stdout.write(`dpi (my-durable) ${pkg.version}\n`);
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (first === "update" || first === "upgrade") {
|
|
83
|
+
const { readFileSync } = await import("node:fs");
|
|
84
|
+
const { spawn } = await import("node:child_process");
|
|
85
|
+
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
86
|
+
const current = pkg.version;
|
|
87
|
+
const checkOnly = rest.includes("--check") || rest.includes("-c");
|
|
88
|
+
|
|
89
|
+
let latest;
|
|
90
|
+
try {
|
|
91
|
+
const res = await fetch("https://registry.npmjs.org/my-durable/latest", {
|
|
92
|
+
headers: { accept: "application/json" },
|
|
93
|
+
});
|
|
94
|
+
if (!res.ok) throw new Error(`registry responded ${res.status}`);
|
|
95
|
+
latest = (await res.json()).version;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
process.stderr.write(`dpi update: cannot reach the npm registry (${error.message})\n`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Numeric triple compare; a prerelease tag on either side counts as older.
|
|
102
|
+
const rank = (v) => {
|
|
103
|
+
const [core, ...pre] = String(v).split("-");
|
|
104
|
+
const nums = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
105
|
+
return { nums: [nums[0] ?? 0, nums[1] ?? 0, nums[2] ?? 0], pre: pre.length > 0 };
|
|
106
|
+
};
|
|
107
|
+
const a = rank(current);
|
|
108
|
+
const b = rank(latest);
|
|
109
|
+
let order = 0;
|
|
110
|
+
for (let i = 0; i < 3; i += 1) {
|
|
111
|
+
if (a.nums[i] !== b.nums[i]) {
|
|
112
|
+
order = a.nums[i] < b.nums[i] ? -1 : 1;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (order === 0 && a.pre !== b.pre) order = a.pre ? -1 : 1;
|
|
117
|
+
|
|
118
|
+
if (order >= 0) {
|
|
119
|
+
process.stdout.write(`dpi ${current} is up to date (latest ${latest}).\n`);
|
|
120
|
+
process.exit(0);
|
|
121
|
+
}
|
|
122
|
+
process.stdout.write(`dpi ${current} -> ${latest} available.\n`);
|
|
123
|
+
if (checkOnly) {
|
|
124
|
+
process.stdout.write("Run `dpi update` to install it.\n");
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const child = spawn("npm", ["install", "-g", `my-durable@${latest}`], { stdio: "inherit" });
|
|
129
|
+
child.on("error", (error) => {
|
|
130
|
+
process.stderr.write(`dpi update: cannot run npm (${error.message})\n`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
});
|
|
133
|
+
child.on("exit", (code) => {
|
|
134
|
+
if (code !== 0) {
|
|
135
|
+
process.stderr.write(`dpi update: npm exited ${code}; still on ${current}.\n`);
|
|
136
|
+
process.exit(code ?? 1);
|
|
137
|
+
}
|
|
138
|
+
process.stdout.write(`dpi updated ${current} -> ${latest}.\n`);
|
|
139
|
+
process.exit(0);
|
|
140
|
+
});
|
|
141
|
+
} else if (!Object.hasOwn(COMMANDS, first)) {
|
|
142
|
+
process.stderr.write(`dpi: unknown command '${first}'\n\n${HELP}`);
|
|
143
|
+
process.exit(2);
|
|
144
|
+
} else {
|
|
145
|
+
// The entries parse process.argv.slice(2) themselves: hand them their own args.
|
|
146
|
+
process.argv = [process.argv[0], process.argv[1], ...rest];
|
|
147
|
+
const { register } = await loadTsx();
|
|
148
|
+
register();
|
|
149
|
+
await import(COMMANDS[first]);
|
|
150
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "my-durable",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"description": "Durable Pi — the durable agent network: hub web UI, device backend daemon, TUI admin and the shared SDK, packaged as one runnable monorepo install",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -15,9 +15,7 @@
|
|
|
15
15
|
"node": ">=22.0.0"
|
|
16
16
|
},
|
|
17
17
|
"bin": {
|
|
18
|
-
"
|
|
19
|
-
"my-durable-device": "bin/device.mjs",
|
|
20
|
-
"my-durable-tui": "bin/tui.mjs"
|
|
18
|
+
"dpi": "bin/dpi.mjs"
|
|
21
19
|
},
|
|
22
20
|
"scripts": {
|
|
23
21
|
"hub": "tsx apps/dpi-playground/apps/dpi-playground-hub/src/main.ts",
|
package/bin/device.mjs
DELETED
package/bin/hub.mjs
DELETED