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.
Files changed (51) hide show
  1. package/README.md +8 -0
  2. package/dist/api.js +96 -56
  3. package/dist/cli.js +210 -180
  4. package/dist/dashboard/assets/dashboard/agent-home.css +33 -10
  5. package/dist/dashboard/assets/dashboard/bay-morning.png +0 -0
  6. package/dist/dashboard/assets/dashboard/golden-gate-afternoon.png +0 -0
  7. package/dist/dashboard/assets/dashboard/inventory.json +1 -1
  8. package/dist/dashboard/assets/dashboard/runs.json +1 -1
  9. package/dist/dashboard/assets/next/static/HZwGKKvvx_oO6caYggjFh/_buildManifest.js +1 -0
  10. package/dist/dashboard/assets/next/static/HZwGKKvvx_oO6caYggjFh/_ssgManifest.js +1 -0
  11. package/dist/dashboard/assets/next/static/chunks/255-09fb0d129bb12f51.js +1 -0
  12. package/dist/dashboard/assets/next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
  13. package/dist/dashboard/assets/next/static/chunks/772-85561381eedc95da.js +67 -0
  14. package/dist/dashboard/assets/next/static/chunks/app/_not-found/page-5dc6a60918e8652d.js +1 -0
  15. package/dist/dashboard/assets/next/static/chunks/app/dashboard/layout-74603c2c8eaf0760.js +1 -0
  16. package/dist/dashboard/assets/next/static/chunks/app/dashboard/page-b79ed135f6bac5f7.js +1 -0
  17. package/dist/dashboard/assets/next/static/chunks/app/demo/page-760a47a5e4dee43f.js +1 -0
  18. package/dist/dashboard/assets/next/static/chunks/app/docs/[[...slug]]/page-80a0c2c82d5b4efd.js +1 -0
  19. package/dist/dashboard/assets/next/static/chunks/app/layout-1a40c59e535513d4.js +1 -0
  20. package/dist/dashboard/assets/next/static/chunks/app/page-de4735052adab906.js +1 -0
  21. package/dist/dashboard/assets/next/static/chunks/app/pricing/page-7703fc31d6dff0f7.js +1 -0
  22. package/dist/dashboard/assets/next/static/chunks/app/sitemap.xml/route-ef5a216a578eb7a8.js +1 -0
  23. package/dist/dashboard/assets/next/static/chunks/framework-292291387d6b2e39.js +1 -0
  24. package/dist/dashboard/assets/next/static/chunks/main-aea6e71289384293.js +1 -0
  25. package/dist/dashboard/assets/next/static/chunks/main-app-99295a8cc37e8a47.js +1 -0
  26. package/dist/dashboard/assets/next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
  27. package/dist/dashboard/assets/next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
  28. package/dist/dashboard/assets/next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  29. package/dist/dashboard/assets/next/static/chunks/webpack-f3b6dd27998e84ce.js +1 -0
  30. package/dist/dashboard/assets/next/static/css/1323d7da89b3fa5e.css +1 -0
  31. package/dist/dashboard/assets/next/static/css/45513ebc8c6ae800.css +1 -0
  32. package/dist/dashboard/assets/next/static/css/61ec74d503f353d4.css +1 -0
  33. package/dist/dashboard/assets/next/static/css/9282330a2fd1ab73.css +1 -0
  34. package/dist/dashboard/assets/next/static/css/96e4f356a1159071.css +1 -0
  35. package/dist/dashboard/assets/next/static/css/c5ac36ef5726e746.css +1 -0
  36. package/dist/dashboard/assets/next/static/media/bay-morning.1a9f358c.png +0 -0
  37. package/dist/dashboard/assets/next/static/media/golden-gate-afternoon.3e7cea58.png +0 -0
  38. package/dist/dashboard/assets/next/static/media/inter-var.e4af272c.woff2 +0 -0
  39. package/dist/dashboard/assets/next/static/media/moonlit-coast.bbe901a1.png +0 -0
  40. package/dist/dashboard/assets/next/static/media/plexmono-400.d3ebbfd6.woff2 +0 -0
  41. package/dist/dashboard/index.html +1 -140
  42. package/dist/gateway.js +96 -56
  43. package/package.json +1 -1
  44. package/dist/dashboard/assets/dashboard/agent-home.js +0 -298
  45. package/dist/dashboard/assets/dashboard/dashboard.js +0 -462
  46. package/dist/dashboard/assets/dashboard/hosted.js +0 -89
  47. package/dist/dashboard/assets/dashboard/workflows.js +0 -39
  48. package/dist/dashboard/assets/vendor/dompurify.LICENSE +0 -202
  49. package/dist/dashboard/assets/vendor/marked.LICENSE +0 -44
  50. package/dist/dashboard/assets/vendor/marked.js +0 -82
  51. 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 tools = data.tools.filter((t) => current.has(t.id) || !known.has(t.title) && t.category !== "access" && t.category !== "tooling");
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 readFile4, writeFile } from "fs/promises";
876
- import { basename, join as join4 } from "path";
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 = join4(cwd, ".agenttwin", "dashboard");
969
+ const directory = join5(cwd, ".agenttwin", "dashboard");
896
970
  let bundle = fileURLToPath(new URL("./dashboard/", import.meta.url));
897
971
  try {
898
- await readFile4(join4(bundle, "index.html"));
972
+ await readFile5(join5(bundle, "index.html"));
899
973
  } catch {
900
- bundle = fileURLToPath(new URL("../../../../site/", import.meta.url));
974
+ bundle = fileURLToPath(new URL("../../dist/dashboard/", import.meta.url));
901
975
  }
902
- await mkdir(join4(directory, "assets", "dashboard"), { recursive: true });
903
- const html = await readFile4(join4(bundle, bundle.endsWith("/site/") ? "dashboard.html" : "index.html"), "utf8");
904
- for (const folder of ["dashboard", "icons", "logo", "fonts", "vendor"]) {
905
- await cp(join4(bundle, "assets", folder), join4(directory, "assets", folder), { recursive: true });
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(join4(bundle, "assets", "brand.css"), join4(directory, "assets", "brand.css"));
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.replaceAll('type="module"', "defer").replace('<script src="assets/dashboard/dashboard.js', `<script id="agenttwin-discovery" type="application/json">${payload}</script><script src="assets/dashboard/dashboard.js`).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");
911
- await writeFile(join4(directory, "index.html"), page2, "utf8");
912
- await writeFile(join4(directory, "assets", "dashboard", "inventory.json"), JSON.stringify(data), "utf8");
913
- await writeFile(join4(directory, "assets", "dashboard", "runs.json"), '{"runs":[]}', "utf8");
914
- return join4(directory, "index.html");
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 writeFile2, readFile as readFile5 } from "fs/promises";
935
- import { join as join5 } from "path";
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 = join5(cwd, ".agenttwin/AGENT-TASK.md");
961
- await writeFile2(path, sessionTask(), "utf8");
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 readFile5(join5(cwd, ".agenttwin/git.json"), "utf8")).head;
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 readFile5(join5(cwd, ".agenttwin/agent-discovery.json"), "utf8");
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 readFile6, writeFile as writeFile3 } from "fs/promises";
1059
- import { join as join6 } from "path";
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 readFile6(join6(cwd, ".agenttwin/inventory.json"), "utf8"));
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 writeFile3(join6(cwd, ".agenttwin/git.json"), JSON.stringify(git) + "\n", { mode: 384 });
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/site/dashboard.html", uploaded: "discovery, Git metadata and run evidence; no application source or credentials" };
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 mkdir3, mkdtemp as mkdtemp2, readFile as readFile9, writeFile as writeFile5, rm as rm2 } from "fs/promises";
1221
- import { join as join9, resolve as resolve2, dirname as dirname2 } from "path";
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 mkdir2, readFile as readFile8, writeFile as writeFile4, realpath, lstat, rm, copyFile } from "fs/promises";
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 join8, relative as relative2, resolve, sep as sep2 } from "path";
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 readFile7 } from "fs/promises";
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 join7 } from "path";
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 readFile7(join7(homedir(), ".agenttwin/runtime.json"), "utf8"));
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 readFile7(join7(stage, file))).buffer, { user: "root" });
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 readFile8(source);
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 = join8(destination, file);
1637
- await mkdir2(dirname(target), { recursive: true });
1638
- await writeFile4(target, contents, { mode: 420 });
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 readFile8(file, "utf8")));
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 readFile8(join8(cwd, ".agenttwin/inventory.json"), "utf8"));
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(join8(tmpdir(), "agenttwin-world-"));
1741
+ const stage = await mkdtemp(join9(tmpdir(), "agenttwin-world-"));
1668
1742
  try {
1669
- const sourceDigests = await stageApplication(dirname(file), spec.files, join8(stage, "app"));
1670
- await writeFile4(join8(stage, "world.json"), JSON.stringify(spec), { mode: 420 });
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 readFile8(join8(cwd, ".agenttwin/inventory.json"), "utf8")).name ?? basename2(cwd);
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(join8(stage, "app"), spec, opts.signal);
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 mkdir2(out2, { recursive: true });
1684
- const evidencePath = join8(out2, "run.json"), reportPath = join8(out2, "report.md");
1685
- await writeFile4(evidencePath, JSON.stringify(receipt2, null, 2) + "\n", { mode: 384 });
1686
- await writeFile4(reportPath, `# ${spec.name}: ${receipt2.status}
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 = join8(dirname(fileURLToPath2(import.meta.url)), "worker.js");
1695
- await copyFile(worker, join8(stage, "worker.js"));
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 mkdir2(out2, { recursive: true });
1704
- const evidencePath = join8(out2, "run.json");
1705
- const reportPath = join8(out2, "report.md");
1706
- await writeFile4(evidencePath, JSON.stringify(receipt2, null, 2) + "\n", { mode: 384 });
1707
- await writeFile4(reportPath, `# ${spec.name}: ${receipt2.status}
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 mkdir2(out, { recursive: true });
1742
- await writeFile4(join8(out, "run.json"), JSON.stringify(receipt, null, 2) + "\n", { mode: 384 });
1743
- await writeFile4(join8(out, "world.json"), JSON.stringify(spec, null, 2) + "\n", { mode: 384 });
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 writeFile4(join8(out, "report.md"), summary);
1746
- return { ...receipt, reportPath: join8(out, "report.md"), evidencePath: join8(out, "run.json") };
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 readFile9(join9(cwd, ".agenttwin/inventory.json"), "utf8"));
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 readFile9(file, "utf8")));
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(join9(tmpdir2(), "agenttwin-prepare-"));
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 readFile9(join9(stage, name))).toString("base64");
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 writeFile5(join9(stage, "agenttwin.world.json"), JSON.stringify(spec, null, 2) + "\n", { mode: 384 });
1816
- await writeFile5(join9(stage, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", { mode: 384 });
1817
- await writeFile5(join9(stage, "agenttwin.bundle.json"), JSON.stringify({ schema: "agenttwin/upload-v1", spec, files, manifest }, null, 2) + "\n", { mode: 384 });
1818
- await mkdir3(join9(stage, ".agenttwin"), { recursive: true });
1819
- await writeFile5(join9(stage, ".agenttwin/inventory.json"), JSON.stringify({ name: discovered.name, workflows: [{ id: workflow.id }] }), { mode: 384 });
1820
- await mkdir3(dirname2(directory), { recursive: true });
1821
- await mkdir3(directory, { mode: 448 });
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(join9(stage, entry), join9(directory, entry), { recursive: true, force: false, errorOnExist: true });
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: join9(directory, "agenttwin.world.json"), bundlePath: join9(directory, "agenttwin.bundle.json"), manifest };
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 mkdir4, writeFile as writeFile6 } from "fs/promises";
1911
+ import { mkdir as mkdir5, writeFile as writeFile7 } from "fs/promises";
1838
1912
  import { homedir as homedir2 } from "os";
1839
- import { join as join10 } from "path";
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 = join10(homedir2(), ".agenttwin");
1848
- await mkdir4(directory, { recursive: true, mode: 448 });
1849
- const path = join10(directory, "runtime.json");
1850
- await writeFile6(path, JSON.stringify({ provider: "e2b", templateId: result.templateId, buildId: result.buildId }, null, 2) + "\n", { mode: 384 });
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 mkdir5, writeFile as writeFile7, stat as stat2 } from "fs/promises";
1856
- import { resolve as resolve3, join as join11 } from "path";
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 mkdir5(directory, { recursive: true });
1901
- await writeFile7(join11(directory, "workflow.mjs"), exampleSource);
1902
- await writeFile7(join11(directory, "agenttwin.world.json"), JSON.stringify(exampleWorld(), null, 2) + "\n");
1903
- await writeFile7(join11(directory, "safe.world.json"), JSON.stringify(exampleWorld(true), null, 2) + "\n");
1904
- await writeFile7(join11(directory, "README.md"), `# Support workflow
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: join11(directory, "agenttwin.world.json") };
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, 2e5);
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 join12 } from "path";
2819
+ import { join as join13 } from "path";
2746
2820
  var CREDENTIALS = [
2747
- { path: join12(homedir3(), ".aws", "config"), subject: "aws", title: "AWS" },
2748
- { path: join12(homedir3(), ".aws", "credentials"), subject: "aws", title: "AWS" },
2749
- { path: join12(homedir3(), ".config", "gcloud"), subject: "gcp", title: "Google Cloud" },
2750
- { path: join12(homedir3(), ".azure"), subject: "azure", title: "Azure" },
2751
- { path: join12(homedir3(), ".kube", "config"), subject: "kubernetes", title: "Kubernetes" }
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 join13 } from "path";
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 = join13(tmpdir3(), `agenttwin-egress-${process.pid}.log`);
2907
- const hookFile = join13(tmpdir3(), `agenttwin-egress-hook-${process.pid}.cjs`);
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, 2e5);
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) => e.signal === "credential") ? "access" : "dependency";
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 toolList = [...tools.values()].sort((a, b) => a.id.localeCompare(b.id));
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/store.ts
4139
- import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile8 } from "fs/promises";
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
- workflows: inventory.workflows.map(
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.padEnd(22)}${tool.kind.padEnd(16)}${facts2 ? tool.facts.map((f) => f.kind).join(",") : ""}`
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("Application dependencies", byCategory("dependency"), 12, true);
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
- bucket("Available access & configuration (not dependencies)", byCategory("access"), 8, false);
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
- ` Coverage: ${counts.tools} tools \xB7 ${counts.unclassified} unknown \xB7 ${failed.length} sources with gaps`
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(" Unknown (needs confirmation)");
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
- const inventory = result.inventory;
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": {