@inetafrica/open-claudia 2.0.3 → 2.0.4

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.
@@ -51,6 +51,27 @@ Reply normally in your final answer. To send a file, image, or voice clip back t
51
51
  - \`open-claudia send-voice <path>\` — ogg/opus voice note
52
52
  Never print or embed bot tokens in prompts, commands, logs, or messages.
53
53
 
54
+ ## Background work
55
+ You can persist work across turns and wake yourself up later. These survive bot restarts.
56
+
57
+ Wake-ups and crons (real schedulers, not hallucinations — use them instead of saying "I'll check back later"):
58
+ - \`open-claudia schedule-wakeup <when> "<prompt>"\` — one-shot. \`<when>\` is a duration like \`30s\`/\`5m\`/\`2h\`/\`1d\` or an ISO datetime. When it fires you wake up in this same conversation with the prompt as if the user typed it.
59
+ - \`open-claudia cron-add "<5-field cron>" "<prompt>"\` — recurring.
60
+ - \`open-claudia cron-list\` — list all wakeups + crons on this channel.
61
+ - \`open-claudia cron-remove <id>\` — cancel one.
62
+
63
+ Persistent todo list (per channel; use for multi-step work so progress survives compaction):
64
+ - \`open-claudia task add "<content>"\`
65
+ - \`open-claudia task list\`
66
+ - \`open-claudia task start <id>\` / \`task done <id>\` / \`task remove <id>\`
67
+
68
+ Sub-agents (spawn a fresh throwaway Claude for focused research — output comes back on stdout):
69
+ - \`open-claudia agent "<prompt>" [--role "<role>"]\`
70
+ - Use when a side question would pollute this conversation, or to fan out independent lookups.
71
+ - The sub-agent has Read/Glob/Grep/Bash but no access to your chat session or send-* tools.
72
+
73
+ If you tell the user "I'll check back in N minutes" or "I'll run this every morning", you MUST schedule it with one of the commands above in the same turn. Otherwise nothing happens.
74
+
54
75
  ## Guidelines
55
76
  - Keep responses concise — many users are on mobile.
56
77
  - Markdown: *bold*, _italic_, \`code\`, \`\`\`code blocks\`\`\` work on both Telegram and Kazee. Skip headers (#) and links [text](url).
package/core/tasks.js ADDED
@@ -0,0 +1,92 @@
1
+ // Per-channel persistent todo list. The agent uses this to track
2
+ // multi-step work that should survive a turn, a compaction, or a
3
+ // restart. Scoped by adapter+channel so Telegram and Kazee see
4
+ // separate lists.
5
+
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const { TASKS_DIR } = require("./config");
9
+
10
+ function safe(s) {
11
+ return String(s || "").replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 80);
12
+ }
13
+
14
+ function filePathFor(adapter, channelId) {
15
+ fs.mkdirSync(TASKS_DIR, { recursive: true });
16
+ return path.join(TASKS_DIR, `${safe(adapter)}-${safe(channelId)}.json`);
17
+ }
18
+
19
+ function load(adapter, channelId) {
20
+ try {
21
+ const raw = JSON.parse(fs.readFileSync(filePathFor(adapter, channelId), "utf-8"));
22
+ return Array.isArray(raw) ? raw : [];
23
+ } catch (e) { return []; }
24
+ }
25
+
26
+ function save(adapter, channelId, list) {
27
+ const f = filePathFor(adapter, channelId);
28
+ const tmp = `${f}.tmp`;
29
+ fs.writeFileSync(tmp, JSON.stringify(list, null, 2));
30
+ fs.renameSync(tmp, f);
31
+ }
32
+
33
+ function nextId() {
34
+ return `task_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
35
+ }
36
+
37
+ function add(adapter, channelId, content) {
38
+ const list = load(adapter, channelId);
39
+ const t = {
40
+ id: nextId(),
41
+ content: String(content || "").trim(),
42
+ status: "pending",
43
+ createdAt: Date.now(),
44
+ updatedAt: Date.now(),
45
+ };
46
+ list.push(t);
47
+ save(adapter, channelId, list);
48
+ return t;
49
+ }
50
+
51
+ function update(adapter, channelId, id, patch) {
52
+ const list = load(adapter, channelId);
53
+ const idx = list.findIndex((t) => t.id === id);
54
+ if (idx < 0) return null;
55
+ list[idx] = { ...list[idx], ...patch, updatedAt: Date.now() };
56
+ save(adapter, channelId, list);
57
+ return list[idx];
58
+ }
59
+
60
+ function remove(adapter, channelId, id) {
61
+ const list = load(adapter, channelId);
62
+ const idx = list.findIndex((t) => t.id === id);
63
+ if (idx < 0) return null;
64
+ const [removed] = list.splice(idx, 1);
65
+ save(adapter, channelId, list);
66
+ return removed;
67
+ }
68
+
69
+ function clearCompleted(adapter, channelId) {
70
+ const list = load(adapter, channelId).filter((t) => t.status !== "completed");
71
+ save(adapter, channelId, list);
72
+ return list;
73
+ }
74
+
75
+ function list(adapter, channelId, opts = {}) {
76
+ const all = load(adapter, channelId);
77
+ if (opts.status) return all.filter((t) => t.status === opts.status);
78
+ return all;
79
+ }
80
+
81
+ function format(t, idx) {
82
+ const mark = t.status === "completed" ? "[x]" : t.status === "in_progress" ? "[~]" : "[ ]";
83
+ const num = typeof idx === "number" ? `${idx + 1}. ` : "";
84
+ return `${num}${mark} ${t.content}`;
85
+ }
86
+
87
+ module.exports = {
88
+ filePathFor,
89
+ load, save,
90
+ add, update, remove, list, clearCompleted,
91
+ format,
92
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "2.0.3",
3
+ "version": "2.0.4",
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/core/cron.js DELETED
@@ -1,77 +0,0 @@
1
- // Cron loader and scheduler. Cron jobs fire from a timer (no inbound
2
- // message, so no chatContext) — we resolve the owner's preferred
3
- // channel from identities and bind the run to that adapter.
4
-
5
- const fs = require("fs");
6
- const path = require("path");
7
- const cron = require("node-cron");
8
- const { CRONS_FILE, WORKSPACE, CHAT_ID } = require("./config");
9
- const { runInChat } = require("./context");
10
- const { canonicalForTelegram, identities } = require("./identity");
11
- const { runClaudeSilent } = require("./runner");
12
-
13
- const activeCrons = new Map();
14
- let adaptersById = new Map();
15
-
16
- function setAdapters(adapters) {
17
- adaptersById = new Map();
18
- for (const a of adapters) adaptersById.set(a.type, a);
19
- }
20
-
21
- function loadCrons() {
22
- try { return JSON.parse(fs.readFileSync(CRONS_FILE, "utf-8")); }
23
- catch (e) { return []; }
24
- }
25
-
26
- function saveCrons(list) {
27
- fs.writeFileSync(CRONS_FILE, JSON.stringify(list, null, 2));
28
- }
29
-
30
- function ownerDispatchTarget() {
31
- // Prefer an explicit owner mapping; fall back to legacy Telegram CHAT_ID.
32
- const ownerId = canonicalForTelegram(CHAT_ID || "");
33
- const preferred = identities.preferred[ownerId];
34
- if (preferred && preferred.transport && adaptersById.get(preferred.transport)) {
35
- return {
36
- adapter: adaptersById.get(preferred.transport),
37
- channelId: String(preferred.channelId),
38
- canonicalUserId: ownerId,
39
- };
40
- }
41
- const tg = adaptersById.get("telegram");
42
- if (tg && CHAT_ID) {
43
- return { adapter: tg, channelId: String(CHAT_ID), canonicalUserId: ownerId };
44
- }
45
- return null;
46
- }
47
-
48
- function scheduleCron(c) {
49
- const cwd = path.join(WORKSPACE, c.project);
50
- if (activeCrons.has(c.id)) activeCrons.get(c.id).task.stop();
51
- const task = cron.schedule(c.schedule, () => {
52
- const target = ownerDispatchTarget();
53
- if (!target) {
54
- console.error(`Cron ${c.label}: no adapter available to deliver result`);
55
- return;
56
- }
57
- runInChat(target, () => runClaudeSilent(c.prompt, cwd, c.label));
58
- });
59
- activeCrons.set(c.id, { task, config: c });
60
- }
61
-
62
- function initCrons() {
63
- for (const c of loadCrons()) {
64
- try { scheduleCron(c); }
65
- catch (e) { console.error("Cron error:", e.message); }
66
- }
67
- console.log(`Loaded ${loadCrons().length} cron(s)`);
68
- }
69
-
70
- module.exports = {
71
- activeCrons,
72
- setAdapters,
73
- loadCrons,
74
- saveCrons,
75
- scheduleCron,
76
- initCrons,
77
- };