@inetafrica/open-claudia 2.2.0 → 2.2.1

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,33 @@ 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
+ return send("Cancelled.");
117
+ }
118
+ if (action === "create") {
119
+ if (!isChatOwner(envelope.channelId)) return send("Owner only.");
120
+ if (vault.exists()) return send("Vault already exists.");
121
+ state.pendingVaultUnlock = true;
122
+ state.pendingVaultUnlockAt = Date.now();
123
+ state.pendingVaultAction = { type: "create" };
124
+ return send("Pick a vault password and send it next.\n(Message will be deleted. Send /vault cancel to abort.)", {
125
+ keyboard: { inline_keyboard: [[{ text: "Cancel", callback_data: "vault:cancel" }]] },
126
+ });
127
+ }
111
128
  if (action === "hint") {
112
129
  return send([
113
130
  "Vault usage:",
131
+ " /vault create — set up the vault (owner, first time)",
114
132
  " /vault set <name> <value> — save a credential (your message is deleted)",
133
+ " /vault get <name> — reveal a credential",
115
134
  " /vault remove <name> — delete a credential",
116
135
  " /vault — list (unlocks if needed)",
117
136
  " /vault lock — lock now",
137
+ " /vault cancel — abort a pending password prompt",
118
138
  ].join("\n"));
119
139
  }
120
140
  return;
package/core/handlers.js CHANGED
@@ -877,27 +877,40 @@ 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.pendingVaultAction = action;
887
+ }
888
+
880
889
  register({
881
- name: "vault", description: "Manage credentials (password required)", args: "[set|remove|lock] ...",
890
+ name: "vault", description: "Manage credentials (password required)", args: "[create|get|set|remove|lock|cancel] ...",
882
891
  handler: async (env, { tail }) => {
883
892
  if (!authorized(env)) return;
884
893
  const state = currentState();
885
894
 
886
895
  if (!tail) {
887
- if (!vault.exists()) return send("No vault found. Run setup first: node setup.js");
896
+ if (!vault.exists()) {
897
+ if (!ownerEnv(env)) return send("No vault. Ask the owner to run /vault create.");
898
+ return send("No vault yet. Use /vault create to set one up.", {
899
+ keyboard: { inline_keyboard: [[{ text: "Create vault", callback_data: "vault:create" }]] },
900
+ });
901
+ }
888
902
  if (vault.isUnlocked()) {
889
903
  const entries = vault.list();
890
904
  const keys = Object.keys(entries);
891
905
  const kb = { inline_keyboard: [[
892
906
  { text: "Lock now", callback_data: "vault:lock" },
893
- { text: "How to set/remove", callback_data: "vault:hint" },
907
+ { text: "How to set/get/remove", callback_data: "vault:hint" },
894
908
  ]] };
895
909
  if (keys.length === 0) await send("Vault is unlocked but empty.\n\nUse /vault set <name> <value>", { keyboard: kb });
896
910
  else await send("Vault (unlocked):\n\n" + keys.map((k) => `${k}: ${entries[k]}`).join("\n") + "\n\nLocks automatically in 5 min.", { keyboard: kb });
897
911
  } 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)");
912
+ armVaultPrompt(state, { type: "list" });
913
+ 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
914
  }
902
915
  return;
903
916
  }
@@ -907,6 +920,21 @@ register({
907
920
  return send("Vault locked.");
908
921
  }
909
922
 
923
+ if (tail === "cancel") {
924
+ if (!state.pendingVaultUnlock) return send("Nothing to cancel.");
925
+ state.pendingVaultUnlock = false;
926
+ state.pendingVaultAction = null;
927
+ state.pendingVaultUnlockAt = 0;
928
+ return send("Cancelled.");
929
+ }
930
+
931
+ if (tail === "create") {
932
+ if (!ownerEnv(env)) return send("Owner only.");
933
+ if (vault.exists()) return send("Vault already exists. Use /vault to unlock, or delete the file first.");
934
+ armVaultPrompt(state, { type: "create" });
935
+ 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 });
936
+ }
937
+
910
938
  const setMatch = tail.match(/^set\s+(\S+)\s+(.+)$/);
911
939
  if (setMatch) {
912
940
  await deleteMessage(env.messageId);
@@ -914,9 +942,19 @@ register({
914
942
  vault.set(setMatch[1], setMatch[2].trim());
915
943
  return send(`Saved: ${setMatch[1]}`);
916
944
  }
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.");
945
+ armVaultPrompt(state, { type: "set", key: setMatch[1], value: setMatch[2].trim() });
946
+ return send("Vault locked. Send password to unlock.", { keyboard: PASSWORD_PROMPT_KB });
947
+ }
948
+
949
+ const getMatch = tail.match(/^get\s+(\S+)$/);
950
+ if (getMatch) {
951
+ if (vault.isUnlocked()) {
952
+ const value = vault.get(getMatch[1]);
953
+ if (value === null) return send(`No such key: ${getMatch[1]}`);
954
+ return send(`${getMatch[1]}: ${value}\n\n(Visible to anyone who reads this chat — delete the message when done.)`);
955
+ }
956
+ armVaultPrompt(state, { type: "get", key: getMatch[1] });
957
+ return send("Vault locked. Send password to unlock.", { keyboard: PASSWORD_PROMPT_KB });
920
958
  }
921
959
 
922
960
  const removeMatch = tail.match(/^remove\s+(\S+)$/);
@@ -925,15 +963,16 @@ register({
925
963
  vault.remove(removeMatch[1]);
926
964
  return send(`Removed: ${removeMatch[1]}`);
927
965
  }
928
- state.pendingVaultUnlock = true;
929
- state.pendingVaultAction = { type: "remove", key: removeMatch[1] };
930
- return send("Vault locked. Send password to unlock.");
966
+ armVaultPrompt(state, { type: "remove", key: removeMatch[1] });
967
+ return send("Vault locked. Send password to unlock.", { keyboard: PASSWORD_PROMPT_KB });
931
968
  }
932
969
 
933
- send("Usage: /vault | /vault set <name> <value> | /vault remove <name> | /vault lock");
970
+ send("Usage: /vault | /vault create | /vault set <name> <value> | /vault get <name> | /vault remove <name> | /vault lock | /vault cancel");
934
971
  },
935
972
  });
936
973
 
974
+ module.exports.PENDING_VAULT_TTL_MS = PENDING_VAULT_TTL_MS;
975
+
937
976
  // ── Cron ───────────────────────────────────────────────────────────
938
977
 
939
978
  register({
package/core/router.js CHANGED
@@ -240,18 +240,34 @@ async function handleText(envelope) {
240
240
  }
241
241
 
242
242
  if (state.pendingVaultUnlock) {
243
- const password = envelope.text;
244
- await deleteMessage(envelope.messageId);
245
- const ok = vault.unlock(password);
246
- if (!ok) {
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) {
247
246
  state.pendingVaultUnlock = false;
248
247
  state.pendingVaultAction = null;
249
- await send("Wrong password.");
248
+ state.pendingVaultUnlockAt = 0;
249
+ await send("Vault prompt timed out. Re-run the command.");
250
250
  return;
251
251
  }
252
+ const password = envelope.text;
253
+ await deleteMessage(envelope.messageId);
252
254
  const action = state.pendingVaultAction;
253
255
  state.pendingVaultUnlock = false;
254
256
  state.pendingVaultAction = null;
257
+ state.pendingVaultUnlockAt = 0;
258
+
259
+ if (action && action.type === "create") {
260
+ if (vault.exists()) { await send("Vault was created in another flow — re-run /vault."); return; }
261
+ vault.create(password);
262
+ await send("Vault created and unlocked.\n\nUse /vault set <name> <value>");
263
+ return;
264
+ }
265
+
266
+ const ok = vault.unlock(password);
267
+ if (!ok) {
268
+ await send("Wrong password.");
269
+ return;
270
+ }
255
271
  if (action.type === "list") {
256
272
  const entries = vault.list();
257
273
  const keys = Object.keys(entries);
@@ -263,6 +279,10 @@ async function handleText(envelope) {
263
279
  } else if (action.type === "remove") {
264
280
  vault.remove(action.key);
265
281
  await send(`Removed: ${action.key}`);
282
+ } else if (action.type === "get") {
283
+ const value = vault.get(action.key);
284
+ if (value === null) await send(`No such key: ${action.key}`);
285
+ else await send(`${action.key}: ${value}\n\n(Visible to anyone who reads this chat — delete the message when done.)`);
266
286
  }
267
287
  return;
268
288
  }
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.1",
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
  }