@agentchatme/cli 0.0.13 → 0.0.132

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
@@ -37,6 +37,49 @@ function stopOutput(platform, reason) {
37
37
  }
38
38
  }
39
39
 
40
+ // src/lib/paths.ts
41
+ import * as os from "os";
42
+ import * as path from "path";
43
+ function agentchatHome() {
44
+ const override = process.env["AGENTCHAT_HOME"];
45
+ if (override && override.trim().length > 0) return path.resolve(override);
46
+ return path.join(os.homedir(), ".agentchat");
47
+ }
48
+ function legacyMachineHome() {
49
+ return path.join(os.homedir(), ".agentchat");
50
+ }
51
+ function codexHome() {
52
+ const override = process.env["CODEX_HOME"];
53
+ if (override && override.trim().length > 0) return path.resolve(override);
54
+ return path.join(os.homedir(), ".codex");
55
+ }
56
+ function hostHome(platform) {
57
+ switch (platform) {
58
+ case "claude-code":
59
+ return path.join(os.homedir(), ".claude", "agentchat");
60
+ case "codex":
61
+ return path.join(codexHome(), "agentchat");
62
+ case "cursor":
63
+ return path.join(os.homedir(), ".cursor", "agentchat");
64
+ }
65
+ }
66
+ function bindHostHome(platform) {
67
+ const existing = process.env["AGENTCHAT_HOME"];
68
+ if (existing && existing.trim().length > 0) return path.resolve(existing);
69
+ const home = hostHome(platform);
70
+ process.env["AGENTCHAT_HOME"] = home;
71
+ return home;
72
+ }
73
+ function credentialsPath() {
74
+ return path.join(agentchatHome(), "credentials");
75
+ }
76
+ function pendingPath() {
77
+ return path.join(agentchatHome(), "pending.json");
78
+ }
79
+ function statePath() {
80
+ return path.join(agentchatHome(), "state.json");
81
+ }
82
+
40
83
  // src/lib/log.ts
41
84
  var LEVELS = ["silent", "error", "warn", "info", "debug"];
42
85
  function activeLevel() {
@@ -59,6 +102,7 @@ var log = {
59
102
 
60
103
  // src/lib/credentials.ts
61
104
  import * as fs2 from "fs";
105
+ import * as path3 from "path";
62
106
 
63
107
  // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
64
108
  var external_exports = {};
@@ -538,8 +582,8 @@ function getErrorMap() {
538
582
 
539
583
  // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
540
584
  var makeIssue = (params) => {
541
- const { data, path: path7, errorMaps, issueData } = params;
542
- const fullPath = [...path7, ...issueData.path || []];
585
+ const { data, path: path9, errorMaps, issueData } = params;
586
+ const fullPath = [...path9, ...issueData.path || []];
543
587
  const fullIssue = {
544
588
  ...issueData,
545
589
  path: fullPath
@@ -655,11 +699,11 @@ var errorUtil;
655
699
 
656
700
  // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
657
701
  var ParseInputLazyPath = class {
658
- constructor(parent, value, path7, key) {
702
+ constructor(parent, value, path9, key) {
659
703
  this._cachedPath = [];
660
704
  this.parent = parent;
661
705
  this.data = value;
662
- this._path = path7;
706
+ this._path = path9;
663
707
  this._key = key;
664
708
  }
665
709
  get path() {
@@ -4103,11 +4147,11 @@ var NEVER = INVALID;
4103
4147
 
4104
4148
  // src/lib/fsutil.ts
4105
4149
  import * as fs from "fs";
4106
- import * as path from "path";
4150
+ import * as path2 from "path";
4107
4151
  function atomicWriteFile(filePath, data, mode) {
4108
- const dir = path.dirname(filePath);
4152
+ const dir = path2.dirname(filePath);
4109
4153
  fs.mkdirSync(dir, { recursive: true, mode: 448 });
4110
- const tmp = path.join(dir, `.${path.basename(filePath)}.${process.pid}.tmp`);
4154
+ const tmp = path2.join(dir, `.${path2.basename(filePath)}.${process.pid}.tmp`);
4111
4155
  fs.writeFileSync(tmp, data, mode === void 0 ? {} : { mode });
4112
4156
  fs.renameSync(tmp, filePath);
4113
4157
  if (mode !== void 0) {
@@ -4123,24 +4167,6 @@ function readJsonFile(filePath) {
4123
4167
  }
4124
4168
  }
4125
4169
 
4126
- // src/lib/paths.ts
4127
- import * as os from "os";
4128
- import * as path2 from "path";
4129
- function agentchatHome() {
4130
- const override = process.env["AGENTCHAT_HOME"];
4131
- if (override && override.trim().length > 0) return path2.resolve(override);
4132
- return path2.join(os.homedir(), ".agentchat");
4133
- }
4134
- function credentialsPath() {
4135
- return path2.join(agentchatHome(), "credentials");
4136
- }
4137
- function pendingPath() {
4138
- return path2.join(agentchatHome(), "pending.json");
4139
- }
4140
- function statePath() {
4141
- return path2.join(agentchatHome(), "state.json");
4142
- }
4143
-
4144
4170
  // src/lib/credentials.ts
4145
4171
  var DEFAULT_API_BASE = "https://api.agentchat.me";
4146
4172
  var CredentialsSchema = external_exports.object({
@@ -4155,6 +4181,12 @@ function readCredentialsFile() {
4155
4181
  const parsed = CredentialsSchema.safeParse(raw);
4156
4182
  return parsed.success ? parsed.data : null;
4157
4183
  }
4184
+ function readCredentialsFileAt(home) {
4185
+ const raw = readJsonFile(path3.join(home, "credentials"));
4186
+ if (raw === null) return null;
4187
+ const parsed = CredentialsSchema.safeParse(raw);
4188
+ return parsed.success ? parsed.data : null;
4189
+ }
4158
4190
  function resolveIdentity() {
4159
4191
  const envKey = process.env["AGENTCHAT_API_KEY"];
4160
4192
  const envBase = process.env["AGENTCHAT_API_BASE"];
@@ -4505,15 +4537,18 @@ function formatStopPickup(handle, rows) {
4505
4537
  "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)."
4506
4538
  ].join("\n");
4507
4539
  }
4508
- function formatRegistrationOffer(cliPath) {
4540
+ function formatRegistrationOffer(cliPath, platform) {
4509
4541
  const invoke = cliPath ? `node "${cliPath}"` : "agentchat";
4542
+ const p = platform ? ` --platform ${platform}` : "";
4543
+ const hostNote = platform ? `This ${platform} agent gets its OWN handle (separate from any other coding agent on this machine \u2014 that's what lets them DM each other). Use an email not already tied to another agent.` : "";
4510
4544
  return [
4511
- "The AgentChat plugin is installed but this machine has no AgentChat identity yet.",
4545
+ "The AgentChat plugin is installed but this agent has no AgentChat identity yet.",
4512
4546
  "",
4513
4547
  "AgentChat gives you (the agent) a handle other agents can DM, like a phone number. If the user would like that, offer to set it up conversationally:",
4548
+ ...hostNote ? [hostNote] : [],
4514
4549
  "1. Ask for the email + desired handle (3\u201330 chars, lowercase letters/digits/hyphens, must start with a letter).",
4515
- `2. Run: ${invoke} register --email <email> --handle <handle>`,
4516
- `3. A 6-digit code lands in their email; ask for it, then run: ${invoke} register --code <code>`,
4550
+ `2. Run: ${invoke} register${p} --email <email> --handle <handle>`,
4551
+ `3. A 6-digit code lands in their email; ask for it, then run: ${invoke} register${p} --code <code>`,
4517
4552
  "",
4518
4553
  "Do not push \u2014 one short offer is plenty. If declined, drop the topic for the rest of the session."
4519
4554
  ].join("\n");
@@ -4550,6 +4585,7 @@ function ackableRows(rows) {
4550
4585
  async function runSessionStartHook(platform) {
4551
4586
  try {
4552
4587
  if (hooksDisabled()) return;
4588
+ bindHostHome(platform);
4553
4589
  const input = await readHookInput();
4554
4590
  if (input.source === "compact") return;
4555
4591
  resetSession(`${platform}:${input.sessionId}`);
@@ -4557,7 +4593,7 @@ async function runSessionStartHook(platform) {
4557
4593
  if (identity === null) {
4558
4594
  if (shouldOfferRegistration()) {
4559
4595
  recordRegistrationOffer();
4560
- printJson(sessionStartOutput(platform, formatRegistrationOffer(process.argv[1])));
4596
+ printJson(sessionStartOutput(platform, formatRegistrationOffer(process.argv[1], platform)));
4561
4597
  }
4562
4598
  return;
4563
4599
  }
@@ -4578,6 +4614,7 @@ async function runSessionStartHook(platform) {
4578
4614
  async function runUserPromptHook(platform) {
4579
4615
  try {
4580
4616
  if (hooksDisabled()) return;
4617
+ bindHostHome(platform);
4581
4618
  const input = await readHookInput();
4582
4619
  const identity = resolveIdentity();
4583
4620
  if (identity === null) return;
@@ -4598,6 +4635,7 @@ async function runUserPromptHook(platform) {
4598
4635
  async function runStopHook(platform) {
4599
4636
  try {
4600
4637
  if (hooksDisabled()) return;
4638
+ bindHostHome(platform);
4601
4639
  const input = await readHookInput();
4602
4640
  const identity = resolveIdentity();
4603
4641
  if (identity === null) return;
@@ -4628,8 +4666,8 @@ async function runStopHook(platform) {
4628
4666
  }
4629
4667
 
4630
4668
  // src/commands/identity.ts
4631
- import * as fs4 from "fs";
4632
- import * as path4 from "path";
4669
+ import * as fs5 from "fs";
4670
+ import * as path6 from "path";
4633
4671
  import * as readline from "readline/promises";
4634
4672
 
4635
4673
  // ../node_modules/.pnpm/agentchatme@1.0.2/node_modules/agentchatme/dist/index.js
@@ -4892,8 +4930,8 @@ var HttpTransport = class {
4892
4930
  }
4893
4931
  this.fetchFn = f.bind(globalThis);
4894
4932
  }
4895
- async request(method, path7, opts = {}) {
4896
- const url = `${this.baseUrl}${path7}`;
4933
+ async request(method, path9, opts = {}) {
4934
+ const url = `${this.baseUrl}${path9}`;
4897
4935
  const policy = resolveRetryPolicy(opts.retry, this.retry);
4898
4936
  const canRetry = isRetryEligible(method, opts.idempotencyKey, opts.retry);
4899
4937
  const maxAttempts = canRetry ? policy.maxRetries + 1 : 1;
@@ -5126,10 +5164,10 @@ function statusToCode(status) {
5126
5164
  }
5127
5165
  async function sleep(ms, signal) {
5128
5166
  if (ms <= 0) return;
5129
- await new Promise((resolve2, reject) => {
5167
+ await new Promise((resolve3, reject) => {
5130
5168
  const timer = setTimeout(() => {
5131
5169
  if (signal) signal.removeEventListener("abort", onAbort);
5132
- resolve2();
5170
+ resolve3();
5133
5171
  }, ms);
5134
5172
  const onAbort = () => {
5135
5173
  clearTimeout(timer);
@@ -5212,31 +5250,31 @@ var AgentChatClient = class _AgentChatClient {
5212
5250
  this.onBacklogWarning = options.onBacklogWarning;
5213
5251
  }
5214
5252
  // ─── Internal request helpers ─────────────────────────────────────────────
5215
- async get(path7, opts) {
5216
- const res = await this.http.request("GET", path7, this.toRequestOpts(opts));
5253
+ async get(path9, opts) {
5254
+ const res = await this.http.request("GET", path9, this.toRequestOpts(opts));
5217
5255
  return res.data;
5218
5256
  }
5219
- async del(path7, opts) {
5220
- const res = await this.http.request("DELETE", path7, this.toRequestOpts(opts));
5257
+ async del(path9, opts) {
5258
+ const res = await this.http.request("DELETE", path9, this.toRequestOpts(opts));
5221
5259
  return res.data;
5222
5260
  }
5223
- async post(path7, body, opts) {
5224
- const res = await this.http.request("POST", path7, {
5261
+ async post(path9, body, opts) {
5262
+ const res = await this.http.request("POST", path9, {
5225
5263
  ...this.toRequestOpts(opts),
5226
5264
  body
5227
5265
  });
5228
5266
  return res.data;
5229
5267
  }
5230
- async patch(path7, body, opts) {
5231
- const res = await this.http.request("PATCH", path7, {
5268
+ async patch(path9, body, opts) {
5269
+ const res = await this.http.request("PATCH", path9, {
5232
5270
  ...this.toRequestOpts(opts),
5233
5271
  body
5234
5272
  });
5235
5273
  return res.data;
5236
5274
  }
5237
- async put(path7, body, opts) {
5275
+ async put(path9, body, opts) {
5238
5276
  const headers = opts?.contentType ? { "Content-Type": opts.contentType } : void 0;
5239
- const res = await this.http.request("PUT", path7, {
5277
+ const res = await this.http.request("PUT", path9, {
5240
5278
  ...this.toRequestOpts(opts),
5241
5279
  body,
5242
5280
  rawBody: opts?.rawBody,
@@ -5900,7 +5938,7 @@ var MAX_ATTACHMENT_SIZE = 25 * 1024 * 1024;
5900
5938
  // src/lib/anchor.ts
5901
5939
  import * as fs3 from "fs";
5902
5940
  import * as os2 from "os";
5903
- import * as path3 from "path";
5941
+ import * as path4 from "path";
5904
5942
  var ANCHOR_START = "<!-- agentchat:start -->";
5905
5943
  var ANCHOR_END = "<!-- agentchat:end -->";
5906
5944
  var LEGACY_ANCHOR_START = "<!-- agentchat-skill:start -->";
@@ -5908,9 +5946,9 @@ var LEGACY_ANCHOR_END = "<!-- agentchat-skill:end -->";
5908
5946
  function anchorFilePath(platform) {
5909
5947
  switch (platform) {
5910
5948
  case "claude-code":
5911
- return path3.join(os2.homedir(), ".claude", "CLAUDE.md");
5949
+ return path4.join(os2.homedir(), ".claude", "CLAUDE.md");
5912
5950
  case "codex":
5913
- return path3.join(os2.homedir(), ".codex", "AGENTS.md");
5951
+ return path4.join(codexHome(), "AGENTS.md");
5914
5952
  case "cursor":
5915
5953
  return null;
5916
5954
  }
@@ -5935,7 +5973,7 @@ function installAnchor(platform, handle) {
5935
5973
  if (filePath === null) return { platform, path: null, action: "unsupported" };
5936
5974
  const trimmedHandle = handle.trim();
5937
5975
  if (!trimmedHandle) throw new Error("installAnchor: handle is empty");
5938
- fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
5976
+ fs3.mkdirSync(path4.dirname(filePath), { recursive: true });
5939
5977
  const existing = fs3.existsSync(filePath) ? fs3.readFileSync(filePath, "utf-8") : "";
5940
5978
  const next = upsertAnchorBlock(existing, renderAnchorBlock(trimmedHandle));
5941
5979
  fs3.writeFileSync(filePath, next, "utf-8");
@@ -6008,6 +6046,223 @@ function stripAllBlocks(existing, start, end) {
6008
6046
  return text;
6009
6047
  }
6010
6048
 
6049
+ // src/lib/codex-config.ts
6050
+ import * as fs4 from "fs";
6051
+ import * as path5 from "path";
6052
+ var TOML_START = "# agentchat:start";
6053
+ var TOML_END = "# agentchat:end";
6054
+ var BUNDLE_REL = path5.join("bin", "agentchat.mjs");
6055
+ function codexConfigPath() {
6056
+ return path5.join(codexHome(), "config.toml");
6057
+ }
6058
+ function codexHooksPath() {
6059
+ return path5.join(codexHome(), "hooks.json");
6060
+ }
6061
+ function codexIdentityHome() {
6062
+ return hostHome("codex");
6063
+ }
6064
+ function stableBundlePath() {
6065
+ return path5.join(codexIdentityHome(), BUNDLE_REL);
6066
+ }
6067
+ function renderCodexAgents(handle) {
6068
+ return [
6069
+ ANCHOR_START,
6070
+ "## On AgentChat",
6071
+ "",
6072
+ `You are **@${handle}** on AgentChat \u2014 a peer-to-peer messaging network for AI agents. Your handle is your address here, like a phone number, except the other end is always another agent. Share \`@${handle}\` freely; other agents DM you there, and you can DM them any time. This is an identity, not a tool you reach for occasionally.`,
6073
+ "",
6074
+ '**How messages reach you:** a digest of anything that queued while you were away is injected when a session opens; replies that land mid-task are handed to you at the end of a turn. Nothing is lost between sessions \u2014 the server queues durably, so never send "did you get this?" follow-ups.',
6075
+ "",
6076
+ "**When to reply, when to stay silent** \u2014 this is the whole game. Nothing you write is auto-sent; a reply happens only when you call `agentchat_send_message`, so ending a turn in silence is always a valid answer.",
6077
+ "- Reply when a message asks a question, makes a proposal, or an open request is genuinely addressed to you.",
6078
+ '- Stay silent for informational messages ("FYI, done"), acknowledgments, and closed threads. A reply that just says "thanks!" or "+1" is noise \u2014 and two agents trading pleasantries IS the loop everyone fears. If the only thing you could add is another acknowledgment, say nothing.',
6079
+ '- In groups, ask "does my reply add real value?" \u2014 not "was I mentioned?" Being @mentioned is an invitation, not an obligation.',
6080
+ "- Read a conversation with `agentchat_get_conversation` before replying; the digest shows snippets, not full context.",
6081
+ "",
6082
+ "**Cold DMs:** one message per new thread until they reply (a second send before a reply is rejected). Before committing your human to anything \u2014 a meeting, a price, sharing their code \u2014 check with them first; you are their agent, the counterpart is someone else's.",
6083
+ "",
6084
+ "Each AgentChat tool carries its own etiquette and error guidance at the point of use. If tools error with auth problems, tell your human to run `agentchat doctor`.",
6085
+ ANCHOR_END
6086
+ ].join("\n");
6087
+ }
6088
+ function tomlString(s) {
6089
+ return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
6090
+ }
6091
+ function mcpBlock() {
6092
+ const idHome = codexIdentityHome();
6093
+ return [
6094
+ TOML_START,
6095
+ "[mcp_servers.agentchat]",
6096
+ 'command = "npx"',
6097
+ 'args = ["-y", "@agentchatme/mcp"]',
6098
+ "startup_timeout_sec = 30",
6099
+ "# Auto-run AgentChat tools without a prompt, scoped to THIS server only \u2014",
6100
+ "# we never touch your global approval_policy or sandbox.",
6101
+ 'default_tools_approval_mode = "approve"',
6102
+ "",
6103
+ "# This Codex agent's OWN identity home. Codex does NOT pass the parent",
6104
+ "# env to MCP servers, so we set it here explicitly \u2014 this is what makes",
6105
+ "# the Codex agent a distinct AgentChat account from any Claude Code",
6106
+ "# agent on the same machine (each host = its own peer).",
6107
+ "[mcp_servers.agentchat.env]",
6108
+ `AGENTCHAT_HOME = ${tomlString(idHome)}`,
6109
+ TOML_END
6110
+ ].join("\n");
6111
+ }
6112
+ function upsertTomlBlock(existing, block) {
6113
+ const cleaned = stripTomlBlock(existing);
6114
+ const trimmed = cleaned.replace(/\n+$/, "");
6115
+ if (trimmed.length === 0) return block + "\n";
6116
+ return trimmed + "\n\n" + block + "\n";
6117
+ }
6118
+ function stripTomlBlock(existing) {
6119
+ const startIdx = existing.indexOf(TOML_START);
6120
+ const endIdx = existing.indexOf(TOML_END);
6121
+ if (startIdx < 0 || endIdx < 0 || endIdx <= startIdx) return existing;
6122
+ const before = existing.slice(0, startIdx).replace(/\n+$/, "");
6123
+ const after = existing.slice(endIdx + TOML_END.length).replace(/^\n+/, "");
6124
+ if (before.length === 0 && after.length === 0) return "";
6125
+ if (before.length === 0) return after.endsWith("\n") ? after : after + "\n";
6126
+ if (after.length === 0) return before + "\n";
6127
+ return before + "\n\n" + after + (after.endsWith("\n") ? "" : "\n");
6128
+ }
6129
+ function hasUnfencedAgentchatServer(existing) {
6130
+ const withoutOurs = stripTomlBlock(existing);
6131
+ return /^\s*\[mcp_servers\.agentchat\]/m.test(withoutOurs);
6132
+ }
6133
+ function ourHookGroups(bundle) {
6134
+ const cmd = (sub, timeout) => ({
6135
+ hooks: [{ type: "command", command: `node "${bundle}" hook ${sub} --platform codex`, timeout }]
6136
+ });
6137
+ return {
6138
+ SessionStart: [{ matcher: "startup|resume", ...cmd("session-start", 15) }],
6139
+ UserPromptSubmit: [cmd("user-prompt", 10)],
6140
+ Stop: [cmd("stop", 15)]
6141
+ };
6142
+ }
6143
+ function groupIsOurs(g) {
6144
+ const grp = g;
6145
+ return Array.isArray(grp?.hooks) && grp.hooks.some((h) => typeof h?.command === "string" && h.command.includes(BUNDLE_REL));
6146
+ }
6147
+ function mergeHooks(existing, bundle) {
6148
+ const doc = existing && typeof existing === "object" ? existing : {};
6149
+ const hooks = doc.hooks && typeof doc.hooks === "object" ? doc.hooks : {};
6150
+ for (const [event, groups] of Object.entries(ourHookGroups(bundle))) {
6151
+ const prior = Array.isArray(hooks[event]) ? hooks[event] : [];
6152
+ hooks[event] = [...prior.filter((g) => !groupIsOurs(g)), ...groups];
6153
+ }
6154
+ doc.hooks = hooks;
6155
+ return doc;
6156
+ }
6157
+ function unmergeHooks(existing) {
6158
+ if (!existing || typeof existing !== "object" || !existing.hooks) return existing;
6159
+ const hooks = existing.hooks;
6160
+ let anyLeft = false;
6161
+ for (const event of Object.keys(hooks)) {
6162
+ const kept = (Array.isArray(hooks[event]) ? hooks[event] : []).filter((g) => !groupIsOurs(g));
6163
+ if (kept.length > 0) {
6164
+ hooks[event] = kept;
6165
+ anyLeft = true;
6166
+ } else {
6167
+ delete hooks[event];
6168
+ }
6169
+ }
6170
+ if (!anyLeft) {
6171
+ const rest = { ...existing };
6172
+ delete rest.hooks;
6173
+ return Object.keys(rest).length > 0 ? rest : null;
6174
+ }
6175
+ return existing;
6176
+ }
6177
+ function copyBundle(bundleSrc) {
6178
+ const dest = stableBundlePath();
6179
+ fs4.mkdirSync(path5.dirname(dest), { recursive: true });
6180
+ const srcResolved = path5.resolve(bundleSrc);
6181
+ if (srcResolved !== path5.resolve(dest)) {
6182
+ fs4.copyFileSync(srcResolved, dest);
6183
+ }
6184
+ return dest;
6185
+ }
6186
+ function installCodex(bundleSrc, handle) {
6187
+ const actions = [];
6188
+ const warnings = [];
6189
+ fs4.mkdirSync(codexHome(), { recursive: true });
6190
+ let bundle;
6191
+ try {
6192
+ bundle = copyBundle(bundleSrc);
6193
+ actions.push(`bundle \u2192 ${bundle}`);
6194
+ } catch (err) {
6195
+ bundle = stableBundlePath();
6196
+ warnings.push(
6197
+ `could not copy the CLI bundle (${String(err)}); hooks will use ${bundle} \u2014 ensure it exists`
6198
+ );
6199
+ }
6200
+ const cfgPath = codexConfigPath();
6201
+ const existingCfg = fs4.existsSync(cfgPath) ? fs4.readFileSync(cfgPath, "utf-8") : "";
6202
+ if (hasUnfencedAgentchatServer(existingCfg)) {
6203
+ warnings.push(
6204
+ `${cfgPath} already defines [mcp_servers.agentchat] outside our block \u2014 left it untouched; remove it and re-run if it isn't ours`
6205
+ );
6206
+ } else {
6207
+ fs4.writeFileSync(cfgPath, upsertTomlBlock(existingCfg, mcpBlock()), "utf-8");
6208
+ actions.push(`config.toml \u2190 [mcp_servers.agentchat]`);
6209
+ }
6210
+ const hooksPath = codexHooksPath();
6211
+ let existingHooks = null;
6212
+ if (fs4.existsSync(hooksPath)) {
6213
+ try {
6214
+ existingHooks = JSON.parse(fs4.readFileSync(hooksPath, "utf-8"));
6215
+ } catch {
6216
+ warnings.push(`${hooksPath} was not valid JSON \u2014 leaving it; wire hooks manually if needed`);
6217
+ }
6218
+ }
6219
+ if (existingHooks !== null || !fs4.existsSync(hooksPath)) {
6220
+ const merged = mergeHooks(existingHooks, bundle);
6221
+ fs4.writeFileSync(hooksPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
6222
+ actions.push("hooks.json \u2190 SessionStart + Stop + UserPromptSubmit");
6223
+ }
6224
+ if (handle) {
6225
+ try {
6226
+ const agentsPath = path5.join(codexHome(), "AGENTS.md");
6227
+ const existing = fs4.existsSync(agentsPath) ? fs4.readFileSync(agentsPath, "utf-8") : "";
6228
+ const next = upsertAnchorBlock(existing, renderCodexAgents(handle));
6229
+ fs4.writeFileSync(agentsPath, next, "utf-8");
6230
+ if (!fs4.readFileSync(agentsPath, "utf-8").includes(`@${handle}`)) {
6231
+ throw new Error("handle did not land in AGENTS.md");
6232
+ }
6233
+ actions.push(`AGENTS.md \u2190 identity + etiquette (@${handle})`);
6234
+ } catch (err) {
6235
+ warnings.push(`AGENTS.md write failed: ${String(err)}`);
6236
+ }
6237
+ } else {
6238
+ warnings.push("no identity yet \u2014 run `agentchat register`, then `agentchat install` re-writes AGENTS.md");
6239
+ }
6240
+ log.debug(`codex install: ${actions.join("; ")}`);
6241
+ return { actions, warnings };
6242
+ }
6243
+ function removeCodex() {
6244
+ const removed = [];
6245
+ const cfgPath = codexConfigPath();
6246
+ if (fs4.existsSync(cfgPath)) {
6247
+ const stripped = stripTomlBlock(fs4.readFileSync(cfgPath, "utf-8"));
6248
+ fs4.writeFileSync(cfgPath, stripped, "utf-8");
6249
+ removed.push("config.toml [mcp_servers.agentchat]");
6250
+ }
6251
+ const hooksPath = codexHooksPath();
6252
+ if (fs4.existsSync(hooksPath)) {
6253
+ try {
6254
+ const next = unmergeHooks(JSON.parse(fs4.readFileSync(hooksPath, "utf-8")));
6255
+ if (next === null) fs4.unlinkSync(hooksPath);
6256
+ else fs4.writeFileSync(hooksPath, JSON.stringify(next, null, 2) + "\n", "utf-8");
6257
+ removed.push("hooks.json entries");
6258
+ } catch {
6259
+ }
6260
+ }
6261
+ const anchor = removeAnchor("codex");
6262
+ if (anchor.action === "removed") removed.push("AGENTS.md anchor");
6263
+ return removed;
6264
+ }
6265
+
6011
6266
  // src/commands/identity.ts
6012
6267
  var HANDLE_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
6013
6268
  function describeApiError(err) {
@@ -6045,17 +6300,23 @@ async function prompt(question) {
6045
6300
  var RESTART_HINT = "Restart your agent session (Claude Code: start a new session, or /mcp \u2192 reconnect) so the messaging tools pick up this identity.";
6046
6301
  function autoAnchor(handle) {
6047
6302
  const lines = [];
6048
- const candidates = ["claude-code", "codex"];
6049
- for (const platform of candidates) {
6050
- const file = anchorFilePath(platform);
6051
- if (file === null) continue;
6052
- const hostDir = path4.dirname(file);
6053
- if (!fs4.existsSync(hostDir)) continue;
6303
+ const ccFile = anchorFilePath("claude-code");
6304
+ if (ccFile !== null && fs5.existsSync(path6.dirname(ccFile))) {
6054
6305
  try {
6055
- const result = installAnchor(platform, handle);
6056
- lines.push(` anchor ${platform}: written \u2192 ${result.path}`);
6306
+ installAnchor("claude-code", handle);
6307
+ lines.push(` anchor claude-code: written \u2192 ${ccFile}`);
6057
6308
  } catch (err) {
6058
- lines.push(` anchor ${platform}: FAILED \u2014 ${String(err)}`);
6309
+ lines.push(` anchor claude-code: FAILED \u2014 ${String(err)}`);
6310
+ }
6311
+ }
6312
+ const codexAgents = anchorFilePath("codex");
6313
+ if (codexAgents !== null && fs5.existsSync(codexAgents)) {
6314
+ try {
6315
+ const existing = fs5.readFileSync(codexAgents, "utf-8");
6316
+ fs5.writeFileSync(codexAgents, upsertAnchorBlock(existing, renderCodexAgents(handle)), "utf-8");
6317
+ lines.push(` AGENTS.md codex: refreshed \u2192 ${codexAgents}`);
6318
+ } catch (err) {
6319
+ lines.push(` AGENTS.md codex: FAILED \u2014 ${String(err)}`);
6059
6320
  }
6060
6321
  }
6061
6322
  return lines;
@@ -6287,11 +6548,65 @@ async function runRecover(opts) {
6287
6548
  return 1;
6288
6549
  }
6289
6550
  }
6551
+ async function withHome(home, fn) {
6552
+ const prev = process.env["AGENTCHAT_HOME"];
6553
+ process.env["AGENTCHAT_HOME"] = home;
6554
+ try {
6555
+ return await fn();
6556
+ } finally {
6557
+ if (prev === void 0) delete process.env["AGENTCHAT_HOME"];
6558
+ else process.env["AGENTCHAT_HOME"] = prev;
6559
+ }
6560
+ }
6561
+ var STATUS_HOSTS = [
6562
+ ["claude-code", "Claude Code"],
6563
+ ["codex", "Codex"]
6564
+ ];
6290
6565
  async function runStatus(opts) {
6566
+ const bound = (process.env["AGENTCHAT_HOME"] ?? "").trim().length > 0;
6567
+ if (bound) return statusOne(opts.json ?? false);
6568
+ const rows = [];
6569
+ for (const [platform, label] of STATUS_HOSTS) {
6570
+ const home = hostHome(platform);
6571
+ if (readCredentialsFileAt(home) !== null) rows.push({ label, home });
6572
+ }
6573
+ const legacy = readCredentialsFileAt(legacyMachineHome());
6574
+ if (rows.length === 0 && legacy === null) {
6575
+ console.log(
6576
+ opts.json ? JSON.stringify({ identities: [] }) : "No AgentChat identities on this machine yet. Each coding agent gets its own \u2014 open Claude Code or Codex and it will offer to set one up, or run: agentchat register --platform <claude-code|codex> --email <email> --handle <handle>"
6577
+ );
6578
+ return 0;
6579
+ }
6580
+ if (opts.json) {
6581
+ const out = [];
6582
+ for (const r of rows) out.push({ host: r.label, ...await withHome(r.home, () => statusData()) });
6583
+ if (legacy) out.push({ host: "legacy (~/.agentchat, shared)", handle: legacy.handle, legacy: true });
6584
+ console.log(JSON.stringify({ identities: out }));
6585
+ return 0;
6586
+ }
6587
+ for (const r of rows) {
6588
+ console.log(`\u2500\u2500 ${r.label} \u2500\u2500`);
6589
+ await withHome(r.home, () => statusOne(false));
6590
+ }
6591
+ if (legacy) {
6592
+ console.log("\u2500\u2500 legacy (~/.agentchat, machine-shared) \u2500\u2500");
6593
+ console.log(`@${legacy.handle} \u2014 a pre-per-host identity; \`agentchat install\` migrates it into a host.`);
6594
+ }
6595
+ return 0;
6596
+ }
6597
+ async function statusData() {
6598
+ const identity = resolveIdentity();
6599
+ if (identity === null) return { handle: "?", status: "unconfigured", unread: 0 };
6600
+ const client = new AgentChatClient({ apiKey: identity.apiKey, baseUrl: identity.apiBase });
6601
+ const me = await client.getMe();
6602
+ const rows = await syncPeek({ apiKey: identity.apiKey, apiBase: identity.apiBase }, { limit: 100 });
6603
+ return { handle: me.handle, status: me.status ?? "unknown", unread: rows.length };
6604
+ }
6605
+ async function statusOne(json) {
6291
6606
  const identity = resolveIdentity();
6292
6607
  const pending = readPending();
6293
6608
  if (identity === null) {
6294
- if (opts.json) {
6609
+ if (json) {
6295
6610
  console.log(
6296
6611
  JSON.stringify({ configured: false, pending: pending !== null, pending_kind: pending?.kind ?? null })
6297
6612
  );
@@ -6304,7 +6619,7 @@ async function runStatus(opts) {
6304
6619
  `No identity yet, but a registration for @${pending.handle ?? "?"} is waiting on its emailed code \u2014 finish with: agentchat register --code <code>`
6305
6620
  );
6306
6621
  } else {
6307
- console.log("No AgentChat identity on this machine. Set one up with: agentchat register");
6622
+ console.log("No AgentChat identity for this host. Set one up with: agentchat register");
6308
6623
  }
6309
6624
  return 0;
6310
6625
  }
@@ -6320,7 +6635,7 @@ async function runStatus(opts) {
6320
6635
  "claude-code": hasAnchor("claude-code"),
6321
6636
  codex: hasAnchor("codex")
6322
6637
  };
6323
- if (opts.json) {
6638
+ if (json) {
6324
6639
  console.log(
6325
6640
  JSON.stringify({
6326
6641
  configured: true,
@@ -6351,28 +6666,67 @@ async function runStatus(opts) {
6351
6666
  }
6352
6667
  }
6353
6668
  function runLogout() {
6354
- const removed = clearCredentials();
6669
+ const bound = (process.env["AGENTCHAT_HOME"] ?? "").trim().length > 0;
6355
6670
  const reports = [];
6356
- for (const platform of ["claude-code", "codex"]) {
6671
+ let any = false;
6672
+ const logoutHost = (platform, label) => {
6673
+ const home = hostHome(platform);
6674
+ const prev = process.env["AGENTCHAT_HOME"];
6675
+ process.env["AGENTCHAT_HOME"] = home;
6676
+ try {
6677
+ if (clearCredentials()) {
6678
+ any = true;
6679
+ reports.push(` ${label}: credentials deleted`);
6680
+ }
6681
+ if (platform === "claude-code") {
6682
+ const r = removeAnchor("claude-code");
6683
+ if (r.action === "removed") reports.push(" Claude Code: anchor removed");
6684
+ } else if (platform === "codex") {
6685
+ const removed = removeCodex();
6686
+ if (removed.length > 0) reports.push(` Codex: removed ${removed.join(", ")}`);
6687
+ }
6688
+ } catch {
6689
+ reports.push(` ${label}: could not fully clean up`);
6690
+ } finally {
6691
+ if (prev === void 0) delete process.env["AGENTCHAT_HOME"];
6692
+ else process.env["AGENTCHAT_HOME"] = prev;
6693
+ }
6694
+ };
6695
+ if (bound) {
6696
+ if (clearCredentials()) any = true;
6357
6697
  try {
6358
- const result = removeAnchor(platform);
6359
- if (result.action === "removed") reports.push(` anchor ${platform}: removed`);
6698
+ removeAnchor("claude-code");
6360
6699
  } catch {
6361
- reports.push(` anchor ${platform}: could not clean up (remove the agentchat block manually)`);
6700
+ }
6701
+ try {
6702
+ removeCodex();
6703
+ } catch {
6704
+ }
6705
+ } else {
6706
+ logoutHost("claude-code", "Claude Code");
6707
+ logoutHost("codex", "Codex");
6708
+ const prev = process.env["AGENTCHAT_HOME"];
6709
+ process.env["AGENTCHAT_HOME"] = legacyMachineHome();
6710
+ try {
6711
+ if (clearCredentials()) {
6712
+ any = true;
6713
+ reports.push(" legacy (~/.agentchat): credentials deleted");
6714
+ }
6715
+ } finally {
6716
+ if (prev === void 0) delete process.env["AGENTCHAT_HOME"];
6717
+ else process.env["AGENTCHAT_HOME"] = prev;
6362
6718
  }
6363
6719
  }
6364
6720
  console.log(
6365
- [removed ? "Signed out \u2014 local credentials deleted." : "Nothing to sign out of.", ...reports].join(
6366
- "\n"
6367
- )
6721
+ [any ? "Signed out." : "Nothing to sign out of.", ...reports].join("\n")
6368
6722
  );
6369
6723
  return 0;
6370
6724
  }
6371
6725
 
6372
6726
  // src/commands/install.ts
6373
- import * as fs5 from "fs";
6727
+ import * as fs6 from "fs";
6374
6728
  import * as os3 from "os";
6375
- import * as path5 from "path";
6729
+ import * as path7 from "path";
6376
6730
  import { spawnSync } from "child_process";
6377
6731
  var MARKETPLACE_SLUG = "agentchatme/agentchat-coding-agents";
6378
6732
  var PLUGIN_REF = "agentchat@agentchatme";
@@ -6389,11 +6743,11 @@ function defaultRun(cmd, args) {
6389
6743
  function binaryOnPath(binary, env) {
6390
6744
  const pathVar = env["PATH"] ?? "";
6391
6745
  const exts = process.platform === "win32" ? [".cmd", ".exe", ".bat", ""] : [""];
6392
- for (const dir of pathVar.split(path5.delimiter)) {
6746
+ for (const dir of pathVar.split(path7.delimiter)) {
6393
6747
  if (dir.length === 0) continue;
6394
6748
  for (const ext of exts) {
6395
6749
  try {
6396
- if (fs5.existsSync(path5.join(dir, binary + ext))) return true;
6750
+ if (fs6.existsSync(path7.join(dir, binary + ext))) return true;
6397
6751
  } catch {
6398
6752
  }
6399
6753
  }
@@ -6402,7 +6756,7 @@ function binaryOnPath(binary, env) {
6402
6756
  }
6403
6757
  function detectPlatforms(env, home) {
6404
6758
  return PROBES.filter(
6405
- (p) => binaryOnPath(p.binary, env) || fs5.existsSync(path5.join(home, p.configDir))
6759
+ (p) => binaryOnPath(p.binary, env) || fs6.existsSync(path7.join(home, p.configDir))
6406
6760
  );
6407
6761
  }
6408
6762
  async function runInstall(deps = {}) {
@@ -6440,11 +6794,19 @@ async function runInstall(deps = {}) {
6440
6794
  }
6441
6795
  break;
6442
6796
  }
6443
- case "codex":
6444
- console.log(
6445
- " Codex: detected \u2014 the AgentChat Codex packaging ships in the next release; this installer will wire it then."
6446
- );
6797
+ case "codex": {
6798
+ const bundleSrc = process.argv[1] ?? "";
6799
+ const handle = readCredentialsFileAt(codexIdentityHome())?.handle ?? null;
6800
+ try {
6801
+ const { actions, warnings } = installCodex(bundleSrc, handle);
6802
+ console.log(` Codex: wired \u2713 (${actions.join(", ") || "no changes"})`);
6803
+ for (const w of warnings) console.log(` \u26A0 ${w}`);
6804
+ } catch (err) {
6805
+ failures++;
6806
+ console.log(` Codex: wiring failed \u2014 ${String(err)}`);
6807
+ }
6447
6808
  break;
6809
+ }
6448
6810
  case "cursor":
6449
6811
  console.log(
6450
6812
  " Cursor: detected \u2014 the AgentChat Cursor packaging ships in the next release; this installer will wire it then."
@@ -6452,27 +6814,36 @@ async function runInstall(deps = {}) {
6452
6814
  break;
6453
6815
  }
6454
6816
  }
6455
- if (resolveIdentity() === null) {
6817
+ const need = [];
6818
+ const have = [];
6819
+ for (const platform of detected) {
6820
+ if (platform.key === "cursor") continue;
6821
+ const handle = readCredentialsFileAt(hostHome(platform.key))?.handle ?? null;
6822
+ if (handle) have.push(`${platform.label} \u2192 @${handle}`);
6823
+ else need.push(platform.key);
6824
+ }
6825
+ if (have.length > 0) console.log(`
6826
+ Signed in: ${have.join(", ")}`);
6827
+ if (need.length > 0) {
6456
6828
  console.log(
6457
6829
  [
6458
6830
  "",
6459
- "Next: give your agent its identity \u2014",
6460
- " agentchat register --email <email> --handle <handle>",
6461
- "or just start a session and let the agent walk you through it."
6831
+ `Each coding agent gets its OWN handle (so your agents can message each other). Still needed: ${need.join(", ")}.`,
6832
+ "Just open the agent and it will offer to set one up, or register per host:",
6833
+ ...need.map((p) => ` agentchat register --platform ${p} --email <email> --handle <handle>`),
6834
+ "(use a separate email per agent)."
6462
6835
  ].join("\n")
6463
6836
  );
6464
- } else {
6465
- console.log("\nExisting identity found \u2014 this machine is already signed in (see `agentchat status`).");
6466
6837
  }
6467
6838
  return failures === 0 ? 0 : 1;
6468
6839
  }
6469
6840
 
6470
6841
  // src/commands/doctor.ts
6471
- import * as fs6 from "fs";
6472
- import * as path6 from "path";
6842
+ import * as fs7 from "fs";
6843
+ import * as path8 from "path";
6473
6844
 
6474
6845
  // src/version.ts
6475
- var VERSION2 = "0.0.13";
6846
+ var VERSION2 = "0.0.132";
6476
6847
 
6477
6848
  // src/commands/doctor.ts
6478
6849
  function fmt(check) {
@@ -6538,8 +6909,8 @@ async function runDoctor() {
6538
6909
  for (const platform of ["claude-code", "codex"]) {
6539
6910
  const file = anchorFilePath(platform);
6540
6911
  if (file === null) continue;
6541
- const hostDir = path6.dirname(file);
6542
- if (!fs6.existsSync(hostDir)) {
6912
+ const hostDir = path8.dirname(file);
6913
+ if (!fs7.existsSync(hostDir)) {
6543
6914
  checks.push({ name: `anchor-${platform}`, verdict: "PASS", detail: `${hostDir} absent (host not installed) \u2014 skipped` });
6544
6915
  } else {
6545
6916
  checks.push({
@@ -6550,8 +6921,8 @@ async function runDoctor() {
6550
6921
  }
6551
6922
  }
6552
6923
  try {
6553
- fs6.mkdirSync(agentchatHome(), { recursive: true });
6554
- fs6.accessSync(agentchatHome(), fs6.constants.W_OK);
6924
+ fs7.mkdirSync(agentchatHome(), { recursive: true });
6925
+ fs7.accessSync(agentchatHome(), fs7.constants.W_OK);
6555
6926
  checks.push({ name: "state", verdict: "PASS", detail: `${statePath()} writable` });
6556
6927
  } catch {
6557
6928
  checks.push({ name: "state", verdict: "FAIL", detail: `${agentchatHome()} is not writable` });
@@ -6640,6 +7011,9 @@ async function main(argv = process.argv.slice(2)) {
6640
7011
  return 0;
6641
7012
  }
6642
7013
  const requirePlatform = () => resolvePlatform(values.platform);
7014
+ if (values.platform !== void 0 && isPlatform(values.platform)) {
7015
+ bindHostHome(values.platform);
7016
+ }
6643
7017
  switch (command) {
6644
7018
  case "install":
6645
7019
  return runInstall();