seclaw 0.1.15 → 0.1.17
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 +199 -168
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -389,13 +389,204 @@ function readComposioLocalKey() {
|
|
|
389
389
|
|
|
390
390
|
// src/scaffold.ts
|
|
391
391
|
import { mkdir, writeFile as writeFile2, cp, rm, readFile as readFile2 } from "fs/promises";
|
|
392
|
-
import { existsSync as
|
|
393
|
-
import { join as join2, resolve as
|
|
392
|
+
import { existsSync as existsSync4 } from "fs";
|
|
393
|
+
import { join as join2, resolve as resolve4 } from "path";
|
|
394
|
+
|
|
395
|
+
// src/docker.ts
|
|
396
|
+
import { execa as execa2, execaSync } from "execa";
|
|
397
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
|
|
398
|
+
import { resolve as resolve3 } from "path";
|
|
399
|
+
async function checkDocker() {
|
|
400
|
+
try {
|
|
401
|
+
await execa2("docker", ["version"]);
|
|
402
|
+
} catch {
|
|
403
|
+
return { ok: false, error: "Docker is not installed. Download it at https://docker.com/get-started" };
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
await execa2("docker", ["info"]);
|
|
407
|
+
} catch {
|
|
408
|
+
return { ok: false, error: "Docker is not running. Open Docker Desktop and try again." };
|
|
409
|
+
}
|
|
410
|
+
return { ok: true };
|
|
411
|
+
}
|
|
412
|
+
async function stopExistingSeclaw() {
|
|
413
|
+
try {
|
|
414
|
+
const result = await execa2("docker", [
|
|
415
|
+
"ps",
|
|
416
|
+
"-a",
|
|
417
|
+
"--filter",
|
|
418
|
+
"label=com.docker.compose.service=agent",
|
|
419
|
+
"--format",
|
|
420
|
+
'{{index .Labels "com.docker.compose.project"}}'
|
|
421
|
+
]);
|
|
422
|
+
const projects = /* @__PURE__ */ new Set();
|
|
423
|
+
for (const line of result.stdout.split("\n").filter(Boolean)) {
|
|
424
|
+
projects.add(line.trim());
|
|
425
|
+
}
|
|
426
|
+
try {
|
|
427
|
+
const portResult = await execa2("docker", [
|
|
428
|
+
"ps",
|
|
429
|
+
"--filter",
|
|
430
|
+
"publish=8288",
|
|
431
|
+
"--format",
|
|
432
|
+
'{{index .Labels "com.docker.compose.project"}}'
|
|
433
|
+
]);
|
|
434
|
+
for (const line of portResult.stdout.split("\n").filter(Boolean)) {
|
|
435
|
+
projects.add(line.trim());
|
|
436
|
+
}
|
|
437
|
+
} catch {
|
|
438
|
+
}
|
|
439
|
+
for (const project of projects) {
|
|
440
|
+
if (!project) continue;
|
|
441
|
+
try {
|
|
442
|
+
await execa2("docker", ["compose", "-p", project, "down"]);
|
|
443
|
+
} catch {
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
} catch {
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
async function findRunningSeclaw() {
|
|
450
|
+
const agentResult = await findContainerByLabel("seclaw");
|
|
451
|
+
if (agentResult) return agentResult;
|
|
452
|
+
try {
|
|
453
|
+
const result = await execa2("docker", [
|
|
454
|
+
"ps",
|
|
455
|
+
"--filter",
|
|
456
|
+
"publish=5678",
|
|
457
|
+
"--format",
|
|
458
|
+
"{{.Names}}"
|
|
459
|
+
]);
|
|
460
|
+
const names = result.stdout.trim();
|
|
461
|
+
if (!names) return null;
|
|
462
|
+
const containerName = names.split("\n")[0];
|
|
463
|
+
try {
|
|
464
|
+
const inspect = await execa2("docker", [
|
|
465
|
+
"inspect",
|
|
466
|
+
containerName,
|
|
467
|
+
"--format",
|
|
468
|
+
'{{index .Config.Labels "com.docker.compose.project.working_dir"}}'
|
|
469
|
+
]);
|
|
470
|
+
return { dir: inspect.stdout.trim() || null };
|
|
471
|
+
} catch {
|
|
472
|
+
return { dir: null };
|
|
473
|
+
}
|
|
474
|
+
} catch {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async function findContainerByLabel(project) {
|
|
479
|
+
try {
|
|
480
|
+
const result = await execa2("docker", [
|
|
481
|
+
"ps",
|
|
482
|
+
"--filter",
|
|
483
|
+
`label=com.docker.compose.project=${project}`,
|
|
484
|
+
"--format",
|
|
485
|
+
"{{.Names}}"
|
|
486
|
+
]);
|
|
487
|
+
const names = result.stdout.trim();
|
|
488
|
+
if (!names) return null;
|
|
489
|
+
const containerName = names.split("\n")[0];
|
|
490
|
+
try {
|
|
491
|
+
const inspect = await execa2("docker", [
|
|
492
|
+
"inspect",
|
|
493
|
+
containerName,
|
|
494
|
+
"--format",
|
|
495
|
+
'{{index .Config.Labels "com.docker.compose.project.working_dir"}}'
|
|
496
|
+
]);
|
|
497
|
+
return { dir: inspect.stdout.trim() || null };
|
|
498
|
+
} catch {
|
|
499
|
+
return { dir: null };
|
|
500
|
+
}
|
|
501
|
+
} catch {
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
var SECLAW_CONFIG_DIR = resolve3(process.env.HOME || "~", ".seclaw");
|
|
506
|
+
var PROJECT_PATH_FILE = resolve3(SECLAW_CONFIG_DIR, "project-path");
|
|
507
|
+
function saveProjectDir(dir) {
|
|
508
|
+
try {
|
|
509
|
+
mkdirSync(SECLAW_CONFIG_DIR, { recursive: true });
|
|
510
|
+
writeFileSync(PROJECT_PATH_FILE, dir);
|
|
511
|
+
} catch {
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function isValidProjectDir(dir) {
|
|
515
|
+
const composePath = resolve3(dir, "docker-compose.yml");
|
|
516
|
+
if (existsSync3(composePath)) {
|
|
517
|
+
try {
|
|
518
|
+
const content = readFileSync2(composePath, "utf-8");
|
|
519
|
+
if (content.includes("agent") && content.includes("agent-net")) return true;
|
|
520
|
+
} catch {
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const envPath = resolve3(dir, ".env");
|
|
524
|
+
if (existsSync3(envPath)) {
|
|
525
|
+
try {
|
|
526
|
+
const env = readFileSync2(envPath, "utf-8");
|
|
527
|
+
if (env.includes("TELEGRAM_BOT_TOKEN")) return true;
|
|
528
|
+
} catch {
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
function findProjectDir() {
|
|
534
|
+
try {
|
|
535
|
+
const saved = readFileSync2(PROJECT_PATH_FILE, "utf-8").trim();
|
|
536
|
+
if (saved && isValidProjectDir(saved)) return saved;
|
|
537
|
+
} catch {
|
|
538
|
+
}
|
|
539
|
+
try {
|
|
540
|
+
const result = execaSync("docker", [
|
|
541
|
+
"ps",
|
|
542
|
+
"--filter",
|
|
543
|
+
"label=com.docker.compose.service=agent",
|
|
544
|
+
"--format",
|
|
545
|
+
"{{.Names}}"
|
|
546
|
+
]);
|
|
547
|
+
const names = result.stdout.trim().split("\n").filter(Boolean);
|
|
548
|
+
for (const name of names) {
|
|
549
|
+
try {
|
|
550
|
+
const inspect = execaSync("docker", [
|
|
551
|
+
"inspect",
|
|
552
|
+
name,
|
|
553
|
+
"--format",
|
|
554
|
+
'{{index .Config.Labels "com.docker.compose.project.working_dir"}}'
|
|
555
|
+
]);
|
|
556
|
+
const dir = inspect.stdout.trim();
|
|
557
|
+
if (dir && isValidProjectDir(dir)) {
|
|
558
|
+
saveProjectDir(dir);
|
|
559
|
+
return dir;
|
|
560
|
+
}
|
|
561
|
+
} catch {
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
} catch {
|
|
565
|
+
}
|
|
566
|
+
const home = process.env.HOME || "~";
|
|
567
|
+
const candidates = [
|
|
568
|
+
process.cwd(),
|
|
569
|
+
resolve3(process.cwd(), ".."),
|
|
570
|
+
home,
|
|
571
|
+
resolve3(home, "seclaw"),
|
|
572
|
+
resolve3(home, "my-agent")
|
|
573
|
+
];
|
|
574
|
+
for (const dir of candidates) {
|
|
575
|
+
if (isValidProjectDir(dir)) {
|
|
576
|
+
saveProjectDir(dir);
|
|
577
|
+
return dir;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// src/scaffold.ts
|
|
394
584
|
async function scaffoldProject(targetDir, answers) {
|
|
585
|
+
saveProjectDir(resolve4(targetDir));
|
|
395
586
|
const ws = answers.workspacePath || "./shared";
|
|
396
587
|
const wsSubdirs = ["tasks", "reports", "notes", "drafts", "memory", "config"];
|
|
397
588
|
for (const sub of wsSubdirs) {
|
|
398
|
-
await mkdir(
|
|
589
|
+
await mkdir(resolve4(targetDir, ws, sub), { recursive: true });
|
|
399
590
|
}
|
|
400
591
|
for (const dir of ["templates", "commander", "agent", "cloudflared"]) {
|
|
401
592
|
await mkdir(join2(targetDir, dir), { recursive: true });
|
|
@@ -602,13 +793,13 @@ done
|
|
|
602
793
|
await writeFile2(join2(dir, "tunnel-start.sh"), script, { mode: 493 });
|
|
603
794
|
}
|
|
604
795
|
async function copyAgentFiles(dir) {
|
|
605
|
-
const agentTemplateSrc =
|
|
796
|
+
const agentTemplateSrc = resolve4(import.meta.dirname, "runtime");
|
|
606
797
|
const agentDest = join2(dir, "agent");
|
|
607
|
-
if (
|
|
798
|
+
if (existsSync4(agentDest)) {
|
|
608
799
|
await rm(agentDest, { recursive: true });
|
|
609
800
|
}
|
|
610
801
|
await mkdir(agentDest, { recursive: true });
|
|
611
|
-
if (
|
|
802
|
+
if (existsSync4(agentTemplateSrc)) {
|
|
612
803
|
await cp(agentTemplateSrc, agentDest, { recursive: true });
|
|
613
804
|
} else {
|
|
614
805
|
await writeMinimalAgent(agentDest);
|
|
@@ -921,13 +1112,13 @@ rules:
|
|
|
921
1112
|
await writeFile2(join2(dir, "permissions.yml"), content);
|
|
922
1113
|
}
|
|
923
1114
|
async function writeIfMissing(path, content) {
|
|
924
|
-
if (!
|
|
1115
|
+
if (!existsSync4(path)) {
|
|
925
1116
|
await writeFile2(path, content);
|
|
926
1117
|
}
|
|
927
1118
|
}
|
|
928
1119
|
async function writeSeedFiles(dir, answers, ws = "./shared") {
|
|
929
1120
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
930
|
-
const wsDir =
|
|
1121
|
+
const wsDir = resolve4(dir, ws);
|
|
931
1122
|
await writeIfMissing(
|
|
932
1123
|
join2(wsDir, "memory/learnings.md"),
|
|
933
1124
|
`# Agent Memory
|
|
@@ -1037,165 +1228,6 @@ templates/
|
|
|
1037
1228
|
await writeIfMissing(join2(dir, ".dockerignore"), content);
|
|
1038
1229
|
}
|
|
1039
1230
|
|
|
1040
|
-
// src/docker.ts
|
|
1041
|
-
import { execa as execa2, execaSync } from "execa";
|
|
1042
|
-
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
1043
|
-
import { resolve as resolve4 } from "path";
|
|
1044
|
-
async function checkDocker() {
|
|
1045
|
-
try {
|
|
1046
|
-
await execa2("docker", ["version"]);
|
|
1047
|
-
} catch {
|
|
1048
|
-
return { ok: false, error: "Docker is not installed. Download it at https://docker.com/get-started" };
|
|
1049
|
-
}
|
|
1050
|
-
try {
|
|
1051
|
-
await execa2("docker", ["info"]);
|
|
1052
|
-
} catch {
|
|
1053
|
-
return { ok: false, error: "Docker is not running. Open Docker Desktop and try again." };
|
|
1054
|
-
}
|
|
1055
|
-
return { ok: true };
|
|
1056
|
-
}
|
|
1057
|
-
async function stopExistingSeclaw() {
|
|
1058
|
-
try {
|
|
1059
|
-
const result = await execa2("docker", [
|
|
1060
|
-
"ps",
|
|
1061
|
-
"-a",
|
|
1062
|
-
"--filter",
|
|
1063
|
-
"label=com.docker.compose.service=agent",
|
|
1064
|
-
"--format",
|
|
1065
|
-
'{{index .Labels "com.docker.compose.project"}}'
|
|
1066
|
-
]);
|
|
1067
|
-
const projects = /* @__PURE__ */ new Set();
|
|
1068
|
-
for (const line of result.stdout.split("\n").filter(Boolean)) {
|
|
1069
|
-
projects.add(line.trim());
|
|
1070
|
-
}
|
|
1071
|
-
try {
|
|
1072
|
-
const portResult = await execa2("docker", [
|
|
1073
|
-
"ps",
|
|
1074
|
-
"--filter",
|
|
1075
|
-
"publish=8288",
|
|
1076
|
-
"--format",
|
|
1077
|
-
'{{index .Labels "com.docker.compose.project"}}'
|
|
1078
|
-
]);
|
|
1079
|
-
for (const line of portResult.stdout.split("\n").filter(Boolean)) {
|
|
1080
|
-
projects.add(line.trim());
|
|
1081
|
-
}
|
|
1082
|
-
} catch {
|
|
1083
|
-
}
|
|
1084
|
-
for (const project of projects) {
|
|
1085
|
-
if (!project) continue;
|
|
1086
|
-
try {
|
|
1087
|
-
await execa2("docker", ["compose", "-p", project, "down"]);
|
|
1088
|
-
} catch {
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
} catch {
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
async function findRunningSeclaw() {
|
|
1095
|
-
const agentResult = await findContainerByLabel("seclaw");
|
|
1096
|
-
if (agentResult) return agentResult;
|
|
1097
|
-
try {
|
|
1098
|
-
const result = await execa2("docker", [
|
|
1099
|
-
"ps",
|
|
1100
|
-
"--filter",
|
|
1101
|
-
"publish=5678",
|
|
1102
|
-
"--format",
|
|
1103
|
-
"{{.Names}}"
|
|
1104
|
-
]);
|
|
1105
|
-
const names = result.stdout.trim();
|
|
1106
|
-
if (!names) return null;
|
|
1107
|
-
const containerName = names.split("\n")[0];
|
|
1108
|
-
try {
|
|
1109
|
-
const inspect = await execa2("docker", [
|
|
1110
|
-
"inspect",
|
|
1111
|
-
containerName,
|
|
1112
|
-
"--format",
|
|
1113
|
-
'{{index .Config.Labels "com.docker.compose.project.working_dir"}}'
|
|
1114
|
-
]);
|
|
1115
|
-
return { dir: inspect.stdout.trim() || null };
|
|
1116
|
-
} catch {
|
|
1117
|
-
return { dir: null };
|
|
1118
|
-
}
|
|
1119
|
-
} catch {
|
|
1120
|
-
return null;
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
async function findContainerByLabel(project) {
|
|
1124
|
-
try {
|
|
1125
|
-
const result = await execa2("docker", [
|
|
1126
|
-
"ps",
|
|
1127
|
-
"--filter",
|
|
1128
|
-
`label=com.docker.compose.project=${project}`,
|
|
1129
|
-
"--format",
|
|
1130
|
-
"{{.Names}}"
|
|
1131
|
-
]);
|
|
1132
|
-
const names = result.stdout.trim();
|
|
1133
|
-
if (!names) return null;
|
|
1134
|
-
const containerName = names.split("\n")[0];
|
|
1135
|
-
try {
|
|
1136
|
-
const inspect = await execa2("docker", [
|
|
1137
|
-
"inspect",
|
|
1138
|
-
containerName,
|
|
1139
|
-
"--format",
|
|
1140
|
-
'{{index .Config.Labels "com.docker.compose.project.working_dir"}}'
|
|
1141
|
-
]);
|
|
1142
|
-
return { dir: inspect.stdout.trim() || null };
|
|
1143
|
-
} catch {
|
|
1144
|
-
return { dir: null };
|
|
1145
|
-
}
|
|
1146
|
-
} catch {
|
|
1147
|
-
return null;
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
function findProjectDir() {
|
|
1151
|
-
try {
|
|
1152
|
-
const result = execaSync("docker", [
|
|
1153
|
-
"ps",
|
|
1154
|
-
"--filter",
|
|
1155
|
-
"label=com.docker.compose.service=agent",
|
|
1156
|
-
"--format",
|
|
1157
|
-
"{{.Names}}"
|
|
1158
|
-
]);
|
|
1159
|
-
const names = result.stdout.trim().split("\n").filter(Boolean);
|
|
1160
|
-
for (const name of names) {
|
|
1161
|
-
try {
|
|
1162
|
-
const inspect = execaSync("docker", [
|
|
1163
|
-
"inspect",
|
|
1164
|
-
name,
|
|
1165
|
-
"--format",
|
|
1166
|
-
'{{index .Config.Labels "com.docker.compose.project.working_dir"}}'
|
|
1167
|
-
]);
|
|
1168
|
-
const dir = inspect.stdout.trim();
|
|
1169
|
-
if (dir && existsSync4(resolve4(dir, "docker-compose.yml"))) {
|
|
1170
|
-
return dir;
|
|
1171
|
-
}
|
|
1172
|
-
} catch {
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
} catch {
|
|
1176
|
-
}
|
|
1177
|
-
const candidates = [
|
|
1178
|
-
process.cwd(),
|
|
1179
|
-
resolve4(process.cwd(), ".."),
|
|
1180
|
-
resolve4(process.env.HOME || "~", "seclaw"),
|
|
1181
|
-
resolve4(process.env.HOME || "~", "my-agent")
|
|
1182
|
-
];
|
|
1183
|
-
for (const dir of candidates) {
|
|
1184
|
-
const composePath = resolve4(dir, "docker-compose.yml");
|
|
1185
|
-
if (existsSync4(composePath)) {
|
|
1186
|
-
try {
|
|
1187
|
-
const content = readFileSync2(composePath, "utf-8");
|
|
1188
|
-
if (content.includes("agent") && content.includes("agent-net")) {
|
|
1189
|
-
return dir;
|
|
1190
|
-
}
|
|
1191
|
-
} catch {
|
|
1192
|
-
continue;
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
return null;
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
1231
|
// src/commands/create.ts
|
|
1200
1232
|
async function create(directory) {
|
|
1201
1233
|
let targetDir = resolve5(process.cwd(), directory);
|
|
@@ -1560,7 +1592,6 @@ async function add(template, options) {
|
|
|
1560
1592
|
p3.log.success(
|
|
1561
1593
|
`Capability '${pc3.bold(templateName)}' installed. ${pc3.cyan(String(installed.capabilities.length))} capabilities active.`
|
|
1562
1594
|
);
|
|
1563
|
-
p3.log.info(`Run ${pc3.cyan("docker compose restart agent")} to apply.`);
|
|
1564
1595
|
}
|
|
1565
1596
|
p3.outro(`${pc3.green("Done!")} Template ${pc3.bold(template)} is ready.`);
|
|
1566
1597
|
}
|