@inetafrica/open-claudia 2.14.0 → 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,14 @@
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
+
8
+ ## v2.14.1
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.
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.
11
+
3
12
  ## v2.14.0
4
13
  - **`/channel add kazee` now finishes cleanly — the validation step was probing the wrong path.** The live adapter mounts every REST route under `{root}/api`, but the setup probe hit `{root}/me`. So the correct host (`https://chat.example.com`) always 404'd at validation, while the only URL that *passed* (`…/api`) then made the adapter build `…/api/api` and break. There was no single URL a user could type to succeed. `kazee-probe` now normalises either form to the root and probes the real `{root}/api/me` (new exported `normalizeKazeeRoot`); the wizard and `setup.js` both store the normalised root. Step 1 now asks for the host and states "I add the /api path myself."
5
14
  - **The bot's own user id is no longer thrown away.** Validation already fetches it from `/me`, but the wizard never persisted `KAZEE_BOT_USER_ID` or passed it to the adapter — so a wizard-added channel booted without self-echo filtering or slash-command registration (and `loadChannels` warned on every restart). The wizard and `setup.js` now save it and hand it to the adapter.
@@ -15,7 +15,7 @@
15
15
  // validateKazee() is exported separately so setup.js can reuse the same
16
16
  // /me probe without going through the chat-driven flow.
17
17
 
18
- const { saveEnvKey, config } = require("./config");
18
+ const { saveEnvKey, config, KAZEE_DEFAULT_URL } = require("./config");
19
19
  const { send, deleteMessage } = require("./io");
20
20
  const { publicCommands } = require("./commands");
21
21
  const registry = require("./adapter-registry");
@@ -59,9 +59,10 @@ async function start(canonicalUserId, kind, envelope) {
59
59
  const origin = envelope?.adapter && envelope?.channelId
60
60
  ? { adapter: envelope.adapter, channelId: envelope.channelId }
61
61
  : null;
62
- sessions.set(canonicalUserId, { kind: "kazee", step: "awaiting-url", data: {}, origin });
62
+ const url = config.KAZEE_URL || KAZEE_DEFAULT_URL;
63
+ sessions.set(canonicalUserId, { kind: "kazee", step: "awaiting-token", data: { url }, origin });
63
64
  await send(
64
- "Add a Kazee channel.\n\nStep 1 of 3: Kazee host URL (e.g. https://chat.example.com). I add the /api path myself.",
65
+ "Add a Kazee channel.\n\nStep 1 of 2: paste the Kazee bot token. I'll delete your message after reading it.",
65
66
  { keyboard: cancelKeyboard },
66
67
  );
67
68
  }
@@ -84,20 +85,6 @@ async function handleText(envelope) {
84
85
  if (!session) return;
85
86
  const text = (envelope.text || "").trim();
86
87
 
87
- if (session.step === "awaiting-url") {
88
- if (!/^https?:\/\//i.test(text)) {
89
- await send("That doesn't look like a URL. Try again, or hit Cancel.", { keyboard: cancelKeyboard });
90
- return;
91
- }
92
- session.data.url = text;
93
- session.step = "awaiting-token";
94
- await send(
95
- "Step 2 of 3: Kazee bot token. I'll delete your message after reading.",
96
- { keyboard: cancelKeyboard },
97
- );
98
- return;
99
- }
100
-
101
88
  if (session.step === "awaiting-token") {
102
89
  await deleteMessage(envelope.messageId);
103
90
  if (!text) {
@@ -106,7 +93,7 @@ async function handleText(envelope) {
106
93
  }
107
94
  session.data.token = text;
108
95
  session.step = "validating";
109
- await send("Validating against the Kazee API…");
96
+ await send("Verifying the bot token…");
110
97
  const check = await validateKazee({ url: session.data.url, token: session.data.token });
111
98
  if (!check.ok) {
112
99
  sessions.delete(envelope.canonicalUserId);
@@ -115,6 +102,7 @@ async function handleText(envelope) {
115
102
  }
116
103
  session.data.url = check.url || session.data.url.replace(/\/+$/, "");
117
104
  session.data.botUserId = check.botUserId || "";
105
+ session.data.botName = check.botName || "";
118
106
 
119
107
  // Bring the adapter live now so it can receive the owner's message.
120
108
  const live = await bringLive(session);
@@ -124,8 +112,11 @@ async function handleText(envelope) {
124
112
  return;
125
113
  }
126
114
  session.step = "awaiting-owner-message";
115
+ const connected = session.data.botName
116
+ ? `Connected as ${session.data.botName} ✅`
117
+ : "Connected to Kazee ✅";
127
118
  await send(
128
- "Connected to Kazee ✅\n\nStep 3 of 3: 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.)`,
129
120
  { keyboard: cancelKeyboard },
130
121
  );
131
122
  return;
package/core/config.js CHANGED
@@ -163,6 +163,11 @@ const FULL_PATH = [
163
163
  ),
164
164
  ].filter(Boolean).join(path.delimiter);
165
165
 
166
+ // Kazee runs on one fixed host — like Telegram's api.telegram.org it's
167
+ // infrastructure, not a per-user setting. Hard-coded so onboarding only asks
168
+ // for the bot token (the actual secret). Override with KAZEE_URL if ever needed.
169
+ const KAZEE_DEFAULT_URL = "https://chat.inet.africa";
170
+
166
171
  // Channels: declarative list of {id, type, opts}. Defaults to telegram for
167
172
  // backwards compat with installs predating CHANNELS.
168
173
  function loadChannels() {
@@ -185,7 +190,7 @@ function loadChannels() {
185
190
  opts: { token: TOKEN, ownerChatId: CHAT_ID || "", chatIds: CHAT_IDS },
186
191
  });
187
192
  } else if (type === "kazee") {
188
- const url = config.KAZEE_URL;
193
+ const url = config.KAZEE_URL || KAZEE_DEFAULT_URL;
189
194
  const token = config.KAZEE_BOT_TOKEN;
190
195
  const ownerUserId = config.KAZEE_OWNER_USER_ID || "";
191
196
  const botUserId = config.KAZEE_BOT_USER_ID || "";
@@ -238,6 +243,7 @@ module.exports = {
238
243
  saveEnvKey,
239
244
  loadChannels,
240
245
  botSubprocessEnv,
246
+ KAZEE_DEFAULT_URL,
241
247
  BOT_DIR,
242
248
  CONFIG_DIR,
243
249
  TOKEN, CHAT_IDS, CHAT_ID,
@@ -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.0",
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
@@ -492,6 +492,10 @@ const STEPS = ["prerequisites", "telegram", "auth", "channels", "workspace", "va
492
492
 
493
493
  const { validateKazee } = require("./core/kazee-probe");
494
494
 
495
+ // Fixed Kazee host — mirrors KAZEE_DEFAULT_URL in core/config.js. Kept inline
496
+ // because config.js exits when no .env exists yet (setup runs before that).
497
+ const KAZEE_DEFAULT_URL = "https://chat.inet.africa";
498
+
495
499
  async function main() {
496
500
  // Check if this is an auth subcommand
497
501
  const args = process.argv.slice(2);
@@ -673,12 +677,11 @@ async function main() {
673
677
  console.log("\n Extra Channels (optional)\n");
674
678
  const addKazee = await ask(" Add a Kazee channel now? (y/n) [n]: ");
675
679
  if (addKazee.toLowerCase() === "y") {
680
+ const url = KAZEE_DEFAULT_URL; // fixed host — never prompted, like Telegram's api host
676
681
  while (true) {
677
- const url = (await ask(" Kazee host URL (e.g. https://chat.example.com — I add /api myself): ")).trim();
678
- if (!/^https?:\/\//i.test(url)) { console.log(" That doesn't look like a URL."); continue; }
679
682
  const token = (await ask(" Kazee bot token: ")).trim();
680
683
  if (!token) { console.log(" Empty token."); continue; }
681
- console.log(" Validating against Kazee /api/me…");
684
+ console.log(" Verifying the bot token…");
682
685
  const check = await validateKazee({ url, token });
683
686
  if (!check.ok) {
684
687
  console.log(` Validation failed: ${check.error}`);
@@ -686,6 +689,7 @@ async function main() {
686
689
  if (retry.toLowerCase() === "n") break;
687
690
  continue;
688
691
  }
692
+ if (check.botName) console.log(` Verified as ${check.botName}.`);
689
693
  const ownerUserId = (await ask(" Kazee owner user id (blank to skip): ")).trim();
690
694
  state.data.kazee = { url: check.url || url.replace(/\/+$/, ""), token, ownerUserId, botUserId: check.botUserId || "" };
691
695
  console.log(" Kazee channel configured.");