catcher-cli 0.1.1 → 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 CHANGED
@@ -1,14 +1,21 @@
1
1
  # catcher-cli
2
2
 
3
- The `catcher` CLI — lets AI agents reach their owner (questions, approvals, SMS/OTP codes, chat) and each other,
4
- end-to-end encrypted. Pair it with the catcher app.
3
+ The `catcher` command for AI agents: an end-to-end encrypted channel to your phone, and, through grants you approve,
4
+ to your other agents.
5
5
 
6
6
  ```sh
7
- npm i -g catcher-cli # Node 22+
8
- catcher register --code <code from the app> --name <agent name>
9
- catcher setup claude-code # or: catcher setup codex
10
- catcher ask "Ship it?" --choices yes,no --wait
11
- catcher help
7
+ npm i -g catcher-cli
8
+ catcher register --name pickle
12
9
  ```
13
10
 
14
- Docs, protocol and threat model: https://github.com/bang9/catcher
11
+ Or tell your agent: "Read https://catcher.bang9.dev/agent.md and install catcher."
12
+
13
+ This package is a small launcher. On first run it downloads the native `catcher` binary for your platform from the
14
+ [GitHub release](https://github.com/bang9/catcher/releases) with the same version, checks its SHA-256 against the
15
+ checksums published inside this package, and caches it in `~/.catcher/bin/<version>/`. It has no dependencies and no
16
+ install scripts. macOS (arm64, x64), Linux (x64, arm64) and Windows (x64) are supported.
17
+
18
+ - Website and docs: https://catcher.bang9.dev
19
+ - Source, protocol and threat model: https://github.com/bang9/catcher
20
+
21
+ Apache-2.0
package/bin/catcher.js ADDED
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+ // catcher-cli: a thin launcher for the native catcher binary (crates/catcher-cli).
3
+ // On first run it downloads the binary for this platform from the GitHub release that matches this package's version,
4
+ // checks its SHA-256 against checksums.json (written into the package at release time), caches it under
5
+ // ~/.catcher/bin/<version>/ and from then on just runs it. No install scripts, no dependencies.
6
+ import { spawn } from 'node:child_process';
7
+ import { createHash } from 'node:crypto';
8
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
9
+ import { homedir } from 'node:os';
10
+ import { join } from 'node:path';
11
+
12
+ const REPO = 'bang9/catcher';
13
+ const MAX_BYTES = 64 * 1024 * 1024;
14
+
15
+ // process.platform-process.arch → release target. Linux builds are static (musl) and run on any distribution.
16
+ const TARGETS = {
17
+ 'darwin-arm64': 'aarch64-apple-darwin',
18
+ 'darwin-x64': 'x86_64-apple-darwin',
19
+ 'linux-arm64': 'aarch64-unknown-linux-musl',
20
+ 'linux-x64': 'x86_64-unknown-linux-musl',
21
+ 'win32-arm64': 'x86_64-pc-windows-msvc',
22
+ 'win32-x64': 'x86_64-pc-windows-msvc',
23
+ };
24
+
25
+ const fail = (message) => {
26
+ process.stderr.write(`catcher-cli: ${message}\n`);
27
+ process.exit(1);
28
+ };
29
+
30
+ const readJson = (name) => JSON.parse(readFileSync(new URL(`../${name}`, import.meta.url), 'utf8'));
31
+
32
+ const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex');
33
+
34
+ function assetName() {
35
+ const target = TARGETS[`${process.platform}-${process.arch}`];
36
+ if (!target) {
37
+ fail(`no catcher build for ${process.platform}/${process.arch}. See https://github.com/${REPO}/releases`);
38
+ }
39
+ if (process.platform === 'win32') return `catcher-${target}.exe`;
40
+ return `catcher-${target}`;
41
+ }
42
+
43
+ async function download(url) {
44
+ const res = await fetch(url, { redirect: 'follow' });
45
+ if (!res.ok) fail(`download failed (${res.status}): ${url}`);
46
+ const bytes = Buffer.from(await res.arrayBuffer());
47
+ if (bytes.length > MAX_BYTES) fail(`download is unexpectedly large: ${url}`);
48
+ return bytes;
49
+ }
50
+
51
+ async function ensureBinary(version, asset) {
52
+ const home = process.env.CATCHER_HOME || join(homedir(), '.catcher');
53
+ const dir = join(home, 'bin', version);
54
+ let file = join(dir, 'catcher');
55
+ if (process.platform === 'win32') file = join(dir, 'catcher.exe');
56
+ if (existsSync(file)) return file;
57
+
58
+ let checksums;
59
+ try {
60
+ checksums = readJson('checksums.json');
61
+ } catch {
62
+ fail('this package has no checksums.json; install a released version: npm i -g catcher-cli');
63
+ }
64
+ const expected = checksums[asset];
65
+ if (!/^[0-9a-f]{64}$/.test(expected || '')) fail(`no checksum for ${asset} in this release`);
66
+
67
+ const url = `https://github.com/${REPO}/releases/download/cli-v${version}/${asset}`;
68
+ process.stderr.write(`catcher-cli: downloading catcher ${version} (${asset}), first run only\n`);
69
+ const bytes = await download(url);
70
+ const actual = sha256(bytes);
71
+ if (actual !== expected)
72
+ fail(`checksum mismatch for ${asset}: expected ${expected}, got ${actual}. Nothing was saved.`);
73
+
74
+ // write to a private temp name, then rename: two first runs at once never see a half-written binary
75
+ mkdirSync(dir, { recursive: true });
76
+ const tmp = `${file}.${process.pid}.tmp`;
77
+ writeFileSync(tmp, bytes, { mode: 0o755 });
78
+ chmodSync(tmp, 0o755);
79
+ try {
80
+ renameSync(tmp, file);
81
+ } catch (e) {
82
+ rmSync(tmp, { force: true });
83
+ if (!existsSync(file)) throw e;
84
+ }
85
+ return file;
86
+ }
87
+
88
+ function run(file) {
89
+ const child = spawn(file, process.argv.slice(2), { stdio: 'inherit' });
90
+ const forward = (signal) => {
91
+ if (child.exitCode === null) child.kill(signal);
92
+ };
93
+ const signals = ['SIGINT', 'SIGTERM', 'SIGHUP'];
94
+ for (const s of signals) process.on(s, forward);
95
+ child.on('error', (e) => fail(`could not run ${file}: ${e.message}`));
96
+ child.on('exit', (code, signal) => {
97
+ if (signal) {
98
+ // die the same way the binary did, so callers see the real signal
99
+ for (const s of signals) process.removeListener(s, forward);
100
+ process.kill(process.pid, signal);
101
+ return;
102
+ }
103
+ process.exit(code ?? 1);
104
+ });
105
+ }
106
+
107
+ const { version } = readJson('package.json');
108
+ let binary = '';
109
+ try {
110
+ binary = await ensureBinary(version, assetName());
111
+ } catch (e) {
112
+ fail(`could not get the catcher binary: ${e.message}`);
113
+ }
114
+ run(binary);
package/checksums.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "catcher-aarch64-apple-darwin": "b7cc470c5e85c86b657a5a7da516e175ef3f0b1679a6ce6e654f48ece565a6eb",
3
+ "catcher-x86_64-apple-darwin": "385abc4a4028862da23d664df4a524fec88b6afb16896052bf1110bfbddf0d58",
4
+ "catcher-aarch64-unknown-linux-musl": "32d25c797de2174e19acb9bd7b2c7d64b99b980b5a71a52f38ac89353e110c27",
5
+ "catcher-x86_64-unknown-linux-musl": "3f73100696ce37bdfb8d7c1ebce332062c44a3e4aa5e59c3e4a949d0f6be8b45",
6
+ "catcher-x86_64-pc-windows-msvc.exe": "ac54116d10a0660a2b679cdc714e3bdc71207540a282a17cce5777683afec95e"
7
+ }
package/package.json CHANGED
@@ -1,32 +1,33 @@
1
1
  {
2
2
  "name": "catcher-cli",
3
- "version": "0.1.1",
4
- "description": "catcher CLI — connect AI agents to their owner and to each other. End-to-end encrypted asks, approvals, OTP hand-off, and agent-to-agent messages.",
3
+ "version": "0.2.0",
4
+ "description": "catcher CLI — connect AI agents to their owner's phone and to each other. End-to-end encrypted asks, approvals, OTP hand-off and agent-to-agent messages.",
5
5
  "keywords": [
6
6
  "agent-to-agent",
7
7
  "ai-agents",
8
8
  "claude-code",
9
9
  "codex",
10
- "e2e-encryption",
11
- "mcp"
10
+ "e2e-encryption"
12
11
  ],
13
- "homepage": "https://github.com/bang9/catcher#readme",
12
+ "homepage": "https://catcher.bang9.dev",
13
+ "bugs": "https://github.com/bang9/catcher/issues",
14
14
  "license": "Apache-2.0",
15
15
  "repository": {
16
16
  "type": "git",
17
17
  "url": "git+https://github.com/bang9/catcher.git",
18
- "directory": "apps/cli"
18
+ "directory": "npm/catcher-cli"
19
19
  },
20
20
  "bin": {
21
- "catcher": "dist/catcher.mjs"
21
+ "catcher": "bin/catcher.js"
22
22
  },
23
23
  "files": [
24
- "dist/catcher.mjs",
24
+ "bin/catcher.js",
25
+ "checksums.json",
25
26
  "README.md",
26
27
  "LICENSE"
27
28
  ],
28
29
  "type": "module",
29
30
  "engines": {
30
- "node": ">=22"
31
+ "node": ">=20"
31
32
  }
32
33
  }