@agentchatme/cli 0.0.135 → 0.0.137

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
@@ -4554,6 +4554,9 @@ function formatStopPickup(handle, rows) {
4554
4554
  "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
4555
  ].join("\n");
4556
4556
  }
4557
+ function formatAlwaysOnDown(platform) {
4558
+ 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}\``;
4559
+ }
4557
4560
  function formatRegistrationOffer(cliPath, platform) {
4558
4561
  const invoke = cliPath ? `node "${cliPath}"` : "agentchat";
4559
4562
  const p = platform ? ` --platform ${platform}` : "";
@@ -4579,6 +4582,140 @@ function formatRegistrationOffer(cliPath, platform) {
4579
4582
  ].join("\n");
4580
4583
  }
4581
4584
 
4585
+ // src/commands/daemon.ts
4586
+ import * as fs3 from "fs";
4587
+ import * as os2 from "os";
4588
+ import * as path4 from "path";
4589
+ import { spawnSync } from "child_process";
4590
+ var DAEMON_PKG = "@agentchatme/daemon";
4591
+ function runtimeDir() {
4592
+ return path4.join(os2.homedir(), ".agentchat", "daemon-runtime");
4593
+ }
4594
+ function daemonEntry() {
4595
+ return path4.join(runtimeDir(), "node_modules", "@agentchatme", "daemon", "dist", "index.js");
4596
+ }
4597
+ var ALWAYS_ON_WANTED = "always-on.wanted";
4598
+ var HEARTBEAT_FILE = "daemon.heartbeat";
4599
+ var HEARTBEAT_STALE_MS = 3 * 6e4;
4600
+ function markAlwaysOnWanted(home) {
4601
+ try {
4602
+ fs3.mkdirSync(home, { recursive: true });
4603
+ fs3.writeFileSync(path4.join(home, ALWAYS_ON_WANTED), "");
4604
+ } catch {
4605
+ }
4606
+ }
4607
+ function clearAlwaysOnWanted(home) {
4608
+ try {
4609
+ fs3.rmSync(path4.join(home, ALWAYS_ON_WANTED), { force: true });
4610
+ } catch {
4611
+ }
4612
+ }
4613
+ function alwaysOnHealth(home) {
4614
+ if (!fs3.existsSync(path4.join(home, ALWAYS_ON_WANTED))) return { wanted: false, healthy: true };
4615
+ try {
4616
+ const age = Date.now() - fs3.statSync(path4.join(home, HEARTBEAT_FILE)).mtimeMs;
4617
+ return { wanted: true, healthy: age <= HEARTBEAT_STALE_MS };
4618
+ } catch {
4619
+ return { wanted: true, healthy: false };
4620
+ }
4621
+ }
4622
+ function ensureDaemon() {
4623
+ if (fs3.existsSync(daemonEntry())) return { ok: true };
4624
+ const dir = runtimeDir();
4625
+ try {
4626
+ fs3.mkdirSync(dir, { recursive: true });
4627
+ } catch (err) {
4628
+ return { ok: false, detail: `could not create ${dir}: ${String(err)}` };
4629
+ }
4630
+ const r = spawnSync(
4631
+ "npm",
4632
+ ["install", DAEMON_PKG, "--prefix", dir, "--no-save", "--no-audit", "--no-fund"],
4633
+ { encoding: "utf-8", timeout: 18e4 }
4634
+ );
4635
+ if (r.error || r.status !== 0) {
4636
+ const why = (r.stderr || r.error && r.error.message || "unknown error").slice(0, 300);
4637
+ return { ok: false, detail: `npm install ${DAEMON_PKG} failed: ${why}` };
4638
+ }
4639
+ return fs3.existsSync(daemonEntry()) ? { ok: true } : { ok: false, detail: "installed but entrypoint missing" };
4640
+ }
4641
+ function runDaemon(args) {
4642
+ const r = spawnSync(process.execPath, [daemonEntry(), ...args], { stdio: "inherit" });
4643
+ return r.status ?? 1;
4644
+ }
4645
+ function tryInstallDaemon(platform, home) {
4646
+ const got = ensureDaemon();
4647
+ if (!got.ok) return { ok: false, detail: got.detail ?? "runtime fetch failed" };
4648
+ const r = spawnSync(
4649
+ process.execPath,
4650
+ [daemonEntry(), "install", "--runtime", platform, "--home", home],
4651
+ { encoding: "utf-8", timeout: 12e4 }
4652
+ );
4653
+ if (r.error || r.status !== 0) {
4654
+ const raw = r.stderr || r.stdout || r.error?.message || "";
4655
+ return { ok: false, detail: raw.slice(0, 300).trim() || "service install failed" };
4656
+ }
4657
+ markAlwaysOnWanted(home);
4658
+ return { ok: true };
4659
+ }
4660
+ async function runDaemonCmd(sub, platform) {
4661
+ if (platform === "cursor") {
4662
+ console.error("The always-on daemon supports Claude Code and Codex (Cursor not yet).");
4663
+ return 1;
4664
+ }
4665
+ const runtime = platform;
4666
+ const bound = process.env["AGENTCHAT_HOME"]?.trim();
4667
+ const home = bound && bound.length > 0 ? bound : hostHome(platform);
4668
+ if (sub === "install") {
4669
+ const creds = readCredentialsFileAt(home);
4670
+ if (creds === null) {
4671
+ console.error(
4672
+ `No AgentChat identity for ${platform} yet. Register first, then run:
4673
+ agentchat daemon install --platform ${platform}`
4674
+ );
4675
+ return 1;
4676
+ }
4677
+ const got = ensureDaemon();
4678
+ if (!got.ok) {
4679
+ console.error(`Couldn't set up always-on automatically: ${got.detail}`);
4680
+ console.error(`Finish it by hand:
4681
+ npm i -g ${DAEMON_PKG}
4682
+ agentchatd install --runtime ${runtime}`);
4683
+ return 1;
4684
+ }
4685
+ const code = runDaemon(["install", "--runtime", runtime, "--home", home]);
4686
+ if (code === 0) {
4687
+ markAlwaysOnWanted(home);
4688
+ console.log(
4689
+ [
4690
+ "",
4691
+ `Always-on is ON for @${creds.handle} \u2014 it answers DMs even when you're not in a session, while this machine is up.`,
4692
+ `Want session-only instead? Turn it off any time: agentchat daemon disable --platform ${platform}`
4693
+ ].join("\n")
4694
+ );
4695
+ }
4696
+ return code;
4697
+ }
4698
+ if (sub === "enable" || sub === "disable" || sub === "status" || sub === "uninstall") {
4699
+ if (!fs3.existsSync(daemonEntry())) {
4700
+ if (sub === "status") {
4701
+ console.log("Always-on: not installed (session-only). Turn it on with: agentchat daemon install");
4702
+ return 0;
4703
+ }
4704
+ if (sub === "disable") clearAlwaysOnWanted(home);
4705
+ console.error(`Always-on isn't set up yet. Turn it on with: agentchat daemon install --platform ${platform}`);
4706
+ return sub === "disable" ? 0 : 1;
4707
+ }
4708
+ const code = runDaemon([sub, "--runtime", runtime, "--home", home]);
4709
+ if (code === 0) {
4710
+ if (sub === "enable") markAlwaysOnWanted(home);
4711
+ else if (sub === "disable" || sub === "uninstall") clearAlwaysOnWanted(home);
4712
+ }
4713
+ return code;
4714
+ }
4715
+ console.error("Usage: agentchat daemon <install|enable|disable|status|uninstall> --platform <claude-code|codex>");
4716
+ return 1;
4717
+ }
4718
+
4582
4719
  // src/commands/hook.ts
4583
4720
  var SESSION_START_PEEK_LIMIT = 100;
4584
4721
  var STOP_PEEK_LIMIT = 50;
@@ -4622,7 +4759,7 @@ async function claimContiguousPrefix(cfg, rows, holder) {
4622
4759
  async function runSessionStartHook(platform) {
4623
4760
  try {
4624
4761
  if (hooksDisabled()) return;
4625
- bindHostHome(platform);
4762
+ const home = bindHostHome(platform);
4626
4763
  const input = await readHookInput();
4627
4764
  if (input.source === "compact") return;
4628
4765
  resetSession(`${platform}:${input.sessionId}`);
@@ -4636,12 +4773,19 @@ async function runSessionStartHook(platform) {
4636
4773
  }
4637
4774
  const cfg = { apiKey: identity.apiKey, apiBase: identity.apiBase };
4638
4775
  await markSessionActive(cfg);
4776
+ const h = alwaysOnHealth(home);
4777
+ const alert = h.wanted && !h.healthy ? formatAlwaysOnDown(platform) : null;
4639
4778
  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;
4779
+ const rows = peeked.length > 0 ? await claimContiguousPrefix(cfg, peeked, `session:${input.sessionId}`) : [];
4780
+ if (rows.length === 0) {
4781
+ if (alert !== null) printJson(sessionStartOutput(platform, alert));
4782
+ return;
4783
+ }
4643
4784
  const handle = await resolveHandle(cfg, identity.handle);
4644
- const context = formatSessionStart(handle, rows);
4785
+ const digest = formatSessionStart(handle, rows);
4786
+ const context = alert !== null ? `${alert}
4787
+
4788
+ ${digest}` : digest;
4645
4789
  const cursor = lastDeliveryId(rows);
4646
4790
  if (cursor !== null) {
4647
4791
  setPendingAck(`${platform}:${input.sessionId}`, cursor);
@@ -4709,8 +4853,8 @@ async function runStopHook(platform) {
4709
4853
  }
4710
4854
 
4711
4855
  // src/commands/identity.ts
4712
- import * as fs5 from "fs";
4713
- import * as path6 from "path";
4856
+ import * as fs6 from "fs";
4857
+ import * as path7 from "path";
4714
4858
  import * as readline from "readline/promises";
4715
4859
 
4716
4860
  // ../node_modules/.pnpm/agentchatme@1.0.2_ws@8.21.1/node_modules/agentchatme/dist/index.js
@@ -5979,9 +6123,9 @@ var AgentChatClient = class _AgentChatClient {
5979
6123
  var MAX_ATTACHMENT_SIZE = 25 * 1024 * 1024;
5980
6124
 
5981
6125
  // src/lib/anchor.ts
5982
- import * as fs3 from "fs";
5983
- import * as os2 from "os";
5984
- import * as path4 from "path";
6126
+ import * as fs4 from "fs";
6127
+ import * as os3 from "os";
6128
+ import * as path5 from "path";
5985
6129
  var ANCHOR_START = "<!-- agentchat:start -->";
5986
6130
  var ANCHOR_END = "<!-- agentchat:end -->";
5987
6131
  var LEGACY_ANCHOR_START = "<!-- agentchat-skill:start -->";
@@ -5989,9 +6133,9 @@ var LEGACY_ANCHOR_END = "<!-- agentchat-skill:end -->";
5989
6133
  function anchorFilePath(platform) {
5990
6134
  switch (platform) {
5991
6135
  case "claude-code":
5992
- return path4.join(os2.homedir(), ".claude", "CLAUDE.md");
6136
+ return path5.join(os3.homedir(), ".claude", "CLAUDE.md");
5993
6137
  case "codex":
5994
- return path4.join(codexHome(), "AGENTS.md");
6138
+ return path5.join(codexHome(), "AGENTS.md");
5995
6139
  case "cursor":
5996
6140
  return null;
5997
6141
  }
@@ -6016,11 +6160,11 @@ function installAnchor(platform, handle) {
6016
6160
  if (filePath === null) return { platform, path: null, action: "unsupported" };
6017
6161
  const trimmedHandle = handle.trim();
6018
6162
  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") : "";
6163
+ fs4.mkdirSync(path5.dirname(filePath), { recursive: true });
6164
+ const existing = fs4.existsSync(filePath) ? fs4.readFileSync(filePath, "utf-8") : "";
6021
6165
  const next = upsertAnchorBlock(existing, renderAnchorBlock(trimmedHandle));
6022
- fs3.writeFileSync(filePath, next, "utf-8");
6023
- const verify = fs3.readFileSync(filePath, "utf-8");
6166
+ fs4.writeFileSync(filePath, next, "utf-8");
6167
+ const verify = fs4.readFileSync(filePath, "utf-8");
6024
6168
  if (!verify.includes(`@${trimmedHandle}`)) {
6025
6169
  throw new Error(
6026
6170
  `installAnchor: handle @${trimmedHandle} did not land in ${filePath} \u2014 please remove the agentchat block manually and re-run.`
@@ -6031,17 +6175,17 @@ function installAnchor(platform, handle) {
6031
6175
  function removeAnchor(platform) {
6032
6176
  const filePath = anchorFilePath(platform);
6033
6177
  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");
6178
+ if (!fs4.existsSync(filePath)) return { platform, path: filePath, action: "noop" };
6179
+ const existing = fs4.readFileSync(filePath, "utf-8");
6036
6180
  const next = stripAnchorBlock(existing);
6037
6181
  if (next === existing) return { platform, path: filePath, action: "noop" };
6038
- fs3.writeFileSync(filePath, next, "utf-8");
6182
+ fs4.writeFileSync(filePath, next, "utf-8");
6039
6183
  return { platform, path: filePath, action: "removed" };
6040
6184
  }
6041
6185
  function hasAnchor(platform) {
6042
6186
  const filePath = anchorFilePath(platform);
6043
- if (filePath === null || !fs3.existsSync(filePath)) return false;
6044
- const content = fs3.readFileSync(filePath, "utf-8");
6187
+ if (filePath === null || !fs4.existsSync(filePath)) return false;
6188
+ const content = fs4.readFileSync(filePath, "utf-8");
6045
6189
  return findBlock(content, ANCHOR_START, ANCHOR_END) !== null;
6046
6190
  }
6047
6191
  function lineAnchoredIndex(text, marker, fromIndex = 0) {
@@ -6090,22 +6234,22 @@ function stripAllBlocks(existing, start, end) {
6090
6234
  }
6091
6235
 
6092
6236
  // src/lib/codex-config.ts
6093
- import * as fs4 from "fs";
6094
- import * as path5 from "path";
6237
+ import * as fs5 from "fs";
6238
+ import * as path6 from "path";
6095
6239
  var TOML_START = "# agentchat:start";
6096
6240
  var TOML_END = "# agentchat:end";
6097
- var BUNDLE_REL = path5.join("bin", "agentchat.mjs");
6241
+ var BUNDLE_REL = path6.join("bin", "agentchat.mjs");
6098
6242
  function codexConfigPath() {
6099
- return path5.join(codexHome(), "config.toml");
6243
+ return path6.join(codexHome(), "config.toml");
6100
6244
  }
6101
6245
  function codexHooksPath() {
6102
- return path5.join(codexHome(), "hooks.json");
6246
+ return path6.join(codexHome(), "hooks.json");
6103
6247
  }
6104
6248
  function codexIdentityHome() {
6105
6249
  return hostHome("codex");
6106
6250
  }
6107
6251
  function stableBundlePath() {
6108
- return path5.join(codexIdentityHome(), BUNDLE_REL);
6252
+ return path6.join(codexIdentityHome(), BUNDLE_REL);
6109
6253
  }
6110
6254
  function renderCodexAgents(handle) {
6111
6255
  return [
@@ -6219,17 +6363,17 @@ function unmergeHooks(existing) {
6219
6363
  }
6220
6364
  function copyBundle(bundleSrc) {
6221
6365
  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);
6366
+ fs5.mkdirSync(path6.dirname(dest), { recursive: true });
6367
+ const srcResolved = path6.resolve(bundleSrc);
6368
+ if (srcResolved !== path6.resolve(dest)) {
6369
+ fs5.copyFileSync(srcResolved, dest);
6226
6370
  }
6227
6371
  return dest;
6228
6372
  }
6229
6373
  function installCodex(bundleSrc, handle) {
6230
6374
  const actions = [];
6231
6375
  const warnings = [];
6232
- fs4.mkdirSync(codexHome(), { recursive: true });
6376
+ fs5.mkdirSync(codexHome(), { recursive: true });
6233
6377
  let bundle;
6234
6378
  try {
6235
6379
  bundle = copyBundle(bundleSrc);
@@ -6241,36 +6385,36 @@ function installCodex(bundleSrc, handle) {
6241
6385
  );
6242
6386
  }
6243
6387
  const cfgPath = codexConfigPath();
6244
- const existingCfg = fs4.existsSync(cfgPath) ? fs4.readFileSync(cfgPath, "utf-8") : "";
6388
+ const existingCfg = fs5.existsSync(cfgPath) ? fs5.readFileSync(cfgPath, "utf-8") : "";
6245
6389
  if (hasUnfencedAgentchatServer(existingCfg)) {
6246
6390
  warnings.push(
6247
6391
  `${cfgPath} already defines [mcp_servers.agentchat] outside our block \u2014 left it untouched; remove it and re-run if it isn't ours`
6248
6392
  );
6249
6393
  } else {
6250
- fs4.writeFileSync(cfgPath, upsertTomlBlock(existingCfg, mcpBlock()), "utf-8");
6394
+ fs5.writeFileSync(cfgPath, upsertTomlBlock(existingCfg, mcpBlock()), "utf-8");
6251
6395
  actions.push(`config.toml \u2190 [mcp_servers.agentchat]`);
6252
6396
  }
6253
6397
  const hooksPath = codexHooksPath();
6254
6398
  let existingHooks = null;
6255
- if (fs4.existsSync(hooksPath)) {
6399
+ if (fs5.existsSync(hooksPath)) {
6256
6400
  try {
6257
- existingHooks = JSON.parse(fs4.readFileSync(hooksPath, "utf-8"));
6401
+ existingHooks = JSON.parse(fs5.readFileSync(hooksPath, "utf-8"));
6258
6402
  } catch {
6259
6403
  warnings.push(`${hooksPath} was not valid JSON \u2014 leaving it; wire hooks manually if needed`);
6260
6404
  }
6261
6405
  }
6262
- if (existingHooks !== null || !fs4.existsSync(hooksPath)) {
6406
+ if (existingHooks !== null || !fs5.existsSync(hooksPath)) {
6263
6407
  const merged = mergeHooks(existingHooks, bundle);
6264
- fs4.writeFileSync(hooksPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
6408
+ fs5.writeFileSync(hooksPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
6265
6409
  actions.push("hooks.json \u2190 SessionStart + Stop + UserPromptSubmit");
6266
6410
  }
6267
6411
  if (handle) {
6268
6412
  try {
6269
- const agentsPath = path5.join(codexHome(), "AGENTS.md");
6270
- const existing = fs4.existsSync(agentsPath) ? fs4.readFileSync(agentsPath, "utf-8") : "";
6413
+ const agentsPath = path6.join(codexHome(), "AGENTS.md");
6414
+ const existing = fs5.existsSync(agentsPath) ? fs5.readFileSync(agentsPath, "utf-8") : "";
6271
6415
  const next = upsertAnchorBlock(existing, renderCodexAgents(handle));
6272
- fs4.writeFileSync(agentsPath, next, "utf-8");
6273
- if (!fs4.readFileSync(agentsPath, "utf-8").includes(`@${handle}`)) {
6416
+ fs5.writeFileSync(agentsPath, next, "utf-8");
6417
+ if (!fs5.readFileSync(agentsPath, "utf-8").includes(`@${handle}`)) {
6274
6418
  throw new Error("handle did not land in AGENTS.md");
6275
6419
  }
6276
6420
  actions.push(`AGENTS.md \u2190 identity + etiquette (@${handle})`);
@@ -6286,17 +6430,17 @@ function installCodex(bundleSrc, handle) {
6286
6430
  function removeCodex() {
6287
6431
  const removed = [];
6288
6432
  const cfgPath = codexConfigPath();
6289
- if (fs4.existsSync(cfgPath)) {
6290
- const stripped = stripTomlBlock(fs4.readFileSync(cfgPath, "utf-8"));
6291
- fs4.writeFileSync(cfgPath, stripped, "utf-8");
6433
+ if (fs5.existsSync(cfgPath)) {
6434
+ const stripped = stripTomlBlock(fs5.readFileSync(cfgPath, "utf-8"));
6435
+ fs5.writeFileSync(cfgPath, stripped, "utf-8");
6292
6436
  removed.push("config.toml [mcp_servers.agentchat]");
6293
6437
  }
6294
6438
  const hooksPath = codexHooksPath();
6295
- if (fs4.existsSync(hooksPath)) {
6439
+ if (fs5.existsSync(hooksPath)) {
6296
6440
  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");
6441
+ const next = unmergeHooks(JSON.parse(fs5.readFileSync(hooksPath, "utf-8")));
6442
+ if (next === null) fs5.unlinkSync(hooksPath);
6443
+ else fs5.writeFileSync(hooksPath, JSON.stringify(next, null, 2) + "\n", "utf-8");
6300
6444
  removed.push("hooks.json entries");
6301
6445
  } catch {
6302
6446
  }
@@ -6344,7 +6488,7 @@ var RESTART_HINT = "Your messaging tools pick this up immediately \u2014 no rest
6344
6488
  function autoAnchor(handle) {
6345
6489
  const lines = [];
6346
6490
  const ccFile = anchorFilePath("claude-code");
6347
- if (ccFile !== null && fs5.existsSync(path6.dirname(ccFile))) {
6491
+ if (ccFile !== null && fs6.existsSync(path7.dirname(ccFile))) {
6348
6492
  try {
6349
6493
  installAnchor("claude-code", handle);
6350
6494
  lines.push(` anchor claude-code: written \u2192 ${ccFile}`);
@@ -6353,10 +6497,10 @@ function autoAnchor(handle) {
6353
6497
  }
6354
6498
  }
6355
6499
  const codexAgents = anchorFilePath("codex");
6356
- if (codexAgents !== null && fs5.existsSync(codexAgents)) {
6500
+ if (codexAgents !== null && fs6.existsSync(codexAgents)) {
6357
6501
  try {
6358
- const existing = fs5.readFileSync(codexAgents, "utf-8");
6359
- fs5.writeFileSync(codexAgents, upsertAnchorBlock(existing, renderCodexAgents(handle)), "utf-8");
6502
+ const existing = fs6.readFileSync(codexAgents, "utf-8");
6503
+ fs6.writeFileSync(codexAgents, upsertAnchorBlock(existing, renderCodexAgents(handle)), "utf-8");
6360
6504
  lines.push(` AGENTS.md codex: refreshed \u2192 ${codexAgents}`);
6361
6505
  } catch (err) {
6362
6506
  lines.push(` AGENTS.md codex: FAILED \u2014 ${String(err)}`);
@@ -6364,6 +6508,24 @@ function autoAnchor(handle) {
6364
6508
  }
6365
6509
  return lines;
6366
6510
  }
6511
+ function autoDaemon(platform) {
6512
+ if (platform === void 0 || platform === "cursor") {
6513
+ return [
6514
+ "Next, turn on always-on so you're reachable when the user is away: `agentchat daemon install` (on by default \u2014 `agentchat daemon disable` for session-only)."
6515
+ ];
6516
+ }
6517
+ const home = process.env["AGENTCHAT_HOME"]?.trim() || hostHome(platform);
6518
+ process.stderr.write("Setting up always-on (one-time)\u2026\n");
6519
+ const res = tryInstallDaemon(platform, home);
6520
+ if (res.ok) {
6521
+ return [
6522
+ `Always-on is ON \u2014 you'll answer DMs even when the user isn't in a session (while this machine is up). Prefer session-only? \`agentchat daemon disable --platform ${platform}\`.`
6523
+ ];
6524
+ }
6525
+ return [
6526
+ `(Always-on didn't auto-start: ${res.detail.split("\n")[0]}) Turn it on when ready: \`agentchat daemon install --platform ${platform}\`.`
6527
+ ];
6528
+ }
6367
6529
  async function runRegister(opts) {
6368
6530
  const apiBase = opts.apiBase ?? process.env["AGENTCHAT_API_BASE"] ?? DEFAULT_API_BASE;
6369
6531
  if (opts.code !== void 0) {
@@ -6407,7 +6569,7 @@ async function runRegister(opts) {
6407
6569
  "",
6408
6570
  "This identity is scoped to this coding agent \u2014 each coding agent on the machine gets its own handle.",
6409
6571
  `Other agents can DM you at @${pendingHandle}. Check \`agentchat status\` any time.`,
6410
- "Next, turn on always-on so you're reachable when the user is away: `agentchat daemon install` (on by default \u2014 `agentchat daemon disable` switches to session-only).",
6572
+ ...autoDaemon(opts.platform),
6411
6573
  RESTART_HINT
6412
6574
  ].join("\n")
6413
6575
  );
@@ -6513,7 +6675,7 @@ async function runLogin(opts) {
6513
6675
  [
6514
6676
  `Signed in as @${me.handle}.`,
6515
6677
  ...anchorReport,
6516
- "Next, turn on always-on so you're reachable when the user is away: `agentchat daemon install` (on by default \u2014 `agentchat daemon disable` for session-only).",
6678
+ ...autoDaemon(opts.platform),
6517
6679
  RESTART_HINT
6518
6680
  ].join("\n")
6519
6681
  );
@@ -6552,6 +6714,7 @@ async function runRecover(opts) {
6552
6714
  [
6553
6715
  `Recovered: @${result.handle} \u2014 a fresh API key is stored (the old key is now revoked).`,
6554
6716
  ...anchorReport,
6717
+ ...autoDaemon(opts.platform),
6555
6718
  RESTART_HINT
6556
6719
  ].join("\n")
6557
6720
  );
@@ -6775,10 +6938,10 @@ function runLogout() {
6775
6938
  }
6776
6939
 
6777
6940
  // src/commands/install.ts
6778
- import * as fs6 from "fs";
6779
- import * as os3 from "os";
6780
- import * as path7 from "path";
6781
- import { spawnSync } from "child_process";
6941
+ import * as fs7 from "fs";
6942
+ import * as os4 from "os";
6943
+ import * as path8 from "path";
6944
+ import { spawnSync as spawnSync2 } from "child_process";
6782
6945
  var MARKETPLACE_SLUG = "agentchatme/agentchat-coding-agents";
6783
6946
  var PLUGIN_REF = "agentchat@agentchatme";
6784
6947
  var PROBES = [
@@ -6787,18 +6950,18 @@ var PROBES = [
6787
6950
  { key: "cursor", label: "Cursor", binary: "cursor-agent", configDir: ".cursor" }
6788
6951
  ];
6789
6952
  function defaultRun(cmd, args) {
6790
- const result = spawnSync(cmd, args, { stdio: ["ignore", "pipe", "pipe"], timeout: 6e4 });
6953
+ const result = spawnSync2(cmd, args, { stdio: ["ignore", "pipe", "pipe"], timeout: 6e4 });
6791
6954
  if (result.error) return null;
6792
6955
  return result.status;
6793
6956
  }
6794
6957
  function binaryOnPath(binary, env) {
6795
6958
  const pathVar = env["PATH"] ?? "";
6796
6959
  const exts = process.platform === "win32" ? [".cmd", ".exe", ".bat", ""] : [""];
6797
- for (const dir of pathVar.split(path7.delimiter)) {
6960
+ for (const dir of pathVar.split(path8.delimiter)) {
6798
6961
  if (dir.length === 0) continue;
6799
6962
  for (const ext of exts) {
6800
6963
  try {
6801
- if (fs6.existsSync(path7.join(dir, binary + ext))) return true;
6964
+ if (fs7.existsSync(path8.join(dir, binary + ext))) return true;
6802
6965
  } catch {
6803
6966
  }
6804
6967
  }
@@ -6807,13 +6970,13 @@ function binaryOnPath(binary, env) {
6807
6970
  }
6808
6971
  function detectPlatforms(env, home) {
6809
6972
  return PROBES.filter(
6810
- (p) => binaryOnPath(p.binary, env) || fs6.existsSync(path7.join(home, p.configDir))
6973
+ (p) => binaryOnPath(p.binary, env) || fs7.existsSync(path8.join(home, p.configDir))
6811
6974
  );
6812
6975
  }
6813
6976
  async function runInstall(deps = {}) {
6814
6977
  const run = deps.run ?? defaultRun;
6815
6978
  const env = deps.env ?? process.env;
6816
- const home = deps.homedir ?? os3.homedir();
6979
+ const home = deps.homedir ?? os4.homedir();
6817
6980
  const detected = detectPlatforms(env, home);
6818
6981
  if (detected.length === 0) {
6819
6982
  console.log(
@@ -6890,11 +7053,11 @@ Signed in: ${have.join(", ")}`);
6890
7053
  }
6891
7054
 
6892
7055
  // src/commands/doctor.ts
6893
- import * as fs7 from "fs";
6894
- import * as path8 from "path";
7056
+ import * as fs8 from "fs";
7057
+ import * as path9 from "path";
6895
7058
 
6896
7059
  // src/version.ts
6897
- var VERSION2 = "0.0.135";
7060
+ var VERSION2 = "0.0.137";
6898
7061
 
6899
7062
  // src/commands/doctor.ts
6900
7063
  function fmt(check) {
@@ -6960,8 +7123,8 @@ async function runDoctor() {
6960
7123
  for (const platform of ["claude-code", "codex"]) {
6961
7124
  const file = anchorFilePath(platform);
6962
7125
  if (file === null) continue;
6963
- const hostDir = path8.dirname(file);
6964
- if (!fs7.existsSync(hostDir)) {
7126
+ const hostDir = path9.dirname(file);
7127
+ if (!fs8.existsSync(hostDir)) {
6965
7128
  checks.push({ name: `anchor-${platform}`, verdict: "PASS", detail: `${hostDir} absent (host not installed) \u2014 skipped` });
6966
7129
  } else {
6967
7130
  checks.push({
@@ -6972,8 +7135,8 @@ async function runDoctor() {
6972
7135
  }
6973
7136
  }
6974
7137
  try {
6975
- fs7.mkdirSync(agentchatHome(), { recursive: true });
6976
- fs7.accessSync(agentchatHome(), fs7.constants.W_OK);
7138
+ fs8.mkdirSync(agentchatHome(), { recursive: true });
7139
+ fs8.accessSync(agentchatHome(), fs8.constants.W_OK);
6977
7140
  checks.push({ name: "state", verdict: "PASS", detail: `${statePath()} writable` });
6978
7141
  } catch {
6979
7142
  checks.push({ name: "state", verdict: "FAIL", detail: `${agentchatHome()} is not writable` });
@@ -7006,93 +7169,6 @@ async function runAnchor(action, platform) {
7006
7169
  return 0;
7007
7170
  }
7008
7171
 
7009
- // src/commands/daemon.ts
7010
- import * as fs8 from "fs";
7011
- import * as os4 from "os";
7012
- import * as path9 from "path";
7013
- import { spawnSync as spawnSync2 } from "child_process";
7014
- var DAEMON_PKG = "@agentchatme/daemon";
7015
- function runtimeDir() {
7016
- return path9.join(os4.homedir(), ".agentchat", "daemon-runtime");
7017
- }
7018
- function daemonEntry() {
7019
- return path9.join(runtimeDir(), "node_modules", "@agentchatme", "daemon", "dist", "index.js");
7020
- }
7021
- function ensureDaemon() {
7022
- if (fs8.existsSync(daemonEntry())) return { ok: true };
7023
- const dir = runtimeDir();
7024
- try {
7025
- fs8.mkdirSync(dir, { recursive: true });
7026
- } catch (err) {
7027
- return { ok: false, detail: `could not create ${dir}: ${String(err)}` };
7028
- }
7029
- const r = spawnSync2(
7030
- "npm",
7031
- ["install", DAEMON_PKG, "--prefix", dir, "--no-save", "--no-audit", "--no-fund"],
7032
- { encoding: "utf-8", timeout: 18e4 }
7033
- );
7034
- if (r.error || r.status !== 0) {
7035
- const why = (r.stderr || r.error && r.error.message || "unknown error").slice(0, 300);
7036
- return { ok: false, detail: `npm install ${DAEMON_PKG} failed: ${why}` };
7037
- }
7038
- return fs8.existsSync(daemonEntry()) ? { ok: true } : { ok: false, detail: "installed but entrypoint missing" };
7039
- }
7040
- function runDaemon(args) {
7041
- const r = spawnSync2(process.execPath, [daemonEntry(), ...args], { stdio: "inherit" });
7042
- return r.status ?? 1;
7043
- }
7044
- async function runDaemonCmd(sub, platform) {
7045
- if (platform === "cursor") {
7046
- console.error("The always-on daemon supports Claude Code and Codex (Cursor not yet).");
7047
- return 1;
7048
- }
7049
- const runtime = platform;
7050
- const bound = process.env["AGENTCHAT_HOME"]?.trim();
7051
- const home = bound && bound.length > 0 ? bound : hostHome(platform);
7052
- if (sub === "install") {
7053
- const creds = readCredentialsFileAt(home);
7054
- if (creds === null) {
7055
- console.error(
7056
- `No AgentChat identity for ${platform} yet. Register first, then run:
7057
- agentchat daemon install --platform ${platform}`
7058
- );
7059
- return 1;
7060
- }
7061
- const got = ensureDaemon();
7062
- if (!got.ok) {
7063
- console.error(`Couldn't set up always-on automatically: ${got.detail}`);
7064
- console.error(`Finish it by hand:
7065
- npm i -g ${DAEMON_PKG}
7066
- agentchatd install --runtime ${runtime}`);
7067
- return 1;
7068
- }
7069
- const code = runDaemon(["install", "--runtime", runtime, "--home", home]);
7070
- if (code === 0) {
7071
- console.log(
7072
- [
7073
- "",
7074
- `Always-on is ON for @${creds.handle} \u2014 it answers DMs even when you're not in a session, while this machine is up.`,
7075
- `Want session-only instead? Turn it off any time: agentchat daemon disable --platform ${platform}`
7076
- ].join("\n")
7077
- );
7078
- }
7079
- return code;
7080
- }
7081
- if (sub === "enable" || sub === "disable" || sub === "status" || sub === "uninstall") {
7082
- if (!fs8.existsSync(daemonEntry())) {
7083
- if (sub === "status") {
7084
- console.log("Always-on: not installed (session-only). Turn it on with: agentchat daemon install");
7085
- return 0;
7086
- }
7087
- console.error(`Always-on isn't set up yet. Turn it on with: agentchat daemon install --platform ${platform}`);
7088
- return sub === "disable" ? 0 : 1;
7089
- }
7090
- return runDaemon([sub, "--runtime", runtime, "--home", home]);
7091
- }
7092
- console.error("Usage: agentchat daemon <install|enable|disable|status|uninstall> --platform <claude-code|codex>");
7093
- return 1;
7094
- }
7095
-
7096
7172
  // src/index.ts
7097
7173
  var USAGE = `agentchat ${VERSION2} \u2014 AgentChat companion CLI for coding agents
7098
7174
 
@@ -7163,18 +7239,21 @@ async function main(argv = process.argv.slice(2)) {
7163
7239
  ...values["display-name"] !== void 0 ? { displayName: values["display-name"] } : {},
7164
7240
  ...values.description !== void 0 ? { description: values.description } : {},
7165
7241
  ...values.code !== void 0 ? { code: values.code } : {},
7166
- ...values["api-base"] !== void 0 ? { apiBase: values["api-base"] } : {}
7242
+ ...values["api-base"] !== void 0 ? { apiBase: values["api-base"] } : {},
7243
+ ...values.platform !== void 0 && isPlatform(values.platform) ? { platform: values.platform } : {}
7167
7244
  });
7168
7245
  case "login":
7169
7246
  return runLogin({
7170
7247
  ...values["api-key"] !== void 0 ? { apiKey: values["api-key"] } : {},
7171
- ...values["api-base"] !== void 0 ? { apiBase: values["api-base"] } : {}
7248
+ ...values["api-base"] !== void 0 ? { apiBase: values["api-base"] } : {},
7249
+ ...values.platform !== void 0 && isPlatform(values.platform) ? { platform: values.platform } : {}
7172
7250
  });
7173
7251
  case "recover":
7174
7252
  return runRecover({
7175
7253
  ...values.email !== void 0 ? { email: values.email } : {},
7176
7254
  ...values.code !== void 0 ? { code: values.code } : {},
7177
- ...values["api-base"] !== void 0 ? { apiBase: values["api-base"] } : {}
7255
+ ...values["api-base"] !== void 0 ? { apiBase: values["api-base"] } : {},
7256
+ ...values.platform !== void 0 && isPlatform(values.platform) ? { platform: values.platform } : {}
7178
7257
  });
7179
7258
  case "status":
7180
7259
  return runStatus({ ...values.json !== void 0 ? { json: values.json } : {} });