@inetafrica/open-claudia 2.1.1 → 2.2.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.
package/core/actions.js CHANGED
@@ -10,6 +10,9 @@ const { currentState, resetSettings, resetSessionUsage, saveState } = require(".
10
10
  const { runClaude, getActiveSessionId } = require("./runner");
11
11
  const { listProjects, projectKeyboard, workspacePath } = require("./projects");
12
12
  const { isChatOwner, approveAuthRequest, denyAuthRequest, authRequestLabel } = require("./access");
13
+ const accessStore = require("./access");
14
+ const peopleStore = require("./people");
15
+ const { vault } = require("./vault-store");
13
16
  const intros = require("./intros");
14
17
  const introFlow = require("./intro-flow");
15
18
  const { finishOnboarding } = require("./onboarding");
@@ -27,8 +30,53 @@ async function handleAction(envelope) {
27
30
  const state = currentState();
28
31
 
29
32
  if (d.startsWith("auth:")) {
30
- if (!isChatOwner(envelope.channelId)) return send("Owner only — auth approvals are restricted.");
31
- const [, action, chatId] = d.split(":");
33
+ if (!isChatOwner(envelope.channelId)) return send("Owner only — auth controls are restricted.");
34
+ const parts = d.split(":");
35
+ const action = parts[1];
36
+
37
+ if (action === "list") {
38
+ const raw = accessStore.listAuthorizedRaw();
39
+ const lines = ["Authorized chats:"];
40
+ for (const a of raw.authorized) {
41
+ const person = peopleStore.findByHandle("telegram", a.chatId) || peopleStore.findByHandle("kazee", a.chatId);
42
+ lines.push(` ${a.chatId}${a.isOwner ? " (owner)" : ""} ${a.name || a.username || ""}${person ? " → " + person.name : " → (no person)"}`);
43
+ }
44
+ if (raw.authorized.length === 0) lines.push(" (none)");
45
+ if (raw.pending.length > 0) {
46
+ lines.push("", "Pending:");
47
+ for (const p of raw.pending) lines.push(` ${p.chatId} ${p.name || p.username || ""}`);
48
+ }
49
+ return send(lines.join("\n"));
50
+ }
51
+
52
+ if (action === "revoke-pick") {
53
+ const raw = accessStore.listAuthorizedRaw();
54
+ const rows = [];
55
+ for (const a of raw.authorized) {
56
+ if (a.isOwner) continue;
57
+ const person = peopleStore.findByHandle("telegram", a.chatId) || peopleStore.findByHandle("kazee", a.chatId);
58
+ const label = `${a.chatId} ${person ? person.name : (a.name || a.username || "")}`.trim();
59
+ rows.push([{ text: `Revoke ${label}`.slice(0, 60), callback_data: `auth:revoke:${a.chatId}` }]);
60
+ }
61
+ if (rows.length === 0) return send("No revocable chats (only owner is authorized).");
62
+ return send("Pick a chat to revoke:", { keyboard: { inline_keyboard: rows } });
63
+ }
64
+
65
+ if (action === "revoke") {
66
+ const chatId = parts.slice(2).join(":");
67
+ if (!chatId) return;
68
+ const person = peopleStore.findByHandle("telegram", chatId) || peopleStore.findByHandle("kazee", chatId);
69
+ if (person && person.isOwner) return send("Refusing to revoke owner chat.");
70
+ if (person) {
71
+ const detected = (person.handles || []).find((h) => String(h.channelId) === String(chatId));
72
+ if (detected) peopleStore.unlinkHandle(person.id, { adapter: detected.adapter, channelId: detected.channelId });
73
+ }
74
+ const r = accessStore.revokeChat(chatId);
75
+ if (!r.ok) return send(`Revoke failed: ${r.reason}`);
76
+ return send(`Revoked ${chatId}${person ? ` (was ${person.name})` : ""}.`);
77
+ }
78
+
79
+ const chatId = parts[2];
32
80
  if (!chatId || !["approve", "deny"].includes(action)) return;
33
81
  const result = action === "approve" ? approveAuthRequest(chatId) : denyAuthRequest(chatId);
34
82
 
@@ -54,6 +102,56 @@ async function handleAction(envelope) {
54
102
  return;
55
103
  }
56
104
 
105
+ if (d.startsWith("vault:")) {
106
+ const action = d.slice("vault:".length);
107
+ if (action === "lock") {
108
+ vault.lock();
109
+ return send("Vault locked.");
110
+ }
111
+ if (action === "hint") {
112
+ return send([
113
+ "Vault usage:",
114
+ " /vault set <name> <value> — save a credential (your message is deleted)",
115
+ " /vault remove <name> — delete a credential",
116
+ " /vault — list (unlocks if needed)",
117
+ " /vault lock — lock now",
118
+ ].join("\n"));
119
+ }
120
+ return;
121
+ }
122
+
123
+ if (d.startsWith("people:")) {
124
+ const rest = d.slice("people:".length);
125
+ if (rest === "hint") {
126
+ return send([
127
+ "People usage:",
128
+ " /people show <name> — details",
129
+ " /people note <name> <text> — add a note",
130
+ " /people add <name> — owner: add person",
131
+ " /people link <name> <adapter> <channel> — owner: link a chat",
132
+ " /people unlink <name> <adapter> <channel> — owner: unlink",
133
+ " /people set-primary <name> <adapter> <channel>",
134
+ " /people remove <name> — owner",
135
+ ].join("\n"));
136
+ }
137
+ if (rest.startsWith("show:")) {
138
+ const id = rest.slice("show:".length);
139
+ const person = peopleStore.findById(id);
140
+ if (!person) return send(`Not found: ${id}`);
141
+ const handles = (person.handles || []).map((h) => ` ${h.adapter}:${h.channelId}${person.primaryChannel && person.primaryChannel.adapter === h.adapter && String(person.primaryChannel.channelId) === String(h.channelId) ? " (primary)" : ""}`).join("\n");
142
+ const notes = (person.notes || []).slice(-5).map((n) => ` - [${(n.at || "").slice(0, 10)}] ${n.text}`).join("\n");
143
+ return send([
144
+ `${person.name}${person.isOwner ? " (owner)" : ""} id=${person.id}`,
145
+ person.bio ? `Bio: ${person.bio}` : null,
146
+ "Handles:",
147
+ handles || " (none)",
148
+ notes ? "Notes:" : null,
149
+ notes || null,
150
+ ].filter((x) => x !== null && x !== "").join("\n"));
151
+ }
152
+ return;
153
+ }
154
+
57
155
  if (d.startsWith("intro:")) {
58
156
  if (!isChatOwner(envelope.channelId)) return send("Owner only — intro approvals are restricted.");
59
157
  const [, action, introId] = d.split(":");
package/core/handlers.js CHANGED
@@ -170,8 +170,15 @@ register({
170
170
  return send("Usage: /auth | /auth list | /auth revoke <chatId>");
171
171
  }
172
172
  if (authorized(env)) {
173
- send("You're already authorized.");
174
- return;
173
+ if (ownerEnv(env)) {
174
+ return send("You're already authorized.", {
175
+ keyboard: { inline_keyboard: [[
176
+ { text: "List authorized", callback_data: "auth:list" },
177
+ { text: "Revoke…", callback_data: "auth:revoke-pick" },
178
+ ]] },
179
+ });
180
+ }
181
+ return send("You're already authorized.");
175
182
  }
176
183
  if (!hasOwner()) {
177
184
  bootstrapOwner({
@@ -881,8 +888,12 @@ register({
881
888
  if (vault.isUnlocked()) {
882
889
  const entries = vault.list();
883
890
  const keys = Object.keys(entries);
884
- if (keys.length === 0) await send("Vault is unlocked but empty.\n\nUse /vault set <name> <value>");
885
- else await send("Vault (unlocked):\n\n" + keys.map((k) => `${k}: ${entries[k]}`).join("\n") + "\n\nLocks automatically in 5 min.");
891
+ const kb = { inline_keyboard: [[
892
+ { text: "Lock now", callback_data: "vault:lock" },
893
+ { text: "How to set/remove", callback_data: "vault:hint" },
894
+ ]] };
895
+ if (keys.length === 0) await send("Vault is unlocked but empty.\n\nUse /vault set <name> <value>", { keyboard: kb });
896
+ else await send("Vault (unlocked):\n\n" + keys.map((k) => `${k}: ${entries[k]}`).join("\n") + "\n\nLocks automatically in 5 min.", { keyboard: kb });
886
897
  } else {
887
898
  state.pendingVaultUnlock = true;
888
899
  state.pendingVaultAction = { type: "list" };
@@ -1052,11 +1063,17 @@ register({
1052
1063
  if (!authorized(env)) return;
1053
1064
  if (!tail || tail === "list") {
1054
1065
  const all = peopleStore.list();
1055
- if (all.length === 0) return send("No people yet.");
1066
+ if (all.length === 0) {
1067
+ return send("No people yet.", ownerEnv(env) ? {
1068
+ keyboard: { inline_keyboard: [[{ text: "How to add/link", callback_data: "people:hint" }]] },
1069
+ } : undefined);
1070
+ }
1056
1071
  const lines = ["Team:"];
1057
1072
  for (const p of all) lines.push(` ${formatPersonShort(p)}`);
1058
- lines.push("", "More: /people show <name>, /people note <name> <text>");
1059
- return send(lines.join("\n"));
1073
+ const rows = [];
1074
+ for (const p of all) rows.push([{ text: `Show ${p.name}`, callback_data: `people:show:${p.id}` }]);
1075
+ if (ownerEnv(env)) rows.push([{ text: "How to note/link/add", callback_data: "people:hint" }]);
1076
+ return send(lines.join("\n"), { keyboard: { inline_keyboard: rows } });
1060
1077
  }
1061
1078
  const showMatch = tail.match(/^show\s+(.+)$/i);
1062
1079
  if (showMatch) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
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": {