huddoai 0.5.0 → 0.6.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/huddo.mjs +106 -5
  2. package/package.json +1 -1
package/huddo.mjs CHANGED
@@ -5216,6 +5216,12 @@ var pick = (list2, random) => list2[Math.floor(random() * list2.length)];
5216
5216
  function randomAgentName(random = Math.random) {
5217
5217
  return `Agent ${pick(NOUNS, random)}`;
5218
5218
  }
5219
+ var MAX_DISPLAY_NAME_CHARS = 32;
5220
+ function displayNameError(name) {
5221
+ const chars = [...name.trim()].length;
5222
+ if (chars === 0) return "display name is empty";
5223
+ return chars > MAX_DISPLAY_NAME_CHARS ? `display name is ${chars} characters; the limit is ${MAX_DISPLAY_NAME_CHARS}` : null;
5224
+ }
5219
5225
 
5220
5226
  // cli/membership.ts
5221
5227
  var SPACE_KINDS = /* @__PURE__ */ new Set(["redeem_invite_capability", "accept_space_invite", "leave_space", "remove_from_space"]);
@@ -5760,7 +5766,12 @@ function account(ctx) {
5760
5766
  throw new UserError(errorText(e));
5761
5767
  }
5762
5768
  }
5769
+ function checkName(name) {
5770
+ const error = name?.trim() ? displayNameError(name) : null;
5771
+ if (error) throw new UserError(error);
5772
+ }
5763
5773
  async function createIdentity(rt, displayName) {
5774
+ checkName(displayName);
5764
5775
  const username = `guest-${Array.from(rt.crypto.generateRandomBytes(5), (b) => b.toString(16).padStart(2, "0")).join("")}`;
5765
5776
  const name = displayName?.trim() || randomAgentName();
5766
5777
  const result = await signup(
@@ -5885,6 +5896,7 @@ function formatBatch(batch, withRoom) {
5885
5896
  return batch.groups.flatMap((g) => g.rows.map((r) => formatRow(r, batch.names, withRoom ? `[${g.room.name} ${g.room.spaceId}] ` : "", operatorTag(r.sender, pairs, batch.self), batch.parents))).join("\n");
5886
5897
  }
5887
5898
  async function join5(ctx, invite2, name) {
5899
+ checkName(name);
5888
5900
  const code = inviteCode(invite2);
5889
5901
  let preview;
5890
5902
  try {
@@ -6517,7 +6529,8 @@ async function identityUse(slug) {
6517
6529
  }
6518
6530
  async function updateName(ctx, name) {
6519
6531
  const trimmed = name.trim();
6520
- if (!trimmed) throw new UserError("display name is empty");
6532
+ const error = displayNameError(trimmed);
6533
+ if (error) throw new UserError(error);
6521
6534
  const acct = account(ctx);
6522
6535
  await updateProfile(acct.http, acct.identity.slug, { display_name: trimmed });
6523
6536
  rememberNames({ [acct.identity.slug]: trimmed });
@@ -6599,7 +6612,7 @@ Humans: just open the invite link in a browser.
6599
6612
  Other tools: huddo_read, huddo_list, huddo_new, huddo_invite,
6600
6613
  huddo_members, huddo_status, huddo_whoami, huddo_update_name,
6601
6614
  huddo_update_avatar, huddo_download, huddo_whisper, huddo_pair, huddo_pair_check,
6602
- huddo_block, huddo_leave; owners: huddo_archive, huddo_kick, huddo_limits.
6615
+ huddo_block, huddo_leave, huddo_update; owners: huddo_archive, huddo_kick, huddo_limits.
6603
6616
  Optional env: HUDDO_HOME=<state dir>.
6604
6617
 
6605
6618
  2. CLI (Node 20+)
@@ -6626,7 +6639,84 @@ Humans: just open the invite link in a browser.
6626
6639
  }
6627
6640
 
6628
6641
  // cli/version.ts
6629
- var VERSION = "0.5.0";
6642
+ var VERSION = "0.6.0";
6643
+
6644
+ // cli/update.ts
6645
+ import { createHash as createHash2, createPublicKey, verify } from "node:crypto";
6646
+ import { realpathSync, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "node:fs";
6647
+
6648
+ // cli/release-key.ts
6649
+ var RELEASE_PUBLIC_KEY = "r0QMGedfn12PCAQFtc5Em02EyOBsQfTJdZBxwZjECW8=";
6650
+
6651
+ // cli/update.ts
6652
+ var ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
6653
+ var UpdateError = class extends Error {
6654
+ };
6655
+ function isNewer(candidate, current) {
6656
+ const parse = (v) => v.split(".").map((part) => Number.parseInt(part, 10) || 0);
6657
+ const [a, b] = [parse(candidate), parse(current)];
6658
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
6659
+ if ((a[i] ?? 0) !== (b[i] ?? 0)) return (a[i] ?? 0) > (b[i] ?? 0);
6660
+ }
6661
+ return false;
6662
+ }
6663
+ function verifyRelease(bytes, manifest, publicKey = RELEASE_PUBLIC_KEY) {
6664
+ if (createHash2("sha256").update(bytes).digest("hex") !== manifest.sha256) return false;
6665
+ const key = createPublicKey({
6666
+ key: Buffer.concat([ED25519_SPKI_PREFIX, Buffer.from(publicKey, "base64")]),
6667
+ format: "der",
6668
+ type: "spki"
6669
+ });
6670
+ try {
6671
+ return verify(null, bytes, key, Buffer.from(manifest.signature, "base64"));
6672
+ } catch {
6673
+ return false;
6674
+ }
6675
+ }
6676
+ function installedViaPackageManager(scriptPath2) {
6677
+ return /[\\/](_npx|node_modules)[\\/]/.test(scriptPath2);
6678
+ }
6679
+ async function fetchManifest() {
6680
+ const res = await fetch(`${ORIGIN}/cli/version.json`);
6681
+ if (!res.ok) throw new UpdateError(`could not read ${ORIGIN}/cli/version.json (HTTP ${res.status})`);
6682
+ const manifest = await res.json();
6683
+ if (!manifest.version || !manifest.sha256 || !manifest.signature) {
6684
+ throw new UpdateError("the published release is not signed; refusing to update");
6685
+ }
6686
+ return manifest;
6687
+ }
6688
+ async function update(opts2 = {}) {
6689
+ const manifest = await fetchManifest();
6690
+ if (!isNewer(manifest.version, VERSION)) {
6691
+ return { json: { current: VERSION, latest: manifest.version, updated: false }, text: `huddo ${VERSION} is up to date` };
6692
+ }
6693
+ if (opts2.check) {
6694
+ return {
6695
+ json: { current: VERSION, latest: manifest.version, updated: false },
6696
+ text: `update available: ${VERSION} -> ${manifest.version} (run: huddo update)`
6697
+ };
6698
+ }
6699
+ const script = realpathSync(opts2.scriptPath ?? process.argv[1]);
6700
+ if (installedViaPackageManager(script)) {
6701
+ return {
6702
+ json: { current: VERSION, latest: manifest.version, updated: false },
6703
+ text: `update available: ${VERSION} -> ${manifest.version}. This copy was installed by npm/npx; run it as \`npx -y huddoai@latest\` (restart your MCP client to pick it up).`
6704
+ };
6705
+ }
6706
+ const res = await fetch(`${ORIGIN}/cli/huddo.mjs`);
6707
+ if (!res.ok) throw new UpdateError(`download failed (HTTP ${res.status})`);
6708
+ const bytes = new Uint8Array(await res.arrayBuffer());
6709
+ if (!verifyRelease(bytes, manifest)) {
6710
+ throw new UpdateError("the downloaded file does not match the signed release; not installing it");
6711
+ }
6712
+ const staged = `${script}.new`;
6713
+ writeFileSync3(staged, bytes);
6714
+ renameSync2(staged, script);
6715
+ return {
6716
+ json: { previous: VERSION, current: manifest.version, updated: true, path: script },
6717
+ text: `updated huddo ${VERSION} -> ${manifest.version} (${script}). Run \`huddo daemon stop\` so the background daemon restarts on the new version; restart your MCP client if it runs this file.`
6718
+ };
6719
+ }
6630
6720
 
6631
6721
  // cli/mcp.ts
6632
6722
  var PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
@@ -6820,6 +6910,12 @@ ${body || "(no messages yet)"}`;
6820
6910
  properties: { name: { type: "string", description: "Group name" }, ...asProp },
6821
6911
  run: async (ctx, a) => (await newHuddo(ctx, str(a, "name"))).text
6822
6912
  },
6913
+ {
6914
+ name: "huddo_update",
6915
+ description: "Check for a newer Huddo CLI/MCP release and, when this server runs from a downloaded huddo.mjs, install it after verifying its signature (restart the MCP client afterwards). npm/npx installs are told to use huddoai@latest instead. check=true only reports.",
6916
+ properties: { check: { type: "boolean", description: "Only report whether an update is available" } },
6917
+ run: async (_ctx, a) => (await update({ check: a.check === true })).text
6918
+ },
6823
6919
  {
6824
6920
  name: "huddo_whoami",
6825
6921
  description: "Show the identity: slug (others mention you as @slug), display name, avatar, default room.",
@@ -6947,7 +7043,7 @@ async function runMcpServer() {
6947
7043
  }
6948
7044
 
6949
7045
  // cli/daemon.ts
6950
- import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync3, renameSync as renameSync2, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
7046
+ import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync3, renameSync as renameSync3, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
6951
7047
  import { randomBytes, timingSafeEqual } from "node:crypto";
6952
7048
  import { createServer, connect as connect2 } from "node:net";
6953
7049
  import { join as join6, resolve as resolve2 } from "node:path";
@@ -6965,7 +7061,7 @@ function log(message) {
6965
7061
  if (foreground) process.stderr.write(line);
6966
7062
  try {
6967
7063
  const path = daemonLogPath();
6968
- if (existsSync4(path) && statSync2(path).size > LOG_CAP_BYTES) renameSync2(path, `${path}.1`);
7064
+ if (existsSync4(path) && statSync2(path).size > LOG_CAP_BYTES) renameSync3(path, `${path}.1`);
6969
7065
  appendFileSync(path, line);
6970
7066
  } catch {
6971
7067
  }
@@ -7559,6 +7655,7 @@ usage: huddo <command> [options]
7559
7655
 
7560
7656
  help ways to connect to Huddo (MCP, CLI, skill, browser)
7561
7657
  version, -v print the CLI version
7658
+ update [--check] install the latest signed release over this file
7562
7659
 
7563
7660
  core:
7564
7661
  join <invite-url-or-code> [--name NAME]
@@ -7716,6 +7813,10 @@ async function main() {
7716
7813
  await runMcpServer();
7717
7814
  return;
7718
7815
  }
7816
+ if (command === "update") {
7817
+ print(await update({ check: opts.check }));
7818
+ return;
7819
+ }
7719
7820
  if (command === "daemon") {
7720
7821
  await daemonCommand(args[0]);
7721
7822
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huddoai",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "MCP server and CLI for Huddo, a group chat where people and AI agents talk in the same room",
5
5
  "mcpName": "ai.huddo/huddo",
6
6
  "license": "MIT",