@forgezero/agent 0.1.124 → 0.1.125

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.
@@ -917,7 +917,7 @@ async function postSignedNode(options, path, body) {
917
917
  }
918
918
 
919
919
  // src/version.ts
920
- var VERSION3 = "0.1.124";
920
+ var VERSION3 = "0.1.125";
921
921
 
922
922
  // src/agent-heartbeat.ts
923
923
  function readAgentHostMetrics() {
package/dist/bootstrap.js CHANGED
@@ -1460,7 +1460,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1460
1460
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1461
1461
 
1462
1462
  // src/version.ts
1463
- var VERSION = "0.1.124";
1463
+ var VERSION = "0.1.125";
1464
1464
 
1465
1465
  // src/software.ts
1466
1466
  var PINNED_BUN_VERSION = "1.3.14";
@@ -9,6 +9,8 @@ export declare const COMMUNITY_REHEARSAL_COORDINATOR_PORT = 18529;
9
9
  export declare const COMMUNITY_REHEARSAL_DATABASE_PORT_RANGE = "18528:18539";
10
10
  export declare const COMMUNITY_REHEARSAL_API_PORT = 18787;
11
11
  export declare const COMMUNITY_REHEARSAL_FIREWALL_COMMENT = "fz-community-rehearsal";
12
+ export declare const COMMUNITY_REHEARSAL_EGRESS_TABLE = "forgezero_agent_egress";
13
+ export declare const COMMUNITY_REHEARSAL_EGRESS_CHAIN = "output";
12
14
  export interface CommunityRehearsalNode {
13
15
  name: string;
14
16
  address: string;
@@ -48,6 +50,11 @@ export type CommunityHostOperation = {
48
50
  path: typeof COMMUNITY_DATABASE_ROOT | typeof COMMUNITY_REHEARSAL_ROOT | typeof COMMUNITY_CREDENTIAL_PARENT;
49
51
  } | {
50
52
  kind: 'clear-firewall';
53
+ } | {
54
+ kind: 'prepare-egress';
55
+ addresses: readonly string[];
56
+ } | {
57
+ kind: 'clear-egress';
51
58
  } | {
52
59
  kind: 'write';
53
60
  path: string;
@@ -75,5 +82,7 @@ export declare function planCommunityRehearsalPrepare(request: Extract<Community
75
82
  /** Remove only the dedicated rehearsal units, credentials, release and data. */
76
83
  export declare function planCommunityRehearsalCleanup(): CommunityHostOperation[];
77
84
  export declare function communityRehearsalFirewallRuleNumbers(status: string): number[];
85
+ export declare function communityRehearsalEgressRuleHandles(ruleset: string): number[];
86
+ export declare function communityRehearsalEgressRuleArguments(uid: string, addresses: readonly string[]): readonly (readonly string[])[];
78
87
  /** Execute only the dedicated four-node rehearsal operation selected by a typed request. */
79
88
  export declare function runCommunityRehearsalHost(request: CommunityRehearsalHostRequest): Promise<Record<string, unknown>>;
@@ -577,6 +577,8 @@ var COMMUNITY_REHEARSAL_COORDINATOR_PORT = 18529;
577
577
  var COMMUNITY_REHEARSAL_DATABASE_PORT_RANGE = "18528:18539";
578
578
  var COMMUNITY_REHEARSAL_API_PORT = 18787;
579
579
  var COMMUNITY_REHEARSAL_FIREWALL_COMMENT = "fz-community-rehearsal";
580
+ var COMMUNITY_REHEARSAL_EGRESS_TABLE = "forgezero_agent_egress";
581
+ var COMMUNITY_REHEARSAL_EGRESS_CHAIN = "output";
580
582
  var RELEASE = /^[a-f0-9]{64}$/;
581
583
  var SAFE_NAME = /^[a-z][a-z0-9-]{0,62}$/;
582
584
  async function ensureCommunityRehearsalArango(execute = executeSoftwareOperation) {
@@ -740,6 +742,7 @@ function planCommunityRehearsalPrepare(request) {
740
742
  { kind: "exec", argv: ["/usr/bin/systemctl", "stop", "forgezero-community-api.service"], accepted: [0, 5] },
741
743
  { kind: "exec", argv: ["/usr/bin/systemctl", "stop", "forgezero-rehearsal-db.service"], accepted: [0, 5] },
742
744
  { kind: "clear-firewall" },
745
+ { kind: "prepare-egress", addresses: topology.map(({ address }) => address) },
743
746
  ...topology.map(({ address }) => ({
744
747
  kind: "exec",
745
748
  argv: [
@@ -802,6 +805,7 @@ function planCommunityRehearsalCleanup() {
802
805
  { kind: "remove-tree", path: COMMUNITY_DATABASE_ROOT },
803
806
  { kind: "remove-tree", path: COMMUNITY_REHEARSAL_ROOT },
804
807
  { kind: "remove-tree", path: COMMUNITY_CREDENTIAL_PARENT },
808
+ { kind: "clear-egress" },
805
809
  { kind: "clear-firewall" },
806
810
  { kind: "exec", argv: ["/usr/bin/systemctl", "daemon-reload"] },
807
811
  { kind: "exec", argv: ["/usr/bin/systemctl", "reset-failed", "forgezero-community-api.service", "forgezero-rehearsal-db.service"], accepted: [0, 1, 5] }
@@ -824,6 +828,10 @@ async function applyOperations(operations) {
824
828
  rmSync2(operation.path, { recursive: true, force: true });
825
829
  else if (operation.kind === "clear-firewall")
826
830
  await clearCommunityRehearsalFirewall();
831
+ else if (operation.kind === "prepare-egress")
832
+ await prepareCommunityRehearsalEgress(operation.addresses);
833
+ else if (operation.kind === "clear-egress")
834
+ await clearCommunityRehearsalEgress();
827
835
  else if (operation.kind === "write") {
828
836
  mkdirSync2(dirname2(operation.path), { recursive: true });
829
837
  writeFileSync2(operation.path, operation.content, { mode: operation.mode });
@@ -854,6 +862,49 @@ function communityRehearsalFirewallRuleNumbers(status) {
854
862
  return match ? [Number(match[1])] : [];
855
863
  }).filter((number) => Number.isSafeInteger(number) && number > 0).sort((left, right) => right - left);
856
864
  }
865
+ function communityRehearsalEgressRuleHandles(ruleset) {
866
+ return ruleset.split(/\r?\n/).flatMap((line) => {
867
+ if (!line.includes(`comment "${COMMUNITY_REHEARSAL_FIREWALL_COMMENT}"`))
868
+ return [];
869
+ const match = line.match(/# handle (\d+)\s*$/);
870
+ return match ? [Number(match[1])] : [];
871
+ }).filter((number) => Number.isSafeInteger(number) && number > 0).sort((left, right) => right - left);
872
+ }
873
+ function communityRehearsalEgressRuleArguments(uid, addresses) {
874
+ if (!/^\d+$/.test(uid) || addresses.some((address) => isIP(address) !== 4) || new Set(addresses).size !== addresses.length) {
875
+ throw new Error("rehearsal egress arguments require one uid and unique IPv4 addresses");
876
+ }
877
+ return addresses.flatMap((address) => [
878
+ [
879
+ "meta",
880
+ "skuid",
881
+ uid,
882
+ "ct",
883
+ "state",
884
+ "established,related",
885
+ "ip",
886
+ "daddr",
887
+ address,
888
+ "accept",
889
+ "comment",
890
+ COMMUNITY_REHEARSAL_FIREWALL_COMMENT
891
+ ],
892
+ [
893
+ "meta",
894
+ "skuid",
895
+ uid,
896
+ "ip",
897
+ "daddr",
898
+ address,
899
+ "tcp",
900
+ "dport",
901
+ String(COMMUNITY_REHEARSAL_COORDINATOR_PORT),
902
+ "accept",
903
+ "comment",
904
+ COMMUNITY_REHEARSAL_FIREWALL_COMMENT
905
+ ]
906
+ ]);
907
+ }
857
908
  async function clearCommunityRehearsalFirewall() {
858
909
  const status = await exec(["/usr/sbin/ufw", "status", "numbered"]);
859
910
  if (status.exitCode !== 0)
@@ -865,6 +916,71 @@ async function clearCommunityRehearsalFirewall() {
865
916
  }
866
917
  }
867
918
  }
919
+ var rehearsalEgressRules = async () => {
920
+ const result = await exec([
921
+ "/usr/sbin/nft",
922
+ "-a",
923
+ "list",
924
+ "chain",
925
+ "inet",
926
+ COMMUNITY_REHEARSAL_EGRESS_TABLE,
927
+ COMMUNITY_REHEARSAL_EGRESS_CHAIN
928
+ ]);
929
+ if (result.exitCode === 0)
930
+ return { exists: true, output: result.output };
931
+ if (/No such file or directory|does not exist/i.test(result.output))
932
+ return { exists: false, output: result.output };
933
+ throw new Error(`unable to inspect rehearsal egress rules: ${result.output.trim()}`);
934
+ };
935
+ async function clearCommunityRehearsalEgress() {
936
+ const listed = await rehearsalEgressRules();
937
+ if (!listed.exists)
938
+ return;
939
+ for (const handle of communityRehearsalEgressRuleHandles(listed.output)) {
940
+ const removed = await exec([
941
+ "/usr/sbin/nft",
942
+ "delete",
943
+ "rule",
944
+ "inet",
945
+ COMMUNITY_REHEARSAL_EGRESS_TABLE,
946
+ COMMUNITY_REHEARSAL_EGRESS_CHAIN,
947
+ "handle",
948
+ String(handle)
949
+ ]);
950
+ if (removed.exitCode !== 0) {
951
+ throw new Error(`unable to remove rehearsal egress rule ${handle}: ${removed.output.trim()}`);
952
+ }
953
+ }
954
+ }
955
+ async function prepareCommunityRehearsalEgress(addresses) {
956
+ await clearCommunityRehearsalEgress();
957
+ if (!(await rehearsalEgressRules()).exists)
958
+ return;
959
+ const identity = await exec(["/usr/bin/id", "-u", "forgezero"]);
960
+ const uid = identity.output.trim();
961
+ if (identity.exitCode !== 0 || !/^\d+$/.test(uid))
962
+ throw new Error("unable to resolve the forgezero service uid");
963
+ for (const rule of communityRehearsalEgressRuleArguments(uid, addresses)) {
964
+ const inserted = await exec([
965
+ "/usr/sbin/nft",
966
+ "insert",
967
+ "rule",
968
+ "inet",
969
+ COMMUNITY_REHEARSAL_EGRESS_TABLE,
970
+ COMMUNITY_REHEARSAL_EGRESS_CHAIN,
971
+ ...rule
972
+ ]);
973
+ if (inserted.exitCode !== 0) {
974
+ await clearCommunityRehearsalEgress();
975
+ throw new Error(`unable to install a rehearsal egress rule: ${inserted.output.trim()}`);
976
+ }
977
+ }
978
+ const installed = communityRehearsalEgressRuleHandles((await rehearsalEgressRules()).output);
979
+ if (installed.length !== addresses.length * 2) {
980
+ await clearCommunityRehearsalEgress();
981
+ throw new Error("rehearsal egress rule verification failed");
982
+ }
983
+ }
868
984
  var responseFor = async (request) => {
869
985
  const node = request.topology.find((entry) => entry.name === request.node);
870
986
  const base = `http://${node.address}:${COMMUNITY_REHEARSAL_API_PORT}`;
@@ -953,10 +1069,14 @@ export {
953
1069
  parseCommunityRehearsalHostRequest,
954
1070
  ensureCommunityRehearsalArango,
955
1071
  communityRehearsalFirewallRuleNumbers,
1072
+ communityRehearsalEgressRuleHandles,
1073
+ communityRehearsalEgressRuleArguments,
956
1074
  COMMUNITY_REHEARSAL_VERSION,
957
1075
  COMMUNITY_REHEARSAL_STARTER_PORT,
958
1076
  COMMUNITY_REHEARSAL_ROOT,
959
1077
  COMMUNITY_REHEARSAL_FIREWALL_COMMENT,
1078
+ COMMUNITY_REHEARSAL_EGRESS_TABLE,
1079
+ COMMUNITY_REHEARSAL_EGRESS_CHAIN,
960
1080
  COMMUNITY_REHEARSAL_DATABASE_PORT_RANGE,
961
1081
  COMMUNITY_REHEARSAL_COORDINATOR_PORT,
962
1082
  COMMUNITY_REHEARSAL_API_PORT,
package/dist/fz-agent.js CHANGED
@@ -9640,7 +9640,7 @@ async function writeAndCloseProcessInput(input, value) {
9640
9640
  }
9641
9641
 
9642
9642
  // src/version.ts
9643
- var VERSION2 = "0.1.124";
9643
+ var VERSION2 = "0.1.125";
9644
9644
 
9645
9645
  // src/ssh-bootstrap.ts
9646
9646
  class SshBootstrapError extends Error {
@@ -17125,6 +17125,8 @@ var COMMUNITY_REHEARSAL_COORDINATOR_PORT = 18529;
17125
17125
  var COMMUNITY_REHEARSAL_DATABASE_PORT_RANGE = "18528:18539";
17126
17126
  var COMMUNITY_REHEARSAL_API_PORT = 18787;
17127
17127
  var COMMUNITY_REHEARSAL_FIREWALL_COMMENT = "fz-community-rehearsal";
17128
+ var COMMUNITY_REHEARSAL_EGRESS_TABLE = "forgezero_agent_egress";
17129
+ var COMMUNITY_REHEARSAL_EGRESS_CHAIN = "output";
17128
17130
  var RELEASE = /^[a-f0-9]{64}$/;
17129
17131
  var SAFE_NAME2 = /^[a-z][a-z0-9-]{0,62}$/;
17130
17132
  async function ensureCommunityRehearsalArango(execute3 = executeSoftwareOperation) {
@@ -17288,6 +17290,7 @@ function planCommunityRehearsalPrepare(request) {
17288
17290
  { kind: "exec", argv: ["/usr/bin/systemctl", "stop", "forgezero-community-api.service"], accepted: [0, 5] },
17289
17291
  { kind: "exec", argv: ["/usr/bin/systemctl", "stop", "forgezero-rehearsal-db.service"], accepted: [0, 5] },
17290
17292
  { kind: "clear-firewall" },
17293
+ { kind: "prepare-egress", addresses: topology.map(({ address }) => address) },
17291
17294
  ...topology.map(({ address }) => ({
17292
17295
  kind: "exec",
17293
17296
  argv: [
@@ -17350,6 +17353,7 @@ function planCommunityRehearsalCleanup() {
17350
17353
  { kind: "remove-tree", path: COMMUNITY_DATABASE_ROOT },
17351
17354
  { kind: "remove-tree", path: COMMUNITY_REHEARSAL_ROOT },
17352
17355
  { kind: "remove-tree", path: COMMUNITY_CREDENTIAL_PARENT },
17356
+ { kind: "clear-egress" },
17353
17357
  { kind: "clear-firewall" },
17354
17358
  { kind: "exec", argv: ["/usr/bin/systemctl", "daemon-reload"] },
17355
17359
  { kind: "exec", argv: ["/usr/bin/systemctl", "reset-failed", "forgezero-community-api.service", "forgezero-rehearsal-db.service"], accepted: [0, 1, 5] }
@@ -17372,6 +17376,10 @@ async function applyOperations(operations) {
17372
17376
  rmSync13(operation.path, { recursive: true, force: true });
17373
17377
  else if (operation.kind === "clear-firewall")
17374
17378
  await clearCommunityRehearsalFirewall();
17379
+ else if (operation.kind === "prepare-egress")
17380
+ await prepareCommunityRehearsalEgress(operation.addresses);
17381
+ else if (operation.kind === "clear-egress")
17382
+ await clearCommunityRehearsalEgress();
17375
17383
  else if (operation.kind === "write") {
17376
17384
  mkdirSync17(dirname15(operation.path), { recursive: true });
17377
17385
  writeFileSync17(operation.path, operation.content, { mode: operation.mode });
@@ -17402,6 +17410,49 @@ function communityRehearsalFirewallRuleNumbers(status) {
17402
17410
  return match ? [Number(match[1])] : [];
17403
17411
  }).filter((number2) => Number.isSafeInteger(number2) && number2 > 0).sort((left, right) => right - left);
17404
17412
  }
17413
+ function communityRehearsalEgressRuleHandles(ruleset) {
17414
+ return ruleset.split(/\r?\n/).flatMap((line) => {
17415
+ if (!line.includes(`comment "${COMMUNITY_REHEARSAL_FIREWALL_COMMENT}"`))
17416
+ return [];
17417
+ const match = line.match(/# handle (\d+)\s*$/);
17418
+ return match ? [Number(match[1])] : [];
17419
+ }).filter((number2) => Number.isSafeInteger(number2) && number2 > 0).sort((left, right) => right - left);
17420
+ }
17421
+ function communityRehearsalEgressRuleArguments(uid, addresses) {
17422
+ if (!/^\d+$/.test(uid) || addresses.some((address) => isIP6(address) !== 4) || new Set(addresses).size !== addresses.length) {
17423
+ throw new Error("rehearsal egress arguments require one uid and unique IPv4 addresses");
17424
+ }
17425
+ return addresses.flatMap((address) => [
17426
+ [
17427
+ "meta",
17428
+ "skuid",
17429
+ uid,
17430
+ "ct",
17431
+ "state",
17432
+ "established,related",
17433
+ "ip",
17434
+ "daddr",
17435
+ address,
17436
+ "accept",
17437
+ "comment",
17438
+ COMMUNITY_REHEARSAL_FIREWALL_COMMENT
17439
+ ],
17440
+ [
17441
+ "meta",
17442
+ "skuid",
17443
+ uid,
17444
+ "ip",
17445
+ "daddr",
17446
+ address,
17447
+ "tcp",
17448
+ "dport",
17449
+ String(COMMUNITY_REHEARSAL_COORDINATOR_PORT),
17450
+ "accept",
17451
+ "comment",
17452
+ COMMUNITY_REHEARSAL_FIREWALL_COMMENT
17453
+ ]
17454
+ ]);
17455
+ }
17405
17456
  async function clearCommunityRehearsalFirewall() {
17406
17457
  const status = await exec(["/usr/sbin/ufw", "status", "numbered"]);
17407
17458
  if (status.exitCode !== 0)
@@ -17413,6 +17464,71 @@ async function clearCommunityRehearsalFirewall() {
17413
17464
  }
17414
17465
  }
17415
17466
  }
17467
+ var rehearsalEgressRules = async () => {
17468
+ const result = await exec([
17469
+ "/usr/sbin/nft",
17470
+ "-a",
17471
+ "list",
17472
+ "chain",
17473
+ "inet",
17474
+ COMMUNITY_REHEARSAL_EGRESS_TABLE,
17475
+ COMMUNITY_REHEARSAL_EGRESS_CHAIN
17476
+ ]);
17477
+ if (result.exitCode === 0)
17478
+ return { exists: true, output: result.output };
17479
+ if (/No such file or directory|does not exist/i.test(result.output))
17480
+ return { exists: false, output: result.output };
17481
+ throw new Error(`unable to inspect rehearsal egress rules: ${result.output.trim()}`);
17482
+ };
17483
+ async function clearCommunityRehearsalEgress() {
17484
+ const listed = await rehearsalEgressRules();
17485
+ if (!listed.exists)
17486
+ return;
17487
+ for (const handle of communityRehearsalEgressRuleHandles(listed.output)) {
17488
+ const removed = await exec([
17489
+ "/usr/sbin/nft",
17490
+ "delete",
17491
+ "rule",
17492
+ "inet",
17493
+ COMMUNITY_REHEARSAL_EGRESS_TABLE,
17494
+ COMMUNITY_REHEARSAL_EGRESS_CHAIN,
17495
+ "handle",
17496
+ String(handle)
17497
+ ]);
17498
+ if (removed.exitCode !== 0) {
17499
+ throw new Error(`unable to remove rehearsal egress rule ${handle}: ${removed.output.trim()}`);
17500
+ }
17501
+ }
17502
+ }
17503
+ async function prepareCommunityRehearsalEgress(addresses) {
17504
+ await clearCommunityRehearsalEgress();
17505
+ if (!(await rehearsalEgressRules()).exists)
17506
+ return;
17507
+ const identity = await exec(["/usr/bin/id", "-u", "forgezero"]);
17508
+ const uid = identity.output.trim();
17509
+ if (identity.exitCode !== 0 || !/^\d+$/.test(uid))
17510
+ throw new Error("unable to resolve the forgezero service uid");
17511
+ for (const rule of communityRehearsalEgressRuleArguments(uid, addresses)) {
17512
+ const inserted = await exec([
17513
+ "/usr/sbin/nft",
17514
+ "insert",
17515
+ "rule",
17516
+ "inet",
17517
+ COMMUNITY_REHEARSAL_EGRESS_TABLE,
17518
+ COMMUNITY_REHEARSAL_EGRESS_CHAIN,
17519
+ ...rule
17520
+ ]);
17521
+ if (inserted.exitCode !== 0) {
17522
+ await clearCommunityRehearsalEgress();
17523
+ throw new Error(`unable to install a rehearsal egress rule: ${inserted.output.trim()}`);
17524
+ }
17525
+ }
17526
+ const installed = communityRehearsalEgressRuleHandles((await rehearsalEgressRules()).output);
17527
+ if (installed.length !== addresses.length * 2) {
17528
+ await clearCommunityRehearsalEgress();
17529
+ throw new Error("rehearsal egress rule verification failed");
17530
+ }
17531
+ }
17416
17532
  var responseFor = async (request) => {
17417
17533
  const node = request.topology.find((entry) => entry.name === request.node);
17418
17534
  const base = `http://${node.address}:${COMMUNITY_REHEARSAL_API_PORT}`;
package/dist/fz.js CHANGED
@@ -4844,7 +4844,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
4844
4844
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
4845
4845
 
4846
4846
  // src/version.ts
4847
- var VERSION2 = "0.1.124";
4847
+ var VERSION2 = "0.1.125";
4848
4848
 
4849
4849
  // src/software.ts
4850
4850
  var PINNED_BUN_VERSION = "1.3.14";
@@ -366,7 +366,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
366
366
  }
367
367
 
368
368
  // src/version.ts
369
- var VERSION = "0.1.124";
369
+ var VERSION = "0.1.125";
370
370
 
371
371
  // src/otel-collector.ts
372
372
  var FORGEZERO_OTEL_COLLECTOR_UNIT = "forgezero-otel-collector.service";
@@ -1460,7 +1460,7 @@ var UPDATE_RETRY_BASE_MS = 5 * 60000;
1460
1460
  var UPDATE_RETRY_MAX_MS = 24 * 60 * 60000;
1461
1461
 
1462
1462
  // src/version.ts
1463
- var VERSION = "0.1.124";
1463
+ var VERSION = "0.1.125";
1464
1464
 
1465
1465
  // src/software.ts
1466
1466
  var PINNED_BUN_VERSION = "1.3.14";
@@ -3020,7 +3020,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
3020
3020
  }
3021
3021
 
3022
3022
  // src/version.ts
3023
- var VERSION3 = "0.1.124";
3023
+ var VERSION3 = "0.1.125";
3024
3024
 
3025
3025
  // src/egress-policy.ts
3026
3026
  import { realpathSync as realpathSync3 } from "node:fs";
package/dist/provision.js CHANGED
@@ -3020,7 +3020,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
3020
3020
  }
3021
3021
 
3022
3022
  // src/version.ts
3023
- var VERSION3 = "0.1.124";
3023
+ var VERSION3 = "0.1.125";
3024
3024
 
3025
3025
  // src/egress-policy.ts
3026
3026
  import { realpathSync as realpathSync3 } from "node:fs";
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** One package version shared by both public binaries. Pinned to package.json by tests. */
2
- export declare const VERSION = "0.1.124";
2
+ export declare const VERSION = "0.1.125";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgezero/agent",
3
- "version": "0.1.124",
3
+ "version": "0.1.125",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",