seclaw 0.1.10 → 0.1.12
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 +108 -10
- package/dist/runtime/agent.js +147 -74
- package/package.json +1 -1
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
|
-
|
|
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 <
|
|
111
|
+
if (attempt < 4) await new Promise((r) => setTimeout(r, 3e3));
|
|
95
112
|
}
|
|
96
113
|
try {
|
|
97
114
|
const res = await fetch(
|
|
@@ -331,9 +348,9 @@ function openUrl(url) {
|
|
|
331
348
|
});
|
|
332
349
|
}
|
|
333
350
|
function discoverInstalledTemplates(targetDir) {
|
|
334
|
-
const
|
|
351
|
+
const templates2 = [];
|
|
335
352
|
const templatesDir = join(targetDir, "templates");
|
|
336
|
-
if (!existsSync2(templatesDir)) return
|
|
353
|
+
if (!existsSync2(templatesDir)) return templates2;
|
|
337
354
|
try {
|
|
338
355
|
const entries = readdirSync(templatesDir);
|
|
339
356
|
for (const entry of entries) {
|
|
@@ -342,7 +359,7 @@ function discoverInstalledTemplates(targetDir) {
|
|
|
342
359
|
if (!existsSync2(manifestPath)) continue;
|
|
343
360
|
try {
|
|
344
361
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
345
|
-
|
|
362
|
+
templates2.push({
|
|
346
363
|
id: manifest.id || entry,
|
|
347
364
|
name: manifest.name || entry,
|
|
348
365
|
description: manifest.description || "",
|
|
@@ -353,7 +370,7 @@ function discoverInstalledTemplates(targetDir) {
|
|
|
353
370
|
}
|
|
354
371
|
} catch {
|
|
355
372
|
}
|
|
356
|
-
return
|
|
373
|
+
return templates2;
|
|
357
374
|
}
|
|
358
375
|
function readComposioLocalKey() {
|
|
359
376
|
try {
|
|
@@ -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
|
|
548
|
-
|
|
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,
|
|
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 {
|
|
@@ -2561,6 +2589,75 @@ async function upgrade() {
|
|
|
2561
2589
|
p9.outro("Upgrade complete!");
|
|
2562
2590
|
}
|
|
2563
2591
|
|
|
2592
|
+
// src/commands/templates.ts
|
|
2593
|
+
import { resolve as resolve12 } from "path";
|
|
2594
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2595
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
2596
|
+
import * as p10 from "@clack/prompts";
|
|
2597
|
+
import pc10 from "picocolors";
|
|
2598
|
+
async function templates() {
|
|
2599
|
+
const { configDir, installed } = await loadInstalled();
|
|
2600
|
+
if (!installed || installed.capabilities.length === 0) {
|
|
2601
|
+
p10.intro(`${pc10.bgCyan(pc10.black(" seclaw "))} Templates`);
|
|
2602
|
+
p10.log.warning("No templates installed.");
|
|
2603
|
+
p10.log.info(`Add one: ${pc10.cyan("npx seclaw add <template>")}`);
|
|
2604
|
+
p10.log.info(`Browse: ${pc10.cyan("https://seclawai.com/templates")}`);
|
|
2605
|
+
p10.outro("");
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
const capabilities = installed.capabilities;
|
|
2609
|
+
const activeMode = installed.active || "auto";
|
|
2610
|
+
p10.intro(`${pc10.bgCyan(pc10.black(" seclaw "))} Installed Templates (${capabilities.length})`);
|
|
2611
|
+
for (const capId of capabilities) {
|
|
2612
|
+
const displayName = capId.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
2613
|
+
const promptPath = resolve12(configDir, "capabilities", capId, "system-prompt.md");
|
|
2614
|
+
const hasPrompt = existsSync9(promptPath);
|
|
2615
|
+
const schedPath = resolve12(configDir, "capabilities", capId, "schedules.json");
|
|
2616
|
+
let schedCount = 0;
|
|
2617
|
+
if (existsSync9(schedPath)) {
|
|
2618
|
+
try {
|
|
2619
|
+
const schedData = JSON.parse(await readFile9(schedPath, "utf-8"));
|
|
2620
|
+
schedCount = schedData.schedules.filter((s) => s.enabled !== false).length;
|
|
2621
|
+
} catch {
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
const icon = hasPrompt ? pc10.green("\u2713") : pc10.yellow("\u26A0");
|
|
2625
|
+
const schedLabel = schedCount > 0 ? pc10.dim(` \u2014 ${schedCount} schedule${schedCount > 1 ? "s" : ""}`) : "";
|
|
2626
|
+
p10.log.info(`${icon} ${pc10.bold(displayName)}${schedLabel}`);
|
|
2627
|
+
}
|
|
2628
|
+
let modeLabel;
|
|
2629
|
+
if (activeMode === "auto") {
|
|
2630
|
+
modeLabel = `Auto \u2014 all ${capabilities.length} capabilities active`;
|
|
2631
|
+
} else {
|
|
2632
|
+
const activeName = activeMode.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
2633
|
+
modeLabel = `${activeName} (focus mode)`;
|
|
2634
|
+
}
|
|
2635
|
+
p10.log.info("");
|
|
2636
|
+
p10.log.info(`Mode: ${pc10.cyan(modeLabel)}`);
|
|
2637
|
+
if (capabilities.length >= 2) {
|
|
2638
|
+
p10.log.info(`Switch: ${pc10.dim("npx seclaw switch")}`);
|
|
2639
|
+
}
|
|
2640
|
+
p10.outro("");
|
|
2641
|
+
}
|
|
2642
|
+
async function loadInstalled() {
|
|
2643
|
+
let wsHostPath = "shared";
|
|
2644
|
+
try {
|
|
2645
|
+
const envContent = await readFile9(resolve12(process.cwd(), ".env"), "utf-8");
|
|
2646
|
+
const wsMatch = envContent.match(/WORKSPACE_HOST_PATH=(.+)/);
|
|
2647
|
+
if (wsMatch?.[1]?.trim()) wsHostPath = wsMatch[1].trim();
|
|
2648
|
+
} catch {
|
|
2649
|
+
}
|
|
2650
|
+
const configDir = resolve12(process.cwd(), wsHostPath, "config");
|
|
2651
|
+
const installedPath = resolve12(configDir, "installed.json");
|
|
2652
|
+
if (!existsSync9(installedPath)) return { configDir, installed: null };
|
|
2653
|
+
try {
|
|
2654
|
+
const installed = JSON.parse(await readFile9(installedPath, "utf-8"));
|
|
2655
|
+
return { configDir, installed };
|
|
2656
|
+
} catch {
|
|
2657
|
+
return { configDir, installed: null };
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2564
2661
|
// src/cli.ts
|
|
2565
2662
|
program.name("seclaw").description("Secure autonomous AI agents in 60 seconds").version("0.1.0");
|
|
2566
2663
|
program.command("create", { isDefault: true }).alias("init").description("Set up a new seclaw project").argument("[directory]", "Project directory", ".").action(create);
|
|
@@ -2571,4 +2668,5 @@ program.command("stop").description("Stop all services").action(stop);
|
|
|
2571
2668
|
program.command("reconnect").description("Reconnect tunnel + Telegram webhook").action(reconnect);
|
|
2572
2669
|
program.command("doctor").description("Diagnose and fix common issues").action(doctor);
|
|
2573
2670
|
program.command("upgrade").description("Pull latest images and restart").action(upgrade);
|
|
2671
|
+
program.command("templates").description("List installed templates and capabilities").action(templates);
|
|
2574
2672
|
program.parse();
|
package/dist/runtime/agent.js
CHANGED
|
@@ -1107,20 +1107,26 @@ import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
|
|
|
1107
1107
|
import { resolve as resolve3 } from "path";
|
|
1108
1108
|
|
|
1109
1109
|
// config.ts
|
|
1110
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3 } from "fs";
|
|
1110
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, readdirSync as readdirSync2 } from "fs";
|
|
1111
1111
|
import { resolve as resolve2 } from "path";
|
|
1112
1112
|
var WORKSPACE = process.env.WORKSPACE_PATH || "/workspace";
|
|
1113
|
-
var
|
|
1113
|
+
var AUTO_BASE_PROMPT = `You are a personal AI assistant running on seclaw. You have multiple capabilities installed \u2014 use the right one based on the user's request.
|
|
1114
1114
|
|
|
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
|
|
1118
|
+
- Be proactive when you have relevant context to share`;
|
|
1119
|
+
function focusBasePrompt(capId) {
|
|
1120
|
+
const displayName = capId.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
1121
|
+
return `You are a specialized AI agent running on seclaw, focused exclusively on: ${displayName}.
|
|
1119
1122
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1123
|
+
IMPORTANT: You are in FOCUS MODE. Only use the capability described below. Do NOT answer questions outside your specialization \u2014 politely redirect the user to switch to the right agent via /templates.
|
|
1124
|
+
|
|
1125
|
+
## Communication
|
|
1126
|
+
- Detect the user's language and respond in the same language
|
|
1127
|
+
- Keep Telegram messages concise \u2014 use bullet points and short paragraphs
|
|
1128
|
+
- Be proactive when you have relevant context to share`;
|
|
1129
|
+
}
|
|
1124
1130
|
function loadConfig() {
|
|
1125
1131
|
return {
|
|
1126
1132
|
// LLM
|
|
@@ -1153,11 +1159,9 @@ function reloadSystemPrompt(workspace) {
|
|
|
1153
1159
|
if (installed.capabilities && installed.capabilities.length > 0) {
|
|
1154
1160
|
const active = installed.active || "auto";
|
|
1155
1161
|
if (active === "auto") {
|
|
1156
|
-
return composeCapabilityPrompt(installed.capabilities);
|
|
1162
|
+
return composeCapabilityPrompt(installed.capabilities, "auto");
|
|
1157
1163
|
}
|
|
1158
|
-
|
|
1159
|
-
const caps = base === active ? [active] : [base, active];
|
|
1160
|
-
return composeCapabilityPrompt(caps);
|
|
1164
|
+
return composeCapabilityPrompt([active], active);
|
|
1161
1165
|
}
|
|
1162
1166
|
} catch (err) {
|
|
1163
1167
|
console.error(`[config] Failed to parse installed.json: ${err.message}`);
|
|
@@ -1198,7 +1202,7 @@ function setActiveMode(workspace, active) {
|
|
|
1198
1202
|
console.error(`[config] Failed to update installed.json: ${err.message}`);
|
|
1199
1203
|
}
|
|
1200
1204
|
}
|
|
1201
|
-
function composeCapabilityPrompt(capabilities) {
|
|
1205
|
+
function composeCapabilityPrompt(capabilities, mode) {
|
|
1202
1206
|
const sections = [];
|
|
1203
1207
|
for (const id of capabilities) {
|
|
1204
1208
|
const promptPath = resolve2(WORKSPACE, "config", "capabilities", id, "system-prompt.md");
|
|
@@ -1210,21 +1214,58 @@ function composeCapabilityPrompt(capabilities) {
|
|
|
1210
1214
|
console.warn(`[config] Capability prompt not found: ${promptPath}`);
|
|
1211
1215
|
}
|
|
1212
1216
|
}
|
|
1217
|
+
const base = mode === "auto" ? AUTO_BASE_PROMPT : focusBasePrompt(mode);
|
|
1213
1218
|
if (sections.length === 0) {
|
|
1214
1219
|
console.warn("[config] No capability prompts loaded, using base prompt only");
|
|
1215
|
-
return
|
|
1220
|
+
return base;
|
|
1216
1221
|
}
|
|
1217
|
-
|
|
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;
|
|
1218
1234
|
}
|
|
1219
1235
|
function loadInstalledCapabilities(workspace) {
|
|
1220
1236
|
const installedPath = resolve2(workspace, "config", "installed.json");
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1237
|
+
const capsDir = resolve2(workspace, "config", "capabilities");
|
|
1238
|
+
let installed = { capabilities: [] };
|
|
1239
|
+
if (existsSync3(installedPath)) {
|
|
1240
|
+
try {
|
|
1241
|
+
installed = JSON.parse(readFileSync3(installedPath, "utf-8"));
|
|
1242
|
+
if (!installed.capabilities) installed.capabilities = [];
|
|
1243
|
+
} catch {
|
|
1244
|
+
}
|
|
1227
1245
|
}
|
|
1246
|
+
if (existsSync3(capsDir)) {
|
|
1247
|
+
let dirty = false;
|
|
1248
|
+
try {
|
|
1249
|
+
const dirs = readdirSync2(capsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1250
|
+
for (const dir of dirs) {
|
|
1251
|
+
const promptPath = resolve2(capsDir, dir, "system-prompt.md");
|
|
1252
|
+
if (existsSync3(promptPath) && !installed.capabilities.includes(dir)) {
|
|
1253
|
+
installed.capabilities.push(dir);
|
|
1254
|
+
dirty = true;
|
|
1255
|
+
console.log(`[config] Auto-discovered capability: ${dir}`);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
} catch {
|
|
1259
|
+
}
|
|
1260
|
+
if (dirty) {
|
|
1261
|
+
try {
|
|
1262
|
+
writeFileSync2(installedPath, JSON.stringify(installed, null, 2) + "\n");
|
|
1263
|
+
console.log(`[config] Updated installed.json: ${installed.capabilities.length} capabilities`);
|
|
1264
|
+
} catch {
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
return installed.capabilities;
|
|
1228
1269
|
}
|
|
1229
1270
|
|
|
1230
1271
|
// scheduler.ts
|
|
@@ -1427,12 +1468,8 @@ async function handleWebhook(req, res, config2, toolCtx2, inngestClient) {
|
|
|
1427
1468
|
await handleSchedules(chatId, config2);
|
|
1428
1469
|
return;
|
|
1429
1470
|
}
|
|
1430
|
-
if (userText.startsWith("/
|
|
1431
|
-
await
|
|
1432
|
-
return;
|
|
1433
|
-
}
|
|
1434
|
-
if (userText.startsWith("/switch")) {
|
|
1435
|
-
await handleSwitch(chatId, config2);
|
|
1471
|
+
if (userText.startsWith("/templates")) {
|
|
1472
|
+
await handleTemplates(chatId, config2);
|
|
1436
1473
|
return;
|
|
1437
1474
|
}
|
|
1438
1475
|
try {
|
|
@@ -1442,12 +1479,12 @@ ${userText}`;
|
|
|
1442
1479
|
let response = await runAgent(config2, toolCtx2, history, augmented);
|
|
1443
1480
|
console.log(`[telegram] Reply (${response.length} chars): ${response.substring(0, 120)}`);
|
|
1444
1481
|
let footer = "";
|
|
1445
|
-
const footerMatch = response.match(/\n
|
|
1482
|
+
const footerMatch = response.match(/\n[-—]{2,3}\s*(.+?)\s*$/);
|
|
1446
1483
|
if (footerMatch) {
|
|
1447
|
-
response = response.replace(/\n
|
|
1484
|
+
response = response.replace(/\n[-—]{2,3}\s*.+?\s*$/, "").trimEnd();
|
|
1448
1485
|
footer = `
|
|
1449
1486
|
|
|
1450
|
-
|
|
1487
|
+
\u2014 ${footerMatch[1].trim()}`;
|
|
1451
1488
|
}
|
|
1452
1489
|
saveHistory(chatId, config2.workspace, [
|
|
1453
1490
|
...history,
|
|
@@ -1468,7 +1505,7 @@ async function sendMessage(token, chatId, text) {
|
|
|
1468
1505
|
const chunks = splitMessage(text, 4e3);
|
|
1469
1506
|
for (const chunk of chunks) {
|
|
1470
1507
|
try {
|
|
1471
|
-
await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
|
1508
|
+
const res = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
|
1472
1509
|
method: "POST",
|
|
1473
1510
|
headers: { "Content-Type": "application/json" },
|
|
1474
1511
|
body: JSON.stringify({
|
|
@@ -1477,6 +1514,15 @@ async function sendMessage(token, chatId, text) {
|
|
|
1477
1514
|
parse_mode: "Markdown"
|
|
1478
1515
|
})
|
|
1479
1516
|
});
|
|
1517
|
+
const data = await res.json();
|
|
1518
|
+
if (!data.ok) {
|
|
1519
|
+
console.log(`[telegram] Markdown failed: ${data.description} \u2014 retrying plain`);
|
|
1520
|
+
await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
|
1521
|
+
method: "POST",
|
|
1522
|
+
headers: { "Content-Type": "application/json" },
|
|
1523
|
+
body: JSON.stringify({ chat_id: chatId, text: chunk })
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1480
1526
|
} catch {
|
|
1481
1527
|
try {
|
|
1482
1528
|
await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
|
@@ -1980,16 +2026,46 @@ async function editMessageWithButtons(token, chatId, messageId, text, keyboard)
|
|
|
1980
2026
|
} catch {
|
|
1981
2027
|
}
|
|
1982
2028
|
}
|
|
1983
|
-
|
|
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
|
+
];
|
|
2048
|
+
async function handleTemplates(chatId, config2) {
|
|
1984
2049
|
const capabilities = loadInstalledCapabilities(config2.workspace);
|
|
1985
2050
|
if (capabilities.length === 0) {
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
)
|
|
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);
|
|
1991
2066
|
return;
|
|
1992
2067
|
}
|
|
2068
|
+
const activeMode = getActiveMode(config2.workspace);
|
|
1993
2069
|
const scheduleConfig = loadScheduleConfig(config2.workspace);
|
|
1994
2070
|
const scheduleCountByCapability = {};
|
|
1995
2071
|
if (scheduleConfig) {
|
|
@@ -2010,49 +2086,46 @@ async function handleCapabilities(chatId, config2) {
|
|
|
2010
2086
|
const statusIcon = hasPrompt ? "\u2705" : "\u26A0\uFE0F";
|
|
2011
2087
|
return `${statusIcon} *${displayName}*${schedLabel}`;
|
|
2012
2088
|
});
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2089
|
+
let modeLabel;
|
|
2090
|
+
if (activeMode === "auto") {
|
|
2091
|
+
modeLabel = `Auto \u2014 all ${capabilities.length} templates active`;
|
|
2092
|
+
} else {
|
|
2093
|
+
const activeName = activeMode.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
2094
|
+
modeLabel = `${activeName} (focus mode)`;
|
|
2095
|
+
}
|
|
2096
|
+
const notInstalled = TEMPLATE_CATALOG.filter((t) => !capabilities.includes(t.id));
|
|
2097
|
+
let text = `*Installed Templates (${capabilities.length})*
|
|
2017
2098
|
|
|
2018
|
-
${lines.join("\n")}
|
|
2019
|
-
|
|
2020
|
-
}
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
config2.telegramToken,
|
|
2027
|
-
chatId,
|
|
2028
|
-
"Only one capability installed. Add more templates to use /switch.\n\nBrowse templates: seclawai.com/templates"
|
|
2029
|
-
);
|
|
2030
|
-
return;
|
|
2099
|
+
${lines.join("\n")}
|
|
2100
|
+
|
|
2101
|
+
Mode: *${modeLabel}*`;
|
|
2102
|
+
if (notInstalled.length > 0) {
|
|
2103
|
+
text += `
|
|
2104
|
+
|
|
2105
|
+
*Browse More (${notInstalled.length})*
|
|
2106
|
+
_Tap to view details:_`;
|
|
2031
2107
|
}
|
|
2032
2108
|
const keyboard = [];
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
const
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2109
|
+
if (capabilities.length >= 2) {
|
|
2110
|
+
const autoLabel = activeMode === "auto" ? "\u2705 Auto (All Templates)" : "\u{1F504} Auto (All Templates)";
|
|
2111
|
+
keyboard.push([{ text: autoLabel, callback_data: "cap_switch:auto" }]);
|
|
2112
|
+
const capButtons = [];
|
|
2113
|
+
for (const capId of capabilities) {
|
|
2114
|
+
const displayName = capId.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
2115
|
+
const icon = activeMode === capId ? "\u2705" : "\u25CB";
|
|
2116
|
+
capButtons.push({ text: `${icon} ${displayName}`, callback_data: `cap_switch:${capId}` });
|
|
2117
|
+
}
|
|
2118
|
+
for (let i = 0; i < capButtons.length; i += 2) {
|
|
2119
|
+
keyboard.push(capButtons.slice(i, i + 2));
|
|
2120
|
+
}
|
|
2043
2121
|
}
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
} else {
|
|
2050
|
-
const activeName = activeMode.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
2051
|
-
statusText += `Current: *${activeName}* (focus mode)
|
|
2052
|
-
`;
|
|
2053
|
-
statusText += "Only this capability + base is active.";
|
|
2122
|
+
for (const t of notInstalled) {
|
|
2123
|
+
keyboard.push([{
|
|
2124
|
+
text: `${t.name} \u2014 ${t.price}`,
|
|
2125
|
+
url: `https://seclawai.com/templates/${t.id}`
|
|
2126
|
+
}]);
|
|
2054
2127
|
}
|
|
2055
|
-
await sendMessageWithButtons(config2.telegramToken, chatId,
|
|
2128
|
+
await sendMessageWithButtons(config2.telegramToken, chatId, text, keyboard);
|
|
2056
2129
|
}
|
|
2057
2130
|
async function handleSwitchCallback(chatId, msgId, capId, config2) {
|
|
2058
2131
|
const capabilities = loadInstalledCapabilities(config2.workspace);
|