agenttwin 0.3.4 → 0.3.6
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/README.md +8 -0
- package/dist/api.js +96 -56
- package/dist/cli.js +210 -180
- package/dist/dashboard/assets/dashboard/agent-home.css +33 -10
- package/dist/dashboard/assets/dashboard/bay-morning.png +0 -0
- package/dist/dashboard/assets/dashboard/golden-gate-afternoon.png +0 -0
- package/dist/dashboard/assets/dashboard/inventory.json +1 -1
- package/dist/dashboard/assets/dashboard/runs.json +1 -1
- package/dist/dashboard/assets/next/static/HZwGKKvvx_oO6caYggjFh/_buildManifest.js +1 -0
- package/dist/dashboard/assets/next/static/HZwGKKvvx_oO6caYggjFh/_ssgManifest.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/255-09fb0d129bb12f51.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/772-85561381eedc95da.js +67 -0
- package/dist/dashboard/assets/next/static/chunks/app/_not-found/page-5dc6a60918e8652d.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/app/dashboard/layout-74603c2c8eaf0760.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/app/dashboard/page-b79ed135f6bac5f7.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/app/demo/page-760a47a5e4dee43f.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/app/docs/[[...slug]]/page-80a0c2c82d5b4efd.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/app/layout-1a40c59e535513d4.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/app/page-de4735052adab906.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/app/pricing/page-7703fc31d6dff0f7.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/app/sitemap.xml/route-ef5a216a578eb7a8.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/framework-292291387d6b2e39.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/main-aea6e71289384293.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/main-app-99295a8cc37e8a47.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dist/dashboard/assets/next/static/chunks/webpack-f3b6dd27998e84ce.js +1 -0
- package/dist/dashboard/assets/next/static/css/1323d7da89b3fa5e.css +1 -0
- package/dist/dashboard/assets/next/static/css/45513ebc8c6ae800.css +1 -0
- package/dist/dashboard/assets/next/static/css/61ec74d503f353d4.css +1 -0
- package/dist/dashboard/assets/next/static/css/9282330a2fd1ab73.css +1 -0
- package/dist/dashboard/assets/next/static/css/96e4f356a1159071.css +1 -0
- package/dist/dashboard/assets/next/static/css/c5ac36ef5726e746.css +1 -0
- package/dist/dashboard/assets/next/static/media/bay-morning.1a9f358c.png +0 -0
- package/dist/dashboard/assets/next/static/media/golden-gate-afternoon.3e7cea58.png +0 -0
- package/dist/dashboard/assets/next/static/media/inter-var.e4af272c.woff2 +0 -0
- package/dist/dashboard/assets/next/static/media/moonlit-coast.bbe901a1.png +0 -0
- package/dist/dashboard/assets/next/static/media/plexmono-400.d3ebbfd6.woff2 +0 -0
- package/dist/dashboard/index.html +1 -140
- package/dist/gateway.js +96 -56
- package/package.json +1 -1
- package/dist/dashboard/assets/dashboard/agent-home.js +0 -298
- package/dist/dashboard/assets/dashboard/dashboard.js +0 -462
- package/dist/dashboard/assets/dashboard/hosted.js +0 -89
- package/dist/dashboard/assets/dashboard/workflows.js +0 -39
- package/dist/dashboard/assets/vendor/dompurify.LICENSE +0 -202
- package/dist/dashboard/assets/vendor/marked.LICENSE +0 -44
- package/dist/dashboard/assets/vendor/marked.js +0 -82
- package/dist/dashboard/assets/vendor/purify.min.js +0 -7
package/dist/cli.js
CHANGED
|
@@ -789,11 +789,84 @@ var init_dashboard_transfer = __esm({
|
|
|
789
789
|
}
|
|
790
790
|
});
|
|
791
791
|
|
|
792
|
+
// src/inventory/store.ts
|
|
793
|
+
import { mkdir, readFile as readFile4, writeFile } from "fs/promises";
|
|
794
|
+
import { join as join4 } from "path";
|
|
795
|
+
function storeDir(cwd) {
|
|
796
|
+
return join4(cwd, STORE_DIR);
|
|
797
|
+
}
|
|
798
|
+
function storePath(cwd, name) {
|
|
799
|
+
return join4(storeDir(cwd), name);
|
|
800
|
+
}
|
|
801
|
+
async function ensureStore(cwd) {
|
|
802
|
+
await mkdir(storeDir(cwd), { recursive: true });
|
|
803
|
+
await writeFile(storePath(cwd, ".gitignore"), STORE_GITIGNORE, "utf8");
|
|
804
|
+
}
|
|
805
|
+
async function readJson(path) {
|
|
806
|
+
try {
|
|
807
|
+
return JSON.parse(await readFile4(path, "utf8"));
|
|
808
|
+
} catch {
|
|
809
|
+
return void 0;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
async function writeJson(path, value) {
|
|
813
|
+
await writeFile(path, `${JSON.stringify(value, null, 2)}
|
|
814
|
+
`, "utf8");
|
|
815
|
+
}
|
|
816
|
+
function defaultSources() {
|
|
817
|
+
return {
|
|
818
|
+
schema: "sources/v1",
|
|
819
|
+
note: "Declares desired discovery sources only. This file grants NO authorization.",
|
|
820
|
+
local: [
|
|
821
|
+
"static",
|
|
822
|
+
"environment",
|
|
823
|
+
"inspect:git-remotes",
|
|
824
|
+
"inspect:git-root",
|
|
825
|
+
"inspect:docker-images",
|
|
826
|
+
"inspect:docker-ps",
|
|
827
|
+
"inspect:docker-contexts",
|
|
828
|
+
"inspect:kubectl-contexts",
|
|
829
|
+
"inspect:aws-profiles"
|
|
830
|
+
]
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
var STORE_DIR, STORE_GITIGNORE, readInventory, writeInventory, readDecisions, writeDecisions, readPlan, writePlan, readPermissions, writePermissions, readSources, writeSources;
|
|
834
|
+
var init_store = __esm({
|
|
835
|
+
"src/inventory/store.ts"() {
|
|
836
|
+
"use strict";
|
|
837
|
+
STORE_DIR = ".agenttwin";
|
|
838
|
+
STORE_GITIGNORE = [
|
|
839
|
+
"inventory.json",
|
|
840
|
+
"plan.json",
|
|
841
|
+
"permissions.local.json",
|
|
842
|
+
"report.html",
|
|
843
|
+
"demo/",
|
|
844
|
+
"trial/",
|
|
845
|
+
"feedback.json",
|
|
846
|
+
"cache/",
|
|
847
|
+
"logs/",
|
|
848
|
+
"connections/",
|
|
849
|
+
""
|
|
850
|
+
].join("\n");
|
|
851
|
+
readInventory = (cwd) => readJson(storePath(cwd, "inventory.json"));
|
|
852
|
+
writeInventory = (cwd, value) => writeJson(storePath(cwd, "inventory.json"), value);
|
|
853
|
+
readDecisions = (cwd) => readJson(storePath(cwd, "decisions.json"));
|
|
854
|
+
writeDecisions = (cwd, value) => writeJson(storePath(cwd, "decisions.json"), value);
|
|
855
|
+
readPlan = (cwd) => readJson(storePath(cwd, "plan.json"));
|
|
856
|
+
writePlan = (cwd, value) => writeJson(storePath(cwd, "plan.json"), value);
|
|
857
|
+
readPermissions = (cwd) => readJson(storePath(cwd, "permissions.local.json"));
|
|
858
|
+
writePermissions = (cwd, value) => writeJson(storePath(cwd, "permissions.local.json"), value);
|
|
859
|
+
readSources = (cwd) => readJson(storePath(cwd, "sources.json"));
|
|
860
|
+
writeSources = (cwd, value) => writeJson(storePath(cwd, "sources.json"), value);
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
|
|
792
864
|
// src/inventory/project-products.ts
|
|
793
865
|
async function projectProducts(cwd, data, base) {
|
|
794
866
|
const current = new Set(base.tools.map((t) => t.id));
|
|
795
867
|
const known = new Set(PROVIDERS.map((p) => p.title));
|
|
796
|
-
const
|
|
868
|
+
const excluded = new Set(((await readDecisions(cwd))?.items ?? []).filter((d) => d.kind === "exclude_tool").map((d) => d.target));
|
|
869
|
+
const tools = data.tools.filter((t) => !excluded.has(t.id) && (current.has(t.id) || !known.has(t.title) && t.category !== "access" && t.category !== "tooling"));
|
|
797
870
|
const ids = new Set(tools.map((t) => t.id));
|
|
798
871
|
const workflows = data.workflows.map((w) => ({ ...w, tools: w.tools.filter((id) => ids.has(id)), steps: w.steps.filter((s) => ids.has(s.tool)) }));
|
|
799
872
|
const google = tools.find((t) => /Google \(Gmail/.test(t.title));
|
|
@@ -847,6 +920,7 @@ var init_project_products = __esm({
|
|
|
847
920
|
init_entrypoints();
|
|
848
921
|
init_catalog();
|
|
849
922
|
init_dashboard_transfer();
|
|
923
|
+
init_store();
|
|
850
924
|
}
|
|
851
925
|
});
|
|
852
926
|
|
|
@@ -872,8 +946,8 @@ var init_sanitize = __esm({
|
|
|
872
946
|
});
|
|
873
947
|
|
|
874
948
|
// src/commands/dashboard.ts
|
|
875
|
-
import { cp, mkdir, readFile as
|
|
876
|
-
import { basename, join as
|
|
949
|
+
import { cp, mkdir as mkdir2, readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
|
|
950
|
+
import { basename, join as join5 } from "path";
|
|
877
951
|
import { fileURLToPath } from "url";
|
|
878
952
|
function dashboardInventory(cwd, inventory) {
|
|
879
953
|
return JSON.parse(sanitizeOutput(JSON.stringify({
|
|
@@ -892,26 +966,26 @@ function dashboardInventory(cwd, inventory) {
|
|
|
892
966
|
})));
|
|
893
967
|
}
|
|
894
968
|
async function writeDashboard(cwd, inventory) {
|
|
895
|
-
const directory =
|
|
969
|
+
const directory = join5(cwd, ".agenttwin", "dashboard");
|
|
896
970
|
let bundle = fileURLToPath(new URL("./dashboard/", import.meta.url));
|
|
897
971
|
try {
|
|
898
|
-
await
|
|
972
|
+
await readFile5(join5(bundle, "index.html"));
|
|
899
973
|
} catch {
|
|
900
|
-
bundle = fileURLToPath(new URL("
|
|
974
|
+
bundle = fileURLToPath(new URL("../../dist/dashboard/", import.meta.url));
|
|
901
975
|
}
|
|
902
|
-
await
|
|
903
|
-
const html = await
|
|
904
|
-
for (const folder of ["dashboard", "icons", "logo", "fonts", "
|
|
905
|
-
await cp(
|
|
976
|
+
await mkdir2(join5(directory, "assets", "dashboard"), { recursive: true });
|
|
977
|
+
const html = await readFile5(join5(bundle, "index.html"), "utf8");
|
|
978
|
+
for (const folder of ["dashboard", "icons", "logo", "fonts", "next"]) {
|
|
979
|
+
await cp(join5(bundle, "assets", folder), join5(directory, "assets", folder), { recursive: true });
|
|
906
980
|
}
|
|
907
|
-
await cp(
|
|
981
|
+
await cp(join5(bundle, "assets", "brand.css"), join5(directory, "assets", "brand.css"));
|
|
908
982
|
const data = await loadDashboardDiscovery(cwd, inventory);
|
|
909
983
|
const payload = JSON.stringify(data).replace(/</g, "\\u003c");
|
|
910
|
-
const page2 = html.
|
|
911
|
-
await
|
|
912
|
-
await
|
|
913
|
-
await
|
|
914
|
-
return
|
|
984
|
+
const page2 = html.replace("</head>", `<script id="agenttwin-discovery" type="application/json">${payload}</script></head>`).replace(/href="index.html"/g, 'href="../report.html"').replace(/href="docs\/"/g, 'href="../report.html"').replace("Docs \u2197", "Discovery report \u2197").replace("Runtime documentation \u2197", "Coverage report \u2197").replace("Back to website \u2197", "Discovery report \u2197");
|
|
985
|
+
await writeFile2(join5(directory, "index.html"), page2, "utf8");
|
|
986
|
+
await writeFile2(join5(directory, "assets", "dashboard", "inventory.json"), JSON.stringify(data), "utf8");
|
|
987
|
+
await writeFile2(join5(directory, "assets", "dashboard", "runs.json"), '{"runs":[]}', "utf8");
|
|
988
|
+
return join5(directory, "index.html");
|
|
915
989
|
}
|
|
916
990
|
var init_dashboard = __esm({
|
|
917
991
|
"src/commands/dashboard.ts"() {
|
|
@@ -931,8 +1005,8 @@ __export(session_exports, {
|
|
|
931
1005
|
writeSessionTask: () => writeSessionTask
|
|
932
1006
|
});
|
|
933
1007
|
import { spawn, spawnSync } from "child_process";
|
|
934
|
-
import { writeFile as
|
|
935
|
-
import { join as
|
|
1008
|
+
import { writeFile as writeFile3, readFile as readFile6 } from "fs/promises";
|
|
1009
|
+
import { join as join6 } from "path";
|
|
936
1010
|
function sessionTask() {
|
|
937
1011
|
return `# Prepare this repository for AgentTwin
|
|
938
1012
|
|
|
@@ -957,8 +1031,8 @@ function installedOpenCode() {
|
|
|
957
1031
|
return spawnSync("opencode", ["--version"], { stdio: "ignore", timeout: 5e3 }).status === 0;
|
|
958
1032
|
}
|
|
959
1033
|
async function writeSessionTask(cwd) {
|
|
960
|
-
const path =
|
|
961
|
-
await
|
|
1034
|
+
const path = join6(cwd, ".agenttwin/AGENT-TASK.md");
|
|
1035
|
+
await writeFile3(path, sessionTask(), "utf8");
|
|
962
1036
|
return path;
|
|
963
1037
|
}
|
|
964
1038
|
async function launchSession(cwd) {
|
|
@@ -973,14 +1047,14 @@ async function launchSession(cwd) {
|
|
|
973
1047
|
async function loadDashboardDiscovery(cwd, inventory) {
|
|
974
1048
|
let previousHead;
|
|
975
1049
|
try {
|
|
976
|
-
previousHead = JSON.parse(await
|
|
1050
|
+
previousHead = JSON.parse(await readFile6(join6(cwd, ".agenttwin/git.json"), "utf8")).head;
|
|
977
1051
|
} catch {
|
|
978
1052
|
}
|
|
979
1053
|
const git = await captureGit(cwd, previousHead);
|
|
980
1054
|
const base = discoveryTransfer({ ...dashboardInventory(cwd, inventory), ...git ? { git } : {} });
|
|
981
1055
|
let raw;
|
|
982
1056
|
try {
|
|
983
|
-
raw = await
|
|
1057
|
+
raw = await readFile6(join6(cwd, ".agenttwin/agent-discovery.json"), "utf8");
|
|
984
1058
|
} catch (error) {
|
|
985
1059
|
if (error.code === "ENOENT") return projectProducts(cwd, base, base);
|
|
986
1060
|
throw error;
|
|
@@ -1055,8 +1129,8 @@ import { fileURLToPath as fileURLToPath3 } from "url";
|
|
|
1055
1129
|
import { parseArgs } from "util";
|
|
1056
1130
|
|
|
1057
1131
|
// src/commands/sync.ts
|
|
1058
|
-
import { readFile as
|
|
1059
|
-
import { join as
|
|
1132
|
+
import { readFile as readFile7, writeFile as writeFile4 } from "fs/promises";
|
|
1133
|
+
import { join as join7 } from "path";
|
|
1060
1134
|
|
|
1061
1135
|
// src/runtime/receipt-sync.ts
|
|
1062
1136
|
init_git();
|
|
@@ -1204,28 +1278,28 @@ async function runSync(cwd) {
|
|
|
1204
1278
|
const token = process.env.AGENTTWIN_TOKEN;
|
|
1205
1279
|
if (!token || !/^at_user_[a-f0-9]{64}$/.test(token)) throw new Error("Set AGENTTWIN_TOKEN to your individual account token.");
|
|
1206
1280
|
const base = apiBase(process.env.AGENTTWIN_API_URL || "https://agenttwin-api.212.56.40.125.sslip.io");
|
|
1207
|
-
const inventory = JSON.parse(await
|
|
1281
|
+
const inventory = JSON.parse(await readFile7(join7(cwd, ".agenttwin/inventory.json"), "utf8"));
|
|
1208
1282
|
const payload = await loadDashboardDiscovery(cwd, inventory);
|
|
1209
1283
|
const git = payload.git;
|
|
1210
1284
|
const response = await fetch(base + "/v1/discovery", { method: "POST", headers: { authorization: "Bearer " + token, "content-type": "application/json" }, body: JSON.stringify(payload), redirect: "error", signal: AbortSignal.timeout(2e4) });
|
|
1211
1285
|
const result = await response.json();
|
|
1212
1286
|
if (!response.ok) throw new Error(result.error || "Discovery sync failed: " + response.status);
|
|
1213
|
-
if (git) await
|
|
1287
|
+
if (git) await writeFile4(join7(cwd, ".agenttwin/git.json"), JSON.stringify(git) + "\n", { mode: 384 });
|
|
1214
1288
|
const receipts = await syncReceipts(cwd, base, token);
|
|
1215
|
-
return { receipts, synced: true, tools: payload.tools.length, workflows: payload.workflows.length, dashboard: "https://agenttwin.si/
|
|
1289
|
+
return { receipts, synced: true, tools: payload.tools.length, workflows: payload.workflows.length, dashboard: "https://agenttwin.si/dashboard/", uploaded: "discovery, Git metadata and run evidence; no application source or credentials" };
|
|
1216
1290
|
}
|
|
1217
1291
|
|
|
1218
1292
|
// src/commands/prepare.ts
|
|
1219
1293
|
init_git();
|
|
1220
|
-
import { mkdir as
|
|
1221
|
-
import { join as
|
|
1294
|
+
import { mkdir as mkdir4, mkdtemp as mkdtemp2, readFile as readFile10, writeFile as writeFile6, rm as rm2 } from "fs/promises";
|
|
1295
|
+
import { join as join10, resolve as resolve2, dirname as dirname2 } from "path";
|
|
1222
1296
|
import { tmpdir as tmpdir2 } from "os";
|
|
1223
1297
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
|
|
1224
1298
|
|
|
1225
1299
|
// src/commands/run.ts
|
|
1226
|
-
import { mkdtemp, mkdir as
|
|
1300
|
+
import { mkdtemp, mkdir as mkdir3, readFile as readFile9, writeFile as writeFile5, realpath, lstat, rm, copyFile } from "fs/promises";
|
|
1227
1301
|
import { tmpdir } from "os";
|
|
1228
|
-
import { basename as basename2, dirname, isAbsolute, join as
|
|
1302
|
+
import { basename as basename2, dirname, isAbsolute, join as join9, relative as relative2, resolve, sep as sep2 } from "path";
|
|
1229
1303
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1230
1304
|
import { createHash, randomUUID } from "crypto";
|
|
1231
1305
|
|
|
@@ -1478,17 +1552,17 @@ init_git();
|
|
|
1478
1552
|
|
|
1479
1553
|
// src/runtime/e2b.ts
|
|
1480
1554
|
import { Sandbox } from "e2b";
|
|
1481
|
-
import { readFile as
|
|
1555
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
1482
1556
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
1483
1557
|
import { homedir } from "os";
|
|
1484
|
-
import { join as
|
|
1558
|
+
import { join as join8 } from "path";
|
|
1485
1559
|
async function executeE2B(stage, spec, log, signal) {
|
|
1486
1560
|
signal?.throwIfAborted();
|
|
1487
1561
|
const apiKey = process.env.E2B_API_KEY;
|
|
1488
1562
|
if (!apiKey) throw new CliError("E2B_API_KEY is not configured", { code: EXIT.BLOCKED, action: "Set E2B_API_KEY in your shell. It remains on the host and is never passed to the application." });
|
|
1489
1563
|
let saved = {};
|
|
1490
1564
|
try {
|
|
1491
|
-
saved = JSON.parse(await
|
|
1565
|
+
saved = JSON.parse(await readFile8(join8(homedir(), ".agenttwin/runtime.json"), "utf8"));
|
|
1492
1566
|
} catch {
|
|
1493
1567
|
}
|
|
1494
1568
|
const template = process.env.E2B_TEMPLATE_ID ?? saved.templateId;
|
|
@@ -1507,7 +1581,7 @@ async function executeE2B(stage, spec, log, signal) {
|
|
|
1507
1581
|
signal?.throwIfAborted();
|
|
1508
1582
|
await sandbox.commands.run("mkdir -p /input/app && chmod 755 /input /input/app", { user: "root" });
|
|
1509
1583
|
for (const file of ["world.json", "worker.js", ...spec.files.map((f) => `app/${f}`)]) {
|
|
1510
|
-
await sandbox.files.write(`/input/${file}`, new Uint8Array(await
|
|
1584
|
+
await sandbox.files.write(`/input/${file}`, new Uint8Array(await readFile8(join8(stage, file))).buffer, { user: "root" });
|
|
1511
1585
|
}
|
|
1512
1586
|
await sandbox.commands.run("chmod -R a+rX /input", { user: "root" });
|
|
1513
1587
|
const runtimeEnv = {};
|
|
@@ -1630,12 +1704,12 @@ async function stageApplication(root, files, destination) {
|
|
|
1630
1704
|
const source = await realpath(resolve(canonicalRoot, file));
|
|
1631
1705
|
const rel = relative2(canonicalRoot, source);
|
|
1632
1706
|
if (rel.startsWith(`..${sep2}`) || rel === ".." || isAbsolute(rel) || source !== resolve(canonicalRoot, file) || !(await lstat(source)).isFile()) throw new CliError(`Files must be regular files within the project, without symlinks: ${file}`, { code: EXIT.USAGE });
|
|
1633
|
-
const contents = await
|
|
1707
|
+
const contents = await readFile9(source);
|
|
1634
1708
|
bytes += contents.length;
|
|
1635
1709
|
if (bytes > 20 * 1024 * 1024) throw new CliError("Application files exceed 20 MiB; supply a smaller built bundle", { code: EXIT.USAGE });
|
|
1636
|
-
const target =
|
|
1637
|
-
await
|
|
1638
|
-
await
|
|
1710
|
+
const target = join9(destination, file);
|
|
1711
|
+
await mkdir3(dirname(target), { recursive: true });
|
|
1712
|
+
await writeFile5(target, contents, { mode: 420 });
|
|
1639
1713
|
digests[file] = createHash("sha256").update(contents).digest("hex");
|
|
1640
1714
|
}
|
|
1641
1715
|
return digests;
|
|
@@ -1644,14 +1718,14 @@ async function runWorld(cwd, opts) {
|
|
|
1644
1718
|
const file = resolve(cwd, opts.file ?? "agenttwin.world.json");
|
|
1645
1719
|
let spec;
|
|
1646
1720
|
try {
|
|
1647
|
-
spec = parseWorld(JSON.parse(await
|
|
1721
|
+
spec = parseWorld(JSON.parse(await readFile9(file, "utf8")));
|
|
1648
1722
|
} catch (error) {
|
|
1649
1723
|
throw new CliError(String(error), { code: EXIT.USAGE, action: "Create agenttwin.world.json; run agenttwin example --out ./agenttwin-example for a working example." });
|
|
1650
1724
|
}
|
|
1651
1725
|
if (spec.workflowId) {
|
|
1652
1726
|
let inventory;
|
|
1653
1727
|
try {
|
|
1654
|
-
inventory = JSON.parse(await
|
|
1728
|
+
inventory = JSON.parse(await readFile9(join9(cwd, ".agenttwin/inventory.json"), "utf8"));
|
|
1655
1729
|
} catch {
|
|
1656
1730
|
throw new CliError("Linked workflow needs discovery inventory; run init first", { code: EXIT.BLOCKED });
|
|
1657
1731
|
}
|
|
@@ -1664,26 +1738,26 @@ async function runWorld(cwd, opts) {
|
|
|
1664
1738
|
const runtime = opts.runtime ?? "local";
|
|
1665
1739
|
if (!["local", "e2b", "hosted"].includes(runtime)) throw new CliError("runtime must be local, hosted or e2b", { code: EXIT.USAGE });
|
|
1666
1740
|
if (runtime === "e2b" && !process.env.E2B_API_KEY) throw new CliError("E2B_API_KEY is not configured", { code: EXIT.BLOCKED, action: "Configure your E2B key in the shell; it will not be uploaded." });
|
|
1667
|
-
const stage = await mkdtemp(
|
|
1741
|
+
const stage = await mkdtemp(join9(tmpdir(), "agenttwin-world-"));
|
|
1668
1742
|
try {
|
|
1669
|
-
const sourceDigests = await stageApplication(dirname(file), spec.files,
|
|
1670
|
-
await
|
|
1743
|
+
const sourceDigests = await stageApplication(dirname(file), spec.files, join9(stage, "app"));
|
|
1744
|
+
await writeFile5(join9(stage, "world.json"), JSON.stringify(spec), { mode: 420 });
|
|
1671
1745
|
const git = await captureGit(cwd);
|
|
1672
1746
|
let project;
|
|
1673
1747
|
try {
|
|
1674
|
-
project = JSON.parse(await
|
|
1748
|
+
project = JSON.parse(await readFile9(join9(cwd, ".agenttwin/inventory.json"), "utf8")).name ?? basename2(cwd);
|
|
1675
1749
|
} catch {
|
|
1676
1750
|
}
|
|
1677
1751
|
if (runtime === "local") {
|
|
1678
1752
|
opts.log("Testing through the CLI with in-process service twins. No cloud VM or containers.");
|
|
1679
|
-
const report2 = await executeLocal(
|
|
1753
|
+
const report2 = await executeLocal(join9(stage, "app"), spec, opts.signal);
|
|
1680
1754
|
const runId2 = randomUUID();
|
|
1681
1755
|
const receipt2 = { ...report2, runId: runId2, runtime, project, git, createdAt: (/* @__PURE__ */ new Date()).toISOString(), sourceDigests, specDigest: createHash("sha256").update(JSON.stringify(spec)).digest("hex"), provenance: "CLI-reported execution" };
|
|
1682
1756
|
const out2 = resolve(cwd, opts.out ?? `.agenttwin/runs/${runId2}`);
|
|
1683
|
-
await
|
|
1684
|
-
const evidencePath =
|
|
1685
|
-
await
|
|
1686
|
-
await
|
|
1757
|
+
await mkdir3(out2, { recursive: true });
|
|
1758
|
+
const evidencePath = join9(out2, "run.json"), reportPath = join9(out2, "report.md");
|
|
1759
|
+
await writeFile5(evidencePath, JSON.stringify(receipt2, null, 2) + "\n", { mode: 384 });
|
|
1760
|
+
await writeFile5(reportPath, `# ${spec.name}: ${receipt2.status}
|
|
1687
1761
|
|
|
1688
1762
|
Runner: CLI
|
|
1689
1763
|
Git: ${git?.head ?? "unavailable"}
|
|
@@ -1691,8 +1765,8 @@ Git: ${git?.head ?? "unavailable"}
|
|
|
1691
1765
|
` + receipt2.assertions.map((a) => `- ${a.passed ? "PASS" : "FAIL"} ${a.service}${a.path}: expected ${a.count}, observed ${a.actual}`).join("\n") + "\n\nSee run.json for activities, fault and initial/final state.\n");
|
|
1692
1766
|
return { ...receipt2, reportPath, evidencePath };
|
|
1693
1767
|
}
|
|
1694
|
-
const worker =
|
|
1695
|
-
await copyFile(worker,
|
|
1768
|
+
const worker = join9(dirname(fileURLToPath2(import.meta.url)), "worker.js");
|
|
1769
|
+
await copyFile(worker, join9(stage, "worker.js"));
|
|
1696
1770
|
if (runtime === "hosted") {
|
|
1697
1771
|
const receipt2 = await executeRemote(stage, spec, opts.log, { git, project });
|
|
1698
1772
|
if (receipt2.schema !== "agenttwin/run-v1" || typeof receipt2.runId !== "string" || !/^[a-f0-9-]{36}$/.test(receipt2.runId) || !["passed", "failed", "error", "incomplete"].includes(String(receipt2.status))) throw new CliError("Invalid hosted receipt");
|
|
@@ -1700,11 +1774,11 @@ Git: ${git?.head ?? "unavailable"}
|
|
|
1700
1774
|
const reportedSources = receipt2.sourceDigests;
|
|
1701
1775
|
if (receipt2.specDigest !== expectedSpec || !reportedSources || Object.keys(reportedSources).length !== Object.keys(sourceDigests).length || Object.entries(sourceDigests).some(([path, digest]) => reportedSources[path] !== digest)) throw new CliError("Hosted receipt does not match the submitted source/specification");
|
|
1702
1776
|
const out2 = resolve(cwd, opts.out ?? `.agenttwin/runs/${receipt2.runId}`);
|
|
1703
|
-
await
|
|
1704
|
-
const evidencePath =
|
|
1705
|
-
const reportPath =
|
|
1706
|
-
await
|
|
1707
|
-
await
|
|
1777
|
+
await mkdir3(out2, { recursive: true });
|
|
1778
|
+
const evidencePath = join9(out2, "run.json");
|
|
1779
|
+
const reportPath = join9(out2, "report.md");
|
|
1780
|
+
await writeFile5(evidencePath, JSON.stringify(receipt2, null, 2) + "\n", { mode: 384 });
|
|
1781
|
+
await writeFile5(reportPath, `# ${spec.name}: ${receipt2.status}
|
|
1708
1782
|
|
|
1709
1783
|
Run: ${receipt2.runId}
|
|
1710
1784
|
|
|
@@ -1738,12 +1812,12 @@ See run.json for assertions, service state, application output and isolation met
|
|
|
1738
1812
|
isolation
|
|
1739
1813
|
};
|
|
1740
1814
|
const out = resolve(cwd, opts.out ?? `.agenttwin/runs/${runId}`);
|
|
1741
|
-
await
|
|
1742
|
-
await
|
|
1743
|
-
await
|
|
1815
|
+
await mkdir3(out, { recursive: true });
|
|
1816
|
+
await writeFile5(join9(out, "run.json"), JSON.stringify(receipt, null, 2) + "\n", { mode: 384 });
|
|
1817
|
+
await writeFile5(join9(out, "world.json"), JSON.stringify(spec, null, 2) + "\n", { mode: 384 });
|
|
1744
1818
|
const summary = [`# ${spec.name}: ${report.status}`, "", `Run: ${runId}`, `Image: ${image}`, "", "## Assertions", "", ...report.assertions.map((a) => `- ${a.passed ? "PASS" : "FAIL"} ${a.service}${a.path}: expected ${a.count}, observed ${a.actual}`), "", "## Infrastructure assertions", "", ...(report.infrastructureAssertions ?? []).map((a) => `- ${a.passed ? "PASS" : "FAIL"} ${a.service}: ${JSON.stringify(a.actual)}`), "", "## Coverage", "", report.coverage, "", "External networking disabled. Only explicitly listed source files were uploaded. New state and tokens are created for each run.", "", "See run.json for application output, faults, events, and initial/final state.", ""].join("\n");
|
|
1745
|
-
await
|
|
1746
|
-
return { ...receipt, reportPath:
|
|
1819
|
+
await writeFile5(join9(out, "report.md"), summary);
|
|
1820
|
+
return { ...receipt, reportPath: join9(out, "report.md"), evidencePath: join9(out, "run.json") };
|
|
1747
1821
|
} finally {
|
|
1748
1822
|
await rm(stage, { recursive: true, force: true });
|
|
1749
1823
|
}
|
|
@@ -1797,36 +1871,36 @@ function reviewPreparation(inventory, input) {
|
|
|
1797
1871
|
async function runPrepare(cwd, opts) {
|
|
1798
1872
|
if (!opts.file) throw new CliError("prepare requires --file preparation.json", { code: EXIT.USAGE });
|
|
1799
1873
|
const file = resolve2(cwd, opts.file);
|
|
1800
|
-
const inventory = JSON.parse(await
|
|
1874
|
+
const inventory = JSON.parse(await readFile10(join10(cwd, ".agenttwin/inventory.json"), "utf8"));
|
|
1801
1875
|
const discovered = await loadDashboardDiscovery(cwd, inventory);
|
|
1802
1876
|
const known = new Set(inventory.workflows.map((w) => w.id));
|
|
1803
1877
|
for (const w of discovered.workflows) if (!known.has(w.id)) {
|
|
1804
1878
|
inventory.workflows.push({ ...w, confidence: "low", apps: [], sharedEntities: [], evidence: [{ source: "agent-analysis", ref: ".agenttwin/agent-discovery.json" }], steps: w.steps.map((s) => ({ ...s, app: "", evidence: [] })), risk: "high", rank: 0, supported: false, hypothesis: true, linkingSignals: ["Coding-agent analysis requires user review"], scopeUncertain: true, verified: false });
|
|
1805
1879
|
}
|
|
1806
1880
|
for (const t of discovered.tools) if (!inventory.tools.some((tool) => tool.id === t.id)) throw new Error("Agent-discovered dependencies must be classified in init before preparation. Review the additional tool, then rerun init.");
|
|
1807
|
-
const { spec, workflow, mappings } = reviewPreparation(inventory, JSON.parse(await
|
|
1881
|
+
const { spec, workflow, mappings } = reviewPreparation(inventory, JSON.parse(await readFile10(file, "utf8")));
|
|
1808
1882
|
const directory = resolve2(cwd, opts.out ?? `.agenttwin/prepared/${spec.name}-${randomUUID2().slice(0, 8)}`);
|
|
1809
|
-
const stage = await mkdtemp2(
|
|
1883
|
+
const stage = await mkdtemp2(join10(tmpdir2(), "agenttwin-prepare-"));
|
|
1810
1884
|
try {
|
|
1811
1885
|
const sourceDigests = await stageApplication(dirname2(file), spec.files, stage);
|
|
1812
1886
|
const files = /* @__PURE__ */ Object.create(null);
|
|
1813
|
-
for (const name of spec.files) files[name] = (await
|
|
1887
|
+
for (const name of spec.files) files[name] = (await readFile10(join10(stage, name))).toString("base64");
|
|
1814
1888
|
const manifest = { project: discovered.name, git: await captureGit(cwd), schema: "agenttwin/prepared-v1", workflowId: workflow.id, invocation: spec.command, sourceDigests, specDigest: createHash2("sha256").update(JSON.stringify(spec)).digest("hex"), mappings, provenance: "user-reviewed invocation and operation contracts; discovery does not verify runtime fidelity", createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1815
|
-
await
|
|
1816
|
-
await
|
|
1817
|
-
await
|
|
1818
|
-
await
|
|
1819
|
-
await
|
|
1820
|
-
await
|
|
1821
|
-
await
|
|
1889
|
+
await writeFile6(join10(stage, "agenttwin.world.json"), JSON.stringify(spec, null, 2) + "\n", { mode: 384 });
|
|
1890
|
+
await writeFile6(join10(stage, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", { mode: 384 });
|
|
1891
|
+
await writeFile6(join10(stage, "agenttwin.bundle.json"), JSON.stringify({ schema: "agenttwin/upload-v1", spec, files, manifest }, null, 2) + "\n", { mode: 384 });
|
|
1892
|
+
await mkdir4(join10(stage, ".agenttwin"), { recursive: true });
|
|
1893
|
+
await writeFile6(join10(stage, ".agenttwin/inventory.json"), JSON.stringify({ name: discovered.name, workflows: [{ id: workflow.id }] }), { mode: 384 });
|
|
1894
|
+
await mkdir4(dirname2(directory), { recursive: true });
|
|
1895
|
+
await mkdir4(directory, { mode: 448 });
|
|
1822
1896
|
try {
|
|
1823
1897
|
const { cp: cp2, readdir: readdir3 } = await import("fs/promises");
|
|
1824
|
-
for (const entry of await readdir3(stage)) await cp2(
|
|
1898
|
+
for (const entry of await readdir3(stage)) await cp2(join10(stage, entry), join10(directory, entry), { recursive: true, force: false, errorOnExist: true });
|
|
1825
1899
|
} catch (error) {
|
|
1826
1900
|
await rm2(directory, { recursive: true, force: true });
|
|
1827
1901
|
throw error;
|
|
1828
1902
|
}
|
|
1829
|
-
return { directory, worldPath:
|
|
1903
|
+
return { directory, worldPath: join10(directory, "agenttwin.world.json"), bundlePath: join10(directory, "agenttwin.bundle.json"), manifest };
|
|
1830
1904
|
} finally {
|
|
1831
1905
|
await rm2(stage, { recursive: true, force: true });
|
|
1832
1906
|
}
|
|
@@ -1834,9 +1908,9 @@ async function runPrepare(cwd, opts) {
|
|
|
1834
1908
|
|
|
1835
1909
|
// src/commands/setup.ts
|
|
1836
1910
|
import { Template } from "e2b";
|
|
1837
|
-
import { mkdir as
|
|
1911
|
+
import { mkdir as mkdir5, writeFile as writeFile7 } from "fs/promises";
|
|
1838
1912
|
import { homedir as homedir2 } from "os";
|
|
1839
|
-
import { join as
|
|
1913
|
+
import { join as join11 } from "path";
|
|
1840
1914
|
async function runSetup(log) {
|
|
1841
1915
|
if (!process.env.E2B_API_KEY) throw new CliError("Set E2B_API_KEY before creating your hosted runtime template", { code: EXIT.BLOCKED });
|
|
1842
1916
|
log("Building the AgentTwin template on E2B (Node 22, PostgreSQL 15, Redis). No local containers are used.");
|
|
@@ -1844,16 +1918,16 @@ async function runSetup(log) {
|
|
|
1844
1918
|
const result = await Template.build(template, "agenttwin-node-v2", { apiKey: process.env.E2B_API_KEY, cpuCount: 2, memoryMB: 2048, onBuildLogs: (entry) => {
|
|
1845
1919
|
if (!entry.message.includes("[stdout]") && !entry.message.includes("[stderr]")) log(entry.message);
|
|
1846
1920
|
} });
|
|
1847
|
-
const directory =
|
|
1848
|
-
await
|
|
1849
|
-
const path =
|
|
1850
|
-
await
|
|
1921
|
+
const directory = join11(homedir2(), ".agenttwin");
|
|
1922
|
+
await mkdir5(directory, { recursive: true, mode: 448 });
|
|
1923
|
+
const path = join11(directory, "runtime.json");
|
|
1924
|
+
await writeFile7(path, JSON.stringify({ provider: "e2b", templateId: result.templateId, buildId: result.buildId }, null, 2) + "\n", { mode: 384 });
|
|
1851
1925
|
return { provider: "e2b", templateId: result.templateId, buildId: result.buildId, configPath: path };
|
|
1852
1926
|
}
|
|
1853
1927
|
|
|
1854
1928
|
// src/commands/example.ts
|
|
1855
|
-
import { mkdir as
|
|
1856
|
-
import { resolve as resolve3, join as
|
|
1929
|
+
import { mkdir as mkdir6, writeFile as writeFile8, stat as stat2 } from "fs/promises";
|
|
1930
|
+
import { resolve as resolve3, join as join12 } from "path";
|
|
1857
1931
|
var exampleSource = `// Replace this file with your own bundled Node workflow.
|
|
1858
1932
|
const safe = process.argv.includes('--safe');
|
|
1859
1933
|
const crm = process.env.AGENTTWIN_CRM_URL;
|
|
@@ -1897,17 +1971,17 @@ function exampleWorld(safe = false) {
|
|
|
1897
1971
|
async function runExample(cwd, out) {
|
|
1898
1972
|
const directory = resolve3(cwd, out ?? "agenttwin-example");
|
|
1899
1973
|
if (await stat2(directory).then(() => true, () => false)) throw new CliError(`Destination exists: ${directory}`, { code: EXIT.USAGE, action: "Choose a new --out directory." });
|
|
1900
|
-
await
|
|
1901
|
-
await
|
|
1902
|
-
await
|
|
1903
|
-
await
|
|
1904
|
-
await
|
|
1974
|
+
await mkdir6(directory, { recursive: true });
|
|
1975
|
+
await writeFile8(join12(directory, "workflow.mjs"), exampleSource);
|
|
1976
|
+
await writeFile8(join12(directory, "agenttwin.world.json"), JSON.stringify(exampleWorld(), null, 2) + "\n");
|
|
1977
|
+
await writeFile8(join12(directory, "safe.world.json"), JSON.stringify(exampleWorld(true), null, 2) + "\n");
|
|
1978
|
+
await writeFile8(join12(directory, "README.md"), `# Support workflow
|
|
1905
1979
|
|
|
1906
1980
|
Run with agenttwin run --file agenttwin.world.json (expected FAIL), then --file safe.world.json (expected PASS). Runs through the CLI with in-process service twins by default. No VM or containers. Use --runtime hosted or --runtime e2b explicitly only when a cloud machine is needed.
|
|
1907
1981
|
|
|
1908
1982
|
This is actual application code using a synthetic custom ticket API plus the supported Slack JSON subset. It is not a clone of a CRM vendor. Replace workflow.mjs with your own built code and edit the explicit file list, endpoints, seed records and assertions. No npm install runs in the sandbox; dependencies must be bundled into your application.
|
|
1909
1983
|
`);
|
|
1910
|
-
return { directory, worldPath:
|
|
1984
|
+
return { directory, worldPath: join12(directory, "agenttwin.world.json") };
|
|
1911
1985
|
}
|
|
1912
1986
|
|
|
1913
1987
|
// src/commands/init.ts
|
|
@@ -2513,7 +2587,7 @@ async function scanProvider(ctx, provider, files) {
|
|
|
2513
2587
|
for (const path of files) {
|
|
2514
2588
|
if (evidence.length >= MAX_EVIDENCE) break;
|
|
2515
2589
|
if (isNonUsagePath(path)) continue;
|
|
2516
|
-
const text = await ctx.files.read(path,
|
|
2590
|
+
const text = await ctx.files.read(path, 1e6);
|
|
2517
2591
|
if (!text) continue;
|
|
2518
2592
|
for (const re of importRes) {
|
|
2519
2593
|
const m = text.match(re);
|
|
@@ -2742,13 +2816,13 @@ var containerDetector = {
|
|
|
2742
2816
|
// src/discovery/detectors/cloud-creds.ts
|
|
2743
2817
|
import { stat as stat3 } from "fs/promises";
|
|
2744
2818
|
import { homedir as homedir3 } from "os";
|
|
2745
|
-
import { join as
|
|
2819
|
+
import { join as join13 } from "path";
|
|
2746
2820
|
var CREDENTIALS = [
|
|
2747
|
-
{ path:
|
|
2748
|
-
{ path:
|
|
2749
|
-
{ path:
|
|
2750
|
-
{ path:
|
|
2751
|
-
{ path:
|
|
2821
|
+
{ path: join13(homedir3(), ".aws", "config"), subject: "aws", title: "AWS" },
|
|
2822
|
+
{ path: join13(homedir3(), ".aws", "credentials"), subject: "aws", title: "AWS" },
|
|
2823
|
+
{ path: join13(homedir3(), ".config", "gcloud"), subject: "gcp", title: "Google Cloud" },
|
|
2824
|
+
{ path: join13(homedir3(), ".azure"), subject: "azure", title: "Azure" },
|
|
2825
|
+
{ path: join13(homedir3(), ".kube", "config"), subject: "kubernetes", title: "Kubernetes" }
|
|
2752
2826
|
];
|
|
2753
2827
|
async function exists(path) {
|
|
2754
2828
|
try {
|
|
@@ -2833,7 +2907,7 @@ import { createServer as createServer2 } from "http";
|
|
|
2833
2907
|
import { connect } from "net";
|
|
2834
2908
|
import { readFileSync, rmSync, writeFileSync } from "fs";
|
|
2835
2909
|
import { tmpdir as tmpdir3 } from "os";
|
|
2836
|
-
import { join as
|
|
2910
|
+
import { join as join14 } from "path";
|
|
2837
2911
|
var EGRESS_HOOK = `
|
|
2838
2912
|
const fs = require('fs');
|
|
2839
2913
|
const dns = require('dns');
|
|
@@ -2903,8 +2977,8 @@ function startProxy(onHost) {
|
|
|
2903
2977
|
async function observeEgress(opts) {
|
|
2904
2978
|
const hosts = /* @__PURE__ */ new Set();
|
|
2905
2979
|
const { server, url } = await startProxy((host) => hosts.add(host));
|
|
2906
|
-
const logFile =
|
|
2907
|
-
const hookFile =
|
|
2980
|
+
const logFile = join14(tmpdir3(), `agenttwin-egress-${process.pid}.log`);
|
|
2981
|
+
const hookFile = join14(tmpdir3(), `agenttwin-egress-hook-${process.pid}.cjs`);
|
|
2908
2982
|
try {
|
|
2909
2983
|
writeFileSync(hookFile, EGRESS_HOOK, "utf8");
|
|
2910
2984
|
writeFileSync(logFile, "", "utf8");
|
|
@@ -3614,7 +3688,7 @@ async function buildRelationships(apps, tools, files, decisions) {
|
|
|
3614
3688
|
const entrypointCalls = [];
|
|
3615
3689
|
const sourceText = /* @__PURE__ */ new Map();
|
|
3616
3690
|
for (const path of sourceFiles) {
|
|
3617
|
-
const text = await files.read(path,
|
|
3691
|
+
const text = await files.read(path, 1e6);
|
|
3618
3692
|
if (!text) continue;
|
|
3619
3693
|
sourceText.set(path, text);
|
|
3620
3694
|
const app = appForPath(apps, path);
|
|
@@ -3921,7 +3995,7 @@ function categoryOfFinding(finding) {
|
|
|
3921
3995
|
return envOrCredOnly ? "access" : "dependency";
|
|
3922
3996
|
}
|
|
3923
3997
|
if (finding.kind === "infra" || finding.kind === "datastore") {
|
|
3924
|
-
return finding.evidence.length > 0 && finding.evidence.every((e) =>
|
|
3998
|
+
return finding.evidence.length > 0 && finding.evidence.every((e) => ["credential", "env", "container", "iac", "ci"].includes(e.signal)) ? "access" : "dependency";
|
|
3925
3999
|
}
|
|
3926
4000
|
if (finding.kind === "language" || finding.kind === "ci") return "tooling";
|
|
3927
4001
|
return "dependency";
|
|
@@ -4072,7 +4146,8 @@ async function buildInventory({
|
|
|
4072
4146
|
confidence: "medium"
|
|
4073
4147
|
});
|
|
4074
4148
|
}
|
|
4075
|
-
const
|
|
4149
|
+
const excluded = new Set(decisions.filter((d) => d.kind === "exclude_tool").map((d) => d.target));
|
|
4150
|
+
const toolList = [...tools.values()].filter((t) => !excluded.has(t.id) && !t.aliases.some((a) => excluded.has(a))).sort((a, b) => a.id.localeCompare(b.id));
|
|
4076
4151
|
const apps = await detectApps(files);
|
|
4077
4152
|
const services = apps.map((app) => ({ id: app.id, title: app.title, frameworks: app.frameworks })).sort((a, b) => a.id.localeCompare(b.id));
|
|
4078
4153
|
const { relationships, workflows, gaps, uncertainOperations } = await buildRelationships(
|
|
@@ -4135,71 +4210,8 @@ async function buildInventory({
|
|
|
4135
4210
|
});
|
|
4136
4211
|
}
|
|
4137
4212
|
|
|
4138
|
-
// src/inventory
|
|
4139
|
-
|
|
4140
|
-
import { join as join14 } from "path";
|
|
4141
|
-
var STORE_DIR = ".agenttwin";
|
|
4142
|
-
var STORE_GITIGNORE = [
|
|
4143
|
-
"inventory.json",
|
|
4144
|
-
"plan.json",
|
|
4145
|
-
"permissions.local.json",
|
|
4146
|
-
"report.html",
|
|
4147
|
-
"demo/",
|
|
4148
|
-
"trial/",
|
|
4149
|
-
"feedback.json",
|
|
4150
|
-
"cache/",
|
|
4151
|
-
"logs/",
|
|
4152
|
-
"connections/",
|
|
4153
|
-
""
|
|
4154
|
-
].join("\n");
|
|
4155
|
-
function storeDir(cwd) {
|
|
4156
|
-
return join14(cwd, STORE_DIR);
|
|
4157
|
-
}
|
|
4158
|
-
function storePath(cwd, name) {
|
|
4159
|
-
return join14(storeDir(cwd), name);
|
|
4160
|
-
}
|
|
4161
|
-
async function ensureStore(cwd) {
|
|
4162
|
-
await mkdir6(storeDir(cwd), { recursive: true });
|
|
4163
|
-
await writeFile8(storePath(cwd, ".gitignore"), STORE_GITIGNORE, "utf8");
|
|
4164
|
-
}
|
|
4165
|
-
async function readJson(path) {
|
|
4166
|
-
try {
|
|
4167
|
-
return JSON.parse(await readFile10(path, "utf8"));
|
|
4168
|
-
} catch {
|
|
4169
|
-
return void 0;
|
|
4170
|
-
}
|
|
4171
|
-
}
|
|
4172
|
-
async function writeJson(path, value) {
|
|
4173
|
-
await writeFile8(path, `${JSON.stringify(value, null, 2)}
|
|
4174
|
-
`, "utf8");
|
|
4175
|
-
}
|
|
4176
|
-
var readInventory = (cwd) => readJson(storePath(cwd, "inventory.json"));
|
|
4177
|
-
var writeInventory = (cwd, value) => writeJson(storePath(cwd, "inventory.json"), value);
|
|
4178
|
-
var readDecisions = (cwd) => readJson(storePath(cwd, "decisions.json"));
|
|
4179
|
-
var writeDecisions = (cwd, value) => writeJson(storePath(cwd, "decisions.json"), value);
|
|
4180
|
-
var readPlan = (cwd) => readJson(storePath(cwd, "plan.json"));
|
|
4181
|
-
var writePlan = (cwd, value) => writeJson(storePath(cwd, "plan.json"), value);
|
|
4182
|
-
var readPermissions = (cwd) => readJson(storePath(cwd, "permissions.local.json"));
|
|
4183
|
-
var writePermissions = (cwd, value) => writeJson(storePath(cwd, "permissions.local.json"), value);
|
|
4184
|
-
var readSources = (cwd) => readJson(storePath(cwd, "sources.json"));
|
|
4185
|
-
var writeSources = (cwd, value) => writeJson(storePath(cwd, "sources.json"), value);
|
|
4186
|
-
function defaultSources() {
|
|
4187
|
-
return {
|
|
4188
|
-
schema: "sources/v1",
|
|
4189
|
-
note: "Declares desired discovery sources only. This file grants NO authorization.",
|
|
4190
|
-
local: [
|
|
4191
|
-
"static",
|
|
4192
|
-
"environment",
|
|
4193
|
-
"inspect:git-remotes",
|
|
4194
|
-
"inspect:git-root",
|
|
4195
|
-
"inspect:docker-images",
|
|
4196
|
-
"inspect:docker-ps",
|
|
4197
|
-
"inspect:docker-contexts",
|
|
4198
|
-
"inspect:kubectl-contexts",
|
|
4199
|
-
"inspect:aws-profiles"
|
|
4200
|
-
]
|
|
4201
|
-
};
|
|
4202
|
-
}
|
|
4213
|
+
// src/commands/inventory.ts
|
|
4214
|
+
init_store();
|
|
4203
4215
|
|
|
4204
4216
|
// src/inspect/registry.ts
|
|
4205
4217
|
function record(store, finding) {
|
|
@@ -5131,7 +5143,7 @@ var CONFIRM_RELATIONSHIP_PREFIX = "confirm_relationship:";
|
|
|
5131
5143
|
function generateQuestions(inventory, max = 5, stored = {}) {
|
|
5132
5144
|
const questions = [];
|
|
5133
5145
|
for (const tool of inventory.tools) {
|
|
5134
|
-
if (tool.confidence !== "low") continue;
|
|
5146
|
+
if (tool.confidence !== "low" || tool.category !== "dependency") continue;
|
|
5135
5147
|
const id = `${TOOL_EXCLUDE_PREFIX}${tool.id}`;
|
|
5136
5148
|
questions.push({
|
|
5137
5149
|
id,
|
|
@@ -5145,6 +5157,7 @@ function generateQuestions(inventory, max = 5, stored = {}) {
|
|
|
5145
5157
|
});
|
|
5146
5158
|
}
|
|
5147
5159
|
for (const evidence of inventory.unclassified) {
|
|
5160
|
+
if (evidence.signal === "env") continue;
|
|
5148
5161
|
const id = `${UNKNOWN_INCLUDE_PREFIX}${evidence.ref}`;
|
|
5149
5162
|
questions.push({
|
|
5150
5163
|
id,
|
|
@@ -5205,7 +5218,9 @@ function applyInventoryAnswers(inventory, answers) {
|
|
|
5205
5218
|
return withCanonicalHash({
|
|
5206
5219
|
...inventory,
|
|
5207
5220
|
tools: tools.sort((a, b) => a.id.localeCompare(b.id)),
|
|
5208
|
-
|
|
5221
|
+
relationships: inventory.relationships.filter((r) => !drop.has(r.from) && !drop.has(r.to)),
|
|
5222
|
+
coverage: { ...inventory.coverage, counts: { ...inventory.coverage.counts, tools: tools.length, classified: tools.filter((t) => t.kind !== "unknown").length } },
|
|
5223
|
+
workflows: inventory.workflows.filter((w) => !w.tools.some((t) => drop.has(t))).map(
|
|
5209
5224
|
(w) => confirmedWorkflows.has(w.id) ? { ...w, supported: true, hypothesis: false, linkingSignals: [...w.linkingSignals, "confirmed by decision"] } : w
|
|
5210
5225
|
)
|
|
5211
5226
|
});
|
|
@@ -5229,6 +5244,9 @@ function storedAnswers(decisions) {
|
|
|
5229
5244
|
return map;
|
|
5230
5245
|
}
|
|
5231
5246
|
|
|
5247
|
+
// src/commands/init.ts
|
|
5248
|
+
init_store();
|
|
5249
|
+
|
|
5232
5250
|
// src/discovery/questions/ask.ts
|
|
5233
5251
|
import { createInterface } from "readline/promises";
|
|
5234
5252
|
|
|
@@ -5383,6 +5401,7 @@ init_session();
|
|
|
5383
5401
|
// src/commands/preview.ts
|
|
5384
5402
|
import { mkdir as mkdir7, readFile as readFile12, writeFile as writeFile10 } from "fs/promises";
|
|
5385
5403
|
import { dirname as dirname3, join as join16, resolve as resolve4 } from "path";
|
|
5404
|
+
init_store();
|
|
5386
5405
|
|
|
5387
5406
|
// src/preview/html.ts
|
|
5388
5407
|
init_sanitize();
|
|
@@ -5498,26 +5517,27 @@ function renderOverview(inventory) {
|
|
|
5498
5517
|
}
|
|
5499
5518
|
for (const tool of tools.slice(0, limit)) {
|
|
5500
5519
|
lines.push(
|
|
5501
|
-
` ${tool.title
|
|
5520
|
+
` ${tool.title} (${tool.kind})${facts2 ? " \u2014 " + tool.facts.map((f) => f.kind).join(", ") : ""}`
|
|
5502
5521
|
);
|
|
5503
5522
|
}
|
|
5504
5523
|
if (tools.length > limit) lines.push(dim(` \u2026 +${tools.length - limit} more`));
|
|
5505
5524
|
};
|
|
5506
|
-
bucket("
|
|
5525
|
+
bucket("Integrations referenced by code or installed packages (usage not verified)", byCategory("dependency"), 20, false);
|
|
5507
5526
|
bucket("Development tooling", byCategory("tooling"), 8, false);
|
|
5508
|
-
|
|
5527
|
+
const configuration = byCategory("access");
|
|
5528
|
+
if (configuration.length) lines.push(dim(`
|
|
5529
|
+
${configuration.length} configuration/access clues saved separately; not included as application dependencies.`));
|
|
5509
5530
|
const failed = inventory.coverage.sources.filter((s) => s.status !== "ok" && s.status !== "skipped");
|
|
5510
5531
|
lines.push("");
|
|
5511
5532
|
lines.push(
|
|
5512
|
-
`
|
|
5533
|
+
` Discovery: ${inventory.workflows.length} workflow candidates \xB7 ${failed.length} scan limitations`
|
|
5513
5534
|
);
|
|
5514
5535
|
if (failed.length > 0) {
|
|
5515
5536
|
lines.push(dim(` Gaps: ${failed.map((s) => `${s.id}=${s.status}`).join(", ")}`));
|
|
5516
5537
|
}
|
|
5517
5538
|
if (inventory.unclassified.length > 0) {
|
|
5518
5539
|
lines.push("");
|
|
5519
|
-
lines.push(
|
|
5520
|
-
for (const u of inventory.unclassified.slice(0, 5)) lines.push(` ${u.ref} \u2014 ${u.reason}`);
|
|
5540
|
+
lines.push(dim(` ${inventory.unclassified.length} unmatched configuration clues saved for review. No setup required.`));
|
|
5521
5541
|
}
|
|
5522
5542
|
lines.push("");
|
|
5523
5543
|
const supported = inventory.workflows.find((w) => w.supported);
|
|
@@ -5565,7 +5585,7 @@ async function runInit(opts) {
|
|
|
5565
5585
|
setQuiet(opts.json);
|
|
5566
5586
|
const interactive = !opts.json && !opts.yes && !opts.dryRun && (opts.interactive === true || !opts.json && process.stdin.isTTY === true && process.stdout.isTTY === true);
|
|
5567
5587
|
const log = (message) => {
|
|
5568
|
-
process.stderr.write(`${message}
|
|
5588
|
+
if (!message.startsWith("inspect ")) process.stderr.write(`${message}
|
|
5569
5589
|
`);
|
|
5570
5590
|
};
|
|
5571
5591
|
if (!opts.json) info(bold("Investigating your development environment\u2026"));
|
|
@@ -5581,7 +5601,12 @@ async function runInit(opts) {
|
|
|
5581
5601
|
incremental: true,
|
|
5582
5602
|
persist: false
|
|
5583
5603
|
});
|
|
5584
|
-
|
|
5604
|
+
let inventory = result.inventory;
|
|
5605
|
+
const excludedNames = new Set((opts.exclude ?? []).flatMap((v) => v.split(",")).map((v) => v.trim().toLowerCase()).filter(Boolean));
|
|
5606
|
+
const excludedTools = inventory.tools.filter((t) => excludedNames.has(t.title.toLowerCase()) || excludedNames.has(t.id.toLowerCase()));
|
|
5607
|
+
const unmatched = [...excludedNames].filter((n) => !excludedTools.some((t) => t.title.toLowerCase() === n || t.id.toLowerCase() === n));
|
|
5608
|
+
if (unmatched.length) throw new Error(`Unknown exclusion: ${unmatched.join(", ")}. Use an exact integration name from discovery.`);
|
|
5609
|
+
inventory = applyInventoryAnswers(inventory, Object.fromEntries(excludedTools.map((t) => [`tool.exclude:${t.id}`, false])));
|
|
5585
5610
|
if (!opts.json) {
|
|
5586
5611
|
info("");
|
|
5587
5612
|
info(renderOverview(inventory));
|
|
@@ -5599,7 +5624,7 @@ async function runInit(opts) {
|
|
|
5599
5624
|
const asked = await askQuestions(questions);
|
|
5600
5625
|
answers = { ...answers, ...asked.answers };
|
|
5601
5626
|
}
|
|
5602
|
-
answers = { ...defaultAnswers(questions), ...answers };
|
|
5627
|
+
answers = { ...stored, ...defaultAnswers(questions), ...answers };
|
|
5603
5628
|
const finalInventory = applyInventoryAnswers(inventory, answers);
|
|
5604
5629
|
const plan = buildPlan(finalInventory);
|
|
5605
5630
|
if (interactive) {
|
|
@@ -5612,6 +5637,8 @@ async function runInit(opts) {
|
|
|
5612
5637
|
}
|
|
5613
5638
|
const existing = opts.force ? void 0 : await readConfig(opts.cwd);
|
|
5614
5639
|
const config = buildConfigFromInventory(finalInventory, existing);
|
|
5640
|
+
const excludedIds = /* @__PURE__ */ new Set([...excludedTools.map((t) => t.id), ...decisions.filter((d) => d.kind === "exclude_tool").map((d) => d.target)]);
|
|
5641
|
+
config.twins = config.twins.filter((id) => !excludedIds.has(id));
|
|
5615
5642
|
let wrote = false;
|
|
5616
5643
|
let reportPath;
|
|
5617
5644
|
let dashboardPath;
|
|
@@ -5619,7 +5646,7 @@ async function runInit(opts) {
|
|
|
5619
5646
|
await ensureStore(opts.cwd);
|
|
5620
5647
|
await writeDecisions(opts.cwd, {
|
|
5621
5648
|
schema: "decisions/v1",
|
|
5622
|
-
items: mergeDecisions(decisions, answersToDecisions(answers, (/* @__PURE__ */ new Date()).toISOString()))
|
|
5649
|
+
items: mergeDecisions(decisions, [...answersToDecisions(answers, (/* @__PURE__ */ new Date()).toISOString()), ...excludedTools.map((t) => ({ id: `exclude_tool:${t.id}`, kind: "exclude_tool", target: t.id, value: true, at: (/* @__PURE__ */ new Date()).toISOString(), note: "Excluded by repository owner during init" }))])
|
|
5623
5650
|
});
|
|
5624
5651
|
await writeInventory(opts.cwd, finalInventory);
|
|
5625
5652
|
await writePlan(opts.cwd, plan);
|
|
@@ -5727,6 +5754,7 @@ function helpText() {
|
|
|
5727
5754
|
` ${"--incremental".padEnd(20)}Reconcile against the previous inventory`,
|
|
5728
5755
|
` ${"--yes / --non-interactive".padEnd(20)}Never prompt`,
|
|
5729
5756
|
` ${"--dry-run".padEnd(20)}init: discover without writing any files`,
|
|
5757
|
+
` ${"--exclude <names>".padEnd(20)}init: remember unused integrations (comma-separated names)`,
|
|
5730
5758
|
` ${"--out <path>".padEnd(20)}run/example: output directory; report: HTML file`,
|
|
5731
5759
|
` ${"--runtime <local|hosted|e2b>".padEnd(20)}run: local CLI by default; hosted API or E2B when a cloud VM is needed`,
|
|
5732
5760
|
` ${"-v, --version".padEnd(20)}Print the installed version`,
|
|
@@ -5751,6 +5779,7 @@ async function main() {
|
|
|
5751
5779
|
observe: { type: "boolean" },
|
|
5752
5780
|
"observe-run": { type: "string" },
|
|
5753
5781
|
answers: { type: "string" },
|
|
5782
|
+
exclude: { type: "string", multiple: true },
|
|
5754
5783
|
"max-questions": { type: "string" },
|
|
5755
5784
|
interactive: { type: "boolean" },
|
|
5756
5785
|
agent: { type: "string" },
|
|
@@ -5862,7 +5891,8 @@ ${result.uploaded}`);
|
|
|
5862
5891
|
maxQuestions: Number(str("max-questions") ?? 8) || 8,
|
|
5863
5892
|
interactive: flag("interactive"),
|
|
5864
5893
|
agent: str("agent"),
|
|
5865
|
-
sync: flag("sync")
|
|
5894
|
+
sync: flag("sync"),
|
|
5895
|
+
exclude: arr("exclude")
|
|
5866
5896
|
});
|
|
5867
5897
|
return;
|
|
5868
5898
|
case "discover": {
|