@inetafrica/open-claudia 2.2.0 → 2.2.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/core/actions.js CHANGED
@@ -108,13 +108,35 @@ async function handleAction(envelope) {
108
108
  vault.lock();
109
109
  return send("Vault locked.");
110
110
  }
111
+ if (action === "cancel") {
112
+ if (!state.pendingVaultUnlock) return send("Nothing to cancel.");
113
+ state.pendingVaultUnlock = false;
114
+ state.pendingVaultAction = null;
115
+ state.pendingVaultUnlockAt = 0;
116
+ state.pendingVaultUnlockAttempts = 0;
117
+ return send("Cancelled.");
118
+ }
119
+ if (action === "create") {
120
+ if (!isChatOwner(envelope.channelId)) return send("Owner only.");
121
+ if (vault.exists()) return send("Vault already exists.");
122
+ state.pendingVaultUnlock = true;
123
+ state.pendingVaultUnlockAt = Date.now();
124
+ state.pendingVaultUnlockAttempts = 3;
125
+ state.pendingVaultAction = { type: "create" };
126
+ return send("Pick a vault password and send it next.\n(Message will be deleted. Send /vault cancel to abort.)", {
127
+ keyboard: { inline_keyboard: [[{ text: "Cancel", callback_data: "vault:cancel" }]] },
128
+ });
129
+ }
111
130
  if (action === "hint") {
112
131
  return send([
113
132
  "Vault usage:",
133
+ " /vault create — set up the vault (owner, first time)",
114
134
  " /vault set <name> <value> — save a credential (your message is deleted)",
135
+ " /vault get <name> — reveal a credential",
115
136
  " /vault remove <name> — delete a credential",
116
137
  " /vault — list (unlocks if needed)",
117
138
  " /vault lock — lock now",
139
+ " /vault cancel — abort a pending password prompt",
118
140
  ].join("\n"));
119
141
  }
120
142
  return;
package/core/handlers.js CHANGED
@@ -877,27 +877,41 @@ register({
877
877
 
878
878
  // ── Vault ──────────────────────────────────────────────────────────
879
879
 
880
+ const PENDING_VAULT_TTL_MS = 90 * 1000;
881
+ const PASSWORD_PROMPT_KB = { inline_keyboard: [[{ text: "Cancel", callback_data: "vault:cancel" }]] };
882
+
883
+ function armVaultPrompt(state, action) {
884
+ state.pendingVaultUnlock = true;
885
+ state.pendingVaultUnlockAt = Date.now();
886
+ state.pendingVaultUnlockAttempts = 3;
887
+ state.pendingVaultAction = action;
888
+ }
889
+
880
890
  register({
881
- name: "vault", description: "Manage credentials (password required)", args: "[set|remove|lock] ...",
891
+ name: "vault", description: "Manage credentials (password required)", args: "[create|get|set|remove|lock|cancel] ...",
882
892
  handler: async (env, { tail }) => {
883
893
  if (!authorized(env)) return;
884
894
  const state = currentState();
885
895
 
886
896
  if (!tail) {
887
- if (!vault.exists()) return send("No vault found. Run setup first: node setup.js");
897
+ if (!vault.exists()) {
898
+ if (!ownerEnv(env)) return send("No vault. Ask the owner to run /vault create.");
899
+ return send("No vault yet. Use /vault create to set one up.", {
900
+ keyboard: { inline_keyboard: [[{ text: "Create vault", callback_data: "vault:create" }]] },
901
+ });
902
+ }
888
903
  if (vault.isUnlocked()) {
889
904
  const entries = vault.list();
890
905
  const keys = Object.keys(entries);
891
906
  const kb = { inline_keyboard: [[
892
907
  { text: "Lock now", callback_data: "vault:lock" },
893
- { text: "How to set/remove", callback_data: "vault:hint" },
908
+ { text: "How to set/get/remove", callback_data: "vault:hint" },
894
909
  ]] };
895
910
  if (keys.length === 0) await send("Vault is unlocked but empty.\n\nUse /vault set <name> <value>", { keyboard: kb });
896
911
  else await send("Vault (unlocked):\n\n" + keys.map((k) => `${k}: ${entries[k]}`).join("\n") + "\n\nLocks automatically in 5 min.", { keyboard: kb });
897
912
  } else {
898
- state.pendingVaultUnlock = true;
899
- state.pendingVaultAction = { type: "list" };
900
- await send("Vault is locked. Send your vault password.\n(Message will be deleted after reading)");
913
+ armVaultPrompt(state, { type: "list" });
914
+ await send("Vault is locked. Send your vault password.\n(Message will be deleted after reading. Times out in 90s.)", { keyboard: PASSWORD_PROMPT_KB });
901
915
  }
902
916
  return;
903
917
  }
@@ -907,6 +921,22 @@ register({
907
921
  return send("Vault locked.");
908
922
  }
909
923
 
924
+ if (tail === "cancel") {
925
+ if (!state.pendingVaultUnlock) return send("Nothing to cancel.");
926
+ state.pendingVaultUnlock = false;
927
+ state.pendingVaultAction = null;
928
+ state.pendingVaultUnlockAt = 0;
929
+ state.pendingVaultUnlockAttempts = 0;
930
+ return send("Cancelled.");
931
+ }
932
+
933
+ if (tail === "create") {
934
+ if (!ownerEnv(env)) return send("Owner only.");
935
+ if (vault.exists()) return send("Vault already exists. Use /vault to unlock, or delete the file first.");
936
+ armVaultPrompt(state, { type: "create" });
937
+ return send("Pick a vault password and send it next.\n(Message will be deleted. Use the button or send /vault cancel to abort.)", { keyboard: PASSWORD_PROMPT_KB });
938
+ }
939
+
910
940
  const setMatch = tail.match(/^set\s+(\S+)\s+(.+)$/);
911
941
  if (setMatch) {
912
942
  await deleteMessage(env.messageId);
@@ -914,9 +944,19 @@ register({
914
944
  vault.set(setMatch[1], setMatch[2].trim());
915
945
  return send(`Saved: ${setMatch[1]}`);
916
946
  }
917
- state.pendingVaultUnlock = true;
918
- state.pendingVaultAction = { type: "set", key: setMatch[1], value: setMatch[2].trim() };
919
- return send("Vault locked. Send password to unlock.");
947
+ armVaultPrompt(state, { type: "set", key: setMatch[1], value: setMatch[2].trim() });
948
+ return send("Vault locked. Send password to unlock.", { keyboard: PASSWORD_PROMPT_KB });
949
+ }
950
+
951
+ const getMatch = tail.match(/^get\s+(\S+)$/);
952
+ if (getMatch) {
953
+ if (vault.isUnlocked()) {
954
+ const value = vault.get(getMatch[1]);
955
+ if (value === null) return send(`No such key: ${getMatch[1]}`);
956
+ return send(`${getMatch[1]}: ${value}\n\n(Visible to anyone who reads this chat — delete the message when done.)`);
957
+ }
958
+ armVaultPrompt(state, { type: "get", key: getMatch[1] });
959
+ return send("Vault locked. Send password to unlock.", { keyboard: PASSWORD_PROMPT_KB });
920
960
  }
921
961
 
922
962
  const removeMatch = tail.match(/^remove\s+(\S+)$/);
@@ -925,15 +965,16 @@ register({
925
965
  vault.remove(removeMatch[1]);
926
966
  return send(`Removed: ${removeMatch[1]}`);
927
967
  }
928
- state.pendingVaultUnlock = true;
929
- state.pendingVaultAction = { type: "remove", key: removeMatch[1] };
930
- return send("Vault locked. Send password to unlock.");
968
+ armVaultPrompt(state, { type: "remove", key: removeMatch[1] });
969
+ return send("Vault locked. Send password to unlock.", { keyboard: PASSWORD_PROMPT_KB });
931
970
  }
932
971
 
933
- send("Usage: /vault | /vault set <name> <value> | /vault remove <name> | /vault lock");
972
+ send("Usage: /vault | /vault create | /vault set <name> <value> | /vault get <name> | /vault remove <name> | /vault lock | /vault cancel");
934
973
  },
935
974
  });
936
975
 
976
+ module.exports.PENDING_VAULT_TTL_MS = PENDING_VAULT_TTL_MS;
977
+
937
978
  // ── Cron ───────────────────────────────────────────────────────────
938
979
 
939
980
  register({
package/core/router.js CHANGED
@@ -240,18 +240,54 @@ async function handleText(envelope) {
240
240
  }
241
241
 
242
242
  if (state.pendingVaultUnlock) {
243
+ const { PENDING_VAULT_TTL_MS } = require("./handlers");
244
+ const armedAt = state.pendingVaultUnlockAt || 0;
245
+ if (PENDING_VAULT_TTL_MS && armedAt && Date.now() - armedAt > PENDING_VAULT_TTL_MS) {
246
+ state.pendingVaultUnlock = false;
247
+ state.pendingVaultAction = null;
248
+ state.pendingVaultUnlockAt = 0;
249
+ state.pendingVaultUnlockAttempts = 0;
250
+ await send("Vault prompt timed out. Re-run the command.");
251
+ return;
252
+ }
243
253
  const password = envelope.text;
244
254
  await deleteMessage(envelope.messageId);
255
+ const action = state.pendingVaultAction;
256
+
257
+ if (action && action.type === "create") {
258
+ state.pendingVaultUnlock = false;
259
+ state.pendingVaultAction = null;
260
+ state.pendingVaultUnlockAt = 0;
261
+ state.pendingVaultUnlockAttempts = 0;
262
+ if (vault.exists()) { await send("Vault was created in another flow — re-run /vault."); return; }
263
+ vault.create(password);
264
+ await send("Vault created and unlocked.\n\nUse /vault set <name> <value>");
265
+ return;
266
+ }
267
+
245
268
  const ok = vault.unlock(password);
246
269
  if (!ok) {
270
+ const left = (state.pendingVaultUnlockAttempts || 1) - 1;
271
+ if (left > 0) {
272
+ state.pendingVaultUnlockAttempts = left;
273
+ state.pendingVaultUnlockAt = Date.now();
274
+ await send(`Wrong password. ${left} attempt${left === 1 ? "" : "s"} left.\nSend the password again or /vault cancel.`, {
275
+ keyboard: { inline_keyboard: [[{ text: "Cancel", callback_data: "vault:cancel" }]] },
276
+ });
277
+ return;
278
+ }
247
279
  state.pendingVaultUnlock = false;
248
280
  state.pendingVaultAction = null;
249
- await send("Wrong password.");
281
+ state.pendingVaultUnlockAt = 0;
282
+ state.pendingVaultUnlockAttempts = 0;
283
+ await send("Too many wrong attempts. Re-run /vault to try again.");
250
284
  return;
251
285
  }
252
- const action = state.pendingVaultAction;
286
+
253
287
  state.pendingVaultUnlock = false;
254
288
  state.pendingVaultAction = null;
289
+ state.pendingVaultUnlockAt = 0;
290
+ state.pendingVaultUnlockAttempts = 0;
255
291
  if (action.type === "list") {
256
292
  const entries = vault.list();
257
293
  const keys = Object.keys(entries);
@@ -263,6 +299,10 @@ async function handleText(envelope) {
263
299
  } else if (action.type === "remove") {
264
300
  vault.remove(action.key);
265
301
  await send(`Removed: ${action.key}`);
302
+ } else if (action.type === "get") {
303
+ const value = vault.get(action.key);
304
+ if (value === null) await send(`No such key: ${action.key}`);
305
+ else await send(`${action.key}: ${value}\n\n(Visible to anyone who reads this chat — delete the message when done.)`);
266
306
  }
267
307
  return;
268
308
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "2.2.0",
3
+ "version": "2.2.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/vault.js CHANGED
@@ -23,7 +23,9 @@ class Vault {
23
23
  // Create a new vault with a password
24
24
  create(password) {
25
25
  this.data = {};
26
+ this._password = password;
26
27
  this._save(password);
28
+ this._resetLockTimer();
27
29
  }
28
30
 
29
31
  // Derive key from password + salt
@@ -125,7 +127,7 @@ class Vault {
125
127
  const entries = {};
126
128
  for (const [k, v] of Object.entries(this.data)) {
127
129
  const val = String(v);
128
- entries[k] = val.length > 8 ? val.slice(0, 4) + "..." + val.slice(-4) : "****";
130
+ entries[k] = val.length >= 4 ? "****" + val.slice(-4) : "****";
129
131
  }
130
132
  return entries;
131
133
  }