airchat 1.0.0 → 1.1.0

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.
Files changed (2) hide show
  1. package/dist/index.js +62 -20
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -3337,7 +3337,7 @@ import * as fs4 from "node:fs";
3337
3337
  import * as path4 from "node:path";
3338
3338
  import * as os3 from "node:os";
3339
3339
  import * as crypto2 from "node:crypto";
3340
- import { execSync as execSync2 } from "node:child_process";
3340
+ import { execSync as execSync2, execFileSync as execFileSync2 } from "node:child_process";
3341
3341
  import { createClient } from "@supabase/supabase-js";
3342
3342
 
3343
3343
  // ../../node_modules/commander/esm.mjs
@@ -3486,6 +3486,20 @@ function unwrapEnvelope(body) {
3486
3486
  }
3487
3487
  return body;
3488
3488
  }
3489
+ var AirChatHttpError = class extends Error {
3490
+ status;
3491
+ /** From the `Retry-After` header, in ms. Undefined when absent or unparseable. */
3492
+ retryAfterMs;
3493
+ constructor(message, status2, headers) {
3494
+ super(message);
3495
+ this.name = "AirChatHttpError";
3496
+ this.status = status2;
3497
+ const seconds = Number(headers?.get("retry-after"));
3498
+ if (Number.isFinite(seconds) && seconds > 0) {
3499
+ this.retryAfterMs = seconds * 1e3;
3500
+ }
3501
+ }
3502
+ };
3489
3503
  var AirChatRestClient = class _AirChatRestClient {
3490
3504
  webUrl;
3491
3505
  machineName;
@@ -3621,6 +3635,20 @@ var AirChatRestClient = class _AirChatRestClient {
3621
3635
  mention_ids: mentionIds
3622
3636
  });
3623
3637
  }
3638
+ // ── Public: channel read cursors ────────────────────────────────────────
3639
+ /** Assert this agent has read `channel` through `through` (default: now). */
3640
+ async markChannelRead(channel, through) {
3641
+ return this.request("POST", "/api/v2/channels/read", void 0, {
3642
+ channel,
3643
+ ...through ? { through } : {}
3644
+ });
3645
+ }
3646
+ /** Who has acknowledged reading `channel`, through when. */
3647
+ async channelReadStatus(channel) {
3648
+ const params = new URLSearchParams();
3649
+ params.set("channel", channel);
3650
+ return this.request("GET", "/api/v2/channels/read", params);
3651
+ }
3624
3652
  // ── Public: direct messages ─────────────────────────────────────────────
3625
3653
  async sendDirectMessage(targetAgent, content) {
3626
3654
  return this.request("POST", "/api/v2/dm", void 0, {
@@ -3772,17 +3800,17 @@ var AirChatRestClient = class _AirChatRestClient {
3772
3800
  const result = await this.doFetch(method, pathname, params, body);
3773
3801
  if (result.status === 401) {
3774
3802
  this.derivedKey = null;
3775
- await this.register();
3803
+ await this.register(true);
3776
3804
  const retry = await this.doFetch(method, pathname, params, body);
3777
3805
  if (!retry.ok) {
3778
3806
  const text = await retry.text().catch(() => "");
3779
- throw new Error(`AirChat API ${method} ${pathname} failed after re-registration: HTTP ${retry.status} \u2014 ${text}`);
3807
+ throw new AirChatHttpError(`AirChat API ${method} ${pathname} failed after re-registration: HTTP ${retry.status} \u2014 ${text}`, retry.status, retry.headers);
3780
3808
  }
3781
3809
  return unwrapEnvelope(await retry.json());
3782
3810
  }
3783
3811
  if (!result.ok) {
3784
3812
  const text = await result.text().catch(() => "");
3785
- throw new Error(`AirChat API ${method} ${pathname} failed: HTTP ${result.status} \u2014 ${text}`);
3813
+ throw new AirChatHttpError(`AirChat API ${method} ${pathname} failed: HTTP ${result.status} \u2014 ${text}`, result.status, result.headers);
3786
3814
  }
3787
3815
  return unwrapEnvelope(await result.json());
3788
3816
  }
@@ -3816,8 +3844,16 @@ var AirChatRestClient = class _AirChatRestClient {
3816
3844
  this.registering = null;
3817
3845
  }
3818
3846
  }
3819
- async register() {
3820
- const cachedKey = this.loadCachedKey();
3847
+ /**
3848
+ * @param ignoreCache Skip the on-disk key and register with the server for
3849
+ * real. Required when recovering from a 401: the cached key *is* the
3850
+ * credential the server just rejected, so reloading it sends the same bad
3851
+ * key again. Without this the retry path could never recover — it reported
3852
+ * "failed after re-registration" having made no registration request at
3853
+ * all, which is how deactivated agents came to look permanently broken.
3854
+ */
3855
+ async register(ignoreCache = false) {
3856
+ const cachedKey = ignoreCache ? null : this.loadCachedKey();
3821
3857
  if (cachedKey) {
3822
3858
  this.derivedKey = cachedKey;
3823
3859
  return;
@@ -3847,7 +3883,7 @@ var AirChatRestClient = class _AirChatRestClient {
3847
3883
  }
3848
3884
  if (!res.ok) {
3849
3885
  const body = await res.text().catch(() => "");
3850
- throw new Error(`Registration failed: HTTP ${res.status} \u2014 ${body}`);
3886
+ throw new AirChatHttpError(`Registration failed: HTTP ${res.status} \u2014 ${body}`, res.status, res.headers);
3851
3887
  }
3852
3888
  this.saveCachedKey(derivedKey);
3853
3889
  this.derivedKey = derivedKey;
@@ -4681,7 +4717,7 @@ async function run(argv = process.argv) {
4681
4717
  // src/harnesses.ts
4682
4718
  import * as fs3 from "node:fs";
4683
4719
  import * as path3 from "node:path";
4684
- import { execSync } from "node:child_process";
4720
+ import { execSync, execFileSync } from "node:child_process";
4685
4721
  function binaryExists(bin) {
4686
4722
  try {
4687
4723
  execSync(process.platform === "win32" ? `where ${bin}` : `command -v ${bin}`, { stdio: "pipe" });
@@ -4726,14 +4762,14 @@ function mergeJsonFile(filePath, mutate, manualSnippet) {
4726
4762
  fs3.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n");
4727
4763
  return { ok: true, message: filePath };
4728
4764
  }
4729
- function tryCliRegister(cmd, manualSnippet) {
4765
+ function tryCliRegister(bin, argv, manualSnippet) {
4730
4766
  try {
4731
- execSync(cmd, { stdio: "pipe" });
4767
+ execFileSync(bin, argv, { stdio: "pipe" });
4732
4768
  return { ok: true, message: "Registered via CLI" };
4733
4769
  } catch {
4734
4770
  return {
4735
4771
  ok: false,
4736
- message: `Could not run "${cmd.split(" ").slice(0, 3).join(" ")} \u2026". Add manually:`,
4772
+ message: `Could not run "${[bin, ...argv.slice(0, 2)].join(" ")} \u2026". Add manually:`,
4737
4773
  manualSnippet
4738
4774
  };
4739
4775
  }
@@ -4746,8 +4782,9 @@ var HARNESSES = [
4746
4782
  detect: (home, hasBinary) => fs3.existsSync(path3.join(home, ".claude")) || hasBinary("claude"),
4747
4783
  registerMcp(launch) {
4748
4784
  const { command, args } = launchParts(launch);
4749
- const cmd = `claude mcp add airchat -s user -- "${command}" ${args.map((a) => `"${a}"`).join(" ")}`;
4750
- return tryCliRegister(cmd, cmd);
4785
+ const argv = ["mcp", "add", "airchat", "-s", "user", "--", command, ...args];
4786
+ const display = `claude mcp add airchat -s user -- "${command}" ${args.map((a) => `"${a}"`).join(" ")}`;
4787
+ return tryCliRegister("claude", argv, display);
4751
4788
  }
4752
4789
  },
4753
4790
  {
@@ -4757,14 +4794,14 @@ var HARNESSES = [
4757
4794
  detect: (home, hasBinary) => fs3.existsSync(path3.join(home, ".codex")) || hasBinary("codex"),
4758
4795
  registerMcp(launch) {
4759
4796
  const { command, args } = launchParts(launch);
4760
- const cmd = `codex mcp add airchat -- "${command}" ${args.map((a) => `"${a}"`).join(" ")}`;
4797
+ const argv = ["mcp", "add", "airchat", "--", command, ...args];
4761
4798
  const toml = [
4762
4799
  "# ~/.codex/config.toml",
4763
4800
  "[mcp_servers.airchat]",
4764
4801
  `command = "${command}"`,
4765
4802
  `args = [${args.map((a) => `"${a}"`).join(", ")}]`
4766
4803
  ].join("\n");
4767
- return tryCliRegister(cmd, toml);
4804
+ return tryCliRegister("codex", argv, toml);
4768
4805
  }
4769
4806
  },
4770
4807
  {
@@ -4865,7 +4902,7 @@ function installInstructions(filePath, content) {
4865
4902
  }
4866
4903
 
4867
4904
  // src/index.ts
4868
- var AIRCHAT_VERSION = true ? "1.0.0" : "dev";
4905
+ var AIRCHAT_VERSION = true ? "1.1.0" : "dev";
4869
4906
  var DEFAULT_AIRCHAT_URL2 = "https://supernode-web-production.up.railway.app";
4870
4907
  var GREEN = "\x1B[32m";
4871
4908
  var YELLOW = "\x1B[33m";
@@ -5091,7 +5128,9 @@ async function cloneAndInstall(config) {
5091
5128
  }
5092
5129
  try {
5093
5130
  console.log(` Cloning airchat...`);
5094
- execSync2(`git clone https://github.com/prone/airchat.git "${config.airchatDir}"`, { stdio: "pipe" });
5131
+ execFileSync2("git", ["clone", "https://github.com/prone/airchat.git", config.airchatDir], {
5132
+ stdio: "pipe"
5133
+ });
5095
5134
  console.log(` Installing dependencies...`);
5096
5135
  execSync2("npm install", { cwd: config.airchatDir, stdio: "pipe" });
5097
5136
  return { name, ok: true, message: "Cloned and installed" };
@@ -5114,7 +5153,7 @@ async function runMigrations(config) {
5114
5153
  }
5115
5154
  try {
5116
5155
  execSync2("which supabase", { stdio: "pipe" });
5117
- execSync2(`supabase db push --db-url "${config.supabaseUrl}"`, {
5156
+ execFileSync2("supabase", ["db", "push", "--db-url", config.supabaseUrl], {
5118
5157
  cwd: config.airchatDir,
5119
5158
  stdio: "pipe"
5120
5159
  });
@@ -5150,7 +5189,9 @@ async function runMigrations(config) {
5150
5189
  const files = fs4.readdirSync(migrationsDir).filter((f) => f.endsWith(".sql")).sort();
5151
5190
  try {
5152
5191
  for (const file of files) {
5153
- execSync2(`psql "${config.databaseUrl}" -f "${path4.join(migrationsDir, file)}"`, { stdio: "pipe" });
5192
+ execFileSync2("psql", [config.databaseUrl, "-f", path4.join(migrationsDir, file)], {
5193
+ stdio: "pipe"
5194
+ });
5154
5195
  }
5155
5196
  return { name, ok: true, message: `Applied ${files.length} migrations` };
5156
5197
  } catch (e) {
@@ -5284,7 +5325,8 @@ function writeWebEnv(config) {
5284
5325
  if (config.databaseUrl) {
5285
5326
  lines.push(`DATABASE_URL=${config.databaseUrl}`);
5286
5327
  }
5287
- fs4.writeFileSync(envPath, lines.join("\n") + "\n");
5328
+ fs4.writeFileSync(envPath, lines.join("\n") + "\n", { mode: 384 });
5329
+ fs4.chmodSync(envPath, 384);
5288
5330
  return { name, ok: true, message: envPath };
5289
5331
  } catch (e) {
5290
5332
  return { name, ok: false, message: e.message };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airchat",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "AirChat \u2014 one command to set up and use a shared message board for your AI agents",
6
6
  "license": "MIT",
@@ -28,7 +28,7 @@
28
28
  "@airchat/shared": "*",
29
29
  "@types/node": "^22.0.0",
30
30
  "esbuild": "^0.28.1",
31
- "tsx": "^4.23.1",
31
+ "tsx": "^4.23.7",
32
32
  "typescript": "^6.0.0"
33
33
  }
34
34
  }