@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 +244 -155
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
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
|
|
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
|
|
5983
|
-
import * as
|
|
5984
|
-
import * as
|
|
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
|
|
6182
|
+
return path5.join(os3.homedir(), ".claude", "CLAUDE.md");
|
|
5993
6183
|
case "codex":
|
|
5994
|
-
return
|
|
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
|
-
|
|
6020
|
-
const existing =
|
|
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
|
-
|
|
6023
|
-
const verify =
|
|
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 (!
|
|
6035
|
-
const existing =
|
|
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
|
-
|
|
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 || !
|
|
6044
|
-
const content =
|
|
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
|
|
6094
|
-
import * as
|
|
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 =
|
|
6287
|
+
var BUNDLE_REL = path6.join("bin", "agentchat.mjs");
|
|
6098
6288
|
function codexConfigPath() {
|
|
6099
|
-
return
|
|
6289
|
+
return path6.join(codexHome(), "config.toml");
|
|
6100
6290
|
}
|
|
6101
6291
|
function codexHooksPath() {
|
|
6102
|
-
return
|
|
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
|
|
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
|
-
|
|
6223
|
-
const srcResolved =
|
|
6224
|
-
if (srcResolved !==
|
|
6225
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 (
|
|
6445
|
+
if (fs5.existsSync(hooksPath)) {
|
|
6256
6446
|
try {
|
|
6257
|
-
existingHooks = JSON.parse(
|
|
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 || !
|
|
6452
|
+
if (existingHooks !== null || !fs5.existsSync(hooksPath)) {
|
|
6263
6453
|
const merged = mergeHooks(existingHooks, bundle);
|
|
6264
|
-
|
|
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 =
|
|
6270
|
-
const existing =
|
|
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
|
-
|
|
6273
|
-
if (!
|
|
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 (
|
|
6290
|
-
const stripped = stripTomlBlock(
|
|
6291
|
-
|
|
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 (
|
|
6485
|
+
if (fs5.existsSync(hooksPath)) {
|
|
6296
6486
|
try {
|
|
6297
|
-
const next = unmergeHooks(JSON.parse(
|
|
6298
|
-
if (next === null)
|
|
6299
|
-
else
|
|
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.
|
|
7106
|
+
var VERSION2 = "0.0.138";
|
|
7018
7107
|
|
|
7019
7108
|
// src/commands/doctor.ts
|
|
7020
7109
|
function fmt(check) {
|