@inetafrica/open-claudia 2.14.1 → 2.14.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## v2.14.2
4
+ - **Token validation now hits the right service — the previous probe could never succeed.** v2.14.0/.1 validated a Kazee bot token by `GET {host}/api/me`, but chat-central has **no `/me` route** (confirmed against `src/config/routes.ts`) — every attempt 404'd. chat-central doesn't resolve identity itself; it delegates on every request to `@libraries/inet-central-auth`, which POSTs the opaque `kzb_…` token to **Central's `POST /api/principal/verify`**. `core/kazee-probe.js` now calls that endpoint: one request both **validates the token and returns the bot's own user id** (`principal.id`, with `principal.kind === "Bot"` enforced) plus its display name. Verified live end-to-end (real token → `ok` + botUserId + "Maya Bot"; bad token → clean 401).
5
+ - **Central is hard-coded too (env-overridable).** New `CENTRAL_DEFAULT_URL = "https://central.inet.africa"` mirrors chat-central's own default; override with `CENTRAL_BASE_URL` for non-prod. The wizard now says "Verifying the bot token…" and greets the bot by name ("Connected as Maya Bot ✅"); `setup.js` prints "Verified as <name>."
6
+ - **Agent-space / OpenClaw:** no new deps, no new required env, no schema change; Docker image builds identically. Correctness fix to the onboarding probe — the wizard flow (token-first, listen-to-capture owner) is unchanged.
7
+
3
8
  ## v2.14.1
4
9
  - **The Kazee host URL is no longer asked — it's fixed infrastructure, like Telegram's `api.telegram.org`.** v2.14.0 fixed the probe but still made Step 1 ask the operator to type the host; the Kazee host never varies, so prompting for it is pure friction (and a chance to get it wrong). `core/config.js` now defines `KAZEE_DEFAULT_URL = "https://chat.inet.africa"` and `loadChannels` falls back to it whenever `KAZEE_URL` is unset. `/channel add kazee` is now **two steps, token-first**: Step 1 asks only for the bot token, Step 2 is the listen-to-capture owner flow. `setup.js` likewise drops its URL prompt (default inlined, since `config.js` exits before an `.env` exists). An explicit `KAZEE_URL` in `.env` still overrides, so nothing breaks for a deployment that set one.
5
10
  - **Agent-space / OpenClaw:** no new deps, no new required env, no schema change; Docker image builds identically. Pure UX fix to the onboarding wizard.
@@ -93,7 +93,7 @@ async function handleText(envelope) {
93
93
  }
94
94
  session.data.token = text;
95
95
  session.step = "validating";
96
- await send("Validating against the Kazee API…");
96
+ await send("Verifying the bot token…");
97
97
  const check = await validateKazee({ url: session.data.url, token: session.data.token });
98
98
  if (!check.ok) {
99
99
  sessions.delete(envelope.canonicalUserId);
@@ -102,6 +102,7 @@ async function handleText(envelope) {
102
102
  }
103
103
  session.data.url = check.url || session.data.url.replace(/\/+$/, "");
104
104
  session.data.botUserId = check.botUserId || "";
105
+ session.data.botName = check.botName || "";
105
106
 
106
107
  // Bring the adapter live now so it can receive the owner's message.
107
108
  const live = await bringLive(session);
@@ -111,8 +112,11 @@ async function handleText(envelope) {
111
112
  return;
112
113
  }
113
114
  session.step = "awaiting-owner-message";
115
+ const connected = session.data.botName
116
+ ? `Connected as ${session.data.botName} ✅`
117
+ : "Connected to Kazee ✅";
114
118
  await send(
115
- "Connected to Kazee ✅\n\nStep 2 of 2: open Kazee and send me any message — I'll grab your account and confirm it here.\n\n(Or send \"skip\" to leave the owner unset.)",
119
+ `${connected}\n\nStep 2 of 2: open Kazee and send me any message — I'll grab your account and confirm it here.\n\n(Or send "skip" to leave the owner unset.)`,
116
120
  { keyboard: cancelKeyboard },
117
121
  );
118
122
  return;
@@ -1,57 +1,85 @@
1
- // Standalone Kazee GET /me probe. Lives outside the adapter so setup.js
2
- // can use it before .env exists (channel-wizard, which is the other
3
- // consumer, imports it indirectly).
1
+ // Standalone Kazee bot-token probe. Lives outside the adapter so setup.js
2
+ // can use it before .env exists (channel-wizard imports it too).
3
+ //
4
+ // A Kazee bot token is an inet-central OPAQUE token (kzb_…). chat-central
5
+ // itself has NO /me endpoint — it delegates auth on every request to
6
+ // inet-central-auth, which POSTs the token to Central's
7
+ // `/api/principal/verify`. That single call both validates the token and
8
+ // returns the principal, whose `id` IS the bot's Kazee user id and whose
9
+ // `kind` must be "Bot". So we validate against Central, not chat-central.
10
+ // (Verified live 2026-07-09: principal { id, kind:"Bot", firstName, … }.)
4
11
 
5
12
  const https = require("https");
6
13
  const http = require("http");
7
14
  const { URL } = require("url");
8
15
 
9
- // The adapter mounts every REST route under {root}/api and talks socket.io
10
- // at {root}. Callers may hand us either the bare root host or a URL that
11
- // already ends in /api — normalise both to the root so we always probe the
12
- // real {root}/api/me and the caller can store one canonical KAZEE_URL.
16
+ // Central is fixed infrastructure like the Kazee host it never varies, so
17
+ // it's hard-coded (env override for non-prod). Mirrors chat-central's own
18
+ // default (src/config/inet-central-auth.ts).
19
+ const CENTRAL_DEFAULT_URL = "https://central.inet.africa";
20
+
21
+ // The adapter mounts REST under {root}/api and talks socket.io at {root}.
22
+ // Callers may hand us either the bare root or a URL already ending in /api —
23
+ // normalise both to the root so we store one canonical KAZEE_URL.
13
24
  function normalizeKazeeRoot(url) {
14
25
  return String(url || "").trim().replace(/\/+$/, "").replace(/\/api$/i, "");
15
26
  }
16
27
 
17
- function validateKazee({ url, token }) {
28
+ // Validate a Kazee bot token and resolve the bot's own user id.
29
+ // url Kazee host (only normalised + echoed back for storage)
30
+ // token the kzb_… bot token to verify
31
+ // centralUrl override Central base (defaults to env or CENTRAL_DEFAULT_URL)
32
+ // Resolves { ok:true, url, botUserId, botName } or { ok:false, error }.
33
+ function validateKazee({ url, token, centralUrl }) {
18
34
  return new Promise((resolve) => {
19
35
  const root = normalizeKazeeRoot(url);
36
+ const central = String(centralUrl || process.env.CENTRAL_BASE_URL || CENTRAL_DEFAULT_URL)
37
+ .trim().replace(/\/+$/, "");
20
38
  let u;
21
- try { u = new URL(root + "/api/me"); }
22
- catch (e) { return resolve({ ok: false, error: `Bad URL: ${e.message}` }); }
39
+ try { u = new URL(central + "/api/principal/verify"); }
40
+ catch (e) { return resolve({ ok: false, error: `Bad Central URL: ${e.message}` }); }
41
+
42
+ const payload = JSON.stringify({ token: String(token || "") });
23
43
  const lib = u.protocol === "https:" ? https : http;
24
44
  const req = lib.request({
25
- method: "GET",
45
+ method: "POST",
26
46
  hostname: u.hostname,
27
47
  port: u.port || (u.protocol === "https:" ? 443 : 80),
28
48
  path: u.pathname + (u.search || ""),
29
- headers: { Authorization: `Bearer ${token}`, Accept: "application/json" },
49
+ headers: {
50
+ "Content-Type": "application/json",
51
+ "Content-Length": Buffer.byteLength(payload),
52
+ Accept: "application/json",
53
+ },
30
54
  }, (res) => {
31
55
  let chunks = "";
32
56
  res.on("data", (c) => { chunks += c; });
33
57
  res.on("end", () => {
34
- if (res.statusCode >= 200 && res.statusCode < 300) {
35
- try {
36
- const body = chunks ? JSON.parse(chunks) : {};
37
- const botUserId = body?.user?._id || body?.user?.id || body?._id || "";
38
- resolve({ ok: true, botUserId, url: root });
39
- } catch (e) {
40
- resolve({ ok: true, botUserId: "", url: root });
41
- }
42
- } else if (res.statusCode === 401 || res.statusCode === 403) {
43
- resolve({ ok: false, error: "Token rejected (401/403). Check that it's a valid Kazee bot token." });
44
- } else if (res.statusCode === 404) {
45
- resolve({ ok: false, error: "GET /api/me returned 404. Check the URL points at your Kazee host (e.g. https://chat.example.com)." });
46
- } else {
47
- resolve({ ok: false, error: `GET /api/me HTTP ${res.statusCode}` });
58
+ let body = null;
59
+ try { body = chunks ? JSON.parse(chunks) : null; } catch (e) { body = null; }
60
+
61
+ if (res.statusCode < 200 || res.statusCode >= 300) {
62
+ const detail = body && body.error ? `: ${body.error}` : "";
63
+ return resolve({ ok: false, error: `Central rejected the token (HTTP ${res.statusCode})${detail}. Check the bot token.` });
64
+ }
65
+ if (!body || !body.success || !body.valid || !body.principal) {
66
+ const detail = body && body.error ? `: ${body.error}` : "";
67
+ return resolve({ ok: false, error: `Token not valid${detail}. Paste a current Kazee bot token.` });
68
+ }
69
+ const p = body.principal;
70
+ if (p.kind !== "Bot") {
71
+ return resolve({ ok: false, error: `That token is a ${p.kind || "non-bot"} principal, not a bot. Paste a bot token (kzb_…).` });
48
72
  }
73
+ const botUserId = String(p.id || p._id || p.userId || "");
74
+ const botName = p.name || [p.firstName, p.lastName].filter(Boolean).join(" ") || "";
75
+ resolve({ ok: true, url: root, botUserId, botName });
49
76
  });
50
77
  });
51
78
  req.on("error", (e) => resolve({ ok: false, error: e.message }));
52
- req.setTimeout(15000, () => req.destroy(new Error("Timed out after 15s")));
79
+ req.setTimeout(15000, () => req.destroy(new Error("Central verify timed out after 15s")));
80
+ req.write(payload);
53
81
  req.end();
54
82
  });
55
83
  }
56
84
 
57
- module.exports = { validateKazee, normalizeKazeeRoot };
85
+ module.exports = { validateKazee, normalizeKazeeRoot, CENTRAL_DEFAULT_URL };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "2.14.1",
3
+ "version": "2.14.2",
4
4
  "description": "Your always-on AI coding assistant — Claude Code, Cursor Agent, and OpenAI Codex via Telegram or Kazee Chat",
5
5
  "main": "bot.js",
6
6
  "bin": {
package/setup.js CHANGED
@@ -681,7 +681,7 @@ async function main() {
681
681
  while (true) {
682
682
  const token = (await ask(" Kazee bot token: ")).trim();
683
683
  if (!token) { console.log(" Empty token."); continue; }
684
- console.log(" Validating against Kazee /api/me…");
684
+ console.log(" Verifying the bot token…");
685
685
  const check = await validateKazee({ url, token });
686
686
  if (!check.ok) {
687
687
  console.log(` Validation failed: ${check.error}`);
@@ -689,6 +689,7 @@ async function main() {
689
689
  if (retry.toLowerCase() === "n") break;
690
690
  continue;
691
691
  }
692
+ if (check.botName) console.log(` Verified as ${check.botName}.`);
692
693
  const ownerUserId = (await ask(" Kazee owner user id (blank to skip): ")).trim();
693
694
  state.data.kazee = { url: check.url || url.replace(/\/+$/, ""), token, ownerUserId, botUserId: check.botUserId || "" };
694
695
  console.log(" Kazee channel configured.");