tether-sync 0.0.0-stage → 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 CHANGED
@@ -1,3 +1,21 @@
1
- # Temporary Holding Version
1
+ # tether-sync
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Keep files and folders identical to a central source of truth, wherever they live on your machine.
4
+
5
+ ```sh
6
+ npm install -g tether-sync
7
+ tether login # opens the browser to sign in
8
+ tether pull <share-id> # get a managed copy of a file or folder
9
+ tether status
10
+ ```
11
+
12
+ Owners publish files with `tether manage <path> --repo <repo>` and share the printed share ID. A managed copy:
13
+ - follows the file when you move or rename it
14
+ - reverts local edits until you run `tether edit <path>`
15
+ - pushes your changes with `tether done <path>` (if you are an editor)
16
+
17
+ Run `tether --help` for every command. The web console is at https://tether-b10cf.web.app.
18
+
19
+ Requires Node.js 22.13 or newer. Windows (NTFS/ReFS) is fully supported; Linux support is experimental.
20
+
21
+ Source: https://github.com/SaiEthihasChanda/Tether
@@ -0,0 +1,109 @@
1
+ // packages/client/src/paths.js
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import crypto from "node:crypto";
5
+ var isWindows = process.platform === "win32";
6
+ function tetherHome() {
7
+ if (process.env.TETHER_HOME) return path.resolve(process.env.TETHER_HOME);
8
+ if (isWindows) return path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "Tether");
9
+ return path.join(process.env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state"), "tether");
10
+ }
11
+ function ipcEndpoint(home = tetherHome()) {
12
+ const tag = crypto.createHash("sha1").update(home.toLowerCase()).digest("hex").slice(0, 12);
13
+ if (isWindows) return `\\\\.\\pipe\\tether-${os.userInfo().username}-${tag}`;
14
+ return path.join(home, "tetherd.sock");
15
+ }
16
+ var files = (home = tetherHome()) => ({
17
+ db: path.join(home, "tether.db"),
18
+ blobs: path.join(home, "blobs"),
19
+ config: path.join(home, "config.json"),
20
+ credentials: path.join(home, isWindows ? "credentials.bin" : "credentials.json"),
21
+ log: path.join(home, "tetherd.log"),
22
+ pid: path.join(home, "tetherd.pid")
23
+ });
24
+
25
+ // packages/client/src/ipc.js
26
+ import net from "node:net";
27
+ import fs from "node:fs";
28
+ var IpcError = class extends Error {
29
+ constructor(message, code) {
30
+ super(message);
31
+ this.code = code;
32
+ }
33
+ };
34
+ async function listen(endpoint, handler) {
35
+ if (!isWindows && fs.existsSync(endpoint)) {
36
+ const alive = await call(endpoint, "ping", {}, 1e3).then(() => true, () => false);
37
+ if (alive) throw new IpcError("another tetherd is already running", "EADDRINUSE");
38
+ fs.rmSync(endpoint, { force: true });
39
+ }
40
+ const server = net.createServer((socket) => {
41
+ let buf = "";
42
+ socket.setEncoding("utf8");
43
+ socket.on("data", async (chunk) => {
44
+ buf += chunk;
45
+ const nl = buf.indexOf("\n");
46
+ if (nl < 0) return;
47
+ let req;
48
+ try {
49
+ req = JSON.parse(buf.slice(0, nl));
50
+ } catch {
51
+ socket.end(JSON.stringify({ error: { message: "bad request" } }) + "\n");
52
+ return;
53
+ }
54
+ buf = "";
55
+ let res;
56
+ try {
57
+ res = { result: await handler(req.method, req.params ?? {}) ?? null };
58
+ } catch (e) {
59
+ res = { error: { message: e.message, code: e.code ?? e.name, status: e.status } };
60
+ }
61
+ socket.end(JSON.stringify(res) + "\n");
62
+ });
63
+ socket.on("error", () => {
64
+ });
65
+ });
66
+ await new Promise((resolve, reject) => {
67
+ server.once("error", (e) => reject(e.code === "EADDRINUSE" ? new IpcError("another tetherd is already running", "EADDRINUSE") : e));
68
+ server.listen(endpoint, resolve);
69
+ });
70
+ if (!isWindows) fs.chmodSync(endpoint, 384);
71
+ return server;
72
+ }
73
+ function call(endpoint, method, params = {}, timeoutMs = 10 * 60 * 1e3) {
74
+ return new Promise((resolve, reject) => {
75
+ const socket = net.connect(endpoint);
76
+ let buf = "";
77
+ const timer = setTimeout(() => {
78
+ socket.destroy();
79
+ reject(new IpcError("the daemon did not answer in time", "ETIMEDOUT"));
80
+ }, timeoutMs);
81
+ socket.setEncoding("utf8");
82
+ socket.on("connect", () => socket.write(JSON.stringify({ method, params }) + "\n"));
83
+ socket.on("data", (c) => buf += c);
84
+ socket.on("error", (e) => {
85
+ clearTimeout(timer);
86
+ reject(new IpcError("the Tether daemon is not running", e.code === "ENOENT" || e.code === "ECONNREFUSED" ? "ENODAEMON" : e.code));
87
+ });
88
+ socket.on("end", () => {
89
+ clearTimeout(timer);
90
+ try {
91
+ const res = JSON.parse(buf);
92
+ if (res.error) reject(new IpcError(res.error.message, res.error.code));
93
+ else resolve(res.result);
94
+ } catch {
95
+ reject(new IpcError("invalid response from the daemon"));
96
+ }
97
+ });
98
+ });
99
+ }
100
+
101
+ export {
102
+ isWindows,
103
+ tetherHome,
104
+ ipcEndpoint,
105
+ files,
106
+ IpcError,
107
+ listen,
108
+ call
109
+ };