seclaw 0.1.10 → 0.1.11
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 +74 -4
- package/dist/runtime/agent.js +96 -61
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -331,9 +331,9 @@ function openUrl(url) {
|
|
|
331
331
|
});
|
|
332
332
|
}
|
|
333
333
|
function discoverInstalledTemplates(targetDir) {
|
|
334
|
-
const
|
|
334
|
+
const templates2 = [];
|
|
335
335
|
const templatesDir = join(targetDir, "templates");
|
|
336
|
-
if (!existsSync2(templatesDir)) return
|
|
336
|
+
if (!existsSync2(templatesDir)) return templates2;
|
|
337
337
|
try {
|
|
338
338
|
const entries = readdirSync(templatesDir);
|
|
339
339
|
for (const entry of entries) {
|
|
@@ -342,7 +342,7 @@ function discoverInstalledTemplates(targetDir) {
|
|
|
342
342
|
if (!existsSync2(manifestPath)) continue;
|
|
343
343
|
try {
|
|
344
344
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
345
|
-
|
|
345
|
+
templates2.push({
|
|
346
346
|
id: manifest.id || entry,
|
|
347
347
|
name: manifest.name || entry,
|
|
348
348
|
description: manifest.description || "",
|
|
@@ -353,7 +353,7 @@ function discoverInstalledTemplates(targetDir) {
|
|
|
353
353
|
}
|
|
354
354
|
} catch {
|
|
355
355
|
}
|
|
356
|
-
return
|
|
356
|
+
return templates2;
|
|
357
357
|
}
|
|
358
358
|
function readComposioLocalKey() {
|
|
359
359
|
try {
|
|
@@ -2561,6 +2561,75 @@ async function upgrade() {
|
|
|
2561
2561
|
p9.outro("Upgrade complete!");
|
|
2562
2562
|
}
|
|
2563
2563
|
|
|
2564
|
+
// src/commands/templates.ts
|
|
2565
|
+
import { resolve as resolve12 } from "path";
|
|
2566
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2567
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
2568
|
+
import * as p10 from "@clack/prompts";
|
|
2569
|
+
import pc10 from "picocolors";
|
|
2570
|
+
async function templates() {
|
|
2571
|
+
const { configDir, installed } = await loadInstalled();
|
|
2572
|
+
if (!installed || installed.capabilities.length === 0) {
|
|
2573
|
+
p10.intro(`${pc10.bgCyan(pc10.black(" seclaw "))} Templates`);
|
|
2574
|
+
p10.log.warning("No templates installed.");
|
|
2575
|
+
p10.log.info(`Add one: ${pc10.cyan("npx seclaw add <template>")}`);
|
|
2576
|
+
p10.log.info(`Browse: ${pc10.cyan("https://seclawai.com/templates")}`);
|
|
2577
|
+
p10.outro("");
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
const capabilities = installed.capabilities;
|
|
2581
|
+
const activeMode = installed.active || "auto";
|
|
2582
|
+
p10.intro(`${pc10.bgCyan(pc10.black(" seclaw "))} Installed Templates (${capabilities.length})`);
|
|
2583
|
+
for (const capId of capabilities) {
|
|
2584
|
+
const displayName = capId.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
2585
|
+
const promptPath = resolve12(configDir, "capabilities", capId, "system-prompt.md");
|
|
2586
|
+
const hasPrompt = existsSync9(promptPath);
|
|
2587
|
+
const schedPath = resolve12(configDir, "capabilities", capId, "schedules.json");
|
|
2588
|
+
let schedCount = 0;
|
|
2589
|
+
if (existsSync9(schedPath)) {
|
|
2590
|
+
try {
|
|
2591
|
+
const schedData = JSON.parse(await readFile9(schedPath, "utf-8"));
|
|
2592
|
+
schedCount = schedData.schedules.filter((s) => s.enabled !== false).length;
|
|
2593
|
+
} catch {
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
const icon = hasPrompt ? pc10.green("\u2713") : pc10.yellow("\u26A0");
|
|
2597
|
+
const schedLabel = schedCount > 0 ? pc10.dim(` \u2014 ${schedCount} schedule${schedCount > 1 ? "s" : ""}`) : "";
|
|
2598
|
+
p10.log.info(`${icon} ${pc10.bold(displayName)}${schedLabel}`);
|
|
2599
|
+
}
|
|
2600
|
+
let modeLabel;
|
|
2601
|
+
if (activeMode === "auto") {
|
|
2602
|
+
modeLabel = `Auto \u2014 all ${capabilities.length} capabilities active`;
|
|
2603
|
+
} else {
|
|
2604
|
+
const activeName = activeMode.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
2605
|
+
modeLabel = `${activeName} (focus mode)`;
|
|
2606
|
+
}
|
|
2607
|
+
p10.log.info("");
|
|
2608
|
+
p10.log.info(`Mode: ${pc10.cyan(modeLabel)}`);
|
|
2609
|
+
if (capabilities.length >= 2) {
|
|
2610
|
+
p10.log.info(`Switch: ${pc10.dim("npx seclaw switch")}`);
|
|
2611
|
+
}
|
|
2612
|
+
p10.outro("");
|
|
2613
|
+
}
|
|
2614
|
+
async function loadInstalled() {
|
|
2615
|
+
let wsHostPath = "shared";
|
|
2616
|
+
try {
|
|
2617
|
+
const envContent = await readFile9(resolve12(process.cwd(), ".env"), "utf-8");
|
|
2618
|
+
const wsMatch = envContent.match(/WORKSPACE_HOST_PATH=(.+)/);
|
|
2619
|
+
if (wsMatch?.[1]?.trim()) wsHostPath = wsMatch[1].trim();
|
|
2620
|
+
} catch {
|
|
2621
|
+
}
|
|
2622
|
+
const configDir = resolve12(process.cwd(), wsHostPath, "config");
|
|
2623
|
+
const installedPath = resolve12(configDir, "installed.json");
|
|
2624
|
+
if (!existsSync9(installedPath)) return { configDir, installed: null };
|
|
2625
|
+
try {
|
|
2626
|
+
const installed = JSON.parse(await readFile9(installedPath, "utf-8"));
|
|
2627
|
+
return { configDir, installed };
|
|
2628
|
+
} catch {
|
|
2629
|
+
return { configDir, installed: null };
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2564
2633
|
// src/cli.ts
|
|
2565
2634
|
program.name("seclaw").description("Secure autonomous AI agents in 60 seconds").version("0.1.0");
|
|
2566
2635
|
program.command("create", { isDefault: true }).alias("init").description("Set up a new seclaw project").argument("[directory]", "Project directory", ".").action(create);
|
|
@@ -2571,4 +2640,5 @@ program.command("stop").description("Stop all services").action(stop);
|
|
|
2571
2640
|
program.command("reconnect").description("Reconnect tunnel + Telegram webhook").action(reconnect);
|
|
2572
2641
|
program.command("doctor").description("Diagnose and fix common issues").action(doctor);
|
|
2573
2642
|
program.command("upgrade").description("Pull latest images and restart").action(upgrade);
|
|
2643
|
+
program.command("templates").description("List installed templates and capabilities").action(templates);
|
|
2574
2644
|
program.parse();
|
package/dist/runtime/agent.js
CHANGED
|
@@ -1107,10 +1107,10 @@ 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
|
|
@@ -1121,6 +1121,21 @@ var BASE_PROMPT = `You are a personal AI assistant running on seclaw. You have m
|
|
|
1121
1121
|
At the end of every response, write on a new line:
|
|
1122
1122
|
--- CapabilityName
|
|
1123
1123
|
Where CapabilityName is the capability you primarily used (e.g. "Inbox Management", "Research & Intelligence"). If no specific capability applies, write: --- General`;
|
|
1124
|
+
function focusBasePrompt(capId) {
|
|
1125
|
+
const displayName = capId.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
1126
|
+
return `You are a specialized AI agent running on seclaw, focused exclusively on: ${displayName}.
|
|
1127
|
+
|
|
1128
|
+
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.
|
|
1129
|
+
|
|
1130
|
+
## Communication
|
|
1131
|
+
- Detect the user's language and respond in the same language
|
|
1132
|
+
- 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}`;
|
|
1138
|
+
}
|
|
1124
1139
|
function loadConfig() {
|
|
1125
1140
|
return {
|
|
1126
1141
|
// LLM
|
|
@@ -1153,11 +1168,9 @@ function reloadSystemPrompt(workspace) {
|
|
|
1153
1168
|
if (installed.capabilities && installed.capabilities.length > 0) {
|
|
1154
1169
|
const active = installed.active || "auto";
|
|
1155
1170
|
if (active === "auto") {
|
|
1156
|
-
return composeCapabilityPrompt(installed.capabilities);
|
|
1171
|
+
return composeCapabilityPrompt(installed.capabilities, "auto");
|
|
1157
1172
|
}
|
|
1158
|
-
|
|
1159
|
-
const caps = base === active ? [active] : [base, active];
|
|
1160
|
-
return composeCapabilityPrompt(caps);
|
|
1173
|
+
return composeCapabilityPrompt([active], active);
|
|
1161
1174
|
}
|
|
1162
1175
|
} catch (err) {
|
|
1163
1176
|
console.error(`[config] Failed to parse installed.json: ${err.message}`);
|
|
@@ -1198,7 +1211,7 @@ function setActiveMode(workspace, active) {
|
|
|
1198
1211
|
console.error(`[config] Failed to update installed.json: ${err.message}`);
|
|
1199
1212
|
}
|
|
1200
1213
|
}
|
|
1201
|
-
function composeCapabilityPrompt(capabilities) {
|
|
1214
|
+
function composeCapabilityPrompt(capabilities, mode) {
|
|
1202
1215
|
const sections = [];
|
|
1203
1216
|
for (const id of capabilities) {
|
|
1204
1217
|
const promptPath = resolve2(WORKSPACE, "config", "capabilities", id, "system-prompt.md");
|
|
@@ -1210,21 +1223,47 @@ function composeCapabilityPrompt(capabilities) {
|
|
|
1210
1223
|
console.warn(`[config] Capability prompt not found: ${promptPath}`);
|
|
1211
1224
|
}
|
|
1212
1225
|
}
|
|
1226
|
+
const base = mode === "auto" ? AUTO_BASE_PROMPT : focusBasePrompt(mode);
|
|
1213
1227
|
if (sections.length === 0) {
|
|
1214
1228
|
console.warn("[config] No capability prompts loaded, using base prompt only");
|
|
1215
|
-
return
|
|
1229
|
+
return base;
|
|
1216
1230
|
}
|
|
1217
|
-
return
|
|
1231
|
+
return base + "\n\n" + sections.join("\n\n");
|
|
1218
1232
|
}
|
|
1219
1233
|
function loadInstalledCapabilities(workspace) {
|
|
1220
1234
|
const installedPath = resolve2(workspace, "config", "installed.json");
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1235
|
+
const capsDir = resolve2(workspace, "config", "capabilities");
|
|
1236
|
+
let installed = { capabilities: [] };
|
|
1237
|
+
if (existsSync3(installedPath)) {
|
|
1238
|
+
try {
|
|
1239
|
+
installed = JSON.parse(readFileSync3(installedPath, "utf-8"));
|
|
1240
|
+
if (!installed.capabilities) installed.capabilities = [];
|
|
1241
|
+
} catch {
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
if (existsSync3(capsDir)) {
|
|
1245
|
+
let dirty = false;
|
|
1246
|
+
try {
|
|
1247
|
+
const dirs = readdirSync2(capsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1248
|
+
for (const dir of dirs) {
|
|
1249
|
+
const promptPath = resolve2(capsDir, dir, "system-prompt.md");
|
|
1250
|
+
if (existsSync3(promptPath) && !installed.capabilities.includes(dir)) {
|
|
1251
|
+
installed.capabilities.push(dir);
|
|
1252
|
+
dirty = true;
|
|
1253
|
+
console.log(`[config] Auto-discovered capability: ${dir}`);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
} catch {
|
|
1257
|
+
}
|
|
1258
|
+
if (dirty) {
|
|
1259
|
+
try {
|
|
1260
|
+
writeFileSync2(installedPath, JSON.stringify(installed, null, 2) + "\n");
|
|
1261
|
+
console.log(`[config] Updated installed.json: ${installed.capabilities.length} capabilities`);
|
|
1262
|
+
} catch {
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1227
1265
|
}
|
|
1266
|
+
return installed.capabilities;
|
|
1228
1267
|
}
|
|
1229
1268
|
|
|
1230
1269
|
// scheduler.ts
|
|
@@ -1427,12 +1466,8 @@ async function handleWebhook(req, res, config2, toolCtx2, inngestClient) {
|
|
|
1427
1466
|
await handleSchedules(chatId, config2);
|
|
1428
1467
|
return;
|
|
1429
1468
|
}
|
|
1430
|
-
if (userText.startsWith("/
|
|
1431
|
-
await
|
|
1432
|
-
return;
|
|
1433
|
-
}
|
|
1434
|
-
if (userText.startsWith("/switch")) {
|
|
1435
|
-
await handleSwitch(chatId, config2);
|
|
1469
|
+
if (userText.startsWith("/templates")) {
|
|
1470
|
+
await handleTemplates(chatId, config2);
|
|
1436
1471
|
return;
|
|
1437
1472
|
}
|
|
1438
1473
|
try {
|
|
@@ -1468,7 +1503,7 @@ async function sendMessage(token, chatId, text) {
|
|
|
1468
1503
|
const chunks = splitMessage(text, 4e3);
|
|
1469
1504
|
for (const chunk of chunks) {
|
|
1470
1505
|
try {
|
|
1471
|
-
await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
|
1506
|
+
const res = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
|
1472
1507
|
method: "POST",
|
|
1473
1508
|
headers: { "Content-Type": "application/json" },
|
|
1474
1509
|
body: JSON.stringify({
|
|
@@ -1477,6 +1512,15 @@ async function sendMessage(token, chatId, text) {
|
|
|
1477
1512
|
parse_mode: "Markdown"
|
|
1478
1513
|
})
|
|
1479
1514
|
});
|
|
1515
|
+
const data = await res.json();
|
|
1516
|
+
if (!data.ok) {
|
|
1517
|
+
console.log(`[telegram] Markdown failed: ${data.description} \u2014 retrying plain`);
|
|
1518
|
+
await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
|
1519
|
+
method: "POST",
|
|
1520
|
+
headers: { "Content-Type": "application/json" },
|
|
1521
|
+
body: JSON.stringify({ chat_id: chatId, text: chunk })
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1480
1524
|
} catch {
|
|
1481
1525
|
try {
|
|
1482
1526
|
await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
|
@@ -1980,16 +2024,17 @@ async function editMessageWithButtons(token, chatId, messageId, text, keyboard)
|
|
|
1980
2024
|
} catch {
|
|
1981
2025
|
}
|
|
1982
2026
|
}
|
|
1983
|
-
async function
|
|
2027
|
+
async function handleTemplates(chatId, config2) {
|
|
1984
2028
|
const capabilities = loadInstalledCapabilities(config2.workspace);
|
|
1985
2029
|
if (capabilities.length === 0) {
|
|
1986
2030
|
await sendMessage(
|
|
1987
2031
|
config2.telegramToken,
|
|
1988
2032
|
chatId,
|
|
1989
|
-
"No
|
|
2033
|
+
"No templates installed. Using default single-prompt mode.\n\nAdd templates: `npx seclaw add <template>`\nBrowse: seclawai.com/templates"
|
|
1990
2034
|
);
|
|
1991
2035
|
return;
|
|
1992
2036
|
}
|
|
2037
|
+
const activeMode = getActiveMode(config2.workspace);
|
|
1993
2038
|
const scheduleConfig = loadScheduleConfig(config2.workspace);
|
|
1994
2039
|
const scheduleCountByCapability = {};
|
|
1995
2040
|
if (scheduleConfig) {
|
|
@@ -2010,49 +2055,39 @@ async function handleCapabilities(chatId, config2) {
|
|
|
2010
2055
|
const statusIcon = hasPrompt ? "\u2705" : "\u26A0\uFE0F";
|
|
2011
2056
|
return `${statusIcon} *${displayName}*${schedLabel}`;
|
|
2012
2057
|
});
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2058
|
+
let modeLabel;
|
|
2059
|
+
if (activeMode === "auto") {
|
|
2060
|
+
modeLabel = `Auto \u2014 all ${capabilities.length} templates active`;
|
|
2061
|
+
} else {
|
|
2062
|
+
const activeName = activeMode.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
2063
|
+
modeLabel = `${activeName} (focus mode)`;
|
|
2064
|
+
}
|
|
2065
|
+
const text = `*Installed Templates (${capabilities.length})*
|
|
2017
2066
|
|
|
2018
|
-
${lines.join("\n")}
|
|
2019
|
-
|
|
2020
|
-
}
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2067
|
+
${lines.join("\n")}
|
|
2068
|
+
|
|
2069
|
+
Mode: *${modeLabel}*`;
|
|
2070
|
+
if (capabilities.length >= 2) {
|
|
2071
|
+
const keyboard = [];
|
|
2072
|
+
const autoLabel = activeMode === "auto" ? "\u2705 Auto (All Templates)" : "\u{1F504} Auto (All Templates)";
|
|
2073
|
+
keyboard.push([{ text: autoLabel, callback_data: "cap_switch:auto" }]);
|
|
2074
|
+
const capButtons = [];
|
|
2075
|
+
for (const capId of capabilities) {
|
|
2076
|
+
const displayName = capId.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
2077
|
+
const icon = activeMode === capId ? "\u2705" : "\u25CB";
|
|
2078
|
+
capButtons.push({ text: `${icon} ${displayName}`, callback_data: `cap_switch:${capId}` });
|
|
2079
|
+
}
|
|
2080
|
+
for (let i = 0; i < capButtons.length; i += 2) {
|
|
2081
|
+
keyboard.push(capButtons.slice(i, i + 2));
|
|
2082
|
+
}
|
|
2083
|
+
await sendMessageWithButtons(config2.telegramToken, chatId, text, keyboard);
|
|
2084
|
+
} else {
|
|
2025
2085
|
await sendMessage(
|
|
2026
2086
|
config2.telegramToken,
|
|
2027
2087
|
chatId,
|
|
2028
|
-
|
|
2088
|
+
text + "\n\nAdd more templates to switch modes:\n`npx seclaw add <template> --key YOUR_KEY`"
|
|
2029
2089
|
);
|
|
2030
|
-
return;
|
|
2031
|
-
}
|
|
2032
|
-
const keyboard = [];
|
|
2033
|
-
const autoLabel = activeMode === "auto" ? "\u2705 Auto (All Capabilities)" : "\u{1F504} Auto (All Capabilities)";
|
|
2034
|
-
keyboard.push([{ text: autoLabel, callback_data: "cap_switch:auto" }]);
|
|
2035
|
-
const capButtons = [];
|
|
2036
|
-
for (const capId of capabilities) {
|
|
2037
|
-
const displayName = capId.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
2038
|
-
const icon = activeMode === capId ? "\u2705" : "\u25CB";
|
|
2039
|
-
capButtons.push({ text: `${icon} ${displayName}`, callback_data: `cap_switch:${capId}` });
|
|
2040
|
-
}
|
|
2041
|
-
for (let i = 0; i < capButtons.length; i += 2) {
|
|
2042
|
-
keyboard.push(capButtons.slice(i, i + 2));
|
|
2043
|
-
}
|
|
2044
|
-
let statusText = "*Agent Mode*\n\n";
|
|
2045
|
-
if (activeMode === "auto") {
|
|
2046
|
-
statusText += `Current: *Auto* \u2014 all ${capabilities.length} capabilities active
|
|
2047
|
-
`;
|
|
2048
|
-
statusText += "The agent uses the right capability based on your message.";
|
|
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.";
|
|
2054
2090
|
}
|
|
2055
|
-
await sendMessageWithButtons(config2.telegramToken, chatId, statusText, keyboard);
|
|
2056
2091
|
}
|
|
2057
2092
|
async function handleSwitchCallback(chatId, msgId, capId, config2) {
|
|
2058
2093
|
const capabilities = loadInstalledCapabilities(config2.workspace);
|