@inetafrica/open-claudia 1.6.0 → 1.7.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/bot.js CHANGED
@@ -170,7 +170,6 @@ bot.setMyCommands([
170
170
  { command: "compact", description: "Summarize conversation context" },
171
171
  { command: "continue", description: "Resume last conversation" },
172
172
  { command: "worktree", description: "Toggle isolated git branch" },
173
- { command: "agents", description: "Toggle direct/parallel agent mode" },
174
173
  { command: "cron", description: "Manage scheduled tasks" },
175
174
  { command: "vault", description: "Manage credentials (password required)" },
176
175
  { command: "soul", description: "View/edit assistant identity" },
@@ -268,8 +267,7 @@ const savedState = loadState();
268
267
 
269
268
  // ── State ───────────────────────────────────────────────────────────
270
269
  let currentSession = savedState.currentSession || null;
271
- let runningProcess = null; // Primary process (direct mode)
272
- let runningTasks = new Map(); // taskId -> { proc, statusMessageId, streamInterval, startTime } (parallel mode)
270
+ let runningProcess = null;
273
271
  let statusMessageId = null;
274
272
  let streamBuffer = "";
275
273
  let streamInterval = null;
@@ -279,14 +277,13 @@ let activeCrons = new Map();
279
277
  let pendingVaultUnlock = false; // Waiting for password
280
278
  let pendingVaultAction = null; // What to do after unlock
281
279
  let isFirstMessage = !lastSessionId; // Track if this is first message in session
282
- let taskCounter = 0;
283
280
 
284
281
  let settings = savedState.settings || {
285
- model: null, effort: null, budget: null, permissionMode: null, worktree: false, agentMode: "direct",
282
+ model: null, effort: null, budget: null, permissionMode: null, worktree: false,
286
283
  };
287
284
 
288
285
  function resetSettings() {
289
- settings = { model: null, effort: null, budget: null, permissionMode: null, worktree: false, agentMode: settings.agentMode };
286
+ settings = { model: null, effort: null, budget: null, permissionMode: null, worktree: false };
290
287
  }
291
288
 
292
289
  function isAuthorized(msg) {
@@ -656,19 +653,6 @@ function parseStreamEvents(data) {
656
653
  return events;
657
654
  }
658
655
 
659
- /**
660
- * Route to the appropriate runner based on agent mode setting.
661
- * In direct mode: serial execution with shared session.
662
- * In parallel mode: concurrent independent processes.
663
- * Commands like /compact and /continue always use direct mode (they need session context).
664
- */
665
- async function runClaudeRouted(prompt, cwd, replyToMsgId, opts = {}) {
666
- if (opts.forceDirect || settings.agentMode !== "parallel") {
667
- return runClaude(prompt, cwd, replyToMsgId, opts);
668
- }
669
- return runClaudeParallel(prompt, cwd, replyToMsgId, opts);
670
- }
671
-
672
656
  function buildClaudeArgs(prompt, opts = {}) {
673
657
  const args = ["-p", "--verbose", "--output-format", "stream-json",
674
658
  "--append-system-prompt", buildSystemPrompt()];
@@ -780,19 +764,29 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
780
764
  try {
781
765
  const finalText = assistantText || "(no output)";
782
766
  const chunks = splitMessage(finalText);
783
- // Send final result as a new message (triggers notification)
784
- const sent = await send(chunks[0], { replyTo: replyToMsgId });
785
- if (!sent) {
786
- // Fallback: if the first send failed completely, try without any options
787
- await send(chunks[0]);
788
- }
789
- for (let i = 1; i < chunks.length; i++) {
790
- await send(chunks[i]);
767
+ const firstChunk = chunks[0];
768
+
769
+ // If the progress message already shows the final text, just edit it clean
770
+ // (no need to send a duplicate). Only send a new message for long tasks
771
+ // or multi-chunk responses to trigger a notification.
772
+ const progressAlreadyHasFinal = statusMessageId && lastUpdate &&
773
+ firstChunk.length < 4000 && chunks.length === 1 &&
774
+ lastUpdate.includes(firstChunk.slice(0, 200));
775
+
776
+ if (progressAlreadyHasFinal) {
777
+ // Edit the progress message to show clean final text (remove tool steps header)
778
+ await editMessage(statusMessageId, firstChunk);
779
+ } else {
780
+ // Send final result as a new message (triggers notification)
781
+ const sent = await send(firstChunk, { replyTo: replyToMsgId });
782
+ if (!sent) await send(firstChunk);
783
+ for (let i = 1; i < chunks.length; i++) {
784
+ await send(chunks[i]);
785
+ }
791
786
  }
792
787
  if (code !== 0 && code !== null) await send(`Exit code: ${code}`);
793
788
  } catch (e) {
794
789
  console.error("Final message delivery failed:", e.message);
795
- // Last-resort attempt to notify user something went wrong
796
790
  await send("Task completed but failed to deliver the response. Send /continue to see the result.");
797
791
  }
798
792
  if (settings.budget) settings.budget = null;
@@ -816,126 +810,6 @@ async function runClaude(prompt, cwd, replyToMsgId, opts = {}) {
816
810
  });
817
811
  }
818
812
 
819
- /**
820
- * Run Claude in parallel mode — each call gets its own independent process.
821
- * No queueing, no shared session. Multiple tasks can run simultaneously.
822
- */
823
- async function runClaudeParallel(prompt, cwd, replyToMsgId, opts = {}) {
824
- bot.sendChatAction(CHAT_ID, "typing").catch(() => {});
825
-
826
- const taskId = `task-${++taskCounter}`;
827
- let taskStatusMsgId = null;
828
- let taskStreamBuf = "";
829
- let taskText = "";
830
- let taskToolUses = [];
831
- let taskCurrentTool = null;
832
- let taskCurrentToolDetail = "";
833
- let taskSessionId = null;
834
-
835
- // Build args — always fresh session in parallel mode (no --resume)
836
- const args = ["-p", "--verbose", "--output-format", "stream-json",
837
- "--append-system-prompt", buildSystemPrompt()];
838
- if (settings.model) args.push("--model", settings.model);
839
- if (settings.effort) args.push("--effort", settings.effort);
840
- if (settings.budget) args.push("--max-budget-usd", String(settings.budget));
841
- if (settings.permissionMode) args.push("--permission-mode", settings.permissionMode);
842
- else args.push("--dangerously-skip-permissions");
843
- if (settings.worktree) args.push("--worktree");
844
- args.push(prompt);
845
-
846
- const proc = spawn(CLAUDE_PATH, args, {
847
- cwd,
848
- env: { ...process.env, PATH: FULL_PATH, HOME: process.env.HOME },
849
- stdio: ["ignore", "pipe", "pipe"],
850
- detached: process.platform !== "win32",
851
- });
852
-
853
- const startTime = Date.now();
854
- let taskInterval = null;
855
- let lastUpdate = "";
856
-
857
- // Adaptive progress updates
858
- const scheduleTaskUpdate = () => {
859
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
860
- const interval = elapsed > 120 ? 5000 : 2000;
861
- taskInterval = setTimeout(doTaskUpdate, interval);
862
- };
863
- const doTaskUpdate = async () => {
864
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
865
- try {
866
- bot.sendChatAction(CHAT_ID, "typing").catch(() => {});
867
- const display = formatProgress(taskText, taskToolUses, taskCurrentTool, elapsed, taskCurrentToolDetail);
868
- if (display && display !== lastUpdate) {
869
- if (!taskStatusMsgId && taskText) {
870
- taskStatusMsgId = await send(display.length > 4000 ? display.slice(-4000) : display, { replyTo: replyToMsgId });
871
- } else if (taskStatusMsgId) {
872
- await editMessage(taskStatusMsgId, display.length > 4000 ? display.slice(-4000) : display);
873
- }
874
- lastUpdate = display;
875
- }
876
- } catch (e) {
877
- console.error(`Task ${taskId} progress error:`, e.message);
878
- }
879
- if (runningTasks.has(taskId)) scheduleTaskUpdate();
880
- };
881
-
882
- runningTasks.set(taskId, { proc, startTime, prompt: prompt.slice(0, 50) });
883
- scheduleTaskUpdate();
884
-
885
- proc.stdout.on("data", (data) => {
886
- taskStreamBuf += data.toString();
887
- const events = parseStreamEvents(taskStreamBuf);
888
- const lastNewline = taskStreamBuf.lastIndexOf("\n");
889
- taskStreamBuf = lastNewline >= 0 ? taskStreamBuf.slice(lastNewline + 1) : taskStreamBuf;
890
- for (const evt of events) {
891
- if (evt.type === "assistant" && evt.message?.content) {
892
- for (const block of evt.message.content) {
893
- if (block.type === "text") taskText += block.text;
894
- else if (block.type === "tool_use") {
895
- taskCurrentTool = block.name;
896
- taskToolUses.push(block.name);
897
- const input = block.input || {};
898
- if (block.name === "Bash" && input.command) taskCurrentToolDetail = input.command.slice(0, 80);
899
- else if ((block.name === "Read" || block.name === "Edit" || block.name === "Write") && input.file_path)
900
- taskCurrentToolDetail = input.file_path.split("/").slice(-2).join("/");
901
- else if (block.name === "Grep" && input.pattern) taskCurrentToolDetail = input.pattern.slice(0, 40);
902
- else if (block.name === "Glob" && input.pattern) taskCurrentToolDetail = input.pattern;
903
- else taskCurrentToolDetail = "";
904
- }
905
- }
906
- }
907
- if (evt.type === "result" && evt.session_id) taskSessionId = evt.session_id;
908
- if (evt.type === "result" && evt.result) taskText = evt.result;
909
- }
910
- });
911
-
912
- proc.stderr.on("data", (d) => console.error(`Task ${taskId} STDERR:`, d.toString()));
913
-
914
- proc.on("close", async (code) => {
915
- runningTasks.delete(taskId);
916
- clearTimeout(taskInterval);
917
- try {
918
- const finalText = taskText || "(no output)";
919
- const chunks = splitMessage(finalText);
920
- const sent = await send(chunks[0], { replyTo: replyToMsgId });
921
- if (!sent) await send(chunks[0]);
922
- for (let i = 1; i < chunks.length; i++) await send(chunks[i]);
923
- if (code !== 0 && code !== null) await send(`Exit code: ${code}`);
924
- } catch (e) {
925
- console.error(`Task ${taskId} final delivery failed:`, e.message);
926
- await send("Task completed but failed to deliver the response.");
927
- }
928
- });
929
-
930
- proc.on("error", async (err) => {
931
- runningTasks.delete(taskId);
932
- clearTimeout(taskInterval);
933
- await send(`Task error: ${err.message}`, { replyTo: replyToMsgId });
934
- });
935
-
936
- return taskId;
937
- }
938
-
939
813
  async function runClaudeSilent(prompt, cwd, label) {
940
814
  return new Promise((resolve) => {
941
815
  const args = ["-p", "--output-format", "text", "--verbose",
@@ -1046,7 +920,7 @@ bot.onText(/\/help/, (msg) => {
1046
920
  if (!isAuthorized(msg)) return;
1047
921
  send([
1048
922
  "Session: /session /sessions /projects /continue /status /stop /end",
1049
- "Settings: /model /effort /budget /plan /compact /worktree /agents",
923
+ "Settings: /model /effort /budget /plan /compact /worktree /mode",
1050
924
  "Automation: /cron /vault /soul",
1051
925
  "System: /restart /upgrade",
1052
926
  "",
@@ -1154,45 +1028,27 @@ bot.onText(/\/compact/, async (msg) => { if (!isAuthorized(msg)) return; if (!re
1154
1028
  bot.onText(/\/continue$/, async (msg) => { if (!isAuthorized(msg)) return; if (!requireSession(msg)) return; await runClaude("continue where we left off", currentSession.dir, msg.message_id, { continueSession: true }); });
1155
1029
  bot.onText(/\/worktree$/, (msg) => { if (!isAuthorized(msg)) return; settings.worktree = !settings.worktree; send(settings.worktree ? "Worktree on." : "Worktree off."); });
1156
1030
 
1157
- bot.onText(/\/agents$/, (msg) => {
1031
+ bot.onText(/\/mode$/, async (msg) => {
1158
1032
  if (!isAuthorized(msg)) return;
1159
- const mode = settings.agentMode || "direct";
1160
- send(`Agent mode: *${mode}*\n\n` +
1161
- `*direct* — one task at a time, shared session context, queues messages\n` +
1162
- `*parallel* — concurrent tasks, independent sessions, no queue`, {
1033
+ await send("Bot mode: *direct* (default)\n\nSwitch to agent mode for non-blocking execution.\nIn agent mode, heavy tasks run in the background and you can keep chatting.", {
1163
1034
  parseMode: "Markdown",
1164
1035
  keyboard: { inline_keyboard: [
1165
- [{ text: mode === "direct" ? "Direct (active)" : "Switch to Direct", callback_data: "am:direct" },
1166
- { text: mode === "parallel" ? "Parallel (active)" : "Switch to Parallel", callback_data: "am:parallel" }],
1036
+ [{ text: "Switch to Agent Mode", callback_data: "mode:agent" }],
1167
1037
  ] },
1168
1038
  });
1169
1039
  });
1170
1040
 
1171
1041
  bot.onText(/\/stop/, async (msg) => {
1172
1042
  if (!isAuthorized(msg)) return;
1173
-
1174
- // Stop parallel tasks
1175
- if (runningTasks.size > 0) {
1176
- for (const [taskId, task] of runningTasks) {
1177
- try { process.kill(-task.proc.pid, "SIGTERM"); } catch (e) {
1178
- try { task.proc.kill("SIGTERM"); } catch (e2) {}
1179
- }
1180
- setTimeout(() => { try { process.kill(-task.proc.pid, "SIGKILL"); } catch (e) {} }, 3000);
1181
- }
1182
- const count = runningTasks.size;
1183
- runningTasks.clear();
1184
- await send(`Cancelled ${count} task${count > 1 ? "s" : ""}.`);
1185
- return;
1186
- }
1187
-
1188
- // Stop direct mode process
1189
1043
  if (runningProcess) {
1190
1044
  const pid = runningProcess.pid;
1191
1045
  try {
1046
+ // Kill entire process group to catch child processes (dev servers, etc.)
1192
1047
  process.kill(-pid, "SIGTERM");
1193
1048
  } catch (e) {
1194
1049
  try { runningProcess.kill("SIGTERM"); } catch (e2) {}
1195
1050
  }
1051
+ // Force kill after 3 seconds if still alive
1196
1052
  setTimeout(() => {
1197
1053
  try { process.kill(-pid, "SIGKILL"); } catch (e) {}
1198
1054
  }, 3000);
@@ -1200,33 +1056,19 @@ bot.onText(/\/stop/, async (msg) => {
1200
1056
  if (streamInterval) clearTimeout(streamInterval);
1201
1057
  messageQueue = [];
1202
1058
  await send("Cancelled.");
1203
- return;
1204
1059
  }
1205
-
1206
- await send("Nothing running.");
1060
+ else await send("Nothing running.");
1207
1061
  });
1208
1062
 
1209
1063
  bot.onText(/\/status/, (msg) => {
1210
1064
  if (!isAuthorized(msg)) return;
1211
1065
  if (!currentSession) return send("No session.", { keyboard: { inline_keyboard: [[{ text: "Pick", callback_data: "show:projects" }]] } });
1212
- const mode = settings.agentMode || "direct";
1213
- const lines = [
1066
+ send([
1214
1067
  `Project: ${currentSession.name}`,
1215
- `Model: ${settings.model || "default"} | Effort: ${settings.effort || "default"} | Mode: ${mode}`,
1068
+ `Model: ${settings.model || "default"} | Effort: ${settings.effort || "default"}`,
1216
1069
  `Vault: ${vault.isUnlocked() ? "unlocked" : "locked"} | Crons: ${activeCrons.size}`,
1217
- ];
1218
- if (mode === "parallel" && runningTasks.size > 0) {
1219
- lines.push(`Running: ${runningTasks.size} task${runningTasks.size > 1 ? "s" : ""}`);
1220
- for (const [id, task] of runningTasks) {
1221
- const elapsed = Math.floor((Date.now() - task.startTime) / 1000);
1222
- lines.push(` ${id}: ${task.prompt}... (${elapsed}s)`);
1223
- }
1224
- } else if (runningProcess) {
1225
- lines.push("Working...");
1226
- } else {
1227
- lines.push("Ready.");
1228
- }
1229
- send(lines.join("\n"));
1070
+ runningProcess ? "Working..." : "Ready.",
1071
+ ].join("\n"));
1230
1072
  });
1231
1073
 
1232
1074
  bot.onText(/\/end/, (msg) => {
@@ -1362,7 +1204,16 @@ bot.on("callback_query", async (q) => {
1362
1204
  if (d.startsWith("m:")) { settings.model = d.slice(2) === "default" ? null : d.slice(2); await send(`Model: ${settings.model || "default"}`); return; }
1363
1205
  if (d.startsWith("e:")) { const e = d.slice(2); settings.effort = e === "default" ? null : e; await send(`Effort: ${settings.effort || "default"}`); return; }
1364
1206
  if (d.startsWith("b:")) { const b = d.slice(2); settings.budget = b === "none" ? null : parseFloat(b); await send(`Budget: ${settings.budget ? "$"+settings.budget : "none"}`); return; }
1365
- if (d.startsWith("am:")) { settings.agentMode = d.slice(3); saveState(); await send(`Agent mode: ${settings.agentMode}`); return; }
1207
+
1208
+ // Mode switching — writes mode file and restarts bot
1209
+ if (d.startsWith("mode:")) {
1210
+ const newMode = d.slice(5);
1211
+ const modeFile = path.join(CONFIG_DIR, ".bot-mode");
1212
+ fs.writeFileSync(modeFile, newMode);
1213
+ await send(`Switching to ${newMode} mode... restarting.`);
1214
+ setTimeout(() => process.exit(0), 500); // Let launchd/systemd restart with new mode
1215
+ return;
1216
+ }
1366
1217
 
1367
1218
  // Cron presets
1368
1219
  if (d.startsWith("cp:") && d !== "cp:clear") {
@@ -1396,7 +1247,7 @@ bot.on("voice", async (msg) => {
1396
1247
  try { fs.unlinkSync(oggPath); } catch (e) {}
1397
1248
  if (!transcript) return send("Couldn't transcribe. Try typing it.");
1398
1249
  await send(`Heard: "${transcript}"`, { replyTo: msg.message_id });
1399
- await runClaudeRouted(transcript, currentSession.dir, msg.message_id);
1250
+ await runClaude(transcript, currentSession.dir, msg.message_id);
1400
1251
  } catch (err) { await send(`Voice failed: ${err.message}`); }
1401
1252
  });
1402
1253
 
@@ -1411,7 +1262,7 @@ bot.on("audio", async (msg) => {
1411
1262
  try { fs.unlinkSync(p); } catch (e) {}
1412
1263
  if (!t) return send("Couldn't transcribe.");
1413
1264
  await send(`Heard: "${t}"`, { replyTo: msg.message_id });
1414
- await runClaudeRouted(t, currentSession.dir, msg.message_id);
1265
+ await runClaude(t, currentSession.dir, msg.message_id);
1415
1266
  } catch (err) { await send(`Audio failed: ${err.message}`); }
1416
1267
  });
1417
1268
 
@@ -1422,7 +1273,7 @@ bot.on("photo", async (msg) => {
1422
1273
  try {
1423
1274
  const p = await downloadFile(msg.photo[msg.photo.length - 1].file_id, ".jpg");
1424
1275
  const caption = msg.caption || "Describe this image. If code/UI/error — explain and fix.";
1425
- await runClaudeRouted(`Image at ${p}\n\nView it, then: ${caption}`, currentSession.dir, msg.message_id);
1276
+ await runClaude(`Image at ${p}\n\nView it, then: ${caption}`, currentSession.dir, msg.message_id);
1426
1277
  } catch (err) { await send(`Image failed: ${err.message}`); }
1427
1278
  });
1428
1279
 
@@ -1450,7 +1301,7 @@ bot.on("document", async (msg) => {
1450
1301
  prompt = `File received: ${fileName} (${mime})\nSaved at: ${savePath}\n\nRead this file and ${caption || "summarize its contents. If it's code, explain what it does. If it's a document, give key points."}`;
1451
1302
  }
1452
1303
  await send(`File saved: ${fileName}`, { replyTo: msg.message_id });
1453
- await runClaudeRouted(prompt, currentSession.dir, msg.message_id);
1304
+ await runClaude(prompt, currentSession.dir, msg.message_id);
1454
1305
  } catch (err) { await send(`Failed: ${err.message}`); }
1455
1306
  });
1456
1307
 
@@ -1555,7 +1406,7 @@ bot.on("message", async (msg) => {
1555
1406
  }
1556
1407
  }
1557
1408
 
1558
- await runClaudeRouted(prompt, currentSession.dir, msg.message_id);
1409
+ await runClaude(prompt, currentSession.dir, msg.message_id);
1559
1410
  });
1560
1411
 
1561
1412
  // ── Startup ─────────────────────────────────────────────────────────
@@ -0,0 +1,47 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ CONFIG_DIR="$HOME/.open-claudia"
5
+ ENV_FILE="$CONFIG_DIR/.env"
6
+
7
+ mkdir -p "$CONFIG_DIR"
8
+
9
+ # If .env doesn't exist and env vars are set, auto-configure
10
+ if [ ! -f "$ENV_FILE" ] && [ -n "$TELEGRAM_BOT_TOKEN" ]; then
11
+ echo "Auto-configuring from environment variables..."
12
+
13
+ cat > "$ENV_FILE" <<ENVEOF
14
+ TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
15
+ TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID:-}
16
+ WORKSPACE=${WORKSPACE:-$HOME/Workspace}
17
+ CLAUDE_PATH=${CLAUDE_PATH:-claude}
18
+ SOUL_FILE=$CONFIG_DIR/soul.md
19
+ CRONS_FILE=$CONFIG_DIR/crons.json
20
+ VAULT_FILE=$CONFIG_DIR/vault.enc
21
+ AUTH_FILE=$CONFIG_DIR/auth.json
22
+ ONBOARDED=true
23
+ WEB_UI=${WEB_UI:-true}
24
+ WEB_PORT=${WEB_PORT:-8080}
25
+ ENVEOF
26
+
27
+ # Create workspace directory
28
+ mkdir -p "${WORKSPACE:-$HOME/Workspace}"
29
+
30
+ # Create default soul
31
+ if [ ! -f "$CONFIG_DIR/soul.md" ]; then
32
+ cat > "$CONFIG_DIR/soul.md" <<'SOUL'
33
+ You are a helpful AI coding assistant running via Telegram.
34
+ Keep responses concise — this is a mobile screen.
35
+ Act on screenshots, fix bugs, implement designs.
36
+ SOUL
37
+ fi
38
+
39
+ # Create empty files if missing
40
+ [ -f "$CONFIG_DIR/crons.json" ] || echo "[]" > "$CONFIG_DIR/crons.json"
41
+ [ -f "$CONFIG_DIR/auth.json" ] || echo '{"authorized":[],"pending":[]}' > "$CONFIG_DIR/auth.json"
42
+
43
+ echo "Configuration written to $ENV_FILE"
44
+ fi
45
+
46
+ # Execute the main command
47
+ exec "$@"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Your always-on AI coding assistant — Claude Code via Telegram",
5
5
  "main": "bot.js",
6
6
  "bin": {
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "files": [
15
15
  "bot.js",
16
+ "bot-agent.js",
16
17
  "vault.js",
17
18
  "setup.js",
18
19
  "web.js",
@@ -20,6 +21,7 @@
20
21
  "bin/",
21
22
  "k8s/",
22
23
  "Dockerfile",
24
+ "docker-entrypoint.sh",
23
25
  ".dockerignore",
24
26
  ".env.example",
25
27
  "README.md"
@@ -1,61 +0,0 @@
1
- apiVersion: apps/v1
2
- kind: Deployment
3
- metadata:
4
- name: open-claudia
5
- labels:
6
- app: open-claudia
7
- spec:
8
- replicas: 1
9
- strategy:
10
- type: Recreate # Only one instance can poll Telegram
11
- selector:
12
- matchLabels:
13
- app: open-claudia
14
- template:
15
- metadata:
16
- labels:
17
- app: open-claudia
18
- spec:
19
- containers:
20
- - name: open-claudia
21
- image: registry.example.com/open-claudia:latest
22
- ports:
23
- - containerPort: 8080
24
- name: web
25
- env:
26
- - name: WEB_UI
27
- value: "true"
28
- - name: WEB_PORT
29
- value: "8080"
30
- - name: ANTHROPIC_API_KEY
31
- valueFrom:
32
- secretKeyRef:
33
- name: open-claudia-secrets
34
- key: anthropic-api-key
35
- optional: true
36
- volumeMounts:
37
- - name: data
38
- mountPath: /data
39
- resources:
40
- requests:
41
- memory: "256Mi"
42
- cpu: "100m"
43
- limits:
44
- memory: "1Gi"
45
- cpu: "1000m"
46
- livenessProbe:
47
- httpGet:
48
- path: /api/status
49
- port: 8080
50
- initialDelaySeconds: 10
51
- periodSeconds: 30
52
- readinessProbe:
53
- httpGet:
54
- path: /api/status
55
- port: 8080
56
- initialDelaySeconds: 5
57
- periodSeconds: 10
58
- volumes:
59
- - name: data
60
- persistentVolumeClaim:
61
- claimName: open-claudia-data
package/k8s/ingress.yaml DELETED
@@ -1,26 +0,0 @@
1
- apiVersion: networking.k8s.io/v1
2
- kind: Ingress
3
- metadata:
4
- name: open-claudia
5
- labels:
6
- app: open-claudia
7
- annotations:
8
- # Uncomment for cert-manager TLS
9
- # cert-manager.io/cluster-issuer: letsencrypt-prod
10
- spec:
11
- rules:
12
- - host: claudia.example.com # Replace with your domain
13
- http:
14
- paths:
15
- - path: /
16
- pathType: Prefix
17
- backend:
18
- service:
19
- name: open-claudia
20
- port:
21
- number: 8080
22
- # Uncomment for TLS
23
- # tls:
24
- # - hosts:
25
- # - claudia.example.com
26
- # secretName: open-claudia-tls
package/k8s/pvc.yaml DELETED
@@ -1,12 +0,0 @@
1
- apiVersion: v1
2
- kind: PersistentVolumeClaim
3
- metadata:
4
- name: open-claudia-data
5
- labels:
6
- app: open-claudia
7
- spec:
8
- accessModes:
9
- - ReadWriteOnce
10
- resources:
11
- requests:
12
- storage: 1Gi
package/k8s/secret.yaml DELETED
@@ -1,10 +0,0 @@
1
- apiVersion: v1
2
- kind: Secret
3
- metadata:
4
- name: open-claudia-secrets
5
- labels:
6
- app: open-claudia
7
- type: Opaque
8
- stringData:
9
- # Replace with your actual API key
10
- anthropic-api-key: "sk-ant-your-key-here"
package/k8s/service.yaml DELETED
@@ -1,14 +0,0 @@
1
- apiVersion: v1
2
- kind: Service
3
- metadata:
4
- name: open-claudia
5
- labels:
6
- app: open-claudia
7
- spec:
8
- type: ClusterIP
9
- ports:
10
- - port: 8080
11
- targetPort: 8080
12
- name: web
13
- selector:
14
- app: open-claudia