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 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.2",
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
- "my-durable-hub": "bin/hub.mjs",
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
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
- // Run the device daemon TypeScript entry through tsx so `my-durable-device` works without a build step.
3
- import { register } from "tsx/esm/api";
4
- register();
5
- await import("../apps/dpi-backend/src/main.ts");
package/bin/hub.mjs DELETED
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
- // Run the hub TypeScript entry through tsx so `my-durable-hub` works without a build step.
3
- import { register } from "tsx/esm/api";
4
- register();
5
- await import("../apps/dpi-playground/apps/dpi-playground-hub/src/main.ts");
package/bin/tui.mjs DELETED
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
- // Run the TUI TypeScript entry through tsx so `my-durable-tui` works without a build step.
3
- import { register } from "tsx/esm/api";
4
- register();
5
- await import("../apps/dpi-tui/src/main.ts");