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 CHANGED
@@ -331,9 +331,9 @@ function openUrl(url) {
331
331
  });
332
332
  }
333
333
  function discoverInstalledTemplates(targetDir) {
334
- const templates = [];
334
+ const templates2 = [];
335
335
  const templatesDir = join(targetDir, "templates");
336
- if (!existsSync2(templatesDir)) return templates;
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
- templates.push({
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 templates;
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();
@@ -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 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.
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
- const base = installed.capabilities[0];
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 BASE_PROMPT;
1229
+ return base;
1216
1230
  }
1217
- return BASE_PROMPT + "\n\n" + sections.join("\n\n");
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
- if (!existsSync3(installedPath)) return [];
1222
- try {
1223
- const installed = JSON.parse(readFileSync3(installedPath, "utf-8"));
1224
- return installed.capabilities || [];
1225
- } catch {
1226
- return [];
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("/capabilities")) {
1431
- await handleCapabilities(chatId, config2);
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 handleCapabilities(chatId, config2) {
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 capabilities installed. Using default single-prompt mode."
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
- await sendMessage(
2014
- config2.telegramToken,
2015
- chatId,
2016
- `*Installed Capabilities*
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
- async function handleSwitch(chatId, config2) {
2022
- const capabilities = loadInstalledCapabilities(config2.workspace);
2023
- const activeMode = getActiveMode(config2.workspace);
2024
- if (capabilities.length <= 1) {
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
- "Only one capability installed. Add more templates to use /switch.\n\nBrowse templates: seclawai.com/templates"
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seclaw",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Secure autonomous AI agents in 60 seconds",
5
5
  "type": "module",
6
6
  "bin": {