farhand 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.
Files changed (3) hide show
  1. package/README.md +11 -0
  2. package/bin/farhand.js +82 -0
  3. package/package.json +13 -0
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # farhand (npm wrapper)
2
+
3
+ `npx farhand` / `npm i -g farhand` runs the prebuilt `farhand` binary for
4
+ your platform. On first run it downloads
5
+ `farhand-<version>-<target>.tar.gz` from the matching GitHub release,
6
+ verifies it against `SHA256SUMS`, and keeps it inside this package. The
7
+ version of this package always equals the binary version it fetches.
8
+
9
+ Everything else — what FarHand is, how to configure it, how it plugs into
10
+ OpenCode, Claude Code and Codex — is in the
11
+ [main README](https://github.com/CogFlux/farhand).
package/bin/farhand.js ADDED
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ // Runs the prebuilt `farhand` binary, fetching it from the GitHub release
3
+ // that matches this package's version on first use. No dependencies.
4
+ "use strict";
5
+ const { spawnSync, execFileSync } = require("node:child_process");
6
+ const fs = require("node:fs");
7
+ const os = require("node:os");
8
+ const path = require("node:path");
9
+ const crypto = require("node:crypto");
10
+ const https = require("node:https");
11
+
12
+ const pkg = require("../package.json");
13
+ const REPO = "CogFlux/farhand";
14
+ const version = pkg.version;
15
+
16
+ function target() {
17
+ const os_t = { darwin: "apple-darwin", linux: "unknown-linux-gnu" }[process.platform];
18
+ const arch_t = { arm64: "aarch64", x64: "x86_64" }[process.arch];
19
+ if (!os_t || !arch_t) {
20
+ console.error(`farhand: unsupported platform ${process.platform}/${process.arch} (macOS and Linux, arm64 and x86_64)`);
21
+ process.exit(1);
22
+ }
23
+ return `${arch_t}-${os_t}`;
24
+ }
25
+
26
+ function fetch(url) {
27
+ return new Promise((resolve, reject) => {
28
+ https
29
+ .get(url, { headers: { "user-agent": `farhand-npm/${version}` } }, (res) => {
30
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
31
+ return fetch(res.headers.location).then(resolve, reject);
32
+ }
33
+ if (res.statusCode !== 200) {
34
+ res.resume();
35
+ return reject(new Error(`${url}: HTTP ${res.statusCode}`));
36
+ }
37
+ const chunks = [];
38
+ res.on("data", (c) => chunks.push(c));
39
+ res.on("end", () => resolve(Buffer.concat(chunks)));
40
+ res.on("error", reject);
41
+ })
42
+ .on("error", reject);
43
+ });
44
+ }
45
+
46
+ async function ensureBinary() {
47
+ const name = `farhand-${version}-${target()}`;
48
+ const vendor = path.join(__dirname, "..", "vendor");
49
+ const bin = path.join(vendor, name, "farhand");
50
+ if (fs.existsSync(bin)) return bin;
51
+
52
+ const base = `https://github.com/${REPO}/releases/download/v${version}`;
53
+ console.error(`farhand: downloading ${name}.tar.gz`);
54
+ const [tarball, sums] = await Promise.all([fetch(`${base}/${name}.tar.gz`), fetch(`${base}/SHA256SUMS`)]);
55
+ const line = sums.toString().split("\n").find((l) => l.endsWith(` ${name}.tar.gz`));
56
+ if (!line) throw new Error(`no checksum for ${name}.tar.gz in SHA256SUMS`);
57
+ const expected = line.split(" ")[0];
58
+ const actual = crypto.createHash("sha256").update(tarball).digest("hex");
59
+ if (expected !== actual) throw new Error("checksum mismatch; refusing to install");
60
+
61
+ fs.mkdirSync(vendor, { recursive: true });
62
+ const tmp = path.join(os.tmpdir(), `${name}-${process.pid}.tar.gz`);
63
+ fs.writeFileSync(tmp, tarball);
64
+ try {
65
+ execFileSync("tar", ["-C", vendor, "-xzf", tmp]);
66
+ } finally {
67
+ fs.rmSync(tmp, { force: true });
68
+ }
69
+ fs.chmodSync(bin, 0o755);
70
+ return bin;
71
+ }
72
+
73
+ ensureBinary()
74
+ .then((bin) => {
75
+ const r = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
76
+ if (r.error) throw r.error;
77
+ process.exit(r.status === null ? 1 : r.status);
78
+ })
79
+ .catch((e) => {
80
+ console.error(`farhand: ${e.message}`);
81
+ process.exit(1);
82
+ });
package/package.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "farhand",
3
+ "version": "0.1.0",
4
+ "description": "Remote hands for coding agents: run OpenCode / Claude Code / Codex work on a remote host over SSH, keep credentials local, audit everything",
5
+ "license": "MIT",
6
+ "repository": { "type": "git", "url": "https://github.com/CogFlux/farhand.git" },
7
+ "homepage": "https://github.com/CogFlux/farhand",
8
+ "bin": { "farhand": "bin/farhand.js" },
9
+ "files": ["bin/farhand.js", "README.md"],
10
+ "os": ["darwin", "linux"],
11
+ "engines": { "node": ">=18" },
12
+ "keywords": ["mcp", "mcp-server", "ssh", "opencode", "claude-code", "codex", "coding-agent", "remote-development"]
13
+ }