@agentchatme/cli 0.0.1 → 0.0.131

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
@@ -538,8 +538,8 @@ function getErrorMap() {
538
538
 
539
539
  // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
540
540
  var makeIssue = (params) => {
541
- const { data, path: path7, errorMaps, issueData } = params;
542
- const fullPath = [...path7, ...issueData.path || []];
541
+ const { data, path: path8, errorMaps, issueData } = params;
542
+ const fullPath = [...path8, ...issueData.path || []];
543
543
  const fullIssue = {
544
544
  ...issueData,
545
545
  path: fullPath
@@ -655,11 +655,11 @@ var errorUtil;
655
655
 
656
656
  // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
657
657
  var ParseInputLazyPath = class {
658
- constructor(parent, value, path7, key) {
658
+ constructor(parent, value, path8, key) {
659
659
  this._cachedPath = [];
660
660
  this.parent = parent;
661
661
  this.data = value;
662
- this._path = path7;
662
+ this._path = path8;
663
663
  this._key = key;
664
664
  }
665
665
  get path() {
@@ -4140,6 +4140,11 @@ function pendingPath() {
4140
4140
  function statePath() {
4141
4141
  return path2.join(agentchatHome(), "state.json");
4142
4142
  }
4143
+ function codexHome() {
4144
+ const override = process.env["CODEX_HOME"];
4145
+ if (override && override.trim().length > 0) return path2.resolve(override);
4146
+ return path2.join(os.homedir(), ".codex");
4147
+ }
4143
4148
 
4144
4149
  // src/lib/credentials.ts
4145
4150
  var DEFAULT_API_BASE = "https://api.agentchat.me";
@@ -4266,7 +4271,14 @@ function firstString(obj, keys) {
4266
4271
  var SESSION_TTL_MS = 48 * 60 * 60 * 1e3;
4267
4272
  var SessionStateSchema = external_exports.object({
4268
4273
  continuations: external_exports.number().int().min(0),
4269
- updated_at: external_exports.string()
4274
+ updated_at: external_exports.string(),
4275
+ // Ack cursor for the batch the session-start hook injected but has NOT
4276
+ // yet committed. Committed by the user-prompt hook — proof the session
4277
+ // actually ran a turn. A session that dies before its first prompt
4278
+ // (arg-error invocations, crashed startups) leaves this uncommitted and
4279
+ // the batch re-digests next session instead of being consumed by a
4280
+ // ghost. Live-fire lesson, 2026-07-12.
4281
+ pending_ack: external_exports.string().optional()
4270
4282
  });
4271
4283
  var StateSchema = external_exports.object({
4272
4284
  sessions: external_exports.record(SessionStateSchema).default({}),
@@ -4313,6 +4325,27 @@ function resetSession(sessionKey) {
4313
4325
  delete state.sessions[sessionKey];
4314
4326
  writeState(state);
4315
4327
  }
4328
+ function setPendingAck(sessionKey, cursor, now2 = /* @__PURE__ */ new Date()) {
4329
+ const state = readState();
4330
+ prune(state, now2);
4331
+ const existing = state.sessions[sessionKey];
4332
+ state.sessions[sessionKey] = {
4333
+ continuations: existing?.continuations ?? 0,
4334
+ updated_at: now2.toISOString(),
4335
+ pending_ack: cursor
4336
+ };
4337
+ writeState(state);
4338
+ }
4339
+ function takePendingAck(sessionKey, now2 = /* @__PURE__ */ new Date()) {
4340
+ const state = readState();
4341
+ const entry = state.sessions[sessionKey];
4342
+ if (entry?.pending_ack === void 0) return null;
4343
+ const cursor = entry.pending_ack;
4344
+ delete entry.pending_ack;
4345
+ entry.updated_at = now2.toISOString();
4346
+ writeState(state);
4347
+ return cursor;
4348
+ }
4316
4349
  var OFFER_COOLDOWN_MS = 24 * 60 * 60 * 1e3;
4317
4350
  function shouldOfferRegistration(now2 = /* @__PURE__ */ new Date()) {
4318
4351
  const last = readState().last_offer_at;
@@ -4331,6 +4364,11 @@ var SyncRowSchema = external_exports.object({
4331
4364
  id: external_exports.string(),
4332
4365
  conversation_id: external_exports.string(),
4333
4366
  delivery_id: external_exports.string().nullable(),
4367
+ // The public message shape carries the sender's handle as `sender`
4368
+ // (live-fire verified against prod 2026-07-12; `sender_handle` is the
4369
+ // dashboard-RPC shape and never appears on this wire — kept only as a
4370
+ // fallback against a future server-side rename).
4371
+ sender: external_exports.string().optional(),
4334
4372
  sender_handle: external_exports.string().optional(),
4335
4373
  type: external_exports.string().optional(),
4336
4374
  content: external_exports.record(external_exports.unknown()).optional(),
@@ -4421,7 +4459,7 @@ function snippetOf(row) {
4421
4459
  function digestConversations(rows) {
4422
4460
  const byConversation = /* @__PURE__ */ new Map();
4423
4461
  for (const row of rows) {
4424
- const sender = row.sender_handle ?? "unknown";
4462
+ const sender = row.sender ?? row.sender_handle ?? "unknown";
4425
4463
  const existing = byConversation.get(row.conversation_id);
4426
4464
  if (existing) {
4427
4465
  existing.count += 1;
@@ -4533,19 +4571,35 @@ async function runSessionStartHook(platform) {
4533
4571
  if (rows.length === 0) return;
4534
4572
  const handle = await resolveHandle(cfg, identity.handle);
4535
4573
  const context = formatSessionStart(handle, rows);
4536
- printJson(sessionStartOutput(platform, context));
4537
4574
  const cursor = lastDeliveryId(rows);
4538
4575
  if (cursor !== null) {
4539
- try {
4540
- await syncAck(cfg, cursor);
4541
- } catch (err) {
4542
- log.warn(`session-start ack failed (will re-surface next session): ${String(err)}`);
4543
- }
4576
+ setPendingAck(`${platform}:${input.sessionId}`, cursor);
4544
4577
  }
4578
+ printJson(sessionStartOutput(platform, context));
4545
4579
  } catch (err) {
4546
4580
  log.warn(`session-start hook degraded to no-op: ${String(err)}`);
4547
4581
  }
4548
4582
  }
4583
+ async function runUserPromptHook(platform) {
4584
+ try {
4585
+ if (hooksDisabled()) return;
4586
+ const input = await readHookInput();
4587
+ const identity = resolveIdentity();
4588
+ if (identity === null) return;
4589
+ const sessionKey = `${platform}:${input.sessionId}`;
4590
+ const cursor = takePendingAck(sessionKey);
4591
+ if (cursor === null) return;
4592
+ const cfg = { apiKey: identity.apiKey, apiBase: identity.apiBase };
4593
+ try {
4594
+ await syncAck(cfg, cursor);
4595
+ } catch (err) {
4596
+ setPendingAck(sessionKey, cursor);
4597
+ log.warn(`user-prompt ack failed (will retry next prompt): ${String(err)}`);
4598
+ }
4599
+ } catch (err) {
4600
+ log.warn(`user-prompt hook degraded to no-op: ${String(err)}`);
4601
+ }
4602
+ }
4549
4603
  async function runStopHook(platform) {
4550
4604
  try {
4551
4605
  if (hooksDisabled()) return;
@@ -4579,8 +4633,8 @@ async function runStopHook(platform) {
4579
4633
  }
4580
4634
 
4581
4635
  // src/commands/identity.ts
4582
- import * as fs4 from "fs";
4583
- import * as path4 from "path";
4636
+ import * as fs5 from "fs";
4637
+ import * as path5 from "path";
4584
4638
  import * as readline from "readline/promises";
4585
4639
 
4586
4640
  // ../node_modules/.pnpm/agentchatme@1.0.2/node_modules/agentchatme/dist/index.js
@@ -4843,8 +4897,8 @@ var HttpTransport = class {
4843
4897
  }
4844
4898
  this.fetchFn = f.bind(globalThis);
4845
4899
  }
4846
- async request(method, path7, opts = {}) {
4847
- const url = `${this.baseUrl}${path7}`;
4900
+ async request(method, path8, opts = {}) {
4901
+ const url = `${this.baseUrl}${path8}`;
4848
4902
  const policy = resolveRetryPolicy(opts.retry, this.retry);
4849
4903
  const canRetry = isRetryEligible(method, opts.idempotencyKey, opts.retry);
4850
4904
  const maxAttempts = canRetry ? policy.maxRetries + 1 : 1;
@@ -5077,10 +5131,10 @@ function statusToCode(status) {
5077
5131
  }
5078
5132
  async function sleep(ms, signal) {
5079
5133
  if (ms <= 0) return;
5080
- await new Promise((resolve2, reject) => {
5134
+ await new Promise((resolve3, reject) => {
5081
5135
  const timer = setTimeout(() => {
5082
5136
  if (signal) signal.removeEventListener("abort", onAbort);
5083
- resolve2();
5137
+ resolve3();
5084
5138
  }, ms);
5085
5139
  const onAbort = () => {
5086
5140
  clearTimeout(timer);
@@ -5163,31 +5217,31 @@ var AgentChatClient = class _AgentChatClient {
5163
5217
  this.onBacklogWarning = options.onBacklogWarning;
5164
5218
  }
5165
5219
  // ─── Internal request helpers ─────────────────────────────────────────────
5166
- async get(path7, opts) {
5167
- const res = await this.http.request("GET", path7, this.toRequestOpts(opts));
5220
+ async get(path8, opts) {
5221
+ const res = await this.http.request("GET", path8, this.toRequestOpts(opts));
5168
5222
  return res.data;
5169
5223
  }
5170
- async del(path7, opts) {
5171
- const res = await this.http.request("DELETE", path7, this.toRequestOpts(opts));
5224
+ async del(path8, opts) {
5225
+ const res = await this.http.request("DELETE", path8, this.toRequestOpts(opts));
5172
5226
  return res.data;
5173
5227
  }
5174
- async post(path7, body, opts) {
5175
- const res = await this.http.request("POST", path7, {
5228
+ async post(path8, body, opts) {
5229
+ const res = await this.http.request("POST", path8, {
5176
5230
  ...this.toRequestOpts(opts),
5177
5231
  body
5178
5232
  });
5179
5233
  return res.data;
5180
5234
  }
5181
- async patch(path7, body, opts) {
5182
- const res = await this.http.request("PATCH", path7, {
5235
+ async patch(path8, body, opts) {
5236
+ const res = await this.http.request("PATCH", path8, {
5183
5237
  ...this.toRequestOpts(opts),
5184
5238
  body
5185
5239
  });
5186
5240
  return res.data;
5187
5241
  }
5188
- async put(path7, body, opts) {
5242
+ async put(path8, body, opts) {
5189
5243
  const headers = opts?.contentType ? { "Content-Type": opts.contentType } : void 0;
5190
- const res = await this.http.request("PUT", path7, {
5244
+ const res = await this.http.request("PUT", path8, {
5191
5245
  ...this.toRequestOpts(opts),
5192
5246
  body,
5193
5247
  rawBody: opts?.rawBody,
@@ -5861,7 +5915,7 @@ function anchorFilePath(platform) {
5861
5915
  case "claude-code":
5862
5916
  return path3.join(os2.homedir(), ".claude", "CLAUDE.md");
5863
5917
  case "codex":
5864
- return path3.join(os2.homedir(), ".codex", "AGENTS.md");
5918
+ return path3.join(codexHome(), "AGENTS.md");
5865
5919
  case "cursor":
5866
5920
  return null;
5867
5921
  }
@@ -5959,6 +6013,215 @@ function stripAllBlocks(existing, start, end) {
5959
6013
  return text;
5960
6014
  }
5961
6015
 
6016
+ // src/lib/codex-config.ts
6017
+ import * as fs4 from "fs";
6018
+ import * as path4 from "path";
6019
+ var TOML_START = "# agentchat:start";
6020
+ var TOML_END = "# agentchat:end";
6021
+ var BUNDLE_REL = path4.join("bin", "agentchat.mjs");
6022
+ function codexConfigPath() {
6023
+ return path4.join(codexHome(), "config.toml");
6024
+ }
6025
+ function codexHooksPath() {
6026
+ return path4.join(codexHome(), "hooks.json");
6027
+ }
6028
+ function stableBundlePath() {
6029
+ return path4.join(agentchatHome(), BUNDLE_REL);
6030
+ }
6031
+ function renderCodexAgents(handle) {
6032
+ return [
6033
+ ANCHOR_START,
6034
+ "## On AgentChat",
6035
+ "",
6036
+ `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.`,
6037
+ "",
6038
+ '**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.',
6039
+ "",
6040
+ "**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.",
6041
+ "- Reply when a message asks a question, makes a proposal, or an open request is genuinely addressed to you.",
6042
+ '- 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.',
6043
+ '- In groups, ask "does my reply add real value?" \u2014 not "was I mentioned?" Being @mentioned is an invitation, not an obligation.',
6044
+ "- Read a conversation with `agentchat_get_conversation` before replying; the digest shows snippets, not full context.",
6045
+ "",
6046
+ "**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.",
6047
+ "",
6048
+ "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`.",
6049
+ ANCHOR_END
6050
+ ].join("\n");
6051
+ }
6052
+ function mcpBlock() {
6053
+ return [
6054
+ TOML_START,
6055
+ "[mcp_servers.agentchat]",
6056
+ 'command = "npx"',
6057
+ 'args = ["-y", "@agentchatme/mcp"]',
6058
+ "startup_timeout_sec = 30",
6059
+ "# Auto-run AgentChat tools without a prompt, scoped to THIS server only \u2014",
6060
+ "# we never touch your global approval_policy or sandbox.",
6061
+ 'default_tools_approval_mode = "approve"',
6062
+ "# Forward our identity env to the server. Codex passes the parent env to",
6063
+ "# hooks but NOT to MCP servers unless named here \u2014 without this the server",
6064
+ "# and the hooks can resolve DIFFERENT identities. Empty/unset in the",
6065
+ "# normal case (server reads ~/.agentchat/credentials); load-bearing only",
6066
+ "# when AGENTCHAT_HOME / a key is set in the environment.",
6067
+ 'env_vars = ["AGENTCHAT_HOME", "AGENTCHAT_API_KEY", "AGENTCHAT_API_BASE"]',
6068
+ TOML_END
6069
+ ].join("\n");
6070
+ }
6071
+ function upsertTomlBlock(existing, block) {
6072
+ const cleaned = stripTomlBlock(existing);
6073
+ const trimmed = cleaned.replace(/\n+$/, "");
6074
+ if (trimmed.length === 0) return block + "\n";
6075
+ return trimmed + "\n\n" + block + "\n";
6076
+ }
6077
+ function stripTomlBlock(existing) {
6078
+ const startIdx = existing.indexOf(TOML_START);
6079
+ const endIdx = existing.indexOf(TOML_END);
6080
+ if (startIdx < 0 || endIdx < 0 || endIdx <= startIdx) return existing;
6081
+ const before = existing.slice(0, startIdx).replace(/\n+$/, "");
6082
+ const after = existing.slice(endIdx + TOML_END.length).replace(/^\n+/, "");
6083
+ if (before.length === 0 && after.length === 0) return "";
6084
+ if (before.length === 0) return after.endsWith("\n") ? after : after + "\n";
6085
+ if (after.length === 0) return before + "\n";
6086
+ return before + "\n\n" + after + (after.endsWith("\n") ? "" : "\n");
6087
+ }
6088
+ function hasUnfencedAgentchatServer(existing) {
6089
+ const withoutOurs = stripTomlBlock(existing);
6090
+ return /^\s*\[mcp_servers\.agentchat\]/m.test(withoutOurs);
6091
+ }
6092
+ function ourHookGroups(bundle) {
6093
+ const cmd = (sub, timeout) => ({
6094
+ hooks: [{ type: "command", command: `node "${bundle}" hook ${sub} --platform codex`, timeout }]
6095
+ });
6096
+ return {
6097
+ SessionStart: [{ matcher: "startup|resume", ...cmd("session-start", 15) }],
6098
+ UserPromptSubmit: [cmd("user-prompt", 10)],
6099
+ Stop: [cmd("stop", 15)]
6100
+ };
6101
+ }
6102
+ function groupIsOurs(g) {
6103
+ const grp = g;
6104
+ return Array.isArray(grp?.hooks) && grp.hooks.some((h) => typeof h?.command === "string" && h.command.includes(BUNDLE_REL));
6105
+ }
6106
+ function mergeHooks(existing, bundle) {
6107
+ const doc = existing && typeof existing === "object" ? existing : {};
6108
+ const hooks = doc.hooks && typeof doc.hooks === "object" ? doc.hooks : {};
6109
+ for (const [event, groups] of Object.entries(ourHookGroups(bundle))) {
6110
+ const prior = Array.isArray(hooks[event]) ? hooks[event] : [];
6111
+ hooks[event] = [...prior.filter((g) => !groupIsOurs(g)), ...groups];
6112
+ }
6113
+ doc.hooks = hooks;
6114
+ return doc;
6115
+ }
6116
+ function unmergeHooks(existing) {
6117
+ if (!existing || typeof existing !== "object" || !existing.hooks) return existing;
6118
+ const hooks = existing.hooks;
6119
+ let anyLeft = false;
6120
+ for (const event of Object.keys(hooks)) {
6121
+ const kept = (Array.isArray(hooks[event]) ? hooks[event] : []).filter((g) => !groupIsOurs(g));
6122
+ if (kept.length > 0) {
6123
+ hooks[event] = kept;
6124
+ anyLeft = true;
6125
+ } else {
6126
+ delete hooks[event];
6127
+ }
6128
+ }
6129
+ if (!anyLeft) {
6130
+ const rest = { ...existing };
6131
+ delete rest.hooks;
6132
+ return Object.keys(rest).length > 0 ? rest : null;
6133
+ }
6134
+ return existing;
6135
+ }
6136
+ function copyBundle(bundleSrc) {
6137
+ const dest = stableBundlePath();
6138
+ fs4.mkdirSync(path4.dirname(dest), { recursive: true });
6139
+ const srcResolved = path4.resolve(bundleSrc);
6140
+ if (srcResolved !== path4.resolve(dest)) {
6141
+ fs4.copyFileSync(srcResolved, dest);
6142
+ }
6143
+ return dest;
6144
+ }
6145
+ function installCodex(bundleSrc, handle) {
6146
+ const actions = [];
6147
+ const warnings = [];
6148
+ fs4.mkdirSync(codexHome(), { recursive: true });
6149
+ let bundle;
6150
+ try {
6151
+ bundle = copyBundle(bundleSrc);
6152
+ actions.push(`bundle \u2192 ${bundle}`);
6153
+ } catch (err) {
6154
+ bundle = stableBundlePath();
6155
+ warnings.push(
6156
+ `could not copy the CLI bundle (${String(err)}); hooks will use ${bundle} \u2014 ensure it exists`
6157
+ );
6158
+ }
6159
+ const cfgPath = codexConfigPath();
6160
+ const existingCfg = fs4.existsSync(cfgPath) ? fs4.readFileSync(cfgPath, "utf-8") : "";
6161
+ if (hasUnfencedAgentchatServer(existingCfg)) {
6162
+ warnings.push(
6163
+ `${cfgPath} already defines [mcp_servers.agentchat] outside our block \u2014 left it untouched; remove it and re-run if it isn't ours`
6164
+ );
6165
+ } else {
6166
+ fs4.writeFileSync(cfgPath, upsertTomlBlock(existingCfg, mcpBlock()), "utf-8");
6167
+ actions.push(`config.toml \u2190 [mcp_servers.agentchat]`);
6168
+ }
6169
+ const hooksPath = codexHooksPath();
6170
+ let existingHooks = null;
6171
+ if (fs4.existsSync(hooksPath)) {
6172
+ try {
6173
+ existingHooks = JSON.parse(fs4.readFileSync(hooksPath, "utf-8"));
6174
+ } catch {
6175
+ warnings.push(`${hooksPath} was not valid JSON \u2014 leaving it; wire hooks manually if needed`);
6176
+ }
6177
+ }
6178
+ if (existingHooks !== null || !fs4.existsSync(hooksPath)) {
6179
+ const merged = mergeHooks(existingHooks, bundle);
6180
+ fs4.writeFileSync(hooksPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
6181
+ actions.push("hooks.json \u2190 SessionStart + Stop + UserPromptSubmit");
6182
+ }
6183
+ if (handle) {
6184
+ try {
6185
+ const agentsPath = path4.join(codexHome(), "AGENTS.md");
6186
+ const existing = fs4.existsSync(agentsPath) ? fs4.readFileSync(agentsPath, "utf-8") : "";
6187
+ const next = upsertAnchorBlock(existing, renderCodexAgents(handle));
6188
+ fs4.writeFileSync(agentsPath, next, "utf-8");
6189
+ if (!fs4.readFileSync(agentsPath, "utf-8").includes(`@${handle}`)) {
6190
+ throw new Error("handle did not land in AGENTS.md");
6191
+ }
6192
+ actions.push(`AGENTS.md \u2190 identity + etiquette (@${handle})`);
6193
+ } catch (err) {
6194
+ warnings.push(`AGENTS.md write failed: ${String(err)}`);
6195
+ }
6196
+ } else {
6197
+ warnings.push("no identity yet \u2014 run `agentchat register`, then `agentchat install` re-writes AGENTS.md");
6198
+ }
6199
+ log.debug(`codex install: ${actions.join("; ")}`);
6200
+ return { actions, warnings };
6201
+ }
6202
+ function removeCodex() {
6203
+ const removed = [];
6204
+ const cfgPath = codexConfigPath();
6205
+ if (fs4.existsSync(cfgPath)) {
6206
+ const stripped = stripTomlBlock(fs4.readFileSync(cfgPath, "utf-8"));
6207
+ fs4.writeFileSync(cfgPath, stripped, "utf-8");
6208
+ removed.push("config.toml [mcp_servers.agentchat]");
6209
+ }
6210
+ const hooksPath = codexHooksPath();
6211
+ if (fs4.existsSync(hooksPath)) {
6212
+ try {
6213
+ const next = unmergeHooks(JSON.parse(fs4.readFileSync(hooksPath, "utf-8")));
6214
+ if (next === null) fs4.unlinkSync(hooksPath);
6215
+ else fs4.writeFileSync(hooksPath, JSON.stringify(next, null, 2) + "\n", "utf-8");
6216
+ removed.push("hooks.json entries");
6217
+ } catch {
6218
+ }
6219
+ }
6220
+ const anchor = removeAnchor("codex");
6221
+ if (anchor.action === "removed") removed.push("AGENTS.md anchor");
6222
+ return removed;
6223
+ }
6224
+
5962
6225
  // src/commands/identity.ts
5963
6226
  var HANDLE_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
5964
6227
  function describeApiError(err) {
@@ -5996,17 +6259,23 @@ async function prompt(question) {
5996
6259
  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.";
5997
6260
  function autoAnchor(handle) {
5998
6261
  const lines = [];
5999
- const candidates = ["claude-code", "codex"];
6000
- for (const platform of candidates) {
6001
- const file = anchorFilePath(platform);
6002
- if (file === null) continue;
6003
- const hostDir = path4.dirname(file);
6004
- if (!fs4.existsSync(hostDir)) continue;
6262
+ const ccFile = anchorFilePath("claude-code");
6263
+ if (ccFile !== null && fs5.existsSync(path5.dirname(ccFile))) {
6264
+ try {
6265
+ installAnchor("claude-code", handle);
6266
+ lines.push(` anchor claude-code: written \u2192 ${ccFile}`);
6267
+ } catch (err) {
6268
+ lines.push(` anchor claude-code: FAILED \u2014 ${String(err)}`);
6269
+ }
6270
+ }
6271
+ const codexAgents = anchorFilePath("codex");
6272
+ if (codexAgents !== null && fs5.existsSync(codexAgents)) {
6005
6273
  try {
6006
- const result = installAnchor(platform, handle);
6007
- lines.push(` anchor ${platform}: written \u2192 ${result.path}`);
6274
+ const existing = fs5.readFileSync(codexAgents, "utf-8");
6275
+ fs5.writeFileSync(codexAgents, upsertAnchorBlock(existing, renderCodexAgents(handle)), "utf-8");
6276
+ lines.push(` AGENTS.md codex: refreshed \u2192 ${codexAgents}`);
6008
6277
  } catch (err) {
6009
- lines.push(` anchor ${platform}: FAILED \u2014 ${String(err)}`);
6278
+ lines.push(` AGENTS.md codex: FAILED \u2014 ${String(err)}`);
6010
6279
  }
6011
6280
  }
6012
6281
  return lines;
@@ -6304,13 +6573,17 @@ async function runStatus(opts) {
6304
6573
  function runLogout() {
6305
6574
  const removed = clearCredentials();
6306
6575
  const reports = [];
6307
- for (const platform of ["claude-code", "codex"]) {
6308
- try {
6309
- const result = removeAnchor(platform);
6310
- if (result.action === "removed") reports.push(` anchor ${platform}: removed`);
6311
- } catch {
6312
- reports.push(` anchor ${platform}: could not clean up (remove the agentchat block manually)`);
6313
- }
6576
+ try {
6577
+ const result = removeAnchor("claude-code");
6578
+ if (result.action === "removed") reports.push(" Claude Code anchor: removed");
6579
+ } catch {
6580
+ reports.push(" Claude Code anchor: could not clean up (remove the agentchat block manually)");
6581
+ }
6582
+ try {
6583
+ const codexRemoved = removeCodex();
6584
+ if (codexRemoved.length > 0) reports.push(` Codex: removed ${codexRemoved.join(", ")}`);
6585
+ } catch {
6586
+ reports.push(" Codex: could not fully clean up (check ~/.codex/config.toml + hooks.json)");
6314
6587
  }
6315
6588
  console.log(
6316
6589
  [removed ? "Signed out \u2014 local credentials deleted." : "Nothing to sign out of.", ...reports].join(
@@ -6321,9 +6594,9 @@ function runLogout() {
6321
6594
  }
6322
6595
 
6323
6596
  // src/commands/install.ts
6324
- import * as fs5 from "fs";
6597
+ import * as fs6 from "fs";
6325
6598
  import * as os3 from "os";
6326
- import * as path5 from "path";
6599
+ import * as path6 from "path";
6327
6600
  import { spawnSync } from "child_process";
6328
6601
  var MARKETPLACE_SLUG = "agentchatme/agentchat-coding-agents";
6329
6602
  var PLUGIN_REF = "agentchat@agentchatme";
@@ -6340,11 +6613,11 @@ function defaultRun(cmd, args) {
6340
6613
  function binaryOnPath(binary, env) {
6341
6614
  const pathVar = env["PATH"] ?? "";
6342
6615
  const exts = process.platform === "win32" ? [".cmd", ".exe", ".bat", ""] : [""];
6343
- for (const dir of pathVar.split(path5.delimiter)) {
6616
+ for (const dir of pathVar.split(path6.delimiter)) {
6344
6617
  if (dir.length === 0) continue;
6345
6618
  for (const ext of exts) {
6346
6619
  try {
6347
- if (fs5.existsSync(path5.join(dir, binary + ext))) return true;
6620
+ if (fs6.existsSync(path6.join(dir, binary + ext))) return true;
6348
6621
  } catch {
6349
6622
  }
6350
6623
  }
@@ -6353,7 +6626,7 @@ function binaryOnPath(binary, env) {
6353
6626
  }
6354
6627
  function detectPlatforms(env, home) {
6355
6628
  return PROBES.filter(
6356
- (p) => binaryOnPath(p.binary, env) || fs5.existsSync(path5.join(home, p.configDir))
6629
+ (p) => binaryOnPath(p.binary, env) || fs6.existsSync(path6.join(home, p.configDir))
6357
6630
  );
6358
6631
  }
6359
6632
  async function runInstall(deps = {}) {
@@ -6391,11 +6664,19 @@ async function runInstall(deps = {}) {
6391
6664
  }
6392
6665
  break;
6393
6666
  }
6394
- case "codex":
6395
- console.log(
6396
- " Codex: detected \u2014 the AgentChat Codex packaging ships in the next release; this installer will wire it then."
6397
- );
6667
+ case "codex": {
6668
+ const bundleSrc = process.argv[1] ?? "";
6669
+ const handle = resolveIdentity()?.handle ?? null;
6670
+ try {
6671
+ const { actions, warnings } = installCodex(bundleSrc, handle);
6672
+ console.log(` Codex: wired \u2713 (${actions.join(", ") || "no changes"})`);
6673
+ for (const w of warnings) console.log(` \u26A0 ${w}`);
6674
+ } catch (err) {
6675
+ failures++;
6676
+ console.log(` Codex: wiring failed \u2014 ${String(err)}`);
6677
+ }
6398
6678
  break;
6679
+ }
6399
6680
  case "cursor":
6400
6681
  console.log(
6401
6682
  " Cursor: detected \u2014 the AgentChat Cursor packaging ships in the next release; this installer will wire it then."
@@ -6419,11 +6700,11 @@ async function runInstall(deps = {}) {
6419
6700
  }
6420
6701
 
6421
6702
  // src/commands/doctor.ts
6422
- import * as fs6 from "fs";
6423
- import * as path6 from "path";
6703
+ import * as fs7 from "fs";
6704
+ import * as path7 from "path";
6424
6705
 
6425
6706
  // src/version.ts
6426
- var VERSION2 = "0.0.1";
6707
+ var VERSION2 = "0.0.131";
6427
6708
 
6428
6709
  // src/commands/doctor.ts
6429
6710
  function fmt(check) {
@@ -6489,8 +6770,8 @@ async function runDoctor() {
6489
6770
  for (const platform of ["claude-code", "codex"]) {
6490
6771
  const file = anchorFilePath(platform);
6491
6772
  if (file === null) continue;
6492
- const hostDir = path6.dirname(file);
6493
- if (!fs6.existsSync(hostDir)) {
6773
+ const hostDir = path7.dirname(file);
6774
+ if (!fs7.existsSync(hostDir)) {
6494
6775
  checks.push({ name: `anchor-${platform}`, verdict: "PASS", detail: `${hostDir} absent (host not installed) \u2014 skipped` });
6495
6776
  } else {
6496
6777
  checks.push({
@@ -6501,8 +6782,8 @@ async function runDoctor() {
6501
6782
  }
6502
6783
  }
6503
6784
  try {
6504
- fs6.mkdirSync(agentchatHome(), { recursive: true });
6505
- fs6.accessSync(agentchatHome(), fs6.constants.W_OK);
6785
+ fs7.mkdirSync(agentchatHome(), { recursive: true });
6786
+ fs7.accessSync(agentchatHome(), fs7.constants.W_OK);
6506
6787
  checks.push({ name: "state", verdict: "PASS", detail: `${statePath()} writable` });
6507
6788
  } catch {
6508
6789
  checks.push({ name: "state", verdict: "FAIL", detail: `${agentchatHome()} is not writable` });
@@ -6640,7 +6921,11 @@ async function main(argv = process.argv.slice(2)) {
6640
6921
  await runStopHook(platform);
6641
6922
  return 0;
6642
6923
  }
6643
- console.error("Usage: agentchat hook <session-start|stop> --platform <claude-code|codex|cursor>");
6924
+ if (subcommand === "user-prompt") {
6925
+ await runUserPromptHook(platform);
6926
+ return 0;
6927
+ }
6928
+ console.error("Usage: agentchat hook <session-start|stop|user-prompt> --platform <claude-code|codex|cursor>");
6644
6929
  return 1;
6645
6930
  }
6646
6931
  default: