hatchee 0.1.5 → 0.1.6

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 +112 -43
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -3942,16 +3942,24 @@ class SpawnedSession {
3942
3942
  daemon;
3943
3943
  sessionId;
3944
3944
  cwd;
3945
+ model;
3945
3946
  claudeBin;
3946
3947
  proc = null;
3947
- buffer = "";
3948
- constructor(daemon, sessionId, cwd, claudeBin = process.env.DROVER_CLAUDE_BIN || "claude") {
3948
+ mode = "edits";
3949
+ claudeSessionId = null;
3950
+ stopped = false;
3951
+ constructor(daemon, sessionId, cwd, model = null, claudeBin = process.env.DROVER_CLAUDE_BIN || "claude") {
3949
3952
  this.daemon = daemon;
3950
3953
  this.sessionId = sessionId;
3951
3954
  this.cwd = cwd;
3955
+ this.model = model;
3952
3956
  this.claudeBin = claudeBin;
3953
3957
  }
3954
3958
  start(firstTask, mode = "edits") {
3959
+ this.mode = mode;
3960
+ this.spawnProc(firstTask);
3961
+ }
3962
+ spawnProc(firstMessage) {
3955
3963
  const args = [
3956
3964
  "--print",
3957
3965
  "--output-format",
@@ -3960,7 +3968,9 @@ class SpawnedSession {
3960
3968
  "stream-json",
3961
3969
  "--verbose",
3962
3970
  "--permission-mode",
3963
- PERMISSION_MODE[mode] ?? "acceptEdits"
3971
+ PERMISSION_MODE[this.mode] ?? "acceptEdits",
3972
+ ...this.model ? ["--model", this.model] : [],
3973
+ ...this.claudeSessionId ? ["--resume", this.claudeSessionId] : []
3964
3974
  ];
3965
3975
  let proc;
3966
3976
  try {
@@ -3976,22 +3986,28 @@ class SpawnedSession {
3976
3986
  if (s)
3977
3987
  this.daemon.spawnLine(this.sessionId, s, "muted");
3978
3988
  });
3979
- proc.on("exit", (code) => {
3980
- this.daemon.spawnExit(this.sessionId, code ?? 0);
3981
- this.proc = null;
3982
- });
3989
+ proc.on("exit", (code) => this.onProcExit(code ?? 0));
3983
3990
  proc.on("error", (e) => this.daemon.spawnFailed(this.sessionId, String(e)));
3984
- if (firstTask.trim())
3985
- this.send(firstTask);
3991
+ if (firstMessage.trim())
3992
+ this.send(firstMessage);
3986
3993
  }
3987
3994
  send(text) {
3988
- if (!this.proc?.stdin.writable)
3989
- return;
3990
- const msg = { type: "user", message: { role: "user", content: text } };
3991
- this.proc.stdin.write(JSON.stringify(msg) + `
3995
+ if (this.proc?.stdin.writable) {
3996
+ const msg = { type: "user", message: { role: "user", content: text } };
3997
+ this.proc.stdin.write(JSON.stringify(msg) + `
3992
3998
  `);
3999
+ } else {
4000
+ this.spawnProc(text);
4001
+ }
4002
+ }
4003
+ onProcExit(_code) {
4004
+ this.proc = null;
4005
+ if (this.stopped)
4006
+ return;
4007
+ this.daemon.spawnState(this.sessionId, "waiting", "ready — send your next message");
3993
4008
  }
3994
4009
  stop() {
4010
+ this.stopped = true;
3995
4011
  try {
3996
4012
  this.proc?.stdin.end();
3997
4013
  } catch {}
@@ -4009,8 +4025,11 @@ class SpawnedSession {
4009
4025
  }
4010
4026
  switch (m.type) {
4011
4027
  case "system":
4012
- if (m.subtype === "init")
4028
+ if (m.subtype === "init") {
4029
+ if (typeof m.session_id === "string")
4030
+ this.claudeSessionId = m.session_id;
4013
4031
  this.daemon.spawnState(this.sessionId, "working", "session ready");
4032
+ }
4014
4033
  break;
4015
4034
  case "assistant": {
4016
4035
  for (const block of m.message?.content ?? []) {
@@ -7263,6 +7282,9 @@ function lanIPv4() {
7263
7282
 
7264
7283
  // src/server.ts
7265
7284
  import { createServer } from "node:http";
7285
+ import { readdirSync, statSync, existsSync as existsSync2 } from "node:fs";
7286
+ import { homedir as homedir2 } from "node:os";
7287
+ import { join as join2, basename } from "node:path";
7266
7288
 
7267
7289
  // ../../node_modules/ws/wrapper.mjs
7268
7290
  var import_stream = __toESM(require_stream(), 1);
@@ -11343,6 +11365,11 @@ var RulesUpdate = exports_external.object({
11343
11365
  t: exports_external.literal("rules"),
11344
11366
  rules: exports_external.array(Rule)
11345
11367
  });
11368
+ var DirEntry = exports_external.object({ path: exports_external.string(), name: exports_external.string(), git: exports_external.boolean().default(false) });
11369
+ var DirsUpdate = exports_external.object({
11370
+ t: exports_external.literal("dirs"),
11371
+ dirs: exports_external.array(DirEntry)
11372
+ });
11346
11373
  var DaemonToPhone = exports_external.discriminatedUnion("t", [
11347
11374
  ServerHello,
11348
11375
  SessionUpsert,
@@ -11353,6 +11380,7 @@ var DaemonToPhone = exports_external.discriminatedUnion("t", [
11353
11380
  ErrorMsg,
11354
11381
  Pong,
11355
11382
  RulesUpdate,
11383
+ DirsUpdate,
11356
11384
  Encrypted
11357
11385
  ]);
11358
11386
  var DaemonInner = exports_external.discriminatedUnion("t", [
@@ -11363,7 +11391,8 @@ var DaemonInner = exports_external.discriminatedUnion("t", [
11363
11391
  PermissionResolved,
11364
11392
  ErrorMsg,
11365
11393
  Pong,
11366
- RulesUpdate
11394
+ RulesUpdate,
11395
+ DirsUpdate
11367
11396
  ]);
11368
11397
  var ClientHello = exports_external.object({
11369
11398
  t: exports_external.literal("hello"),
@@ -11392,12 +11421,14 @@ var Spawn = exports_external.object({
11392
11421
  t: exports_external.literal("spawn"),
11393
11422
  task: exports_external.string().default(""),
11394
11423
  cwd: exports_external.string().nullable().default(null),
11395
- mode: SpawnMode.default("edits")
11424
+ mode: SpawnMode.default("edits"),
11425
+ model: exports_external.string().nullable().default(null)
11396
11426
  });
11397
11427
  var Stop = exports_external.object({
11398
11428
  t: exports_external.literal("stop"),
11399
11429
  sessionId: exports_external.string()
11400
11430
  });
11431
+ var ListDirs = exports_external.object({ t: exports_external.literal("list_dirs") });
11401
11432
  var Ping = exports_external.object({ t: exports_external.literal("ping") });
11402
11433
  var PushRegister = exports_external.object({
11403
11434
  t: exports_external.literal("push_register"),
@@ -11412,6 +11443,7 @@ var PhoneToDaemon = exports_external.discriminatedUnion("t", [
11412
11443
  UserMessage,
11413
11444
  Spawn,
11414
11445
  Stop,
11446
+ ListDirs,
11415
11447
  Ping,
11416
11448
  PushRegister,
11417
11449
  Encrypted
@@ -11422,6 +11454,7 @@ var PhoneInner = exports_external.discriminatedUnion("t", [
11422
11454
  UserMessage,
11423
11455
  Spawn,
11424
11456
  Stop,
11457
+ ListDirs,
11425
11458
  Ping,
11426
11459
  PushRegister
11427
11460
  ]);
@@ -11579,7 +11612,7 @@ class Daemon {
11579
11612
  relayLeave(conn) {
11580
11613
  this.phones.delete(conn);
11581
11614
  }
11582
- async spawnSession(task, cwd, mode = "edits") {
11615
+ async spawnSession(task, cwd, mode = "edits", model = null) {
11583
11616
  const { SpawnedSession: SpawnedSession2 } = await Promise.resolve().then(() => (init_spawn(), exports_spawn));
11584
11617
  const id = `spawn-${crypto.randomUUID()}`;
11585
11618
  const dir = cwd || this.defaultCwd;
@@ -11597,10 +11630,43 @@ class Daemon {
11597
11630
  this.registry.appendLine(id, { text: `❯ ${task}`, color: "me" });
11598
11631
  this.broadcast({ t: "line", sessionId: id, line: { text: `❯ ${task}`, color: "me" } });
11599
11632
  }
11600
- const session = new SpawnedSession2(this, id, dir);
11633
+ const session = new SpawnedSession2(this, id, dir, model);
11601
11634
  this.spawned.set(id, session);
11602
11635
  session.start(task, mode);
11603
11636
  }
11637
+ listProjectDirs() {
11638
+ const home = homedir2();
11639
+ const roots = [home, ...["Projects", "code", "dev", "src", "repos", "Developer", "work"].map((d) => join2(home, d))];
11640
+ const out = new Map;
11641
+ const add2 = (p) => {
11642
+ if (out.has(p))
11643
+ return;
11644
+ out.set(p, { path: p, name: basename(p) || p, git: existsSync2(join2(p, ".git")) });
11645
+ };
11646
+ add2(this.defaultCwd);
11647
+ for (const root of roots) {
11648
+ let entries = [];
11649
+ try {
11650
+ entries = readdirSync(root);
11651
+ } catch {
11652
+ continue;
11653
+ }
11654
+ for (const e of entries) {
11655
+ if (e.startsWith(".") || out.size > 80)
11656
+ continue;
11657
+ const p = join2(root, e);
11658
+ try {
11659
+ if (!statSync(p).isDirectory())
11660
+ continue;
11661
+ } catch {
11662
+ continue;
11663
+ }
11664
+ if (root !== home || existsSync2(join2(p, ".git")))
11665
+ add2(p);
11666
+ }
11667
+ }
11668
+ return [...out.values()].sort((a, b) => Number(b.git) - Number(a.git) || a.name.localeCompare(b.name)).slice(0, 50);
11669
+ }
11604
11670
  spawnLine(sessionId, text, color) {
11605
11671
  this.registry.appendLine(sessionId, { text, color });
11606
11672
  this.broadcast({ t: "line", sessionId, line: { text, color } });
@@ -11777,9 +11843,12 @@ class Daemon {
11777
11843
  break;
11778
11844
  }
11779
11845
  case "spawn": {
11780
- this.spawnSession(msg.task ?? "", msg.cwd ?? null, msg.mode ?? "edits");
11846
+ this.spawnSession(msg.task ?? "", msg.cwd ?? null, msg.mode ?? "edits", msg.model ?? null);
11781
11847
  break;
11782
11848
  }
11849
+ case "list_dirs":
11850
+ this.sendTo(p, { t: "dirs", dirs: this.listProjectDirs() });
11851
+ break;
11783
11852
  case "stop": {
11784
11853
  const owned = this.spawned.get(msg.sessionId);
11785
11854
  if (owned) {
@@ -12108,16 +12177,16 @@ function b64url2(bytes) {
12108
12177
  }
12109
12178
 
12110
12179
  // src/hooks.ts
12111
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "node:fs";
12112
- import { homedir as homedir2 } from "node:os";
12113
- import { join as join2 } from "node:path";
12114
- var claudeDir = () => process.env.CLAUDE_CONFIG_DIR || join2(homedir2(), ".claude");
12115
- var settingsPath = () => join2(claudeDir(), "settings.json");
12180
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "node:fs";
12181
+ import { homedir as homedir3 } from "node:os";
12182
+ import { join as join3 } from "node:path";
12183
+ var claudeDir = () => process.env.CLAUDE_CONFIG_DIR || join3(homedir3(), ".claude");
12184
+ var settingsPath = () => join3(claudeDir(), "settings.json");
12116
12185
  var EVENTS = ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Notification", "Stop"];
12117
12186
  function installHooks(droverBin) {
12118
12187
  mkdirSync2(claudeDir(), { recursive: true });
12119
12188
  let settings = {};
12120
- if (existsSync2(settingsPath())) {
12189
+ if (existsSync3(settingsPath())) {
12121
12190
  try {
12122
12191
  settings = JSON.parse(readFileSync2(settingsPath(), "utf8"));
12123
12192
  } catch {
@@ -12154,7 +12223,7 @@ function isOurs(x) {
12154
12223
  return cmd.includes("hatchee") || cmd.includes("drover") || cmd.includes("daemon/src/cli.ts");
12155
12224
  }
12156
12225
  function uninstallHooks() {
12157
- if (!existsSync2(settingsPath()))
12226
+ if (!existsSync3(settingsPath()))
12158
12227
  return;
12159
12228
  try {
12160
12229
  const settings = JSON.parse(readFileSync2(settingsPath(), "utf8"));
@@ -12219,27 +12288,27 @@ async function runHook(hookPort) {
12219
12288
  }
12220
12289
 
12221
12290
  // src/service.ts
12222
- import { homedir as homedir3 } from "node:os";
12223
- import { join as join3, dirname } from "node:path";
12224
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, copyFileSync, chmodSync, existsSync as existsSync3, rmSync } from "node:fs";
12291
+ import { homedir as homedir4 } from "node:os";
12292
+ import { join as join4, dirname } from "node:path";
12293
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, copyFileSync, chmodSync, existsSync as existsSync4, rmSync } from "node:fs";
12225
12294
  import { execFileSync } from "node:child_process";
12226
- var HOME = homedir3();
12227
- var HATCHEE_DIR = join3(HOME, ".hatchee");
12228
- var BIN_DIR = join3(HATCHEE_DIR, "bin");
12229
- var STABLE_BIN = join3(BIN_DIR, "hatchee.mjs");
12230
- var LOG = join3(HATCHEE_DIR, "daemon.log");
12295
+ var HOME = homedir4();
12296
+ var HATCHEE_DIR = join4(HOME, ".hatchee");
12297
+ var BIN_DIR = join4(HATCHEE_DIR, "bin");
12298
+ var STABLE_BIN = join4(BIN_DIR, "hatchee.mjs");
12299
+ var LOG = join4(HATCHEE_DIR, "daemon.log");
12231
12300
  var LABEL = "cloud.hatchee.daemon";
12232
- var PLIST = join3(HOME, "Library", "LaunchAgents", `${LABEL}.plist`);
12233
- var UNIT = join3(HOME, ".config", "systemd", "user", "hatchee.service");
12301
+ var PLIST = join4(HOME, "Library", "LaunchAgents", `${LABEL}.plist`);
12302
+ var UNIT = join4(HOME, ".config", "systemd", "user", "hatchee.service");
12234
12303
  function servicePath(node) {
12235
12304
  const system = ["/usr/bin", "/bin", "/usr/sbin", "/sbin"];
12236
12305
  const userTool = [
12237
12306
  dirname(node),
12238
12307
  "/opt/homebrew/bin",
12239
12308
  "/usr/local/bin",
12240
- join3(HOME, ".bun/bin"),
12241
- join3(HOME, ".npm-global/bin"),
12242
- join3(HOME, ".local/bin")
12309
+ join4(HOME, ".bun/bin"),
12310
+ join4(HOME, ".npm-global/bin"),
12311
+ join4(HOME, ".local/bin")
12243
12312
  ];
12244
12313
  const seen = new Set;
12245
12314
  return [...system, ...userTool].filter((p) => p && !seen.has(p) && seen.add(p)).join(":");
@@ -12390,12 +12459,12 @@ function uninstallService() {
12390
12459
  if (process.platform === "darwin") {
12391
12460
  const uid = String(process.getuid?.() ?? "");
12392
12461
  run("launchctl", ["bootout", `gui/${uid}/${LABEL}`]);
12393
- if (existsSync3(PLIST))
12462
+ if (existsSync4(PLIST))
12394
12463
  rmSync(PLIST);
12395
12464
  console.log(` ✓ background service removed (launchd: ${LABEL})`);
12396
12465
  } else if (process.platform === "linux") {
12397
12466
  run("systemctl", ["--user", "disable", "--now", "hatchee.service"]);
12398
- if (existsSync3(UNIT)) {
12467
+ if (existsSync4(UNIT)) {
12399
12468
  rmSync(UNIT);
12400
12469
  run("systemctl", ["--user", "daemon-reload"]);
12401
12470
  }
@@ -12403,12 +12472,12 @@ function uninstallService() {
12403
12472
  } else {
12404
12473
  console.log(` no background service on ${process.platform}.`);
12405
12474
  }
12406
- if (existsSync3(STABLE_BIN))
12475
+ if (existsSync4(STABLE_BIN))
12407
12476
  rmSync(STABLE_BIN);
12408
12477
  }
12409
12478
 
12410
12479
  // src/cli.ts
12411
- var VERSION2 = "0.1.5";
12480
+ var VERSION2 = "0.1.6";
12412
12481
  var cmd = process.argv[2] ?? "help";
12413
12482
  switch (cmd) {
12414
12483
  case "up": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hatchee",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
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" },