seclaw 0.1.2 → 0.1.4

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.
Files changed (2) hide show
  1. package/dist/cli.js +125 -219
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -61,14 +61,12 @@ async function fetchTunnelUrlFromLogs(cwd) {
61
61
  try {
62
62
  const result = await execa(
63
63
  "docker",
64
- ["compose", "logs", "cloudflared", "--no-log-prefix"],
65
- { cwd, env: { ...process.env, COMPOSE_PROJECT_NAME: "seclaw" } }
64
+ ["compose", "logs", "cloudflared", "--no-log-prefix", "--tail", "50"],
65
+ { cwd }
66
66
  );
67
67
  const combined = result.stdout + "\n" + result.stderr;
68
- const match = combined.match(
69
- /https:\/\/[a-z0-9-]+\.trycloudflare\.com/
70
- );
71
- return match ? match[0] : null;
68
+ const matches = [...combined.matchAll(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/g)];
69
+ return matches.length > 0 ? matches[matches.length - 1][0] : null;
72
70
  } catch {
73
71
  return null;
74
72
  }
@@ -315,9 +313,9 @@ function openUrl(url) {
315
313
  });
316
314
  }
317
315
  function discoverInstalledTemplates(targetDir) {
318
- const templates2 = [];
316
+ const templates = [];
319
317
  const templatesDir = join(targetDir, "templates");
320
- if (!existsSync2(templatesDir)) return templates2;
318
+ if (!existsSync2(templatesDir)) return templates;
321
319
  try {
322
320
  const entries = readdirSync(templatesDir);
323
321
  for (const entry of entries) {
@@ -326,7 +324,7 @@ function discoverInstalledTemplates(targetDir) {
326
324
  if (!existsSync2(manifestPath)) continue;
327
325
  try {
328
326
  const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
329
- templates2.push({
327
+ templates.push({
330
328
  id: manifest.id || entry,
331
329
  name: manifest.name || entry,
332
330
  description: manifest.description || "",
@@ -337,7 +335,7 @@ function discoverInstalledTemplates(targetDir) {
337
335
  }
338
336
  } catch {
339
337
  }
340
- return templates2;
338
+ return templates;
341
339
  }
342
340
  function readComposioLocalKey() {
343
341
  try {
@@ -1915,120 +1913,19 @@ function openBrowser(url) {
1915
1913
  });
1916
1914
  }
1917
1915
 
1918
- // src/commands/templates.ts
1916
+ // src/commands/status.ts
1919
1917
  import { resolve as resolve8 } from "path";
1920
- import { existsSync as existsSync8 } from "fs";
1921
- import { readFile as readFile5, readdir } from "fs/promises";
1918
+ import { readFile as readFile5 } from "fs/promises";
1922
1919
  import * as p5 from "@clack/prompts";
1923
- import pc5 from "picocolors";
1924
- var TEMPLATES = [
1925
- { id: "productivity-agent", name: "Productivity Agent", price: 0, tier: "free", description: "Personal assistant \u2014 task management, daily reports, email drafting, file organization", hook: "npx seclaw add productivity-agent" },
1926
- { id: "data-analyst", name: "Data Analyst", price: 0, tier: "free", description: "Privacy-first local data analysis \u2014 drop CSV/JSON files, ask questions, get Python-powered insights", hook: "npx seclaw add data-analyst" },
1927
- { id: "inbox-agent", name: "Inbox Agent", price: 19, tier: "paid", description: "AI inbox manager that categorizes, summarizes, and triages your email", hook: "3 urgent, 5 action needed, 12 FYI, 8 newsletter" },
1928
- { id: "reddit-hn-digest", name: "Reddit & HN Digest", price: 19, tier: "paid", description: "Daily curated digest from Reddit and Hacker News", hook: "Never miss what matters on Reddit and HN" },
1929
- { id: "youtube-digest", name: "YouTube Digest", price: 19, tier: "paid", description: "Morning summary of new videos from favorite channels", hook: "Your YouTube feed, distilled to what matters" },
1930
- { id: "health-tracker", name: "Health Tracker", price: 29, tier: "paid", description: "Food & symptom tracking with weekly correlation analysis", hook: "Understand what your body is telling you" },
1931
- { id: "earnings-tracker", name: "Earnings Tracker", price: 29, tier: "paid", description: "Tech/AI earnings reports with beat/miss analysis", hook: "Every earnings call, analyzed in minutes" },
1932
- { id: "research-agent", name: "Research Agent", price: 39, tier: "paid", description: "AI research analyst that monitors competitors, trends, and industry news", hook: "Know when competitors change anything -- in 5 minutes" },
1933
- { id: "knowledge-base", name: "Knowledge Base", price: 39, tier: "paid", description: "Personal knowledge management from URLs, articles, tweets", hook: "Your second brain, always organized" },
1934
- { id: "family-calendar", name: "Family Calendar", price: 39, tier: "paid", description: "Household coordination with calendar aggregation", hook: "Everyone in sync, nothing forgotten" },
1935
- { id: "content-agent", name: "Content Agent", price: 49, tier: "paid", description: "AI content strategist that researches trends, drafts posts in your voice, and tracks engagement", hook: "Your X account grows while you sleep" },
1936
- { id: "personal-crm", name: "Personal CRM", price: 49, tier: "paid", description: "Contact management with auto-discovery from email", hook: "Never lose touch with anyone important" },
1937
- { id: "youtube-creator", name: "YouTube Creator", price: 69, tier: "paid", description: "Content pipeline for YouTube creators", hook: "From idea to upload, fully assisted" },
1938
- { id: "devops-agent", name: "DevOps Agent", price: 79, tier: "paid", description: "Infrastructure monitoring + self-healing", hook: "Sleep through incidents, wake up to fixes" },
1939
- { id: "customer-service", name: "Customer Service", price: 79, tier: "paid", description: "Multi-channel customer support with knowledge base", hook: "24/7 support without the headcount" },
1940
- { id: "sales-agent", name: "Sales Agent", price: 79, tier: "paid", description: "AI-powered B2B sales development representative", hook: "Find leads overnight, inbox full by morning" },
1941
- { id: "six-agent-company", name: "Six Agent Company", price: 149, tier: "paid", description: "6 AI agents running your company: CEO, Engineer, QA, Data, Marketing, Growth", hook: "6 AI agents running your company for $8/month" }
1942
- ];
1943
- async function templates() {
1944
- p5.intro(`${pc5.bgCyan(pc5.black(" seclaw "))} Available Templates`);
1945
- const all = await loadManifests();
1946
- if (all.length === 0) {
1947
- p5.log.warn("No templates found. Try updating: npm i -g seclaw");
1948
- return;
1949
- }
1950
- const free = all.filter((t) => t.tier === "free");
1951
- const paid = all.filter((t) => t.tier === "paid");
1952
- if (free.length > 0) {
1953
- p5.log.info(pc5.bold("Free"));
1954
- for (const t of free) {
1955
- p5.log.message(
1956
- ` ${pc5.green(t.id)}
1957
- ${t.description}
1958
- ${pc5.dim(t.hook)}`
1959
- );
1960
- }
1961
- }
1962
- if (paid.length > 0) {
1963
- p5.log.info(pc5.bold("Paid"));
1964
- for (const t of paid) {
1965
- p5.log.message(
1966
- ` ${pc5.yellow(t.id)} ${pc5.dim(`$${t.price}`)}
1967
- ${t.description}
1968
- ${pc5.dim(t.hook)}`
1969
- );
1970
- }
1971
- }
1972
- p5.log.info("");
1973
- p5.log.info(pc5.bold("Usage:"));
1974
- p5.log.message(` ${pc5.cyan("npx seclaw add productivity-agent")} ${pc5.dim("free")}`);
1975
- p5.log.message(` ${pc5.cyan("npx seclaw add content-agent --key KEY")} ${pc5.dim("paid")}`);
1976
- p5.outro(`${pc5.dim("Browse:")} https://seclawai.com/templates`);
1977
- }
1978
- async function loadManifests() {
1979
- const manifests = [];
1980
- const seen = /* @__PURE__ */ new Set();
1981
- const searchRoots = [
1982
- resolve8(import.meta.dirname, "templates"),
1983
- resolve8(process.cwd(), "packages", "cli", "templates")
1984
- ];
1985
- for (const root of searchRoots) {
1986
- for (const tier of ["free", "paid"]) {
1987
- const base = resolve8(root, tier);
1988
- if (!existsSync8(base)) continue;
1989
- let entries;
1990
- try {
1991
- entries = await readdir(base, { withFileTypes: true });
1992
- } catch {
1993
- continue;
1994
- }
1995
- for (const entry of entries) {
1996
- if (!entry.isDirectory()) continue;
1997
- if (seen.has(entry.name)) continue;
1998
- const manifestPath = resolve8(base, entry.name, "manifest.json");
1999
- if (!existsSync8(manifestPath)) continue;
2000
- try {
2001
- const raw = await readFile5(manifestPath, "utf-8");
2002
- manifests.push(JSON.parse(raw));
2003
- seen.add(entry.name);
2004
- } catch {
2005
- }
2006
- }
2007
- }
2008
- }
2009
- for (const t of TEMPLATES) {
2010
- if (!seen.has(t.id)) {
2011
- manifests.push(t);
2012
- seen.add(t.id);
2013
- }
2014
- }
2015
- manifests.sort((a, b) => a.price - b.price);
2016
- return manifests;
2017
- }
2018
-
2019
- // src/commands/status.ts
2020
- import { resolve as resolve9 } from "path";
2021
- import { readFile as readFile6 } from "fs/promises";
2022
- import * as p6 from "@clack/prompts";
2023
1920
  import { execa as execa5 } from "execa";
2024
- import pc6 from "picocolors";
1921
+ import pc5 from "picocolors";
2025
1922
  async function status() {
2026
- p6.intro(`${pc6.bgCyan(pc6.black(" seclaw "))} Status`);
1923
+ p5.intro(`${pc5.bgCyan(pc5.black(" seclaw "))} Status`);
2027
1924
  const projectDir = findProjectDir();
2028
1925
  if (!projectDir) {
2029
- p6.log.warn("No seclaw project found.");
2030
- p6.log.info(` Start one with: ${pc6.cyan("npx seclaw my-agent")}`);
2031
- p6.outro("");
1926
+ p5.log.warn("No seclaw project found.");
1927
+ p5.log.info(` Start one with: ${pc5.cyan("npx seclaw my-agent")}`);
1928
+ p5.outro("");
2032
1929
  return;
2033
1930
  }
2034
1931
  try {
@@ -2041,15 +1938,15 @@ async function status() {
2041
1938
  (l) => JSON.parse(l)
2042
1939
  );
2043
1940
  if (services.length === 0) {
2044
- p6.log.warn("Services are not running.");
2045
- p6.log.info(` Start with: ${pc6.cyan("npx seclaw")} in ${pc6.dim(projectDir)}`);
2046
- p6.outro("");
1941
+ p5.log.warn("Services are not running.");
1942
+ p5.log.info(` Start with: ${pc5.cyan("npx seclaw")} in ${pc5.dim(projectDir)}`);
1943
+ p5.outro("");
2047
1944
  return;
2048
1945
  }
2049
1946
  for (const svc of services) {
2050
- const icon = svc.State === "running" ? pc6.green("\u25CF") : pc6.red("\u25CF");
2051
- const health = svc.Status.includes("healthy") ? pc6.green("healthy") : svc.Status.includes("starting") ? pc6.yellow("starting") : pc6.dim(svc.Status);
2052
- p6.log.info(`${icon} ${pc6.bold(svc.Service.padEnd(20))} ${health}`);
1947
+ const icon = svc.State === "running" ? pc5.green("\u25CF") : pc5.red("\u25CF");
1948
+ const health = svc.Status.includes("healthy") ? pc5.green("healthy") : svc.Status.includes("starting") ? pc5.yellow("starting") : pc5.dim(svc.Status);
1949
+ p5.log.info(`${icon} ${pc5.bold(svc.Service.padEnd(20))} ${health}`);
2053
1950
  }
2054
1951
  const infoLines = [];
2055
1952
  let agentOk = false;
@@ -2070,16 +1967,16 @@ async function status() {
2070
1967
  } catch {
2071
1968
  }
2072
1969
  infoLines.push(
2073
- `${pc6.white(pc6.bold("Agent:"))} ${agentOk ? pc6.green("healthy") : pc6.yellow("starting...")}`
1970
+ `${pc5.white(pc5.bold("Agent:"))} ${agentOk ? pc5.green("healthy") : pc5.yellow("starting...")}`
2074
1971
  );
2075
1972
  const tunnelUrl = await getTunnelUrl(projectDir, 3);
2076
1973
  if (tunnelUrl) {
2077
- infoLines.push(`${pc6.white(pc6.bold("Public URL:"))} ${pc6.cyan(tunnelUrl)}`);
1974
+ infoLines.push(`${pc5.white(pc5.bold("Public URL:"))} ${pc5.cyan(tunnelUrl)}`);
2078
1975
  } else {
2079
- infoLines.push(`${pc6.white(pc6.bold("Public URL:"))} ${pc6.yellow("not detected yet")}`);
1976
+ infoLines.push(`${pc5.white(pc5.bold("Public URL:"))} ${pc5.yellow("not detected yet")}`);
2080
1977
  }
2081
1978
  try {
2082
- const env = await readFile6(resolve9(projectDir, ".env"), "utf-8");
1979
+ const env = await readFile5(resolve8(projectDir, ".env"), "utf-8");
2083
1980
  const tokenMatch = env.match(/TELEGRAM_BOT_TOKEN=(.+)/);
2084
1981
  if (tokenMatch && tokenMatch[1].includes(":")) {
2085
1982
  const botToken = tokenMatch[1].trim();
@@ -2087,50 +1984,50 @@ async function status() {
2087
1984
  const res = await fetch(`https://api.telegram.org/bot${botToken}/getMe`);
2088
1985
  const data = await res.json();
2089
1986
  if (data.ok && data.result) {
2090
- infoLines.push(`${pc6.white(pc6.bold("Telegram:"))} ${pc6.green("@" + data.result.username)}`);
1987
+ infoLines.push(`${pc5.white(pc5.bold("Telegram:"))} ${pc5.green("@" + data.result.username)}`);
2091
1988
  }
2092
1989
  } catch {
2093
- infoLines.push(`${pc6.white(pc6.bold("Telegram:"))} ${pc6.green("configured")}`);
1990
+ infoLines.push(`${pc5.white(pc5.bold("Telegram:"))} ${pc5.green("configured")}`);
2094
1991
  }
2095
1992
  }
2096
1993
  } catch {
2097
1994
  }
2098
1995
  try {
2099
- const env = await readFile6(resolve9(projectDir, ".env"), "utf-8");
1996
+ const env = await readFile5(resolve8(projectDir, ".env"), "utf-8");
2100
1997
  if (env.includes("COMPOSIO_API_KEY=")) {
2101
- infoLines.push(`${pc6.white(pc6.bold("Composio:"))} ${pc6.green("configured")}`);
1998
+ infoLines.push(`${pc5.white(pc5.bold("Composio:"))} ${pc5.green("configured")}`);
2102
1999
  }
2103
2000
  } catch {
2104
2001
  }
2105
2002
  let wsPath = "shared";
2106
2003
  try {
2107
- const envContent = await readFile6(resolve9(projectDir, ".env"), "utf-8");
2004
+ const envContent = await readFile5(resolve8(projectDir, ".env"), "utf-8");
2108
2005
  const wsMatch = envContent.match(/WORKSPACE_HOST_PATH=(.+)/);
2109
2006
  if (wsMatch?.[1]?.trim()) wsPath = wsMatch[1].trim();
2110
2007
  } catch {
2111
2008
  }
2112
- infoLines.push(`${pc6.white(pc6.bold("Workspace:"))} ${resolve9(projectDir, wsPath)}`);
2113
- infoLines.push(`${pc6.white(pc6.bold("Project:"))} ${projectDir}`);
2114
- p6.note(infoLines.join("\n"), "seclaw");
2009
+ infoLines.push(`${pc5.white(pc5.bold("Workspace:"))} ${resolve8(projectDir, wsPath)}`);
2010
+ infoLines.push(`${pc5.white(pc5.bold("Project:"))} ${projectDir}`);
2011
+ p5.note(infoLines.join("\n"), "seclaw");
2115
2012
  } catch {
2116
- p6.log.error("Could not check status. Is Docker running?");
2013
+ p5.log.error("Could not check status. Is Docker running?");
2117
2014
  }
2118
- p6.outro(`${pc6.white("Manage:")} npx seclaw stop | integrations | upgrade`);
2015
+ p5.outro(`${pc5.white("Manage:")} npx seclaw stop | integrations | upgrade`);
2119
2016
  }
2120
2017
 
2121
2018
  // src/commands/stop.ts
2122
- import * as p7 from "@clack/prompts";
2019
+ import * as p6 from "@clack/prompts";
2123
2020
  import { execa as execa6 } from "execa";
2124
- import pc7 from "picocolors";
2021
+ import pc6 from "picocolors";
2125
2022
  async function stop() {
2126
- p7.intro(`${pc7.bgCyan(pc7.black(" seclaw "))} Stopping services...`);
2023
+ p6.intro(`${pc6.bgCyan(pc6.black(" seclaw "))} Stopping services...`);
2127
2024
  const projectDir = findProjectDir();
2128
2025
  if (!projectDir) {
2129
- p7.log.warn("No seclaw project found. Nothing to stop.");
2130
- p7.outro("");
2026
+ p6.log.warn("No seclaw project found. Nothing to stop.");
2027
+ p6.outro("");
2131
2028
  return;
2132
2029
  }
2133
- const s = p7.spinner();
2030
+ const s = p6.spinner();
2134
2031
  s.start("Stopping containers...");
2135
2032
  try {
2136
2033
  await execa6("docker", ["compose", "down"], {
@@ -2140,26 +2037,26 @@ async function stop() {
2140
2037
  s.stop("All services stopped.");
2141
2038
  } catch {
2142
2039
  s.stop("Failed to stop services.");
2143
- p7.log.error(`Try manually: cd ${projectDir} && docker compose down`);
2040
+ p6.log.error(`Try manually: cd ${projectDir} && docker compose down`);
2144
2041
  }
2145
- p7.outro(`Restart anytime: ${pc7.cyan("npx seclaw")} in ${pc7.dim(projectDir)}`);
2042
+ p6.outro(`Restart anytime: ${pc6.cyan("npx seclaw")} in ${pc6.dim(projectDir)}`);
2146
2043
  }
2147
2044
 
2148
2045
  // src/commands/reconnect.ts
2149
- import { resolve as resolve10 } from "path";
2150
- import { readFile as readFile7 } from "fs/promises";
2046
+ import { resolve as resolve9 } from "path";
2047
+ import { readFile as readFile6 } from "fs/promises";
2151
2048
  import { execa as execa7 } from "execa";
2152
- import * as p8 from "@clack/prompts";
2153
- import pc8 from "picocolors";
2049
+ import * as p7 from "@clack/prompts";
2050
+ import pc7 from "picocolors";
2154
2051
  async function reconnect() {
2155
2052
  const projectDir = findProjectDir();
2156
2053
  if (!projectDir) {
2157
- p8.intro(`${pc8.bgCyan(pc8.black(" seclaw "))}`);
2158
- p8.log.error("No seclaw project found. Run from your project directory.");
2054
+ p7.intro(`${pc7.bgCyan(pc7.black(" seclaw "))}`);
2055
+ p7.log.error("No seclaw project found. Run from your project directory.");
2159
2056
  return;
2160
2057
  }
2161
- p8.intro(`${pc8.bgCyan(pc8.black(" seclaw "))} Reconnect`);
2162
- const s = p8.spinner();
2058
+ p7.intro(`${pc7.bgCyan(pc7.black(" seclaw "))} Reconnect`);
2059
+ const s = p7.spinner();
2163
2060
  const env = { ...process.env, COMPOSE_PROJECT_NAME: "seclaw" };
2164
2061
  s.start("Restarting all services...");
2165
2062
  await clearTunnelCache(projectDir);
@@ -2168,7 +2065,7 @@ async function reconnect() {
2168
2065
  await execa7("docker", ["compose", "up", "-d"], { cwd: projectDir, env });
2169
2066
  } catch {
2170
2067
  s.stop("Failed to restart services.");
2171
- p8.log.error("Is Docker running? Try: npx seclaw status");
2068
+ p7.log.error("Is Docker running? Try: npx seclaw status");
2172
2069
  return;
2173
2070
  }
2174
2071
  s.stop("Services restarted.");
@@ -2179,12 +2076,12 @@ async function reconnect() {
2179
2076
  const tunnelUrl = await getTunnelUrl(projectDir, 30);
2180
2077
  if (!tunnelUrl) {
2181
2078
  s.stop("Could not detect tunnel URL.");
2182
- p8.log.error("Tunnel may still be starting. Try again in a minute.");
2079
+ p7.log.error("Tunnel may still be starting. Try again in a minute.");
2183
2080
  return;
2184
2081
  }
2185
- s.stop(`Tunnel ready: ${pc8.cyan(tunnelUrl)}`);
2082
+ s.stop(`Tunnel ready: ${pc7.cyan(tunnelUrl)}`);
2186
2083
  try {
2187
- const envContent = await readFile7(resolve10(projectDir, ".env"), "utf-8");
2084
+ const envContent = await readFile6(resolve9(projectDir, ".env"), "utf-8");
2188
2085
  const tokenMatch = envContent.match(/TELEGRAM_BOT_TOKEN=(.+)/);
2189
2086
  if (tokenMatch && tokenMatch[1].includes(":")) {
2190
2087
  s.start("Setting Telegram webhook...");
@@ -2193,27 +2090,27 @@ async function reconnect() {
2193
2090
  }
2194
2091
  } catch {
2195
2092
  }
2196
- p8.note(
2197
- `${pc8.white(pc8.bold("Tunnel:"))} ${pc8.cyan(tunnelUrl)}`,
2093
+ p7.note(
2094
+ `${pc7.white(pc7.bold("Tunnel:"))} ${pc7.cyan(tunnelUrl)}`,
2198
2095
  "seclaw"
2199
2096
  );
2200
- p8.outro("Reconnected! Send a message on Telegram to test.");
2097
+ p7.outro("Reconnected! Send a message on Telegram to test.");
2201
2098
  }
2202
2099
 
2203
2100
  // src/commands/doctor.ts
2204
- import { resolve as resolve11 } from "path";
2205
- import { existsSync as existsSync9 } from "fs";
2206
- import { readFile as readFile8 } from "fs/promises";
2101
+ import { resolve as resolve10 } from "path";
2102
+ import { existsSync as existsSync8 } from "fs";
2103
+ import { readFile as readFile7 } from "fs/promises";
2207
2104
  import { execa as execa8 } from "execa";
2208
- import * as p9 from "@clack/prompts";
2209
- import pc9 from "picocolors";
2210
- var PASS = pc9.green("PASS");
2211
- var FAIL = pc9.red("FAIL");
2212
- var FIX = pc9.cyan("FIX ");
2105
+ import * as p8 from "@clack/prompts";
2106
+ import pc8 from "picocolors";
2107
+ var PASS = pc8.green("PASS");
2108
+ var FAIL = pc8.red("FAIL");
2109
+ var FIX = pc8.cyan("FIX ");
2213
2110
  async function doctor() {
2214
- p9.intro(`${pc9.bgCyan(pc9.black(" seclaw "))} Doctor`);
2111
+ p8.intro(`${pc8.bgCyan(pc8.black(" seclaw "))} Doctor`);
2215
2112
  const results = [];
2216
- const s = p9.spinner();
2113
+ const s = p8.spinner();
2217
2114
  s.start("Checking Docker...");
2218
2115
  const dockerCheck = await checkDockerHealth();
2219
2116
  results.push(dockerCheck);
@@ -2224,7 +2121,7 @@ async function doctor() {
2224
2121
  }
2225
2122
  s.start("Finding project...");
2226
2123
  const projectDir = findProjectDir();
2227
- const projectCheck = projectDir ? { name: "Project", ok: true, message: `Found at ${pc9.dim(projectDir)}` } : { name: "Project", ok: false, message: "No seclaw project found. Run from your project directory." };
2124
+ const projectCheck = projectDir ? { name: "Project", ok: true, message: `Found at ${pc8.dim(projectDir)}` } : { name: "Project", ok: false, message: "No seclaw project found. Run from your project directory." };
2228
2125
  results.push(projectCheck);
2229
2126
  s.stop(formatCheck(projectCheck));
2230
2127
  if (!projectDir) {
@@ -2260,21 +2157,18 @@ async function doctor() {
2260
2157
  if (fixable.length > 0) {
2261
2158
  let shouldFix = false;
2262
2159
  try {
2263
- const answer = await p9.confirm({
2160
+ const answer = await p8.confirm({
2264
2161
  message: `Found ${fixable.length} fixable issue(s). Auto-fix?`,
2265
2162
  initialValue: true
2266
2163
  });
2267
- shouldFix = !p9.isCancel(answer) && !!answer;
2164
+ shouldFix = !p8.isCancel(answer) && !!answer;
2268
2165
  } catch {
2269
2166
  shouldFix = true;
2270
2167
  }
2271
2168
  if (!shouldFix) {
2272
- p9.outro("Run again after fixing manually.");
2169
+ p8.outro("Run again after fixing manually.");
2273
2170
  return;
2274
2171
  }
2275
- s.start("Stopping conflicting containers...");
2276
- await stopExistingSeclaw();
2277
- s.stop(`${FIX} Cleared conflicting containers`);
2278
2172
  for (const check of fixable) {
2279
2173
  s.start(`Fixing: ${check.name}...`);
2280
2174
  try {
@@ -2284,10 +2178,10 @@ async function doctor() {
2284
2178
  s.stop(`${FAIL} ${check.name} \u2014 ${err instanceof Error ? err.message : "unknown error"}`);
2285
2179
  }
2286
2180
  }
2287
- p9.outro("Fixes applied! Run doctor again to verify.");
2181
+ p8.outro("Fixes applied! Run doctor again to verify.");
2288
2182
  } else {
2289
2183
  const allOk = results.every((r) => r.ok);
2290
- p9.outro(allOk ? "All checks passed!" : "Some issues need manual intervention.");
2184
+ p8.outro(allOk ? "All checks passed!" : "Some issues need manual intervention.");
2291
2185
  }
2292
2186
  }
2293
2187
  async function checkDockerHealth() {
@@ -2423,12 +2317,12 @@ async function checkTunnel(projectDir) {
2423
2317
  try {
2424
2318
  const result = await execa8(
2425
2319
  "docker",
2426
- ["compose", "logs", "cloudflared", "--no-log-prefix"],
2320
+ ["compose", "logs", "cloudflared", "--no-log-prefix", "--tail", "50"],
2427
2321
  { cwd: projectDir, env: { ...process.env, COMPOSE_PROJECT_NAME: getProjectName(projectDir) } }
2428
2322
  );
2429
2323
  const combined = result.stdout + "\n" + result.stderr;
2430
- const match = combined.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/);
2431
- if (!match) {
2324
+ const matches = [...combined.matchAll(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/g)];
2325
+ if (matches.length === 0) {
2432
2326
  return {
2433
2327
  name: "Tunnel",
2434
2328
  ok: false,
@@ -2442,7 +2336,7 @@ async function checkTunnel(projectDir) {
2442
2336
  }
2443
2337
  };
2444
2338
  }
2445
- const tunnelUrl = match[0];
2339
+ const tunnelUrl = matches[matches.length - 1][0];
2446
2340
  try {
2447
2341
  const res = await fetch(`${tunnelUrl}/health`, {
2448
2342
  signal: AbortSignal.timeout(1e4)
@@ -2460,8 +2354,15 @@ async function checkTunnel(projectDir) {
2460
2354
  return {
2461
2355
  name: "Tunnel",
2462
2356
  ok: false,
2463
- message: `URL found (${pc9.dim(tunnelUrl)}) but not reachable`,
2464
- tunnelUrl
2357
+ message: `URL found (${pc8.dim(tunnelUrl)}) but not reachable`,
2358
+ tunnelUrl,
2359
+ fix: async () => {
2360
+ await clearTunnelCache(projectDir);
2361
+ const env = { ...process.env, COMPOSE_PROJECT_NAME: getProjectName(projectDir) };
2362
+ await execa8("docker", ["compose", "restart", "cloudflared"], { cwd: projectDir, env });
2363
+ const newUrl = await getTunnelUrl(projectDir, 20);
2364
+ return newUrl ? `New tunnel: ${newUrl}` : "Restarted cloudflared \u2014 run doctor again";
2365
+ }
2465
2366
  };
2466
2367
  }
2467
2368
  } catch {
@@ -2469,13 +2370,13 @@ async function checkTunnel(projectDir) {
2469
2370
  }
2470
2371
  }
2471
2372
  async function checkTelegram(projectDir, tunnelCheck) {
2472
- const envPath = resolve11(projectDir, ".env");
2473
- if (!existsSync9(envPath)) {
2373
+ const envPath = resolve10(projectDir, ".env");
2374
+ if (!existsSync8(envPath)) {
2474
2375
  return { name: "Telegram", ok: false, message: ".env not found" };
2475
2376
  }
2476
2377
  let botToken = "";
2477
2378
  try {
2478
- const envContent = await readFile8(envPath, "utf-8");
2379
+ const envContent = await readFile7(envPath, "utf-8");
2479
2380
  const match = envContent.match(/TELEGRAM_BOT_TOKEN=(.+)/);
2480
2381
  botToken = match?.[1]?.trim() || "";
2481
2382
  } catch {
@@ -2498,15 +2399,18 @@ async function checkTelegram(projectDir, tunnelCheck) {
2498
2399
  }
2499
2400
  const wh = whData.result;
2500
2401
  const tunnelUrl = tunnelCheck.tunnelUrl || "";
2402
+ const webhookFix = async () => {
2403
+ const freshUrl = await getTunnelUrl(projectDir, 5);
2404
+ if (!freshUrl) return "No tunnel URL available \u2014 fix tunnel first";
2405
+ const ok = await setTelegramWebhook(botToken, freshUrl);
2406
+ return ok ? `Webhook set to ${freshUrl}` : "Could not set webhook";
2407
+ };
2501
2408
  if (!wh.url) {
2502
2409
  return {
2503
2410
  name: `Telegram (${botName})`,
2504
2411
  ok: false,
2505
2412
  message: "Webhook not set",
2506
- fix: tunnelUrl ? async () => {
2507
- const ok = await setTelegramWebhook(botToken, tunnelUrl);
2508
- return ok ? "Webhook set" : "Could not set webhook";
2509
- } : void 0
2413
+ fix: webhookFix
2510
2414
  };
2511
2415
  }
2512
2416
  if (tunnelUrl && !wh.url.includes(tunnelUrl.replace("https://", ""))) {
@@ -2514,10 +2418,7 @@ async function checkTelegram(projectDir, tunnelCheck) {
2514
2418
  name: `Telegram (${botName})`,
2515
2419
  ok: false,
2516
2420
  message: "Webhook points to old tunnel",
2517
- fix: async () => {
2518
- const ok = await setTelegramWebhook(botToken, tunnelUrl);
2519
- return ok ? "Webhook updated" : "Could not update webhook";
2520
- }
2421
+ fix: webhookFix
2521
2422
  };
2522
2423
  }
2523
2424
  if (wh.last_error_message) {
@@ -2525,7 +2426,13 @@ async function checkTelegram(projectDir, tunnelCheck) {
2525
2426
  return {
2526
2427
  name: `Telegram (${botName})`,
2527
2428
  ok: false,
2528
- message: `Error ${age}m ago: ${wh.last_error_message}`
2429
+ message: `Error ${age}m ago: ${wh.last_error_message}`,
2430
+ fix: async () => {
2431
+ const freshUrl = await getTunnelUrl(projectDir, 5);
2432
+ if (!freshUrl) return "No tunnel URL available \u2014 fix tunnel first";
2433
+ const ok = await setTelegramWebhook(botToken, freshUrl);
2434
+ return ok ? `Webhook re-set to ${freshUrl}` : "Could not set webhook";
2435
+ }
2529
2436
  };
2530
2437
  }
2531
2438
  return {
@@ -2539,13 +2446,13 @@ async function checkTelegram(projectDir, tunnelCheck) {
2539
2446
  }
2540
2447
  async function checkComposio(projectDir) {
2541
2448
  try {
2542
- const env = await readFile8(resolve11(projectDir, ".env"), "utf-8");
2449
+ const env = await readFile7(resolve10(projectDir, ".env"), "utf-8");
2543
2450
  const match = env.match(/COMPOSIO_API_KEY=(\S+)/);
2544
2451
  if (!match) {
2545
2452
  return {
2546
2453
  name: "Composio",
2547
2454
  ok: false,
2548
- message: `Not configured \u2014 run ${pc9.cyan("npx seclaw integrations")}`
2455
+ message: `Not configured \u2014 run ${pc8.cyan("npx seclaw integrations")}`
2549
2456
  };
2550
2457
  }
2551
2458
  return { name: "Composio", ok: true, message: "API key configured" };
@@ -2555,46 +2462,46 @@ async function checkComposio(projectDir) {
2555
2462
  }
2556
2463
  function formatCheck(check) {
2557
2464
  const status2 = check.ok ? PASS : FAIL;
2558
- const fixable = !check.ok && check.fix ? pc9.dim(" (auto-fixable)") : "";
2465
+ const fixable = !check.ok && check.fix ? pc8.dim(" (auto-fixable)") : "";
2559
2466
  return `${status2} ${check.name} \u2014 ${check.message}${fixable}`;
2560
2467
  }
2561
2468
  function showSummary(results) {
2562
2469
  const passed = results.filter((r) => r.ok).length;
2563
2470
  const failed = results.filter((r) => !r.ok).length;
2564
2471
  const fixable = results.filter((r) => !r.ok && r.fix).length;
2565
- p9.log.message("");
2566
- p9.log.message(
2567
- `${pc9.bold("Summary:")} ${pc9.green(`${passed} passed`)}, ${failed > 0 ? pc9.red(`${failed} failed`) : pc9.green("0 failed")}` + (fixable > 0 ? `, ${pc9.cyan(`${fixable} auto-fixable`)}` : "")
2472
+ p8.log.message("");
2473
+ p8.log.message(
2474
+ `${pc8.bold("Summary:")} ${pc8.green(`${passed} passed`)}, ${failed > 0 ? pc8.red(`${failed} failed`) : pc8.green("0 failed")}` + (fixable > 0 ? `, ${pc8.cyan(`${fixable} auto-fixable`)}` : "")
2568
2475
  );
2569
2476
  }
2570
2477
 
2571
2478
  // src/commands/upgrade.ts
2572
- import * as p10 from "@clack/prompts";
2479
+ import * as p9 from "@clack/prompts";
2573
2480
  import { execa as execa9 } from "execa";
2574
- import pc10 from "picocolors";
2575
- import { readFile as readFile9 } from "fs/promises";
2576
- import { resolve as resolve12 } from "path";
2481
+ import pc9 from "picocolors";
2482
+ import { readFile as readFile8 } from "fs/promises";
2483
+ import { resolve as resolve11 } from "path";
2577
2484
  async function upgrade() {
2578
- p10.intro(`${pc10.bgCyan(pc10.black(" seclaw "))} Upgrading...`);
2485
+ p9.intro(`${pc9.bgCyan(pc9.black(" seclaw "))} Upgrading...`);
2579
2486
  const projectDir = findProjectDir();
2580
2487
  if (!projectDir) {
2581
- p10.log.warn("No seclaw project found.");
2582
- p10.log.info(` Start one with: ${pc10.cyan("npx seclaw my-agent")}`);
2583
- p10.outro("");
2488
+ p9.log.warn("No seclaw project found.");
2489
+ p9.log.info(` Start one with: ${pc9.cyan("npx seclaw my-agent")}`);
2490
+ p9.outro("");
2584
2491
  return;
2585
2492
  }
2586
2493
  const opts = {
2587
2494
  cwd: projectDir,
2588
2495
  env: { ...process.env, COMPOSE_PROJECT_NAME: "seclaw" }
2589
2496
  };
2590
- const s = p10.spinner();
2497
+ const s = p9.spinner();
2591
2498
  s.start("Pulling latest images...");
2592
2499
  try {
2593
2500
  await execa9("docker", ["compose", "pull"], opts);
2594
2501
  s.stop("Images updated.");
2595
2502
  } catch {
2596
2503
  s.stop("Failed to pull images.");
2597
- p10.log.error("Check your internet connection and try again.");
2504
+ p9.log.error("Check your internet connection and try again.");
2598
2505
  return;
2599
2506
  }
2600
2507
  s.start("Restarting services...");
@@ -2603,15 +2510,15 @@ async function upgrade() {
2603
2510
  s.stop("Services restarted.");
2604
2511
  } catch {
2605
2512
  s.stop("Failed to restart.");
2606
- p10.log.error(`Try manually: cd ${projectDir} && docker compose up -d`);
2513
+ p9.log.error(`Try manually: cd ${projectDir} && docker compose up -d`);
2607
2514
  return;
2608
2515
  }
2609
2516
  s.start("Reconnecting tunnel...");
2610
2517
  const tunnelUrl = await getTunnelUrl(projectDir);
2611
2518
  if (tunnelUrl) {
2612
- s.stop(`Tunnel ready: ${pc10.cyan(tunnelUrl)}`);
2519
+ s.stop(`Tunnel ready: ${pc9.cyan(tunnelUrl)}`);
2613
2520
  try {
2614
- const env = await readFile9(resolve12(projectDir, ".env"), "utf-8");
2521
+ const env = await readFile8(resolve11(projectDir, ".env"), "utf-8");
2615
2522
  const tokenMatch = env.match(/TELEGRAM_BOT_TOKEN=(.+)/);
2616
2523
  if (tokenMatch && tokenMatch[1].includes(":")) {
2617
2524
  s.start("Updating Telegram webhook...");
@@ -2623,7 +2530,7 @@ async function upgrade() {
2623
2530
  } else {
2624
2531
  s.stop("Tunnel starting...");
2625
2532
  }
2626
- p10.outro("Upgrade complete!");
2533
+ p9.outro("Upgrade complete!");
2627
2534
  }
2628
2535
 
2629
2536
  // src/cli.ts
@@ -2631,7 +2538,6 @@ program.name("seclaw").description("Secure autonomous AI agents in 60 seconds").
2631
2538
  program.command("create", { isDefault: true }).alias("init").description("Set up a new seclaw project").argument("[directory]", "Project directory", ".").action(create);
2632
2539
  program.command("add <template>").description("Add a template to your project").option("--key <license>", "License key for paid templates").action(add);
2633
2540
  program.command("integrations").description("Manage integrations (Gmail, GitHub, Notion...)").action(integrations);
2634
- program.command("templates").alias("list").description("List available templates").action(templates);
2635
2541
  program.command("status").description("Check running services").action(status);
2636
2542
  program.command("stop").description("Stop all services").action(stop);
2637
2543
  program.command("reconnect").description("Reconnect tunnel + Telegram webhook").action(reconnect);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "seclaw",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Secure autonomous AI agents in 60 seconds",
5
5
  "type": "module",
6
6
  "bin": {