@leconome/cli 0.2.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 ADDED
@@ -0,0 +1,20 @@
1
+ # @leconome/cli
2
+
3
+ One command to join the Econome IPFS cluster as a follower.
4
+
5
+ ```bash
6
+ npx -y @leconome/cli join <join-url-from-dashboard>
7
+ ```
8
+
9
+ Requires Docker. Manage the follower with:
10
+
11
+ ```bash
12
+ econome status # is it running / replicating, pin count
13
+ econome logs -f # tail follower logs
14
+ econome stop # stop (keeps data)
15
+ econome update # pull newer images and restart
16
+ ```
17
+
18
+ The join URL comes from the dashboard's **Onboarding** page (one per minted
19
+ token). The CLI fetches the cluster config, starts a Kubo + ipfs-cluster
20
+ follower under `~/.econome`, and registers the new peer with the dashboard.
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/docker.ts
4
+ import { spawn } from "child_process";
5
+ function run(cmd, args, cwd) {
6
+ return new Promise((resolve, reject) => {
7
+ const child = spawn(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
8
+ let out = "";
9
+ let err = "";
10
+ child.stdout.on("data", (d) => {
11
+ out += d.toString();
12
+ });
13
+ child.stderr.on("data", (d) => {
14
+ err += d.toString();
15
+ });
16
+ child.on("error", reject);
17
+ child.on("close", (code) => {
18
+ if (code === 0) resolve(out);
19
+ else reject(new Error(err.trim() || `${cmd} exited with code ${code}`));
20
+ });
21
+ });
22
+ }
23
+ function runInherit(cmd, args, cwd) {
24
+ return new Promise((resolve, reject) => {
25
+ const child = spawn(cmd, args, { cwd, stdio: "inherit" });
26
+ child.on("error", reject);
27
+ child.on("close", () => resolve());
28
+ });
29
+ }
30
+ async function dockerAvailable() {
31
+ try {
32
+ await run("docker", ["compose", "version"]);
33
+ return true;
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+ async function composeUp(dir) {
39
+ await run("docker", ["compose", "up", "-d"], dir);
40
+ }
41
+ async function composeDown(dir) {
42
+ await run("docker", ["compose", "down"], dir);
43
+ }
44
+ async function composePull(dir) {
45
+ await run("docker", ["compose", "pull"], dir);
46
+ }
47
+ async function composeExec(dir, service, cmd) {
48
+ return run("docker", ["compose", "exec", "-T", service, ...cmd], dir);
49
+ }
50
+ async function composeLogs(dir, follow) {
51
+ const args = ["compose", "logs"];
52
+ if (follow) args.push("-f");
53
+ await runInherit("docker", args, dir);
54
+ }
55
+
56
+ // src/lib/project.ts
57
+ import { mkdir, readFile, writeFile } from "fs/promises";
58
+ import { homedir } from "os";
59
+ import { join } from "path";
60
+ function projectDir() {
61
+ return process.env.ECONOME_DIR ?? join(homedir(), ".econome");
62
+ }
63
+ async function writeProject(dir, cfg, meta) {
64
+ await mkdir(dir, { recursive: true });
65
+ await writeFile(join(dir, "docker-compose.yml"), cfg.compose, "utf8");
66
+ await writeFile(join(dir, "kubo-init.sh"), cfg.kuboInit, "utf8");
67
+ await writeFile(
68
+ join(dir, "config.json"),
69
+ `${JSON.stringify(meta, null, 2)}
70
+ `,
71
+ "utf8"
72
+ );
73
+ }
74
+ async function readProjectConfig(dir) {
75
+ try {
76
+ const raw = await readFile(join(dir, "config.json"), "utf8");
77
+ return JSON.parse(raw);
78
+ } catch {
79
+ return null;
80
+ }
81
+ }
82
+
83
+ export {
84
+ dockerAvailable,
85
+ composeUp,
86
+ composeDown,
87
+ composePull,
88
+ composeExec,
89
+ composeLogs,
90
+ projectDir,
91
+ writeProject,
92
+ readProjectConfig
93
+ };
94
+ //# sourceMappingURL=chunk-6A35JEOA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/docker.ts","../src/lib/project.ts"],"sourcesContent":["import { spawn } from \"node:child_process\";\n\n/** Run a command, capturing stdout. Rejects on non-zero exit. */\nfunction run(cmd: string, args: string[], cwd?: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, { cwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n let out = \"\";\n let err = \"\";\n child.stdout.on(\"data\", (d) => {\n out += d.toString();\n });\n child.stderr.on(\"data\", (d) => {\n err += d.toString();\n });\n child.on(\"error\", reject);\n child.on(\"close\", (code) => {\n if (code === 0) resolve(out);\n else reject(new Error(err.trim() || `${cmd} exited with code ${code}`));\n });\n });\n}\n\n/** Run a command inheriting the parent's stdio (for live logs). */\nfunction runInherit(cmd: string, args: string[], cwd?: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, { cwd, stdio: \"inherit\" });\n child.on(\"error\", reject);\n child.on(\"close\", () => resolve());\n });\n}\n\n/** True if `docker compose` is usable on this machine. */\nexport async function dockerAvailable(): Promise<boolean> {\n try {\n await run(\"docker\", [\"compose\", \"version\"]);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function composeUp(dir: string): Promise<void> {\n await run(\"docker\", [\"compose\", \"up\", \"-d\"], dir);\n}\n\nexport async function composeDown(dir: string): Promise<void> {\n await run(\"docker\", [\"compose\", \"down\"], dir);\n}\n\nexport async function composePull(dir: string): Promise<void> {\n await run(\"docker\", [\"compose\", \"pull\"], dir);\n}\n\nexport async function composeExec(\n dir: string,\n service: string,\n cmd: string[],\n): Promise<string> {\n return run(\"docker\", [\"compose\", \"exec\", \"-T\", service, ...cmd], dir);\n}\n\nexport async function composeLogs(dir: string, follow: boolean): Promise<void> {\n const args = [\"compose\", \"logs\"];\n if (follow) args.push(\"-f\");\n await runInherit(\"docker\", args, dir);\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { FollowerConfig } from \"./config\";\n\nexport interface ProjectConfig {\n server: string;\n token: string;\n clusterName: string;\n}\n\n/** The follower project directory (`~/.econome`, overridable via ECONOME_DIR). */\nexport function projectDir(): string {\n return process.env.ECONOME_DIR ?? join(homedir(), \".econome\");\n}\n\n/** Write the compose, kubo init, and config files into `dir` (created if needed). */\nexport async function writeProject(\n dir: string,\n cfg: FollowerConfig,\n meta: ProjectConfig,\n): Promise<void> {\n await mkdir(dir, { recursive: true });\n await writeFile(join(dir, \"docker-compose.yml\"), cfg.compose, \"utf8\");\n await writeFile(join(dir, \"kubo-init.sh\"), cfg.kuboInit, \"utf8\");\n await writeFile(\n join(dir, \"config.json\"),\n `${JSON.stringify(meta, null, 2)}\\n`,\n \"utf8\",\n );\n}\n\n/** Read the stored project config, or null if the project hasn't been set up. */\nexport async function readProjectConfig(\n dir: string,\n): Promise<ProjectConfig | null> {\n try {\n const raw = await readFile(join(dir, \"config.json\"), \"utf8\");\n return JSON.parse(raw) as ProjectConfig;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;AAAA,SAAS,aAAa;AAGtB,SAAS,IAAI,KAAa,MAAgB,KAA+B;AACvE,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,QAAQ,MAAM,KAAK,MAAM,EAAE,KAAK,OAAO,CAAC,UAAU,QAAQ,MAAM,EAAE,CAAC;AACzE,QAAI,MAAM;AACV,QAAI,MAAM;AACV,UAAM,OAAO,GAAG,QAAQ,CAAC,MAAM;AAC7B,aAAO,EAAE,SAAS;AAAA,IACpB,CAAC;AACD,UAAM,OAAO,GAAG,QAAQ,CAAC,MAAM;AAC7B,aAAO,EAAE,SAAS;AAAA,IACpB,CAAC;AACD,UAAM,GAAG,SAAS,MAAM;AACxB,UAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,UAAI,SAAS,EAAG,SAAQ,GAAG;AAAA,UACtB,QAAO,IAAI,MAAM,IAAI,KAAK,KAAK,GAAG,GAAG,qBAAqB,IAAI,EAAE,CAAC;AAAA,IACxE,CAAC;AAAA,EACH,CAAC;AACH;AAGA,SAAS,WAAW,KAAa,MAAgB,KAA6B;AAC5E,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,QAAQ,MAAM,KAAK,MAAM,EAAE,KAAK,OAAO,UAAU,CAAC;AACxD,UAAM,GAAG,SAAS,MAAM;AACxB,UAAM,GAAG,SAAS,MAAM,QAAQ,CAAC;AAAA,EACnC,CAAC;AACH;AAGA,eAAsB,kBAAoC;AACxD,MAAI;AACF,UAAM,IAAI,UAAU,CAAC,WAAW,SAAS,CAAC;AAC1C,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,UAAU,KAA4B;AAC1D,QAAM,IAAI,UAAU,CAAC,WAAW,MAAM,IAAI,GAAG,GAAG;AAClD;AAEA,eAAsB,YAAY,KAA4B;AAC5D,QAAM,IAAI,UAAU,CAAC,WAAW,MAAM,GAAG,GAAG;AAC9C;AAEA,eAAsB,YAAY,KAA4B;AAC5D,QAAM,IAAI,UAAU,CAAC,WAAW,MAAM,GAAG,GAAG;AAC9C;AAEA,eAAsB,YACpB,KACA,SACA,KACiB;AACjB,SAAO,IAAI,UAAU,CAAC,WAAW,QAAQ,MAAM,SAAS,GAAG,GAAG,GAAG,GAAG;AACtE;AAEA,eAAsB,YAAY,KAAa,QAAgC;AAC7E,QAAM,OAAO,CAAC,WAAW,MAAM;AAC/B,MAAI,OAAQ,MAAK,KAAK,IAAI;AAC1B,QAAM,WAAW,UAAU,MAAM,GAAG;AACtC;;;ACjEA,SAAS,OAAO,UAAU,iBAAiB;AAC3C,SAAS,eAAe;AACxB,SAAS,YAAY;AAUd,SAAS,aAAqB;AACnC,SAAO,QAAQ,IAAI,eAAe,KAAK,QAAQ,GAAG,UAAU;AAC9D;AAGA,eAAsB,aACpB,KACA,KACA,MACe;AACf,QAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACpC,QAAM,UAAU,KAAK,KAAK,oBAAoB,GAAG,IAAI,SAAS,MAAM;AACpE,QAAM,UAAU,KAAK,KAAK,cAAc,GAAG,IAAI,UAAU,MAAM;AAC/D,QAAM;AAAA,IACJ,KAAK,KAAK,aAAa;AAAA,IACvB,GAAG,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA;AAAA,IAChC;AAAA,EACF;AACF;AAGA,eAAsB,kBACpB,KAC+B;AAC/B,MAAI;AACF,UAAM,MAAM,MAAM,SAAS,KAAK,KAAK,aAAa,GAAG,MAAM;AAC3D,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/peer.ts
4
+ function parsePeerId(stdout) {
5
+ try {
6
+ const parsed = JSON.parse(stdout);
7
+ return typeof parsed.id === "string" && parsed.id.length > 0 ? parsed.id : null;
8
+ } catch {
9
+ return null;
10
+ }
11
+ }
12
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
13
+ async function pollPeerId(getStdout, opts = {}) {
14
+ const attempts = opts.attempts ?? 30;
15
+ const delayMs = opts.delayMs ?? 2e3;
16
+ for (let i = 0; i < attempts; i++) {
17
+ let id = null;
18
+ try {
19
+ id = parsePeerId(await getStdout());
20
+ } catch {
21
+ id = null;
22
+ }
23
+ if (id) return id;
24
+ if (i < attempts - 1) await sleep(delayMs);
25
+ }
26
+ return null;
27
+ }
28
+
29
+ export {
30
+ parsePeerId,
31
+ pollPeerId
32
+ };
33
+ //# sourceMappingURL=chunk-OFG2G5CZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/peer.ts"],"sourcesContent":["/** Parse a cluster peer id from `ipfs-cluster-ctl --enc=json id` stdout. */\nexport function parsePeerId(stdout: string): string | null {\n try {\n const parsed = JSON.parse(stdout) as { id?: unknown };\n return typeof parsed.id === \"string\" && parsed.id.length > 0\n ? parsed.id\n : null;\n } catch {\n return null;\n }\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\n/**\n * Poll for the follower's cluster peer id until it appears or attempts run out.\n * `getStdout` is expected to run the cluster-ctl id command; failures (the\n * container still starting) are treated like \"not ready yet\".\n */\nexport async function pollPeerId(\n getStdout: () => Promise<string>,\n opts: { attempts?: number; delayMs?: number } = {},\n): Promise<string | null> {\n const attempts = opts.attempts ?? 30;\n const delayMs = opts.delayMs ?? 2000;\n for (let i = 0; i < attempts; i++) {\n let id: string | null = null;\n try {\n id = parsePeerId(await getStdout());\n } catch {\n id = null;\n }\n if (id) return id;\n if (i < attempts - 1) await sleep(delayMs);\n }\n return null;\n}\n"],"mappings":";;;AACO,SAAS,YAAY,QAA+B;AACzD,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,MAAM;AAChC,WAAO,OAAO,OAAO,OAAO,YAAY,OAAO,GAAG,SAAS,IACvD,OAAO,KACP;AAAA,EACN,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,QAAQ,CAAC,OAAe,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAOlE,eAAsB,WACpB,WACA,OAAgD,CAAC,GACzB;AACxB,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,UAAU,KAAK,WAAW;AAChC,WAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,QAAI,KAAoB;AACxB,QAAI;AACF,WAAK,YAAY,MAAM,UAAU,CAAC;AAAA,IACpC,QAAQ;AACN,WAAK;AAAA,IACP;AACA,QAAI,GAAI,QAAO;AACf,QAAI,IAAI,WAAW,EAAG,OAAM,MAAM,OAAO;AAAA,EAC3C;AACA,SAAO;AACT;","names":[]}
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ var program = new Command();
6
+ program.name("econome").description("Join and manage an Econome IPFS cluster follower.").version("0.1.0");
7
+ program.command("join").argument("[url]", "join URL from the dashboard onboarding page").description("Start a follower that replicates the cluster pinset").action(async (url) => {
8
+ const { join } = await import("./join-N5PEB7VV.js");
9
+ await join(url);
10
+ });
11
+ program.command("status").description("Show whether the follower is running and replicating").action(async () => {
12
+ const { status } = await import("./status-BFLJZEV6.js");
13
+ await status();
14
+ });
15
+ program.command("logs").option("-f, --follow", "follow log output").description("Show follower logs").action(async (opts) => {
16
+ const { logs } = await import("./logs-VEI2IJXW.js");
17
+ await logs(Boolean(opts.follow));
18
+ });
19
+ program.command("stop").description("Stop the follower (keeps data)").action(async () => {
20
+ const { stop } = await import("./stop-KDYCFQEZ.js");
21
+ await stop();
22
+ });
23
+ program.command("update").description("Pull newer images and restart the follower").action(async () => {
24
+ const { update } = await import("./update-LLAH5SHH.js");
25
+ await update();
26
+ });
27
+ program.parseAsync();
28
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { Command } from \"commander\";\n\nconst program = new Command();\n\nprogram\n .name(\"econome\")\n .description(\"Join and manage an Econome IPFS cluster follower.\")\n .version(\"0.1.0\");\n\nprogram\n .command(\"join\")\n .argument(\"[url]\", \"join URL from the dashboard onboarding page\")\n .description(\"Start a follower that replicates the cluster pinset\")\n .action(async (url?: string) => {\n const { join } = await import(\"./commands/join.js\");\n await join(url);\n });\n\nprogram\n .command(\"status\")\n .description(\"Show whether the follower is running and replicating\")\n .action(async () => {\n const { status } = await import(\"./commands/status.js\");\n await status();\n });\n\nprogram\n .command(\"logs\")\n .option(\"-f, --follow\", \"follow log output\")\n .description(\"Show follower logs\")\n .action(async (opts: { follow?: boolean }) => {\n const { logs } = await import(\"./commands/logs.js\");\n await logs(Boolean(opts.follow));\n });\n\nprogram\n .command(\"stop\")\n .description(\"Stop the follower (keeps data)\")\n .action(async () => {\n const { stop } = await import(\"./commands/stop.js\");\n await stop();\n });\n\nprogram\n .command(\"update\")\n .description(\"Pull newer images and restart the follower\")\n .action(async () => {\n const { update } = await import(\"./commands/update.js\");\n await update();\n });\n\nprogram.parseAsync();\n"],"mappings":";;;AAAA,SAAS,eAAe;AAExB,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,SAAS,EACd,YAAY,mDAAmD,EAC/D,QAAQ,OAAO;AAElB,QACG,QAAQ,MAAM,EACd,SAAS,SAAS,6CAA6C,EAC/D,YAAY,qDAAqD,EACjE,OAAO,OAAO,QAAiB;AAC9B,QAAM,EAAE,KAAK,IAAI,MAAM,OAAO,oBAAoB;AAClD,QAAM,KAAK,GAAG;AAChB,CAAC;AAEH,QACG,QAAQ,QAAQ,EAChB,YAAY,sDAAsD,EAClE,OAAO,YAAY;AAClB,QAAM,EAAE,OAAO,IAAI,MAAM,OAAO,sBAAsB;AACtD,QAAM,OAAO;AACf,CAAC;AAEH,QACG,QAAQ,MAAM,EACd,OAAO,gBAAgB,mBAAmB,EAC1C,YAAY,oBAAoB,EAChC,OAAO,OAAO,SAA+B;AAC5C,QAAM,EAAE,KAAK,IAAI,MAAM,OAAO,oBAAoB;AAClD,QAAM,KAAK,QAAQ,KAAK,MAAM,CAAC;AACjC,CAAC;AAEH,QACG,QAAQ,MAAM,EACd,YAAY,gCAAgC,EAC5C,OAAO,YAAY;AAClB,QAAM,EAAE,KAAK,IAAI,MAAM,OAAO,oBAAoB;AAClD,QAAM,KAAK;AACb,CAAC;AAEH,QACG,QAAQ,QAAQ,EAChB,YAAY,4CAA4C,EACxD,OAAO,YAAY;AAClB,QAAM,EAAE,OAAO,IAAI,MAAM,OAAO,sBAAsB;AACtD,QAAM,OAAO;AACf,CAAC;AAEH,QAAQ,WAAW;","names":[]}
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ pollPeerId
4
+ } from "./chunk-OFG2G5CZ.js";
5
+ import {
6
+ composeExec,
7
+ composeUp,
8
+ dockerAvailable,
9
+ projectDir,
10
+ writeProject
11
+ } from "./chunk-6A35JEOA.js";
12
+
13
+ // src/commands/join.ts
14
+ import { existsSync } from "fs";
15
+ import { homedir } from "os";
16
+ import { join as pathJoin } from "path";
17
+ import * as p from "@clack/prompts";
18
+
19
+ // src/lib/config.ts
20
+ async function fetchFollowerConfig(url) {
21
+ let res;
22
+ try {
23
+ res = await fetch(url, { headers: { accept: "application/json" } });
24
+ } catch (err) {
25
+ throw new Error(
26
+ `Could not reach the dashboard at ${url}: ${err instanceof Error ? err.message : String(err)}`
27
+ );
28
+ }
29
+ let body;
30
+ try {
31
+ body = await res.json();
32
+ } catch {
33
+ throw new Error(
34
+ `The dashboard returned a non-JSON response (HTTP ${res.status}).`
35
+ );
36
+ }
37
+ if (body && typeof body === "object" && "error" in body) {
38
+ throw new Error(String(body.error));
39
+ }
40
+ const cfg = body;
41
+ if (!cfg || typeof cfg.compose !== "string" || typeof cfg.kuboInit !== "string") {
42
+ throw new Error("The dashboard response was missing the follower config.");
43
+ }
44
+ return {
45
+ clusterName: typeof cfg.clusterName === "string" ? cfg.clusterName : "econome",
46
+ compose: cfg.compose,
47
+ kuboInit: cfg.kuboInit
48
+ };
49
+ }
50
+
51
+ // src/lib/join-url.ts
52
+ function parseJoinUrl(input) {
53
+ let url;
54
+ try {
55
+ url = new URL(input.trim());
56
+ } catch {
57
+ throw new Error(`"${input}" is not a valid URL.`);
58
+ }
59
+ const parts = url.pathname.split("/").filter(Boolean);
60
+ const [first, token] = parts;
61
+ if (first !== "join" || !token) {
62
+ throw new Error(`"${input}" is not a join URL (expected \u2026/join/<token>).`);
63
+ }
64
+ return { origin: url.origin, token };
65
+ }
66
+
67
+ // src/lib/register.ts
68
+ async function registerPeer(origin, token, peerId) {
69
+ const res = await fetch(`${origin}/join/${token}/register`, {
70
+ method: "POST",
71
+ headers: { "content-type": "application/json" },
72
+ body: JSON.stringify({ peerId })
73
+ });
74
+ if (!res.ok) {
75
+ throw new Error(`registration failed (HTTP ${res.status})`);
76
+ }
77
+ }
78
+
79
+ // src/commands/join.ts
80
+ async function join(url) {
81
+ p.intro("Econome follower");
82
+ let joinUrl = url;
83
+ if (!joinUrl) {
84
+ const answer = await p.text({
85
+ message: "Paste the join URL from the dashboard",
86
+ placeholder: "https://your-dashboard/join/onb_\u2026"
87
+ });
88
+ if (p.isCancel(answer)) {
89
+ p.cancel("Cancelled.");
90
+ return;
91
+ }
92
+ joinUrl = answer;
93
+ }
94
+ let origin;
95
+ let token;
96
+ try {
97
+ ({ origin, token } = parseJoinUrl(joinUrl));
98
+ } catch (err) {
99
+ p.cancel(err instanceof Error ? err.message : String(err));
100
+ process.exitCode = 1;
101
+ return;
102
+ }
103
+ const dockerSpin = p.spinner();
104
+ dockerSpin.start("Checking Docker");
105
+ if (!await dockerAvailable()) {
106
+ dockerSpin.stop("Docker not found");
107
+ p.cancel(
108
+ "Docker Compose is required. Install it: https://docs.docker.com/get-docker/"
109
+ );
110
+ process.exitCode = 1;
111
+ return;
112
+ }
113
+ dockerSpin.stop("Docker is ready");
114
+ if (existsSync(pathJoin(homedir(), "econome-follower"))) {
115
+ p.log.warn(
116
+ "A bash-installed follower may already exist at ~/econome-follower. This CLI uses ~/.econome instead."
117
+ );
118
+ }
119
+ const cfgSpin = p.spinner();
120
+ cfgSpin.start("Fetching cluster config");
121
+ let config;
122
+ try {
123
+ config = await fetchFollowerConfig(joinUrl);
124
+ } catch (err) {
125
+ cfgSpin.stop("Could not fetch config");
126
+ p.cancel(err instanceof Error ? err.message : String(err));
127
+ process.exitCode = 1;
128
+ return;
129
+ }
130
+ cfgSpin.stop(`Joining "${config.clusterName}"`);
131
+ const dir = projectDir();
132
+ await writeProject(dir, config, {
133
+ server: origin,
134
+ token,
135
+ clusterName: config.clusterName
136
+ });
137
+ const upSpin = p.spinner();
138
+ upSpin.start("Starting follower (docker compose up)");
139
+ try {
140
+ await composeUp(dir);
141
+ } catch (err) {
142
+ upSpin.stop("Failed to start");
143
+ p.cancel(err instanceof Error ? err.message : String(err));
144
+ process.exitCode = 1;
145
+ return;
146
+ }
147
+ upSpin.stop("Follower started");
148
+ const idSpin = p.spinner();
149
+ idSpin.start("Waiting for the cluster peer to come online");
150
+ const peerId = await pollPeerId(
151
+ () => composeExec(dir, "cluster", ["ipfs-cluster-ctl", "--enc=json", "id"])
152
+ );
153
+ if (!peerId) {
154
+ idSpin.stop("Peer not online yet");
155
+ p.log.warn(
156
+ "The follower is running but hasn't reported a peer id yet. Check `econome logs`."
157
+ );
158
+ p.outro(
159
+ `Replicating "${config.clusterName}". Manage with: econome status | logs | stop`
160
+ );
161
+ return;
162
+ }
163
+ idSpin.stop(`Peer online: ${peerId}`);
164
+ const regSpin = p.spinner();
165
+ regSpin.start("Registering with the dashboard");
166
+ try {
167
+ await registerPeer(origin, token, peerId);
168
+ regSpin.stop("Registered");
169
+ } catch {
170
+ regSpin.stop("Could not register (the follower is still running)");
171
+ p.log.warn("Registration failed; re-run `econome join` later to retry.");
172
+ }
173
+ p.outro(
174
+ `Replicating "${config.clusterName}". Manage with: econome status | logs | stop`
175
+ );
176
+ }
177
+ export {
178
+ join
179
+ };
180
+ //# sourceMappingURL=join-N5PEB7VV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/join.ts","../src/lib/config.ts","../src/lib/join-url.ts","../src/lib/register.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join as pathJoin } from \"node:path\";\nimport * as p from \"@clack/prompts\";\nimport { fetchFollowerConfig } from \"../lib/config.js\";\nimport { composeExec, composeUp, dockerAvailable } from \"../lib/docker.js\";\nimport { parseJoinUrl } from \"../lib/join-url.js\";\nimport { pollPeerId } from \"../lib/peer.js\";\nimport { projectDir, writeProject } from \"../lib/project.js\";\nimport { registerPeer } from \"../lib/register.js\";\n\nexport async function join(url?: string): Promise<void> {\n p.intro(\"Econome follower\");\n\n // 1. Resolve the join URL (prompt if not given).\n let joinUrl = url;\n if (!joinUrl) {\n const answer = await p.text({\n message: \"Paste the join URL from the dashboard\",\n placeholder: \"https://your-dashboard/join/onb_…\",\n });\n if (p.isCancel(answer)) {\n p.cancel(\"Cancelled.\");\n return;\n }\n joinUrl = answer;\n }\n\n let origin: string;\n let token: string;\n try {\n ({ origin, token } = parseJoinUrl(joinUrl));\n } catch (err) {\n p.cancel(err instanceof Error ? err.message : String(err));\n process.exitCode = 1;\n return;\n }\n\n // 2. Preconditions.\n const dockerSpin = p.spinner();\n dockerSpin.start(\"Checking Docker\");\n if (!(await dockerAvailable())) {\n dockerSpin.stop(\"Docker not found\");\n p.cancel(\n \"Docker Compose is required. Install it: https://docs.docker.com/get-docker/\",\n );\n process.exitCode = 1;\n return;\n }\n dockerSpin.stop(\"Docker is ready\");\n\n if (existsSync(pathJoin(homedir(), \"econome-follower\"))) {\n p.log.warn(\n \"A bash-installed follower may already exist at ~/econome-follower. This CLI uses ~/.econome instead.\",\n );\n }\n\n // 3. Fetch the rendered follower config.\n const cfgSpin = p.spinner();\n cfgSpin.start(\"Fetching cluster config\");\n let config: Awaited<ReturnType<typeof fetchFollowerConfig>>;\n try {\n config = await fetchFollowerConfig(joinUrl);\n } catch (err) {\n cfgSpin.stop(\"Could not fetch config\");\n p.cancel(err instanceof Error ? err.message : String(err));\n process.exitCode = 1;\n return;\n }\n cfgSpin.stop(`Joining \"${config.clusterName}\"`);\n\n // 4. Write the project + start the follower.\n const dir = projectDir();\n await writeProject(dir, config, {\n server: origin,\n token,\n clusterName: config.clusterName,\n });\n\n const upSpin = p.spinner();\n upSpin.start(\"Starting follower (docker compose up)\");\n try {\n await composeUp(dir);\n } catch (err) {\n upSpin.stop(\"Failed to start\");\n p.cancel(err instanceof Error ? err.message : String(err));\n process.exitCode = 1;\n return;\n }\n upSpin.stop(\"Follower started\");\n\n // 5. Wait for the cluster peer id, then register it (best-effort).\n const idSpin = p.spinner();\n idSpin.start(\"Waiting for the cluster peer to come online\");\n const peerId = await pollPeerId(() =>\n composeExec(dir, \"cluster\", [\"ipfs-cluster-ctl\", \"--enc=json\", \"id\"]),\n );\n if (!peerId) {\n idSpin.stop(\"Peer not online yet\");\n p.log.warn(\n \"The follower is running but hasn't reported a peer id yet. Check `econome logs`.\",\n );\n p.outro(\n `Replicating \"${config.clusterName}\". Manage with: econome status | logs | stop`,\n );\n return;\n }\n idSpin.stop(`Peer online: ${peerId}`);\n\n const regSpin = p.spinner();\n regSpin.start(\"Registering with the dashboard\");\n try {\n await registerPeer(origin, token, peerId);\n regSpin.stop(\"Registered\");\n } catch {\n regSpin.stop(\"Could not register (the follower is still running)\");\n p.log.warn(\"Registration failed; re-run `econome join` later to retry.\");\n }\n\n p.outro(\n `Replicating \"${config.clusterName}\". Manage with: econome status | logs | stop`,\n );\n}\n","export interface FollowerConfig {\n clusterName: string;\n compose: string;\n kuboInit: string;\n}\n\n/**\n * Fetch the rendered follower config from a dashboard join URL. The server\n * returns `{ clusterName, compose, kuboInit }` on success or `{ error }` for an\n * invalid/expired token or unconfigured cluster.\n */\nexport async function fetchFollowerConfig(\n url: string,\n): Promise<FollowerConfig> {\n let res: Response;\n try {\n res = await fetch(url, { headers: { accept: \"application/json\" } });\n } catch (err) {\n throw new Error(\n `Could not reach the dashboard at ${url}: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n\n let body: unknown;\n try {\n body = await res.json();\n } catch {\n throw new Error(\n `The dashboard returned a non-JSON response (HTTP ${res.status}).`,\n );\n }\n\n if (body && typeof body === \"object\" && \"error\" in body) {\n throw new Error(String((body as { error: unknown }).error));\n }\n\n const cfg = body as Partial<FollowerConfig>;\n if (\n !cfg ||\n typeof cfg.compose !== \"string\" ||\n typeof cfg.kuboInit !== \"string\"\n ) {\n throw new Error(\"The dashboard response was missing the follower config.\");\n }\n return {\n clusterName:\n typeof cfg.clusterName === \"string\" ? cfg.clusterName : \"econome\",\n compose: cfg.compose,\n kuboInit: cfg.kuboInit,\n };\n}\n","/**\n * Parse a dashboard join URL (e.g. `https://host/join/onb_abc`) into the\n * server origin and onboarding token. Throws a friendly Error on bad input.\n */\nexport function parseJoinUrl(input: string): { origin: string; token: string } {\n let url: URL;\n try {\n url = new URL(input.trim());\n } catch {\n throw new Error(`\"${input}\" is not a valid URL.`);\n }\n\n const parts = url.pathname.split(\"/\").filter(Boolean); // drops empties\n const [first, token] = parts;\n if (first !== \"join\" || !token) {\n throw new Error(`\"${input}\" is not a join URL (expected …/join/<token>).`);\n }\n return { origin: url.origin, token };\n}\n","/** Register the follower's cluster peer id with the dashboard (best-effort). */\nexport async function registerPeer(\n origin: string,\n token: string,\n peerId: string,\n): Promise<void> {\n const res = await fetch(`${origin}/join/${token}/register`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ peerId }),\n });\n if (!res.ok) {\n throw new Error(`registration failed (HTTP ${res.status})`);\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ composeLogs,
4
+ projectDir,
5
+ readProjectConfig
6
+ } from "./chunk-6A35JEOA.js";
7
+
8
+ // src/commands/logs.ts
9
+ import * as p from "@clack/prompts";
10
+ async function logs(follow) {
11
+ const dir = projectDir();
12
+ if (!await readProjectConfig(dir)) {
13
+ p.log.error("No follower set up. Run `econome join <url>` first.");
14
+ process.exitCode = 1;
15
+ return;
16
+ }
17
+ await composeLogs(dir, follow);
18
+ }
19
+ export {
20
+ logs
21
+ };
22
+ //# sourceMappingURL=logs-VEI2IJXW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/logs.ts"],"sourcesContent":["import * as p from \"@clack/prompts\";\nimport { composeLogs } from \"../lib/docker.js\";\nimport { projectDir, readProjectConfig } from \"../lib/project.js\";\n\nexport async function logs(follow: boolean): Promise<void> {\n const dir = projectDir();\n if (!(await readProjectConfig(dir))) {\n p.log.error(\"No follower set up. Run `econome join <url>` first.\");\n process.exitCode = 1;\n return;\n }\n await composeLogs(dir, follow);\n}\n"],"mappings":";;;;;;;;AAAA,YAAY,OAAO;AAInB,eAAsB,KAAK,QAAgC;AACzD,QAAM,MAAM,WAAW;AACvB,MAAI,CAAE,MAAM,kBAAkB,GAAG,GAAI;AACnC,IAAE,MAAI,MAAM,qDAAqD;AACjE,YAAQ,WAAW;AACnB;AAAA,EACF;AACA,QAAM,YAAY,KAAK,MAAM;AAC/B;","names":[]}
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ parsePeerId
4
+ } from "./chunk-OFG2G5CZ.js";
5
+ import {
6
+ composeExec,
7
+ projectDir,
8
+ readProjectConfig
9
+ } from "./chunk-6A35JEOA.js";
10
+
11
+ // src/commands/status.ts
12
+ import * as p from "@clack/prompts";
13
+ async function status() {
14
+ const dir = projectDir();
15
+ const cfg = await readProjectConfig(dir);
16
+ if (!cfg) {
17
+ p.log.error("No follower set up. Run `econome join <url>` first.");
18
+ process.exitCode = 1;
19
+ return;
20
+ }
21
+ p.intro(`Econome follower \u2014 ${cfg.clusterName}`);
22
+ try {
23
+ const idOut = await composeExec(dir, "cluster", [
24
+ "ipfs-cluster-ctl",
25
+ "--enc=json",
26
+ "id"
27
+ ]);
28
+ const peerId = parsePeerId(idOut);
29
+ if (peerId) p.log.success(`Online \u2014 peer ${peerId}`);
30
+ else p.log.warn("Cluster container is up but not reporting an id yet.");
31
+ const pins = await composeExec(dir, "cluster", [
32
+ "ipfs-cluster-ctl",
33
+ "status"
34
+ ]);
35
+ const pinCount = pins.split("\n").filter((l) => l.trim().length > 0).length;
36
+ p.log.info(`Tracked pins: ${pinCount}`);
37
+ } catch (err) {
38
+ p.log.error(
39
+ `Follower not reachable: ${err instanceof Error ? err.message : String(err)}`
40
+ );
41
+ process.exitCode = 1;
42
+ }
43
+ p.outro("Done");
44
+ }
45
+ export {
46
+ status
47
+ };
48
+ //# sourceMappingURL=status-BFLJZEV6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/status.ts"],"sourcesContent":["import * as p from \"@clack/prompts\";\nimport { composeExec } from \"../lib/docker.js\";\nimport { parsePeerId } from \"../lib/peer.js\";\nimport { projectDir, readProjectConfig } from \"../lib/project.js\";\n\nexport async function status(): Promise<void> {\n const dir = projectDir();\n const cfg = await readProjectConfig(dir);\n if (!cfg) {\n p.log.error(\"No follower set up. Run `econome join <url>` first.\");\n process.exitCode = 1;\n return;\n }\n\n p.intro(`Econome follower — ${cfg.clusterName}`);\n try {\n const idOut = await composeExec(dir, \"cluster\", [\n \"ipfs-cluster-ctl\",\n \"--enc=json\",\n \"id\",\n ]);\n const peerId = parsePeerId(idOut);\n if (peerId) p.log.success(`Online — peer ${peerId}`);\n else p.log.warn(\"Cluster container is up but not reporting an id yet.\");\n\n const pins = await composeExec(dir, \"cluster\", [\n \"ipfs-cluster-ctl\",\n \"status\",\n ]);\n const pinCount = pins.split(\"\\n\").filter((l) => l.trim().length > 0).length;\n p.log.info(`Tracked pins: ${pinCount}`);\n } catch (err) {\n p.log.error(\n `Follower not reachable: ${err instanceof Error ? err.message : String(err)}`,\n );\n process.exitCode = 1;\n }\n p.outro(\"Done\");\n}\n"],"mappings":";;;;;;;;;;;AAAA,YAAY,OAAO;AAKnB,eAAsB,SAAwB;AAC5C,QAAM,MAAM,WAAW;AACvB,QAAM,MAAM,MAAM,kBAAkB,GAAG;AACvC,MAAI,CAAC,KAAK;AACR,IAAE,MAAI,MAAM,qDAAqD;AACjE,YAAQ,WAAW;AACnB;AAAA,EACF;AAEA,EAAE,QAAM,2BAAsB,IAAI,WAAW,EAAE;AAC/C,MAAI;AACF,UAAM,QAAQ,MAAM,YAAY,KAAK,WAAW;AAAA,MAC9C;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,UAAM,SAAS,YAAY,KAAK;AAChC,QAAI,OAAQ,CAAE,MAAI,QAAQ,sBAAiB,MAAM,EAAE;AAAA,QAC9C,CAAE,MAAI,KAAK,sDAAsD;AAEtE,UAAM,OAAO,MAAM,YAAY,KAAK,WAAW;AAAA,MAC7C;AAAA,MACA;AAAA,IACF,CAAC;AACD,UAAM,WAAW,KAAK,MAAM,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE;AACrE,IAAE,MAAI,KAAK,iBAAiB,QAAQ,EAAE;AAAA,EACxC,SAAS,KAAK;AACZ,IAAE,MAAI;AAAA,MACJ,2BAA2B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IAC7E;AACA,YAAQ,WAAW;AAAA,EACrB;AACA,EAAE,QAAM,MAAM;AAChB;","names":[]}
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ composeDown,
4
+ projectDir,
5
+ readProjectConfig
6
+ } from "./chunk-6A35JEOA.js";
7
+
8
+ // src/commands/stop.ts
9
+ import * as p from "@clack/prompts";
10
+ async function stop() {
11
+ const dir = projectDir();
12
+ if (!await readProjectConfig(dir)) {
13
+ p.log.error("No follower set up. Run `econome join <url>` first.");
14
+ process.exitCode = 1;
15
+ return;
16
+ }
17
+ const s = p.spinner();
18
+ s.start("Stopping follower");
19
+ await composeDown(dir);
20
+ s.stop(
21
+ "Stopped (data kept). Re-start with `econome update` or `econome join`."
22
+ );
23
+ }
24
+ export {
25
+ stop
26
+ };
27
+ //# sourceMappingURL=stop-KDYCFQEZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/stop.ts"],"sourcesContent":["import * as p from \"@clack/prompts\";\nimport { composeDown } from \"../lib/docker.js\";\nimport { projectDir, readProjectConfig } from \"../lib/project.js\";\n\nexport async function stop(): Promise<void> {\n const dir = projectDir();\n if (!(await readProjectConfig(dir))) {\n p.log.error(\"No follower set up. Run `econome join <url>` first.\");\n process.exitCode = 1;\n return;\n }\n const s = p.spinner();\n s.start(\"Stopping follower\");\n await composeDown(dir);\n s.stop(\n \"Stopped (data kept). Re-start with `econome update` or `econome join`.\",\n );\n}\n"],"mappings":";;;;;;;;AAAA,YAAY,OAAO;AAInB,eAAsB,OAAsB;AAC1C,QAAM,MAAM,WAAW;AACvB,MAAI,CAAE,MAAM,kBAAkB,GAAG,GAAI;AACnC,IAAE,MAAI,MAAM,qDAAqD;AACjE,YAAQ,WAAW;AACnB;AAAA,EACF;AACA,QAAM,IAAM,UAAQ;AACpB,IAAE,MAAM,mBAAmB;AAC3B,QAAM,YAAY,GAAG;AACrB,IAAE;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ composePull,
4
+ composeUp,
5
+ projectDir,
6
+ readProjectConfig
7
+ } from "./chunk-6A35JEOA.js";
8
+
9
+ // src/commands/update.ts
10
+ import * as p from "@clack/prompts";
11
+ async function update() {
12
+ const dir = projectDir();
13
+ if (!await readProjectConfig(dir)) {
14
+ p.log.error("No follower set up. Run `econome join <url>` first.");
15
+ process.exitCode = 1;
16
+ return;
17
+ }
18
+ const s = p.spinner();
19
+ s.start("Pulling newer images");
20
+ await composePull(dir);
21
+ s.message("Restarting");
22
+ await composeUp(dir);
23
+ s.stop("Updated and restarted.");
24
+ }
25
+ export {
26
+ update
27
+ };
28
+ //# sourceMappingURL=update-LLAH5SHH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/update.ts"],"sourcesContent":["import * as p from \"@clack/prompts\";\nimport { composePull, composeUp } from \"../lib/docker.js\";\nimport { projectDir, readProjectConfig } from \"../lib/project.js\";\n\nexport async function update(): Promise<void> {\n const dir = projectDir();\n if (!(await readProjectConfig(dir))) {\n p.log.error(\"No follower set up. Run `econome join <url>` first.\");\n process.exitCode = 1;\n return;\n }\n const s = p.spinner();\n s.start(\"Pulling newer images\");\n await composePull(dir);\n s.message(\"Restarting\");\n await composeUp(dir);\n s.stop(\"Updated and restarted.\");\n}\n"],"mappings":";;;;;;;;;AAAA,YAAY,OAAO;AAInB,eAAsB,SAAwB;AAC5C,QAAM,MAAM,WAAW;AACvB,MAAI,CAAE,MAAM,kBAAkB,GAAG,GAAI;AACnC,IAAE,MAAI,MAAM,qDAAqD;AACjE,YAAQ,WAAW;AACnB;AAAA,EACF;AACA,QAAM,IAAM,UAAQ;AACpB,IAAE,MAAM,sBAAsB;AAC9B,QAAM,YAAY,GAAG;AACrB,IAAE,QAAQ,YAAY;AACtB,QAAM,UAAU,GAAG;AACnB,IAAE,KAAK,wBAAwB;AACjC;","names":[]}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@leconome/cli",
3
+ "version": "0.2.0",
4
+ "description": "One-command CLI to join the Econome IPFS cluster as a follower.",
5
+ "keywords": [
6
+ "ipfs",
7
+ "ipfs-cluster",
8
+ "econome",
9
+ "cli"
10
+ ],
11
+ "license": "MIT",
12
+ "type": "module",
13
+ "bin": {
14
+ "econome": "./dist/index.js"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
22
+ "dependencies": {
23
+ "@clack/prompts": "^0.11.0",
24
+ "commander": "^14.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/node": "^22.15.3",
28
+ "tsup": "^8.3.5",
29
+ "typescript": "5.9.2",
30
+ "vitest": "^3.2.4",
31
+ "@repo/typescript-config": "0.0.0"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "scripts": {
37
+ "build": "tsup",
38
+ "check-types": "tsc --noEmit",
39
+ "test": "vitest run"
40
+ }
41
+ }