seclaw 0.1.11 → 0.1.13

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/dist/cli.js CHANGED
@@ -73,7 +73,20 @@ async function fetchTunnelUrlFromLogs(cwd) {
73
73
  }
74
74
  async function setTelegramWebhook(botToken, tunnelUrl) {
75
75
  const webhookUrl = `${tunnelUrl}/webhook`;
76
- for (let attempt = 0; attempt < 3; attempt++) {
76
+ const hostname = tunnelUrl.replace("https://", "");
77
+ for (let i = 0; i < 30; i++) {
78
+ try {
79
+ const { resolve4: resolve42 } = await import("dns/promises");
80
+ await resolve42(hostname);
81
+ break;
82
+ } catch {
83
+ if (i === 29) {
84
+ break;
85
+ }
86
+ await new Promise((r) => setTimeout(r, 2e3));
87
+ }
88
+ }
89
+ for (let attempt = 0; attempt < 5; attempt++) {
77
90
  try {
78
91
  const res = await fetch(
79
92
  `https://api.telegram.org/bot${botToken}/setWebhook`,
@@ -89,9 +102,13 @@ async function setTelegramWebhook(botToken, tunnelUrl) {
89
102
  );
90
103
  const data = await res.json();
91
104
  if (data.ok) return true;
105
+ if (data.description?.includes("resolve host")) {
106
+ await new Promise((r) => setTimeout(r, 5e3));
107
+ continue;
108
+ }
92
109
  } catch {
93
110
  }
94
- if (attempt < 2) await new Promise((r) => setTimeout(r, 2e3));
111
+ if (attempt < 4) await new Promise((r) => setTimeout(r, 3e3));
95
112
  }
96
113
  try {
97
114
  const res = await fetch(
@@ -542,10 +559,20 @@ ENTRYPOINT ["/bin/sh"]
542
559
  const script = `#!/bin/sh
543
560
  # Auto-updates Telegram webhook when tunnel URL changes on restart.
544
561
 
562
+ wait_for_dns() {
563
+ HOST=$(echo "$1" | sed 's|https://||')
564
+ for i in $(seq 1 30); do
565
+ nslookup "$HOST" >/dev/null 2>&1 && return 0
566
+ printf '[tunnel] Waiting for DNS (%s, attempt %d/30)...\\n' "$HOST" "$i" >&2
567
+ sleep 2
568
+ done
569
+ return 1
570
+ }
571
+
545
572
  set_webhook() {
546
573
  URL="$1"
547
- for i in 1 2 3; do
548
- sleep 10
574
+ wait_for_dns "$URL" || { printf '[tunnel] DNS resolution failed for %s\\n' "$URL" >&2; return 1; }
575
+ for i in 1 2 3 4 5; do
549
576
  RESP=$(curl -s -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook" \\
550
577
  -H "Content-Type: application/json" \\
551
578
  -d "{\\"url\\":\\"$URL/webhook\\",\\"allowed_updates\\":[\\"message\\",\\"callback_query\\"]}")
@@ -556,6 +583,7 @@ set_webhook() {
556
583
  ;;
557
584
  esac
558
585
  printf '[tunnel] Webhook attempt %d failed: %s\\n' "$i" "$RESP" >&2
586
+ sleep 5
559
587
  done
560
588
  return 1
561
589
  }
@@ -2428,10 +2456,10 @@ async function checkTelegram(projectDir, tunnelCheck) {
2428
2456
  const wh = whData.result;
2429
2457
  const tunnelUrl = tunnelCheck.tunnelUrl || "";
2430
2458
  const webhookFix = async () => {
2431
- const freshUrl = await getTunnelUrl(projectDir, 5);
2459
+ const freshUrl = await getTunnelUrl(projectDir, 15);
2432
2460
  if (!freshUrl) return "No tunnel URL available \u2014 fix tunnel first";
2433
2461
  const ok = await setTelegramWebhook(botToken, freshUrl);
2434
- return ok ? `Webhook set to ${freshUrl}` : "Could not set webhook";
2462
+ return ok ? `Webhook set to ${freshUrl}` : "Could not set webhook \u2014 DNS may still be propagating, try again in 30s";
2435
2463
  };
2436
2464
  if (!wh.url) {
2437
2465
  return {
@@ -1115,12 +1115,7 @@ var AUTO_BASE_PROMPT = `You are a personal AI assistant running on seclaw. You h
1115
1115
  ## Communication
1116
1116
  - Detect the user's language and respond in the same language
1117
1117
  - Keep Telegram messages concise \u2014 use bullet points and short paragraphs
1118
- - Be proactive when you have relevant context to share
1119
-
1120
- ## Response Format
1121
- At the end of every response, write on a new line:
1122
- --- CapabilityName
1123
- Where CapabilityName is the capability you primarily used (e.g. "Inbox Management", "Research & Intelligence"). If no specific capability applies, write: --- General`;
1118
+ - Be proactive when you have relevant context to share`;
1124
1119
  function focusBasePrompt(capId) {
1125
1120
  const displayName = capId.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
1126
1121
  return `You are a specialized AI agent running on seclaw, focused exclusively on: ${displayName}.
@@ -1130,11 +1125,7 @@ IMPORTANT: You are in FOCUS MODE. Only use the capability described below. Do NO
1130
1125
  ## Communication
1131
1126
  - Detect the user's language and respond in the same language
1132
1127
  - Keep Telegram messages concise \u2014 use bullet points and short paragraphs
1133
- - Be proactive when you have relevant context to share
1134
-
1135
- ## Response Format
1136
- At the end of every response, write on a new line:
1137
- --- ${displayName}`;
1128
+ - Be proactive when you have relevant context to share`;
1138
1129
  }
1139
1130
  function loadConfig() {
1140
1131
  return {
@@ -1228,7 +1219,18 @@ function composeCapabilityPrompt(capabilities, mode) {
1228
1219
  console.warn("[config] No capability prompts loaded, using base prompt only");
1229
1220
  return base;
1230
1221
  }
1231
- return base + "\n\n" + sections.join("\n\n");
1222
+ const capNames = capabilities.map(
1223
+ (id) => id.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ")
1224
+ );
1225
+ const footerInstruction = mode === "auto" ? `## MANDATORY Response Footer
1226
+ You MUST end EVERY response with this exact format on a new line:
1227
+ --- CapabilityName
1228
+ Replace CapabilityName with the capability you used (one of: ${capNames.join(", ")}). If none applies, write: --- General
1229
+ This line is REQUIRED. Never skip it.` : `## MANDATORY Response Footer
1230
+ You MUST end EVERY response with this exact format on a new line:
1231
+ --- ${capNames[0]}
1232
+ This line is REQUIRED. Never skip it.`;
1233
+ return base + "\n\n" + sections.join("\n\n") + "\n\n" + footerInstruction;
1232
1234
  }
1233
1235
  function loadInstalledCapabilities(workspace) {
1234
1236
  const installedPath = resolve2(workspace, "config", "installed.json");
@@ -1477,12 +1479,12 @@ ${userText}`;
1477
1479
  let response = await runAgent(config2, toolCtx2, history, augmented);
1478
1480
  console.log(`[telegram] Reply (${response.length} chars): ${response.substring(0, 120)}`);
1479
1481
  let footer = "";
1480
- const footerMatch = response.match(/\n--- (.+)$/);
1482
+ const footerMatch = response.match(/\n[-—]{2,3}\s*(.+?)\s*$/);
1481
1483
  if (footerMatch) {
1482
- response = response.replace(/\n--- .+$/, "");
1484
+ response = response.replace(/\n[-—]{2,3}\s*.+?\s*$/, "").trimEnd();
1483
1485
  footer = `
1484
1486
 
1485
- _\u2014 ${footerMatch[1]}_`;
1487
+ \u2014 ${footerMatch[1].trim()}`;
1486
1488
  }
1487
1489
  saveHistory(chatId, config2.workspace, [
1488
1490
  ...history,
@@ -2024,14 +2026,43 @@ async function editMessageWithButtons(token, chatId, messageId, text, keyboard)
2024
2026
  } catch {
2025
2027
  }
2026
2028
  }
2029
+ var TEMPLATE_CATALOG = [
2030
+ { id: "productivity-agent", name: "Productivity Agent", price: "Free" },
2031
+ { id: "data-analyst", name: "Data Analyst", price: "Free" },
2032
+ { id: "inbox-agent", name: "Inbox Agent", price: "$19" },
2033
+ { id: "reddit-hn-digest", name: "Reddit & HN Digest", price: "$19" },
2034
+ { id: "youtube-digest", name: "YouTube Digest", price: "$19" },
2035
+ { id: "health-tracker", name: "Health Tracker", price: "$29" },
2036
+ { id: "earnings-tracker", name: "Earnings Tracker", price: "$29" },
2037
+ { id: "research-agent", name: "Research Agent", price: "$39" },
2038
+ { id: "knowledge-base", name: "Knowledge Base", price: "$39" },
2039
+ { id: "family-calendar", name: "Family Calendar", price: "$39" },
2040
+ { id: "content-agent", name: "Content Agent", price: "$49" },
2041
+ { id: "personal-crm", name: "Personal CRM", price: "$49" },
2042
+ { id: "youtube-creator", name: "YouTube Creator", price: "$69" },
2043
+ { id: "devops-agent", name: "DevOps Agent", price: "$79" },
2044
+ { id: "customer-service", name: "Customer Service", price: "$79" },
2045
+ { id: "sales-agent", name: "Sales Agent", price: "$79" },
2046
+ { id: "six-agent-company", name: "6-Agent Company", price: "$149" }
2047
+ ];
2027
2048
  async function handleTemplates(chatId, config2) {
2028
2049
  const capabilities = loadInstalledCapabilities(config2.workspace);
2029
2050
  if (capabilities.length === 0) {
2030
- await sendMessage(
2031
- config2.telegramToken,
2032
- chatId,
2033
- "No templates installed. Using default single-prompt mode.\n\nAdd templates: `npx seclaw add <template>`\nBrowse: seclawai.com/templates"
2034
- );
2051
+ const lines2 = TEMPLATE_CATALOG.map((t) => {
2052
+ const tag = t.price === "Free" ? "FREE" : t.price;
2053
+ return `\u2022 *${t.name}* \u2014 ${tag}`;
2054
+ });
2055
+ const text2 = `*seclaw Templates (${TEMPLATE_CATALOG.length})*
2056
+
2057
+ ` + lines2.join("\n") + "\n\n_Tap a template to view details:_";
2058
+ const keyboard2 = [];
2059
+ for (const t of TEMPLATE_CATALOG) {
2060
+ keyboard2.push([{
2061
+ text: `${t.name} \u2014 ${t.price}`,
2062
+ url: `https://seclawai.com/templates/${t.id}`
2063
+ }]);
2064
+ }
2065
+ await sendMessageWithButtons(config2.telegramToken, chatId, text2, keyboard2);
2035
2066
  return;
2036
2067
  }
2037
2068
  const activeMode = getActiveMode(config2.workspace);
@@ -2062,13 +2093,19 @@ async function handleTemplates(chatId, config2) {
2062
2093
  const activeName = activeMode.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
2063
2094
  modeLabel = `${activeName} (focus mode)`;
2064
2095
  }
2065
- const text = `*Installed Templates (${capabilities.length})*
2096
+ const notInstalled = TEMPLATE_CATALOG.filter((t) => !capabilities.includes(t.id));
2097
+ let text = `*Installed Templates (${capabilities.length})*
2066
2098
 
2067
2099
  ${lines.join("\n")}
2068
2100
 
2069
2101
  Mode: *${modeLabel}*`;
2102
+ if (notInstalled.length > 0) {
2103
+ text += `
2104
+
2105
+ ${notInstalled.length} more available:`;
2106
+ }
2107
+ const keyboard = [];
2070
2108
  if (capabilities.length >= 2) {
2071
- const keyboard = [];
2072
2109
  const autoLabel = activeMode === "auto" ? "\u2705 Auto (All Templates)" : "\u{1F504} Auto (All Templates)";
2073
2110
  keyboard.push([{ text: autoLabel, callback_data: "cap_switch:auto" }]);
2074
2111
  const capButtons = [];
@@ -2080,14 +2117,14 @@ Mode: *${modeLabel}*`;
2080
2117
  for (let i = 0; i < capButtons.length; i += 2) {
2081
2118
  keyboard.push(capButtons.slice(i, i + 2));
2082
2119
  }
2083
- await sendMessageWithButtons(config2.telegramToken, chatId, text, keyboard);
2084
- } else {
2085
- await sendMessage(
2086
- config2.telegramToken,
2087
- chatId,
2088
- text + "\n\nAdd more templates to switch modes:\n`npx seclaw add <template> --key YOUR_KEY`"
2089
- );
2090
2120
  }
2121
+ for (const t of notInstalled) {
2122
+ keyboard.push([{
2123
+ text: `${t.name} \u2014 Details`,
2124
+ url: `https://seclawai.com/templates/${t.id}`
2125
+ }]);
2126
+ }
2127
+ await sendMessageWithButtons(config2.telegramToken, chatId, text, keyboard);
2091
2128
  }
2092
2129
  async function handleSwitchCallback(chatId, msgId, capId, config2) {
2093
2130
  const capabilities = loadInstalledCapabilities(config2.workspace);
@@ -2106,7 +2143,7 @@ async function handleSwitchCallback(chatId, msgId, capId, config2) {
2106
2143
  async function sendMessageWithButtons(token, chatId, text, keyboard) {
2107
2144
  if (!token || !chatId) return;
2108
2145
  try {
2109
- await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
2146
+ const res = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
2110
2147
  method: "POST",
2111
2148
  headers: { "Content-Type": "application/json" },
2112
2149
  body: JSON.stringify({
@@ -2116,7 +2153,25 @@ async function sendMessageWithButtons(token, chatId, text, keyboard) {
2116
2153
  reply_markup: { inline_keyboard: keyboard }
2117
2154
  })
2118
2155
  });
2119
- } catch {
2156
+ const data = await res.json();
2157
+ if (!data.ok) {
2158
+ console.log(`[telegram] Markdown failed (buttons): ${data.description} \u2014 retrying plain`);
2159
+ const retryRes = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
2160
+ method: "POST",
2161
+ headers: { "Content-Type": "application/json" },
2162
+ body: JSON.stringify({
2163
+ chat_id: chatId,
2164
+ text,
2165
+ reply_markup: { inline_keyboard: keyboard }
2166
+ })
2167
+ });
2168
+ const retryData = await retryRes.json();
2169
+ if (!retryData.ok) {
2170
+ console.error(`[telegram] Plain also failed (buttons): ${retryData.description}`);
2171
+ }
2172
+ }
2173
+ } catch (err) {
2174
+ console.error(`[telegram] sendMessageWithButtons error:`, err.message);
2120
2175
  try {
2121
2176
  await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
2122
2177
  method: "POST",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seclaw",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Secure autonomous AI agents in 60 seconds",
5
5
  "type": "module",
6
6
  "bin": {