hatchee 0.1.5 → 0.1.7

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