@agentchatme/cli 0.0.136 → 0.0.138

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/dist/index.js CHANGED
@@ -4401,6 +4401,19 @@ var SyncRowSchema = external_exports.object({
4401
4401
  content: external_exports.record(external_exports.unknown()).optional(),
4402
4402
  created_at: external_exports.string().optional()
4403
4403
  }).passthrough();
4404
+ function contextOf(row) {
4405
+ const raw = row.context;
4406
+ const c = raw && typeof raw === "object" ? raw : {};
4407
+ const sender = c.sender && typeof c.sender === "object" ? c.sender : {};
4408
+ const conv = c.conversation && typeof c.conversation === "object" ? c.conversation : {};
4409
+ return {
4410
+ senderDisplayName: typeof sender.display_name === "string" ? sender.display_name : null,
4411
+ senderKind: sender.kind === "system" ? "system" : "agent",
4412
+ groupName: typeof conv.group_name === "string" ? conv.group_name : null,
4413
+ memberCount: typeof conv.member_count === "number" ? conv.member_count : null,
4414
+ mentions: Array.isArray(c.mentions) ? c.mentions.filter((m) => typeof m === "string").map((m) => m.toLowerCase()) : []
4415
+ };
4416
+ }
4404
4417
  var DEFAULT_TIMEOUT_MS = 4e3;
4405
4418
  async function request(cfg, method, pathname, body) {
4406
4419
  const url = cfg.apiBase.replace(/\/+$/, "") + pathname;
@@ -4491,6 +4504,27 @@ function lastDeliveryId(rows) {
4491
4504
  return null;
4492
4505
  }
4493
4506
 
4507
+ // src/lib/when.ts
4508
+ var SEC = 1e3;
4509
+ var MIN = 60 * SEC;
4510
+ var HOUR = 60 * MIN;
4511
+ var DAY = 24 * HOUR;
4512
+ function relativeAge(ms) {
4513
+ if (ms < 45 * SEC) return "just now";
4514
+ if (ms < 90 * SEC) return "1 minute ago";
4515
+ if (ms < 45 * MIN) return `${Math.round(ms / MIN)} minutes ago`;
4516
+ if (ms < 90 * MIN) return "1 hour ago";
4517
+ if (ms < 22 * HOUR) return `${Math.round(ms / HOUR)} hours ago`;
4518
+ if (ms < 36 * HOUR) return "1 day ago";
4519
+ return `${Math.round(ms / DAY)} days ago`;
4520
+ }
4521
+ function relativeWhen(createdAt, now2 = Date.now()) {
4522
+ if (!createdAt) return "";
4523
+ const t = Date.parse(createdAt);
4524
+ if (Number.isNaN(t)) return "";
4525
+ return relativeAge(Math.max(0, now2 - t));
4526
+ }
4527
+
4494
4528
  // src/lib/summary.ts
4495
4529
  var SNIPPET_MAX = 140;
4496
4530
  function snippetOf(row) {
@@ -4500,22 +4534,31 @@ function snippetOf(row) {
4500
4534
  const oneLine = text.replace(/\s+/g, " ").trim();
4501
4535
  return oneLine.length > SNIPPET_MAX ? `${oneLine.slice(0, SNIPPET_MAX - 1)}\u2026` : oneLine;
4502
4536
  }
4503
- function digestConversations(rows) {
4537
+ function digestConversations(rows, selfHandle = null) {
4538
+ const self = selfHandle?.replace(/^@/, "").toLowerCase() ?? null;
4504
4539
  const byConversation = /* @__PURE__ */ new Map();
4505
4540
  for (const row of rows) {
4506
4541
  const sender = row.sender ?? row.sender_handle ?? "unknown";
4542
+ const ctx = contextOf(row);
4543
+ const mentionsSelf = self !== null && ctx.mentions.includes(self);
4507
4544
  const existing = byConversation.get(row.conversation_id);
4508
4545
  if (existing) {
4509
4546
  existing.count += 1;
4510
4547
  if (!existing.senders.includes(sender)) existing.senders.push(sender);
4511
4548
  existing.latestSnippet = snippetOf(row);
4549
+ existing.latestCreatedAt = row.created_at ?? existing.latestCreatedAt;
4550
+ existing.groupName = ctx.groupName ?? existing.groupName;
4551
+ existing.mentionsYou = existing.mentionsYou || mentionsSelf;
4512
4552
  } else {
4513
4553
  byConversation.set(row.conversation_id, {
4514
4554
  conversationId: row.conversation_id,
4515
4555
  isGroup: row.conversation_id.startsWith("grp_"),
4516
4556
  senders: [sender],
4517
4557
  count: 1,
4518
- latestSnippet: snippetOf(row)
4558
+ latestSnippet: snippetOf(row),
4559
+ latestCreatedAt: row.created_at,
4560
+ groupName: ctx.groupName,
4561
+ mentionsYou: mentionsSelf
4519
4562
  });
4520
4563
  }
4521
4564
  }
@@ -4524,13 +4567,16 @@ function digestConversations(rows) {
4524
4567
  function digestLines(digests) {
4525
4568
  return digests.map((d, i) => {
4526
4569
  const who = d.senders.map((s) => `@${s}`).join(", ");
4527
- const kind = d.isGroup ? `group ${d.conversationId}` : d.conversationId;
4570
+ const kind = d.isGroup ? d.groupName ? `group "${d.groupName}"` : `group ${d.conversationId}` : d.conversationId;
4528
4571
  const count = d.count === 1 ? "1 message" : `${d.count} messages`;
4529
- return `${i + 1}. ${who} (${count}, ${kind}): "${d.latestSnippet}"`;
4572
+ const age = relativeWhen(d.latestCreatedAt);
4573
+ const recency = age ? `, latest ${age}` : "";
4574
+ const mention = d.mentionsYou ? " \u2014 mentions you" : "";
4575
+ return `${i + 1}. ${who} (${count}, ${kind}${recency}${mention}): "${d.latestSnippet}"`;
4530
4576
  });
4531
4577
  }
4532
4578
  function formatSessionStart(handle, rows) {
4533
- const digests = digestConversations(rows);
4579
+ const digests = digestConversations(rows, handle);
4534
4580
  const total = rows.length;
4535
4581
  const identity = handle ? `You are @${handle} on AgentChat. ` : "AgentChat: ";
4536
4582
  const header = identity + `${total} unread message${total === 1 ? "" : "s"} in ${digests.length} conversation${digests.length === 1 ? "" : "s"}:`;
@@ -4543,7 +4589,7 @@ function formatSessionStart(handle, rows) {
4543
4589
  ].join("\n");
4544
4590
  }
4545
4591
  function formatStopPickup(handle, rows) {
4546
- const digests = digestConversations(rows);
4592
+ const digests = digestConversations(rows, handle);
4547
4593
  const total = rows.length;
4548
4594
  const addressee = handle ? ` for @${handle}` : "";
4549
4595
  return [
@@ -4554,6 +4600,9 @@ function formatStopPickup(handle, rows) {
4554
4600
  "Handle these per your AgentChat skill, then finish. Reply via agentchat_send_message only where warranted \u2014 if nothing is actionable, simply end the turn (silence is a valid outcome)."
4555
4601
  ].join("\n");
4556
4602
  }
4603
+ function formatAlwaysOnDown(platform) {
4604
+ return `\u26A0 Always-on is down \u2014 while you are away I won\u2019t be able to answer messages (they queue for your next session, nothing is lost). Turn it back on: \`agentchat daemon install --platform ${platform}\``;
4605
+ }
4557
4606
  function formatRegistrationOffer(cliPath, platform) {
4558
4607
  const invoke = cliPath ? `node "${cliPath}"` : "agentchat";
4559
4608
  const p = platform ? ` --platform ${platform}` : "";
@@ -4579,6 +4628,140 @@ function formatRegistrationOffer(cliPath, platform) {
4579
4628
  ].join("\n");
4580
4629
  }
4581
4630
 
4631
+ // src/commands/daemon.ts
4632
+ import * as fs3 from "fs";
4633
+ import * as os2 from "os";
4634
+ import * as path4 from "path";
4635
+ import { spawnSync } from "child_process";
4636
+ var DAEMON_PKG = "@agentchatme/daemon";
4637
+ function runtimeDir() {
4638
+ return path4.join(os2.homedir(), ".agentchat", "daemon-runtime");
4639
+ }
4640
+ function daemonEntry() {
4641
+ return path4.join(runtimeDir(), "node_modules", "@agentchatme", "daemon", "dist", "index.js");
4642
+ }
4643
+ var ALWAYS_ON_WANTED = "always-on.wanted";
4644
+ var HEARTBEAT_FILE = "daemon.heartbeat";
4645
+ var HEARTBEAT_STALE_MS = 3 * 6e4;
4646
+ function markAlwaysOnWanted(home) {
4647
+ try {
4648
+ fs3.mkdirSync(home, { recursive: true });
4649
+ fs3.writeFileSync(path4.join(home, ALWAYS_ON_WANTED), "");
4650
+ } catch {
4651
+ }
4652
+ }
4653
+ function clearAlwaysOnWanted(home) {
4654
+ try {
4655
+ fs3.rmSync(path4.join(home, ALWAYS_ON_WANTED), { force: true });
4656
+ } catch {
4657
+ }
4658
+ }
4659
+ function alwaysOnHealth(home) {
4660
+ if (!fs3.existsSync(path4.join(home, ALWAYS_ON_WANTED))) return { wanted: false, healthy: true };
4661
+ try {
4662
+ const age = Date.now() - fs3.statSync(path4.join(home, HEARTBEAT_FILE)).mtimeMs;
4663
+ return { wanted: true, healthy: age <= HEARTBEAT_STALE_MS };
4664
+ } catch {
4665
+ return { wanted: true, healthy: false };
4666
+ }
4667
+ }
4668
+ function ensureDaemon() {
4669
+ if (fs3.existsSync(daemonEntry())) return { ok: true };
4670
+ const dir = runtimeDir();
4671
+ try {
4672
+ fs3.mkdirSync(dir, { recursive: true });
4673
+ } catch (err) {
4674
+ return { ok: false, detail: `could not create ${dir}: ${String(err)}` };
4675
+ }
4676
+ const r = spawnSync(
4677
+ "npm",
4678
+ ["install", DAEMON_PKG, "--prefix", dir, "--no-save", "--no-audit", "--no-fund"],
4679
+ { encoding: "utf-8", timeout: 18e4 }
4680
+ );
4681
+ if (r.error || r.status !== 0) {
4682
+ const why = (r.stderr || r.error && r.error.message || "unknown error").slice(0, 300);
4683
+ return { ok: false, detail: `npm install ${DAEMON_PKG} failed: ${why}` };
4684
+ }
4685
+ return fs3.existsSync(daemonEntry()) ? { ok: true } : { ok: false, detail: "installed but entrypoint missing" };
4686
+ }
4687
+ function runDaemon(args) {
4688
+ const r = spawnSync(process.execPath, [daemonEntry(), ...args], { stdio: "inherit" });
4689
+ return r.status ?? 1;
4690
+ }
4691
+ function tryInstallDaemon(platform, home) {
4692
+ const got = ensureDaemon();
4693
+ if (!got.ok) return { ok: false, detail: got.detail ?? "runtime fetch failed" };
4694
+ const r = spawnSync(
4695
+ process.execPath,
4696
+ [daemonEntry(), "install", "--runtime", platform, "--home", home],
4697
+ { encoding: "utf-8", timeout: 12e4 }
4698
+ );
4699
+ if (r.error || r.status !== 0) {
4700
+ const raw = r.stderr || r.stdout || r.error?.message || "";
4701
+ return { ok: false, detail: raw.slice(0, 300).trim() || "service install failed" };
4702
+ }
4703
+ markAlwaysOnWanted(home);
4704
+ return { ok: true };
4705
+ }
4706
+ async function runDaemonCmd(sub, platform) {
4707
+ if (platform === "cursor") {
4708
+ console.error("The always-on daemon supports Claude Code and Codex (Cursor not yet).");
4709
+ return 1;
4710
+ }
4711
+ const runtime = platform;
4712
+ const bound = process.env["AGENTCHAT_HOME"]?.trim();
4713
+ const home = bound && bound.length > 0 ? bound : hostHome(platform);
4714
+ if (sub === "install") {
4715
+ const creds = readCredentialsFileAt(home);
4716
+ if (creds === null) {
4717
+ console.error(
4718
+ `No AgentChat identity for ${platform} yet. Register first, then run:
4719
+ agentchat daemon install --platform ${platform}`
4720
+ );
4721
+ return 1;
4722
+ }
4723
+ const got = ensureDaemon();
4724
+ if (!got.ok) {
4725
+ console.error(`Couldn't set up always-on automatically: ${got.detail}`);
4726
+ console.error(`Finish it by hand:
4727
+ npm i -g ${DAEMON_PKG}
4728
+ agentchatd install --runtime ${runtime}`);
4729
+ return 1;
4730
+ }
4731
+ const code = runDaemon(["install", "--runtime", runtime, "--home", home]);
4732
+ if (code === 0) {
4733
+ markAlwaysOnWanted(home);
4734
+ console.log(
4735
+ [
4736
+ "",
4737
+ `Always-on is ON for @${creds.handle} \u2014 it answers DMs even when you're not in a session, while this machine is up.`,
4738
+ `Want session-only instead? Turn it off any time: agentchat daemon disable --platform ${platform}`
4739
+ ].join("\n")
4740
+ );
4741
+ }
4742
+ return code;
4743
+ }
4744
+ if (sub === "enable" || sub === "disable" || sub === "status" || sub === "uninstall") {
4745
+ if (!fs3.existsSync(daemonEntry())) {
4746
+ if (sub === "status") {
4747
+ console.log("Always-on: not installed (session-only). Turn it on with: agentchat daemon install");
4748
+ return 0;
4749
+ }
4750
+ if (sub === "disable") clearAlwaysOnWanted(home);
4751
+ console.error(`Always-on isn't set up yet. Turn it on with: agentchat daemon install --platform ${platform}`);
4752
+ return sub === "disable" ? 0 : 1;
4753
+ }
4754
+ const code = runDaemon([sub, "--runtime", runtime, "--home", home]);
4755
+ if (code === 0) {
4756
+ if (sub === "enable") markAlwaysOnWanted(home);
4757
+ else if (sub === "disable" || sub === "uninstall") clearAlwaysOnWanted(home);
4758
+ }
4759
+ return code;
4760
+ }
4761
+ console.error("Usage: agentchat daemon <install|enable|disable|status|uninstall> --platform <claude-code|codex>");
4762
+ return 1;
4763
+ }
4764
+
4582
4765
  // src/commands/hook.ts
4583
4766
  var SESSION_START_PEEK_LIMIT = 100;
4584
4767
  var STOP_PEEK_LIMIT = 50;
@@ -4622,7 +4805,7 @@ async function claimContiguousPrefix(cfg, rows, holder) {
4622
4805
  async function runSessionStartHook(platform) {
4623
4806
  try {
4624
4807
  if (hooksDisabled()) return;
4625
- bindHostHome(platform);
4808
+ const home = bindHostHome(platform);
4626
4809
  const input = await readHookInput();
4627
4810
  if (input.source === "compact") return;
4628
4811
  resetSession(`${platform}:${input.sessionId}`);
@@ -4636,12 +4819,19 @@ async function runSessionStartHook(platform) {
4636
4819
  }
4637
4820
  const cfg = { apiKey: identity.apiKey, apiBase: identity.apiBase };
4638
4821
  await markSessionActive(cfg);
4822
+ const h = alwaysOnHealth(home);
4823
+ const alert = h.wanted && !h.healthy ? formatAlwaysOnDown(platform) : null;
4639
4824
  const peeked = ackableRows(await syncPeek(cfg, { limit: SESSION_START_PEEK_LIMIT }));
4640
- if (peeked.length === 0) return;
4641
- const rows = await claimContiguousPrefix(cfg, peeked, `session:${input.sessionId}`);
4642
- if (rows.length === 0) return;
4825
+ const rows = peeked.length > 0 ? await claimContiguousPrefix(cfg, peeked, `session:${input.sessionId}`) : [];
4826
+ if (rows.length === 0) {
4827
+ if (alert !== null) printJson(sessionStartOutput(platform, alert));
4828
+ return;
4829
+ }
4643
4830
  const handle = await resolveHandle(cfg, identity.handle);
4644
- const context = formatSessionStart(handle, rows);
4831
+ const digest = formatSessionStart(handle, rows);
4832
+ const context = alert !== null ? `${alert}
4833
+
4834
+ ${digest}` : digest;
4645
4835
  const cursor = lastDeliveryId(rows);
4646
4836
  if (cursor !== null) {
4647
4837
  setPendingAck(`${platform}:${input.sessionId}`, cursor);
@@ -5979,9 +6169,9 @@ var AgentChatClient = class _AgentChatClient {
5979
6169
  var MAX_ATTACHMENT_SIZE = 25 * 1024 * 1024;
5980
6170
 
5981
6171
  // src/lib/anchor.ts
5982
- import * as fs3 from "fs";
5983
- import * as os2 from "os";
5984
- import * as path4 from "path";
6172
+ import * as fs4 from "fs";
6173
+ import * as os3 from "os";
6174
+ import * as path5 from "path";
5985
6175
  var ANCHOR_START = "<!-- agentchat:start -->";
5986
6176
  var ANCHOR_END = "<!-- agentchat:end -->";
5987
6177
  var LEGACY_ANCHOR_START = "<!-- agentchat-skill:start -->";
@@ -5989,9 +6179,9 @@ var LEGACY_ANCHOR_END = "<!-- agentchat-skill:end -->";
5989
6179
  function anchorFilePath(platform) {
5990
6180
  switch (platform) {
5991
6181
  case "claude-code":
5992
- return path4.join(os2.homedir(), ".claude", "CLAUDE.md");
6182
+ return path5.join(os3.homedir(), ".claude", "CLAUDE.md");
5993
6183
  case "codex":
5994
- return path4.join(codexHome(), "AGENTS.md");
6184
+ return path5.join(codexHome(), "AGENTS.md");
5995
6185
  case "cursor":
5996
6186
  return null;
5997
6187
  }
@@ -6016,11 +6206,11 @@ function installAnchor(platform, handle) {
6016
6206
  if (filePath === null) return { platform, path: null, action: "unsupported" };
6017
6207
  const trimmedHandle = handle.trim();
6018
6208
  if (!trimmedHandle) throw new Error("installAnchor: handle is empty");
6019
- fs3.mkdirSync(path4.dirname(filePath), { recursive: true });
6020
- const existing = fs3.existsSync(filePath) ? fs3.readFileSync(filePath, "utf-8") : "";
6209
+ fs4.mkdirSync(path5.dirname(filePath), { recursive: true });
6210
+ const existing = fs4.existsSync(filePath) ? fs4.readFileSync(filePath, "utf-8") : "";
6021
6211
  const next = upsertAnchorBlock(existing, renderAnchorBlock(trimmedHandle));
6022
- fs3.writeFileSync(filePath, next, "utf-8");
6023
- const verify = fs3.readFileSync(filePath, "utf-8");
6212
+ fs4.writeFileSync(filePath, next, "utf-8");
6213
+ const verify = fs4.readFileSync(filePath, "utf-8");
6024
6214
  if (!verify.includes(`@${trimmedHandle}`)) {
6025
6215
  throw new Error(
6026
6216
  `installAnchor: handle @${trimmedHandle} did not land in ${filePath} \u2014 please remove the agentchat block manually and re-run.`
@@ -6031,17 +6221,17 @@ function installAnchor(platform, handle) {
6031
6221
  function removeAnchor(platform) {
6032
6222
  const filePath = anchorFilePath(platform);
6033
6223
  if (filePath === null) return { platform, path: null, action: "unsupported" };
6034
- if (!fs3.existsSync(filePath)) return { platform, path: filePath, action: "noop" };
6035
- const existing = fs3.readFileSync(filePath, "utf-8");
6224
+ if (!fs4.existsSync(filePath)) return { platform, path: filePath, action: "noop" };
6225
+ const existing = fs4.readFileSync(filePath, "utf-8");
6036
6226
  const next = stripAnchorBlock(existing);
6037
6227
  if (next === existing) return { platform, path: filePath, action: "noop" };
6038
- fs3.writeFileSync(filePath, next, "utf-8");
6228
+ fs4.writeFileSync(filePath, next, "utf-8");
6039
6229
  return { platform, path: filePath, action: "removed" };
6040
6230
  }
6041
6231
  function hasAnchor(platform) {
6042
6232
  const filePath = anchorFilePath(platform);
6043
- if (filePath === null || !fs3.existsSync(filePath)) return false;
6044
- const content = fs3.readFileSync(filePath, "utf-8");
6233
+ if (filePath === null || !fs4.existsSync(filePath)) return false;
6234
+ const content = fs4.readFileSync(filePath, "utf-8");
6045
6235
  return findBlock(content, ANCHOR_START, ANCHOR_END) !== null;
6046
6236
  }
6047
6237
  function lineAnchoredIndex(text, marker, fromIndex = 0) {
@@ -6090,22 +6280,22 @@ function stripAllBlocks(existing, start, end) {
6090
6280
  }
6091
6281
 
6092
6282
  // src/lib/codex-config.ts
6093
- import * as fs4 from "fs";
6094
- import * as path5 from "path";
6283
+ import * as fs5 from "fs";
6284
+ import * as path6 from "path";
6095
6285
  var TOML_START = "# agentchat:start";
6096
6286
  var TOML_END = "# agentchat:end";
6097
- var BUNDLE_REL = path5.join("bin", "agentchat.mjs");
6287
+ var BUNDLE_REL = path6.join("bin", "agentchat.mjs");
6098
6288
  function codexConfigPath() {
6099
- return path5.join(codexHome(), "config.toml");
6289
+ return path6.join(codexHome(), "config.toml");
6100
6290
  }
6101
6291
  function codexHooksPath() {
6102
- return path5.join(codexHome(), "hooks.json");
6292
+ return path6.join(codexHome(), "hooks.json");
6103
6293
  }
6104
6294
  function codexIdentityHome() {
6105
6295
  return hostHome("codex");
6106
6296
  }
6107
6297
  function stableBundlePath() {
6108
- return path5.join(codexIdentityHome(), BUNDLE_REL);
6298
+ return path6.join(codexIdentityHome(), BUNDLE_REL);
6109
6299
  }
6110
6300
  function renderCodexAgents(handle) {
6111
6301
  return [
@@ -6219,17 +6409,17 @@ function unmergeHooks(existing) {
6219
6409
  }
6220
6410
  function copyBundle(bundleSrc) {
6221
6411
  const dest = stableBundlePath();
6222
- fs4.mkdirSync(path5.dirname(dest), { recursive: true });
6223
- const srcResolved = path5.resolve(bundleSrc);
6224
- if (srcResolved !== path5.resolve(dest)) {
6225
- fs4.copyFileSync(srcResolved, dest);
6412
+ fs5.mkdirSync(path6.dirname(dest), { recursive: true });
6413
+ const srcResolved = path6.resolve(bundleSrc);
6414
+ if (srcResolved !== path6.resolve(dest)) {
6415
+ fs5.copyFileSync(srcResolved, dest);
6226
6416
  }
6227
6417
  return dest;
6228
6418
  }
6229
6419
  function installCodex(bundleSrc, handle) {
6230
6420
  const actions = [];
6231
6421
  const warnings = [];
6232
- fs4.mkdirSync(codexHome(), { recursive: true });
6422
+ fs5.mkdirSync(codexHome(), { recursive: true });
6233
6423
  let bundle;
6234
6424
  try {
6235
6425
  bundle = copyBundle(bundleSrc);
@@ -6241,36 +6431,36 @@ function installCodex(bundleSrc, handle) {
6241
6431
  );
6242
6432
  }
6243
6433
  const cfgPath = codexConfigPath();
6244
- const existingCfg = fs4.existsSync(cfgPath) ? fs4.readFileSync(cfgPath, "utf-8") : "";
6434
+ const existingCfg = fs5.existsSync(cfgPath) ? fs5.readFileSync(cfgPath, "utf-8") : "";
6245
6435
  if (hasUnfencedAgentchatServer(existingCfg)) {
6246
6436
  warnings.push(
6247
6437
  `${cfgPath} already defines [mcp_servers.agentchat] outside our block \u2014 left it untouched; remove it and re-run if it isn't ours`
6248
6438
  );
6249
6439
  } else {
6250
- fs4.writeFileSync(cfgPath, upsertTomlBlock(existingCfg, mcpBlock()), "utf-8");
6440
+ fs5.writeFileSync(cfgPath, upsertTomlBlock(existingCfg, mcpBlock()), "utf-8");
6251
6441
  actions.push(`config.toml \u2190 [mcp_servers.agentchat]`);
6252
6442
  }
6253
6443
  const hooksPath = codexHooksPath();
6254
6444
  let existingHooks = null;
6255
- if (fs4.existsSync(hooksPath)) {
6445
+ if (fs5.existsSync(hooksPath)) {
6256
6446
  try {
6257
- existingHooks = JSON.parse(fs4.readFileSync(hooksPath, "utf-8"));
6447
+ existingHooks = JSON.parse(fs5.readFileSync(hooksPath, "utf-8"));
6258
6448
  } catch {
6259
6449
  warnings.push(`${hooksPath} was not valid JSON \u2014 leaving it; wire hooks manually if needed`);
6260
6450
  }
6261
6451
  }
6262
- if (existingHooks !== null || !fs4.existsSync(hooksPath)) {
6452
+ if (existingHooks !== null || !fs5.existsSync(hooksPath)) {
6263
6453
  const merged = mergeHooks(existingHooks, bundle);
6264
- fs4.writeFileSync(hooksPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
6454
+ fs5.writeFileSync(hooksPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
6265
6455
  actions.push("hooks.json \u2190 SessionStart + Stop + UserPromptSubmit");
6266
6456
  }
6267
6457
  if (handle) {
6268
6458
  try {
6269
- const agentsPath = path5.join(codexHome(), "AGENTS.md");
6270
- const existing = fs4.existsSync(agentsPath) ? fs4.readFileSync(agentsPath, "utf-8") : "";
6459
+ const agentsPath = path6.join(codexHome(), "AGENTS.md");
6460
+ const existing = fs5.existsSync(agentsPath) ? fs5.readFileSync(agentsPath, "utf-8") : "";
6271
6461
  const next = upsertAnchorBlock(existing, renderCodexAgents(handle));
6272
- fs4.writeFileSync(agentsPath, next, "utf-8");
6273
- if (!fs4.readFileSync(agentsPath, "utf-8").includes(`@${handle}`)) {
6462
+ fs5.writeFileSync(agentsPath, next, "utf-8");
6463
+ if (!fs5.readFileSync(agentsPath, "utf-8").includes(`@${handle}`)) {
6274
6464
  throw new Error("handle did not land in AGENTS.md");
6275
6465
  }
6276
6466
  actions.push(`AGENTS.md \u2190 identity + etiquette (@${handle})`);
@@ -6286,17 +6476,17 @@ function installCodex(bundleSrc, handle) {
6286
6476
  function removeCodex() {
6287
6477
  const removed = [];
6288
6478
  const cfgPath = codexConfigPath();
6289
- if (fs4.existsSync(cfgPath)) {
6290
- const stripped = stripTomlBlock(fs4.readFileSync(cfgPath, "utf-8"));
6291
- fs4.writeFileSync(cfgPath, stripped, "utf-8");
6479
+ if (fs5.existsSync(cfgPath)) {
6480
+ const stripped = stripTomlBlock(fs5.readFileSync(cfgPath, "utf-8"));
6481
+ fs5.writeFileSync(cfgPath, stripped, "utf-8");
6292
6482
  removed.push("config.toml [mcp_servers.agentchat]");
6293
6483
  }
6294
6484
  const hooksPath = codexHooksPath();
6295
- if (fs4.existsSync(hooksPath)) {
6485
+ if (fs5.existsSync(hooksPath)) {
6296
6486
  try {
6297
- const next = unmergeHooks(JSON.parse(fs4.readFileSync(hooksPath, "utf-8")));
6298
- if (next === null) fs4.unlinkSync(hooksPath);
6299
- else fs4.writeFileSync(hooksPath, JSON.stringify(next, null, 2) + "\n", "utf-8");
6487
+ const next = unmergeHooks(JSON.parse(fs5.readFileSync(hooksPath, "utf-8")));
6488
+ if (next === null) fs5.unlinkSync(hooksPath);
6489
+ else fs5.writeFileSync(hooksPath, JSON.stringify(next, null, 2) + "\n", "utf-8");
6300
6490
  removed.push("hooks.json entries");
6301
6491
  } catch {
6302
6492
  }
@@ -6306,107 +6496,6 @@ function removeCodex() {
6306
6496
  return removed;
6307
6497
  }
6308
6498
 
6309
- // src/commands/daemon.ts
6310
- import * as fs5 from "fs";
6311
- import * as os3 from "os";
6312
- import * as path6 from "path";
6313
- import { spawnSync } from "child_process";
6314
- var DAEMON_PKG = "@agentchatme/daemon";
6315
- function runtimeDir() {
6316
- return path6.join(os3.homedir(), ".agentchat", "daemon-runtime");
6317
- }
6318
- function daemonEntry() {
6319
- return path6.join(runtimeDir(), "node_modules", "@agentchatme", "daemon", "dist", "index.js");
6320
- }
6321
- function ensureDaemon() {
6322
- if (fs5.existsSync(daemonEntry())) return { ok: true };
6323
- const dir = runtimeDir();
6324
- try {
6325
- fs5.mkdirSync(dir, { recursive: true });
6326
- } catch (err) {
6327
- return { ok: false, detail: `could not create ${dir}: ${String(err)}` };
6328
- }
6329
- const r = spawnSync(
6330
- "npm",
6331
- ["install", DAEMON_PKG, "--prefix", dir, "--no-save", "--no-audit", "--no-fund"],
6332
- { encoding: "utf-8", timeout: 18e4 }
6333
- );
6334
- if (r.error || r.status !== 0) {
6335
- const why = (r.stderr || r.error && r.error.message || "unknown error").slice(0, 300);
6336
- return { ok: false, detail: `npm install ${DAEMON_PKG} failed: ${why}` };
6337
- }
6338
- return fs5.existsSync(daemonEntry()) ? { ok: true } : { ok: false, detail: "installed but entrypoint missing" };
6339
- }
6340
- function runDaemon(args) {
6341
- const r = spawnSync(process.execPath, [daemonEntry(), ...args], { stdio: "inherit" });
6342
- return r.status ?? 1;
6343
- }
6344
- function tryInstallDaemon(platform, home) {
6345
- const got = ensureDaemon();
6346
- if (!got.ok) return { ok: false, detail: got.detail ?? "runtime fetch failed" };
6347
- const r = spawnSync(
6348
- process.execPath,
6349
- [daemonEntry(), "install", "--runtime", platform, "--home", home],
6350
- { encoding: "utf-8", timeout: 12e4 }
6351
- );
6352
- if (r.error || r.status !== 0) {
6353
- const raw = r.stderr || r.stdout || r.error?.message || "";
6354
- return { ok: false, detail: raw.slice(0, 300).trim() || "service install failed" };
6355
- }
6356
- return { ok: true };
6357
- }
6358
- async function runDaemonCmd(sub, platform) {
6359
- if (platform === "cursor") {
6360
- console.error("The always-on daemon supports Claude Code and Codex (Cursor not yet).");
6361
- return 1;
6362
- }
6363
- const runtime = platform;
6364
- const bound = process.env["AGENTCHAT_HOME"]?.trim();
6365
- const home = bound && bound.length > 0 ? bound : hostHome(platform);
6366
- if (sub === "install") {
6367
- const creds = readCredentialsFileAt(home);
6368
- if (creds === null) {
6369
- console.error(
6370
- `No AgentChat identity for ${platform} yet. Register first, then run:
6371
- agentchat daemon install --platform ${platform}`
6372
- );
6373
- return 1;
6374
- }
6375
- const got = ensureDaemon();
6376
- if (!got.ok) {
6377
- console.error(`Couldn't set up always-on automatically: ${got.detail}`);
6378
- console.error(`Finish it by hand:
6379
- npm i -g ${DAEMON_PKG}
6380
- agentchatd install --runtime ${runtime}`);
6381
- return 1;
6382
- }
6383
- const code = runDaemon(["install", "--runtime", runtime, "--home", home]);
6384
- if (code === 0) {
6385
- console.log(
6386
- [
6387
- "",
6388
- `Always-on is ON for @${creds.handle} \u2014 it answers DMs even when you're not in a session, while this machine is up.`,
6389
- `Want session-only instead? Turn it off any time: agentchat daemon disable --platform ${platform}`
6390
- ].join("\n")
6391
- );
6392
- }
6393
- return code;
6394
- }
6395
- if (sub === "enable" || sub === "disable" || sub === "status" || sub === "uninstall") {
6396
- if (!fs5.existsSync(daemonEntry())) {
6397
- if (sub === "status") {
6398
- console.log("Always-on: not installed (session-only). Turn it on with: agentchat daemon install");
6399
- return 0;
6400
- }
6401
- console.error(`Always-on isn't set up yet. Turn it on with: agentchat daemon install --platform ${platform}`);
6402
- return sub === "disable" ? 0 : 1;
6403
- }
6404
- return runDaemon([sub, "--runtime", runtime, "--home", home]);
6405
- }
6406
- console.error("Usage: agentchat daemon <install|enable|disable|status|uninstall> --platform <claude-code|codex>");
6407
- return 1;
6408
- }
6409
-
6410
6499
  // src/commands/identity.ts
6411
6500
  var HANDLE_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
6412
6501
  function describeApiError(err) {
@@ -7014,7 +7103,7 @@ import * as fs8 from "fs";
7014
7103
  import * as path9 from "path";
7015
7104
 
7016
7105
  // src/version.ts
7017
- var VERSION2 = "0.0.136";
7106
+ var VERSION2 = "0.0.138";
7018
7107
 
7019
7108
  // src/commands/doctor.ts
7020
7109
  function fmt(check) {