hatchee 0.1.1 → 0.1.2

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 (2) hide show
  1. package/dist/cli.mjs +187 -14
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -12165,8 +12165,171 @@ async function runHook(hookPort) {
12165
12165
  process.exit(0);
12166
12166
  }
12167
12167
 
12168
+ // src/service.ts
12169
+ import { homedir as homedir3 } from "node:os";
12170
+ import { join as join3, dirname } from "node:path";
12171
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, copyFileSync, chmodSync, existsSync as existsSync3, rmSync } from "node:fs";
12172
+ import { execFileSync } from "node:child_process";
12173
+ var HOME = homedir3();
12174
+ var HATCHEE_DIR = join3(HOME, ".hatchee");
12175
+ var BIN_DIR = join3(HATCHEE_DIR, "bin");
12176
+ var STABLE_BIN = join3(BIN_DIR, "hatchee.mjs");
12177
+ var LOG = join3(HATCHEE_DIR, "daemon.log");
12178
+ var LABEL = "cloud.hatchee.daemon";
12179
+ var PLIST = join3(HOME, "Library", "LaunchAgents", `${LABEL}.plist`);
12180
+ var UNIT = join3(HOME, ".config", "systemd", "user", "hatchee.service");
12181
+ function servicePath(node) {
12182
+ return [
12183
+ dirname(node),
12184
+ join3(HOME, ".bun/bin"),
12185
+ join3(HOME, ".npm-global/bin"),
12186
+ join3(HOME, ".local/bin"),
12187
+ "/opt/homebrew/bin",
12188
+ "/usr/local/bin",
12189
+ "/usr/bin",
12190
+ "/bin",
12191
+ "/usr/sbin",
12192
+ "/sbin"
12193
+ ].join(":");
12194
+ }
12195
+ function copyStableBin() {
12196
+ mkdirSync3(BIN_DIR, { recursive: true });
12197
+ const src = process.argv[1];
12198
+ if (src && src !== STABLE_BIN) {
12199
+ copyFileSync(src, STABLE_BIN);
12200
+ chmodSync(STABLE_BIN, 493);
12201
+ }
12202
+ }
12203
+ function plistContent(node, bin) {
12204
+ const args = [node, bin, "up"].map((s) => ` <string>${s}</string>`).join(`
12205
+ `);
12206
+ return `<?xml version="1.0" encoding="UTF-8"?>
12207
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
12208
+ <plist version="1.0">
12209
+ <dict>
12210
+ <key>Label</key><string>${LABEL}</string>
12211
+ <key>ProgramArguments</key>
12212
+ <array>
12213
+ ${args}
12214
+ </array>
12215
+ <key>RunAtLoad</key><true/>
12216
+ <key>KeepAlive</key><true/>
12217
+ <key>ThrottleInterval</key><integer>10</integer>
12218
+ <key>StandardOutPath</key><string>${LOG}</string>
12219
+ <key>StandardErrorPath</key><string>${LOG}</string>
12220
+ <key>EnvironmentVariables</key>
12221
+ <dict><key>PATH</key><string>${servicePath(node)}</string></dict>
12222
+ </dict>
12223
+ </plist>
12224
+ `;
12225
+ }
12226
+ function unitContent(node, bin) {
12227
+ return `[Unit]
12228
+ Description=Hatchee — watch & approve your coding agents from your phone
12229
+ After=network-online.target
12230
+
12231
+ [Service]
12232
+ ExecStart=${node} ${bin} up
12233
+ Restart=always
12234
+ RestartSec=10
12235
+ Environment=PATH=${servicePath(node)}
12236
+
12237
+ [Install]
12238
+ WantedBy=default.target
12239
+ `;
12240
+ }
12241
+ function run(cmd, args) {
12242
+ try {
12243
+ execFileSync(cmd, args, { stdio: "pipe" });
12244
+ } catch (e) {
12245
+ console.log(` · ${cmd} ${args.join(" ")} → ${String(e?.stderr || e?.message || e).trim().slice(0, 200)}`);
12246
+ }
12247
+ }
12248
+ function installService(print = false) {
12249
+ const node = process.execPath;
12250
+ if (process.platform === "darwin") {
12251
+ const content = plistContent(node, STABLE_BIN);
12252
+ if (print) {
12253
+ console.log(`
12254
+ would write ${PLIST}:
12255
+
12256
+ ${content}
12257
+ bundle → ${STABLE_BIN}
12258
+ load → launchctl bootstrap gui/$(id -u) ${PLIST}
12259
+ `);
12260
+ return;
12261
+ }
12262
+ copyStableBin();
12263
+ mkdirSync3(dirname(PLIST), { recursive: true });
12264
+ writeFileSync3(PLIST, content);
12265
+ const uid = String(process.getuid?.() ?? "");
12266
+ run("launchctl", ["bootout", `gui/${uid}/${LABEL}`]);
12267
+ run("launchctl", ["bootstrap", `gui/${uid}`, PLIST]);
12268
+ run("launchctl", ["enable", `gui/${uid}/${LABEL}`]);
12269
+ console.log(`
12270
+ ✓ Hatchee will now run in the background (launchd: ${LABEL})`);
12271
+ console.log(` logs ${LOG}`);
12272
+ console.log(` pair npx hatchee pair (one terminal, once — to add a phone)`);
12273
+ console.log(` stop npx hatchee uninstall-service`);
12274
+ console.log(`
12275
+ Don't also run \`hatchee up\` manually — the service is the single instance.
12276
+ `);
12277
+ return;
12278
+ }
12279
+ if (process.platform === "linux") {
12280
+ const content = unitContent(node, STABLE_BIN);
12281
+ if (print) {
12282
+ console.log(`
12283
+ would write ${UNIT}:
12284
+
12285
+ ${content}
12286
+ bundle → ${STABLE_BIN}
12287
+ enable → systemctl --user enable --now hatchee.service (+ loginctl enable-linger)
12288
+ `);
12289
+ return;
12290
+ }
12291
+ copyStableBin();
12292
+ mkdirSync3(dirname(UNIT), { recursive: true });
12293
+ writeFileSync3(UNIT, content);
12294
+ run("systemctl", ["--user", "daemon-reload"]);
12295
+ run("systemctl", ["--user", "enable", "--now", "hatchee.service"]);
12296
+ run("loginctl", ["enable-linger", process.env.USER ?? ""]);
12297
+ console.log(`
12298
+ ✓ Hatchee will now run in the background (systemd --user: hatchee.service)`);
12299
+ console.log(` logs journalctl --user -u hatchee.service -f`);
12300
+ console.log(` pair npx hatchee pair`);
12301
+ console.log(` stop npx hatchee uninstall-service
12302
+ `);
12303
+ return;
12304
+ }
12305
+ console.log(`
12306
+ Auto-start service isn't supported on ${process.platform} yet.`);
12307
+ console.log(` Run \`npx hatchee up\` in a terminal you keep open (or use WSL on Windows).
12308
+ `);
12309
+ }
12310
+ function uninstallService() {
12311
+ if (process.platform === "darwin") {
12312
+ const uid = String(process.getuid?.() ?? "");
12313
+ run("launchctl", ["bootout", `gui/${uid}/${LABEL}`]);
12314
+ if (existsSync3(PLIST))
12315
+ rmSync(PLIST);
12316
+ console.log(` ✓ background service removed (launchd: ${LABEL})`);
12317
+ } else if (process.platform === "linux") {
12318
+ run("systemctl", ["--user", "disable", "--now", "hatchee.service"]);
12319
+ if (existsSync3(UNIT)) {
12320
+ rmSync(UNIT);
12321
+ run("systemctl", ["--user", "daemon-reload"]);
12322
+ }
12323
+ console.log(` ✓ background service removed (systemd --user: hatchee.service)`);
12324
+ } else {
12325
+ console.log(` no background service on ${process.platform}.`);
12326
+ }
12327
+ if (existsSync3(STABLE_BIN))
12328
+ rmSync(STABLE_BIN);
12329
+ }
12330
+
12168
12331
  // src/cli.ts
12169
- var VERSION2 = "0.1.1";
12332
+ var VERSION2 = "0.1.2";
12170
12333
  var cmd = process.argv[2] ?? "help";
12171
12334
  switch (cmd) {
12172
12335
  case "up": {
@@ -12259,6 +12422,12 @@ switch (cmd) {
12259
12422
  console.log(`revoked ${dev.name}`);
12260
12423
  break;
12261
12424
  }
12425
+ case "install-service":
12426
+ installService(process.argv[3] === "--print" || process.argv[3] === "--dry-run");
12427
+ break;
12428
+ case "uninstall-service":
12429
+ uninstallService();
12430
+ break;
12262
12431
  case "uninstall-hooks":
12263
12432
  uninstallHooks();
12264
12433
  console.log("hatchee hooks removed from ~/.claude/settings.json");
@@ -12271,16 +12440,19 @@ switch (cmd) {
12271
12440
  default:
12272
12441
  banner();
12273
12442
  console.log(` usage:
12274
- hatchee pair pair a phone \u2014 prints a QR + 6-digit code (do this first)
12275
- hatchee up start watching (no new pairing) \u2014 installs Claude Code hooks
12276
- hatchee devices list paired phones
12277
- hatchee revoke <id> revoke a lost phone
12278
- hatchee uninstall-hooks remove Hatchee's hooks from ~/.claude/settings.json
12443
+ hatchee pair pair a phone \u2014 prints a QR + 6-digit code (do this first)
12444
+ hatchee up start watching (no new pairing) \u2014 installs Claude Code hooks
12445
+ hatchee install-service run in the background across logins/reboots (recommended)
12446
+ hatchee uninstall-service stop running in the background
12447
+ hatchee devices list paired phones
12448
+ hatchee revoke <id> revoke a lost phone
12449
+ hatchee uninstall-hooks remove Hatchee's hooks from ~/.claude/settings.json
12279
12450
  hatchee version
12280
12451
 
12281
- update npx hatchee@latest pair (npx always fetches the newest)
12282
- uninstall npx hatchee uninstall-hooks then rm -rf ~/.hatchee
12283
- config ~/.hatchee/config.json (your daemon identity + paired devices)`);
12452
+ keep it on npx hatchee install-service (so it survives closing the terminal)
12453
+ update npx hatchee@latest pair (npx always fetches the newest)
12454
+ uninstall npx hatchee uninstall-service && npx hatchee uninstall-hooks && rm -rf ~/.hatchee
12455
+ config ~/.hatchee/config.json (your daemon identity + paired devices)`);
12284
12456
  }
12285
12457
  function banner() {
12286
12458
  console.log(`
@@ -12290,11 +12462,12 @@ function banner() {
12290
12462
  function tips() {
12291
12463
  console.log(`
12292
12464
  \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
12293
- stop ^C (your coding agents keep running \u2014 you just stop watching)
12294
- update npx hatchee@latest pair
12295
- uninstall npx hatchee uninstall-hooks (removes our Claude Code hooks)
12296
- rm -rf ~/.hatchee (also wipes pairing + identity)
12297
- help npx hatchee
12465
+ keep it on npx hatchee install-service (survives closing the terminal / reboot)
12466
+ stop ^C (your coding agents keep running \u2014 you just stop watching)
12467
+ update npx hatchee@latest pair
12468
+ uninstall npx hatchee uninstall-service && npx hatchee uninstall-hooks
12469
+ rm -rf ~/.hatchee (also wipes pairing + identity)
12470
+ help npx hatchee
12298
12471
  \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
12299
12472
  }
12300
12473
  function safeListen(daemon) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hatchee",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Your coding agents, alive on your iPhone lock screen — Hatchee daemon. Approve Claude Code / Codex from your phone.",
5
5
  "type": "module",
6
6
  "bin": { "hatchee": "dist/cli.mjs" },