@catheadowl/ukp 0.1.1 → 0.2.1

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/src/server.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { dirname, join } from "node:path";
2
- import { readFileSync, writeFileSync } from "node:fs";
2
+ import { readFileSync, writeFileSync, fstatSync, constants as fsConstants } from "node:fs";
3
+ import { createServer as netCreateServer, connect as netConnect, type Server as NetServer } from "node:net";
3
4
  import { ENDPOINT_NAME, loadManifest, type LoadedManifest } from "./config/manifest.ts";
4
5
  import { localPathOf, readRegistry } from "./registry.ts";
5
6
  import { FILE_NATIVE_CAPABILITIES, resolveFileNativeCapability } from "./config/file-native.ts";
7
+ import { EXTERNAL_PROVIDER, EXTERNAL_TOOL_CAPABILITIES } from "./config/external-tool.ts";
6
8
  import {
7
9
  PROPOSE_SLUG,
8
10
  ProposeBusyError,
@@ -83,11 +85,24 @@ export interface ServeConfig {
83
85
  * identity under the Service folder (`.ukp/tls/`) — in door mode under the
84
86
  * registry's directory, since a door has no single Service folder;
85
87
  * `certificates` serves operator-provided PEM files (Let's Encrypt IP
86
- * certs, mkcert, private CA). `opensslCommand` is a test injection point
87
- * for the self-signing. */
88
+ * certs, mkcert, private CA). `sanEntries` (the `--tls-san` flag, already
89
+ * normalized `IP:x`/`DNS:y`) merges extra coverage into the self-signed
90
+ * SAN — a NAT/EIP public address no NIC carries; a persisted certificate
91
+ * lacking a requested entry is re-signed over the same key (pin unchanged,
92
+ * clients re-anchor). `opensslCommand` is a test injection point for the
93
+ * self-signing. */
88
94
  tls?:
89
- | { mode: "self-signed"; opensslCommand?: readonly string[] }
95
+ | { mode: "self-signed"; opensslCommand?: readonly string[]; sanEntries?: readonly string[] }
90
96
  | { mode: "certificates"; certPath: string; keyPath: string };
97
+ /** W9 / ADR-REM-006: self-reap after this many seconds without requests.
98
+ * The orphan backstop for on-demand-woken doors — a no-TTY remote command
99
+ * can escape SIGHUP on an abrupt disconnect, so the door bounds its own
100
+ * lifetime instead of trusting the session to reap it. */
101
+ maxIdleSeconds?: number;
102
+ /** W10 / ADR-REM-006: serve on the systemd socket-activation listener
103
+ * (LISTEN_FDS fd 3) instead of binding a port — the port belongs to the
104
+ * socket unit; the process only exists while in use. Linux-only. */
105
+ systemdSocket?: boolean;
91
106
  }
92
107
 
93
108
  export interface DiscoveryDocument {
@@ -136,11 +151,57 @@ export interface ServeInfo {
136
151
  port: number;
137
152
  authRequired: boolean;
138
153
  /** TLS identity summary (W5' / D-079) when serving over HTTPS; the pin is
139
- * what remote clients TOFU-pin at registration. */
154
+ * what remote clients TOFU-pin at registration. `source` "re-signed" marks
155
+ * the one start that grew SAN coverage over the existing key (--tls-san
156
+ * added an entry the persisted certificate lacked) — pin unchanged. */
140
157
  tls?: {
141
158
  pin: string;
142
159
  san: string;
143
- source: "generated" | "persisted" | "operator";
160
+ source: "generated" | "re-signed" | "persisted" | "operator";
161
+ };
162
+ /** Present when --max-idle armed the self-reap timer (W9). */
163
+ maxIdleSeconds?: number;
164
+ }
165
+
166
+ /** W9 / ADR-REM-006 (--max-idle): wrap a serve handler so the listener stops
167
+ * itself after `maxIdleSeconds` without requests. Requests re-arm the timer
168
+ * on entry and again once the response settles; the exit mirrors the SIGINT
169
+ * path (stderr line + server.stop(true)), after which the process exits with
170
+ * its already-set code. The timer is unref'd so a SIGINT/SIGTERM stop is
171
+ * immediate — the listener, not the timer, is what holds the serve process
172
+ * open. `stopped` fences the settle-rearm of a request that outlived the
173
+ * window (no second fire after the first stop). The bind indirection exists
174
+ * because Bun.serve hands back the server only after `fetch` is fixed. */
175
+ function armIdleExit(
176
+ handler: (request: Request) => Response | Promise<Response>,
177
+ maxIdleSeconds: number,
178
+ ): { fetch: (request: Request) => Response | Promise<Response>; bind: (stop: () => void) => void } {
179
+ const control = { stop: (): void => {} };
180
+ let timer: ReturnType<typeof setTimeout> | undefined;
181
+ let stopped = false;
182
+ const rearm = () => {
183
+ if (stopped) return;
184
+ if (timer !== undefined) clearTimeout(timer);
185
+ timer = setTimeout(() => {
186
+ stopped = true;
187
+ console.error(`ukp serve: idle exit (--max-idle ${maxIdleSeconds}s without requests)`);
188
+ control.stop();
189
+ }, maxIdleSeconds * 1000);
190
+ // Best-effort unref: present in Bun and Node; a missing no-op keeps this
191
+ // portable without changing the firing behavior while serving.
192
+ (timer as { unref?: () => void }).unref?.();
193
+ };
194
+ rearm();
195
+ return {
196
+ fetch: (request: Request) => {
197
+ rearm();
198
+ const response = handler(request);
199
+ Promise.resolve(response).then(rearm, () => {});
200
+ return response;
201
+ },
202
+ bind(stop: () => void) {
203
+ control.stop = stop;
204
+ },
144
205
  };
145
206
  }
146
207
 
@@ -171,13 +232,19 @@ function readInstanceUid(serviceFolder: string): string {
171
232
  }
172
233
 
173
234
  /** Capability projection shared by endpoint and door documents: derived
174
- * file-native defaults (read/nav) are always present; declared capabilities
175
- * override or extend them (a declared nav replaces the derived entry). */
235
+ * file-native defaults (read/nav) are always present, then the external-tool
236
+ * base tier (rg, ADR-RG-003 — served on /v1/rg for every endpoint; a missing
237
+ * binary degrades to per-endpoint availability data per the W6 wire
238
+ * contract, never an undeclared capability); declared capabilities override
239
+ * or extend both (a declared nav or rg replaces the default entry). */
176
240
  function projectCapabilities(loaded: LoadedManifest): DiscoveryDocument["capabilities"] {
177
241
  const capabilities: DiscoveryDocument["capabilities"] = {};
178
242
  for (const [name, spec] of Object.entries(FILE_NATIVE_CAPABILITIES)) {
179
243
  if (spec.derived) capabilities[name] = { provider: "file", derived: true };
180
244
  }
245
+ for (const name of Object.keys(EXTERNAL_TOOL_CAPABILITIES)) {
246
+ capabilities[name] = { provider: EXTERNAL_PROVIDER, derived: true };
247
+ }
181
248
  for (const name of Object.keys(loaded.manifest.capabilities).sort()) {
182
249
  const declared = loaded.manifest.capabilities[name]!;
183
250
  capabilities[name] = { provider: declared.provider ?? "file" };
@@ -449,45 +516,202 @@ function parseRgParams(
449
516
  export interface StartedServe {
450
517
  server: ReturnType<typeof Bun.serve>;
451
518
  info: ServeInfo;
519
+ /** Composite stop: in socket-activation mode this also closes the fd3
520
+ * acceptor (Bun.serve's own stop alone would leave the process alive
521
+ * holding the systemd-passed listener). Safe to call from the SIGINT/
522
+ * SIGTERM/SIGHUP handlers and the idle-exit path alike. */
523
+ stopAll: () => void;
524
+ }
525
+
526
+ /** W10 / ADR-REM-006: parse the systemd LISTEN_FDS convention — exactly one
527
+ * listening fd expected (fd 3), PID-guarded, and the variables are CONSUMED
528
+ * (unset) so any child process does not double-count inherited activation
529
+ * fds (the podman fd-counting pitfall, knowledge §5.2). */
530
+ const S_ISSOCK = fsConstants.S_IFSOCK;
531
+
532
+ export function parseListenFds(env: NodeJS.ProcessEnv): { fd: number } | { error: string } {
533
+ if ((env.LISTEN_FDS ?? "") === "" || (env.LISTEN_PID ?? "") === "") {
534
+ return {
535
+ error:
536
+ "--systemd-socket requires systemd socket activation: LISTEN_FDS/LISTEN_PID are not set (run under a .socket unit or systemd-socket-activate)",
537
+ };
538
+ }
539
+ if (Number(env.LISTEN_PID) !== process.pid) {
540
+ return { error: `LISTEN_PID ${env.LISTEN_PID} does not match this process (${process.pid}) — inherited activation variables belong to another process` };
541
+ }
542
+ const count = Number(env.LISTEN_FDS);
543
+ if (!Number.isSafeInteger(count) || count < 1) {
544
+ return { error: `LISTEN_FDS=${String(env.LISTEN_FDS)} is not a positive integer` };
545
+ }
546
+ if (count !== 1) {
547
+ return { error: `expected exactly one listening fd (LISTEN_FDS=1), got ${count} — run one socket unit per serve instance` };
548
+ }
549
+ delete env.LISTEN_FDS;
550
+ delete env.LISTEN_PID;
551
+ return { fd: 3 };
552
+ }
553
+
554
+ /** W10 / ADR-REM-006: the in-process bridge. Bun.serve cannot adopt a raw
555
+ * listening fd (no public API; ali probe 2026-09-19), but node:net can
556
+ * (`createServer().listen({fd: 3})` verified under bun). Each connection the
557
+ * systemd-held listener accepts is piped to the real Bun.serve listener on
558
+ * an ephemeral loopback port — the fake-ssh proxy pattern, in-process. */
559
+ export function createSocketBridge(acceptor: NetServer, targetPort: number): { destroyAll: () => void } {
560
+ // Live connections are tracked so stopAll can force-close the pairs — a
561
+ // peer that receives FIN but never closes (half-open keep-alive shape)
562
+ // would otherwise hold the process open after the idle exit forever
563
+ // (review P2: probe-confirmed liveness hang), letting stale serve
564
+ // processes accumulate beside systemd's fresh spawns.
565
+ const live = new Set<{ socket: import("node:net").Socket; upstream: import("node:net").Socket }>();
566
+ acceptor.on("connection", (socket) => {
567
+ const upstream = netConnect({ host: "127.0.0.1", port: targetPort });
568
+ const pair = { socket, upstream };
569
+ live.add(pair);
570
+ socket.pipe(upstream);
571
+ upstream.pipe(socket);
572
+ const drop = () => {
573
+ live.delete(pair);
574
+ socket.destroy();
575
+ upstream.destroy();
576
+ };
577
+ socket.on("error", drop);
578
+ upstream.on("error", drop);
579
+ socket.on("close", () => {
580
+ live.delete(pair);
581
+ upstream.destroy();
582
+ });
583
+ upstream.on("close", () => {
584
+ live.delete(pair);
585
+ socket.destroy();
586
+ });
587
+ });
588
+ return {
589
+ destroyAll() {
590
+ for (const pair of live) {
591
+ pair.socket.destroy();
592
+ pair.upstream.destroy();
593
+ }
594
+ live.clear();
595
+ },
596
+ };
597
+ }
598
+
599
+ /** One serve listener, both modes (W10): plain binding by default; under
600
+ * `systemdSocket` the systemd-passed fd 3 is adopted via node:net and
601
+ * bridged to the real Bun.serve listener on an ephemeral loopback port. */
602
+ function startServeListener(
603
+ handler: (request: Request) => Response | Promise<Response>,
604
+ options: {
605
+ host: string;
606
+ port?: number;
607
+ tlsMaterial?: { cert: string; key: string };
608
+ maxIdleSeconds?: number;
609
+ systemdSocket?: boolean;
610
+ },
611
+ ): { server: ReturnType<typeof Bun.serve>; stopAll: () => void; publicUrl: string; port: number } {
612
+ const idle = options.maxIdleSeconds !== undefined ? armIdleExit(handler, options.maxIdleSeconds) : undefined;
613
+ const fetch = idle?.fetch ?? handler;
614
+ const tls = options.tlsMaterial !== undefined ? { tls: { cert: options.tlsMaterial.cert, key: options.tlsMaterial.key } } : {};
615
+ const scheme = options.tlsMaterial !== undefined ? "https" : "http";
616
+
617
+ if (options.systemdSocket !== true) {
618
+ const server = Bun.serve({ hostname: options.host, port: options.port ?? 8570, ...tls, fetch });
619
+ idle?.bind(() => server.stop(true));
620
+ const port = server.port ?? (options.port ?? 8570);
621
+ return { server, stopAll: () => server.stop(true), publicUrl: `${scheme}://${options.host}:${port}`, port };
622
+ }
623
+
624
+ if (process.platform !== "linux") {
625
+ throw new ServeSetupError("--systemd-socket is Linux-only (systemd socket activation)");
626
+ }
627
+ const parsed = parseListenFds(process.env);
628
+ if ("error" in parsed) throw new ServeSetupError(parsed.error);
629
+ // Validate the fd synchronously: systemd passes a bound LISTENING socket;
630
+ // anything else (EBADF, wrong type) must fail setup, not crash later.
631
+ let isSocket = false;
632
+ try {
633
+ const stats = fstatSync(parsed.fd);
634
+ isSocket = (stats.mode & S_ISSOCK) === S_ISSOCK;
635
+ } catch {
636
+ isSocket = false;
637
+ }
638
+ if (!isSocket) {
639
+ throw new ServeSetupError(`fd ${parsed.fd} is not an open socket — socket activation must pass a bound listening socket`);
640
+ }
641
+ const server = Bun.serve({ hostname: "127.0.0.1", port: 0, ...tls, fetch });
642
+ const acceptor = netCreateServer();
643
+ const bridge = createSocketBridge(acceptor, server.port ?? 0);
644
+ acceptor.on("error", (error) => {
645
+ // Async setup failure channel (e.g. fd 3 is a CONNECTED socket — passes
646
+ // the S_IFSOCK pre-check but cannot listen): no hard exit from library
647
+ // code — report, stop both listeners, and let the loop drain with a
648
+ // failing exit code.
649
+ console.error(`ukp serve: fd${parsed.fd} acceptor failed: ${error instanceof Error ? error.message : String(error)}`);
650
+ process.exitCode = 1;
651
+ acceptor.close();
652
+ bridge.destroyAll();
653
+ server.stop(true);
654
+ });
655
+ acceptor.listen({ fd: parsed.fd });
656
+ const stopAll = () => {
657
+ acceptor.close();
658
+ bridge.destroyAll();
659
+ server.stop(true);
660
+ };
661
+ idle?.bind(stopAll);
662
+ return {
663
+ server,
664
+ stopAll,
665
+ publicUrl: `systemd:fd${parsed.fd} (bridge ${scheme}://127.0.0.1:${server.port})`,
666
+ port: server.port ?? 0,
667
+ };
452
668
  }
453
669
 
454
670
  /** TLS material resolution (W5' / D-079), shared by both serve modes: a
455
- * self-signed identity is generated/persisted under `identityDir` (the
456
- * Service folder's `.ukp/tls/` in endpoint mode, the registry directory in
457
- * door mode — a door has no single Service folder), explicit certificates
671
+ * self-signed identity is generated/re-signed/persisted under `identityDir`
672
+ * (the Service folder's `.ukp/tls/` in endpoint mode, the registry directory
673
+ * in door mode — a door has no single Service folder), explicit certificates
458
674
  * are read from the operator's paths. Bun.serve takes PEM contents
459
- * (path-string handling is platform-dependent). */
675
+ * (path-string handling is platform-dependent). Every failure — unreadable
676
+ * operator files, an unusable persisted identity, openssl unable to sign —
677
+ * surfaces as one ServeSetupError naming the certificate path. */
460
678
  function resolveTlsMaterial(
461
679
  config: ServeConfig,
462
680
  identityDir: string,
463
- ): { cert: string; key: string; pin: string; san: string; source: "generated" | "persisted" | "operator" } | undefined {
681
+ ): { cert: string; key: string; pin: string; san: string; source: "generated" | "re-signed" | "persisted" | "operator" } | undefined {
464
682
  if (config.tls === undefined) return undefined;
465
683
  const tls = config.tls;
466
- const paths = tls.mode === "self-signed"
467
- ? (() => {
468
- const identity = ensureSelfSignedTlsFiles(
469
- identityDir,
470
- tls.mode === "self-signed" && tls.opensslCommand !== undefined ? { opensslCommand: tls.opensslCommand } : {},
471
- );
472
- return {
473
- certPath: identity.certPath,
474
- keyPath: identity.keyPath,
475
- source: identity.created ? ("generated" as const) : ("persisted" as const),
476
- };
477
- })()
478
- : { certPath: tls.certPath, keyPath: tls.keyPath, source: "operator" as const };
684
+ let certPath: string | undefined;
479
685
  try {
480
- const cert = readFileSync(paths.certPath, "utf8");
686
+ let keyPath: string;
687
+ let source: "generated" | "re-signed" | "persisted" | "operator";
688
+ if (tls.mode === "self-signed") {
689
+ const identity = ensureSelfSignedTlsFiles(
690
+ identityDir,
691
+ {
692
+ ...(tls.opensslCommand !== undefined ? { opensslCommand: tls.opensslCommand } : {}),
693
+ ...(tls.sanEntries !== undefined ? { extraSanEntries: tls.sanEntries } : {}),
694
+ },
695
+ );
696
+ certPath = identity.certPath;
697
+ keyPath = identity.keyPath;
698
+ source = identity.source;
699
+ } else {
700
+ certPath = tls.certPath;
701
+ keyPath = tls.keyPath;
702
+ source = "operator";
703
+ }
704
+ const cert = readFileSync(certPath, "utf8");
481
705
  return {
482
706
  cert,
483
- key: readFileSync(paths.keyPath, "utf8"),
707
+ key: readFileSync(keyPath, "utf8"),
484
708
  pin: spkiPinOf(cert),
485
709
  san: certSanOf(cert),
486
- source: paths.source,
710
+ source,
487
711
  };
488
712
  } catch (error) {
489
713
  const reason = error instanceof Error ? error.message : String(error);
490
- throw new ServeSetupError(`TLS material is unusable (${paths.certPath}): ${reason}`);
714
+ throw new ServeSetupError(`TLS material is unusable (${certPath ?? join(identityDir, "cert.pem")}): ${reason}`);
491
715
  }
492
716
  }
493
717
 
@@ -650,12 +874,14 @@ function startDoorServer(config: ServeConfig, host: string, tlsMaterial: ReturnT
650
874
  return doorNotFound(undefined, config.registryPath);
651
875
  };
652
876
 
653
- const server = Bun.serve({
654
- hostname: host,
655
- port: config.port ?? 8570,
656
- ...(tlsMaterial !== undefined ? { tls: { cert: tlsMaterial.cert, key: tlsMaterial.key } } : {}),
657
- fetch: handler,
877
+ const listener = startServeListener(handler, {
878
+ host,
879
+ ...(config.port !== undefined ? { port: config.port } : {}),
880
+ ...(tlsMaterial !== undefined ? { tlsMaterial } : {}),
881
+ ...(config.maxIdleSeconds !== undefined ? { maxIdleSeconds: config.maxIdleSeconds } : {}),
882
+ ...(config.systemdSocket === true ? { systemdSocket: true } : {}),
658
883
  });
884
+ const { server } = listener;
659
885
 
660
886
  const info: ServeInfo = {
661
887
  mode: "door",
@@ -663,15 +889,16 @@ function startDoorServer(config: ServeConfig, host: string, tlsMaterial: ReturnT
663
889
  endpoints: listDoorEndpoints(config.registryPath).map((endpoint) => endpoint.name),
664
890
  write: listWriteEndpoints(config.registryPath),
665
891
  },
666
- url: `${tlsMaterial !== undefined ? "https" : "http"}://${host}:${server.port ?? (config.port ?? 8570)}`,
892
+ url: listener.publicUrl,
667
893
  host,
668
- port: server.port ?? (config.port ?? 8570),
894
+ port: listener.port,
669
895
  authRequired: (config.tokens?.length ?? 0) > 0,
670
896
  ...(tlsMaterial !== undefined
671
897
  ? { tls: { pin: tlsMaterial.pin, san: tlsMaterial.san, source: tlsMaterial.source } }
672
898
  : {}),
899
+ ...(config.maxIdleSeconds !== undefined ? { maxIdleSeconds: config.maxIdleSeconds } : {}),
673
900
  };
674
- return { server, info };
901
+ return { server, info, stopAll: listener.stopAll };
675
902
  }
676
903
 
677
904
  export function startUkpServer(config: ServeConfig): StartedServe {
@@ -781,27 +1008,30 @@ export function startUkpServer(config: ServeConfig): StartedServe {
781
1008
  );
782
1009
  };
783
1010
 
784
- const server = Bun.serve({
785
- hostname: host,
786
- port: config.port ?? 8570,
787
- ...(tlsMaterial !== undefined ? { tls: { cert: tlsMaterial.cert, key: tlsMaterial.key } } : {}),
788
- fetch: handler,
1011
+ const listener = startServeListener(handler, {
1012
+ host,
1013
+ ...(config.port !== undefined ? { port: config.port } : {}),
1014
+ ...(tlsMaterial !== undefined ? { tlsMaterial } : {}),
1015
+ ...(config.maxIdleSeconds !== undefined ? { maxIdleSeconds: config.maxIdleSeconds } : {}),
1016
+ ...(config.systemdSocket === true ? { systemdSocket: true } : {}),
789
1017
  });
1018
+ const { server } = listener;
790
1019
 
791
1020
  const info: ServeInfo = {
792
1021
  mode: "endpoint",
793
1022
  endpoint: binding.name,
794
1023
  folder: serviceFolder,
795
1024
  instanceUid: readInstanceUid(serviceFolder),
796
- url: `${tlsMaterial !== undefined ? "https" : "http"}://${host}:${server.port ?? (config.port ?? 8570)}`,
1025
+ url: listener.publicUrl,
797
1026
  host,
798
- port: server.port ?? (config.port ?? 8570),
1027
+ port: listener.port,
799
1028
  authRequired: (config.tokens?.length ?? 0) > 0,
800
1029
  ...(tlsMaterial !== undefined
801
1030
  ? { tls: { pin: tlsMaterial.pin, san: tlsMaterial.san, source: tlsMaterial.source } }
802
1031
  : {}),
1032
+ ...(config.maxIdleSeconds !== undefined ? { maxIdleSeconds: config.maxIdleSeconds } : {}),
803
1033
  };
804
- return { server, info };
1034
+ return { server, info, stopAll: listener.stopAll };
805
1035
  }
806
1036
 
807
1037
  async function handleSearch(
package/bun.lock DELETED
@@ -1,35 +0,0 @@
1
- {
2
- "lockfileVersion": 1,
3
- "configVersion": 1,
4
- "workspaces": {
5
- "": {
6
- "name": "@catheadowl/ukp",
7
- "dependencies": {
8
- "commander": "15.0.0",
9
- "smol-toml": "1.7.1",
10
- "zod": "4.4.3",
11
- },
12
- "devDependencies": {
13
- "@types/bun": "1.3.5",
14
- "typescript": "5.9.3",
15
- },
16
- },
17
- },
18
- "packages": {
19
- "@types/bun": ["@types/bun@1.3.5", "https://registry.npmmirror.com/@types/bun/-/bun-1.3.5.tgz", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
20
-
21
- "@types/node": ["@types/node@26.1.1", "https://registry.npmmirror.com/@types/node/-/node-26.1.1.tgz", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="],
22
-
23
- "bun-types": ["bun-types@1.3.5", "https://registry.npmmirror.com/bun-types/-/bun-types-1.3.5.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
24
-
25
- "commander": ["commander@15.0.0", "https://registry.npmmirror.com/commander/-/commander-15.0.0.tgz", {}, "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg=="],
26
-
27
- "smol-toml": ["smol-toml@1.7.1", "https://registry.npmmirror.com/smol-toml/-/smol-toml-1.7.1.tgz", {}, "sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ=="],
28
-
29
- "typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
30
-
31
- "undici-types": ["undici-types@8.3.0", "https://registry.npmmirror.com/undici-types/-/undici-types-8.3.0.tgz", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
32
-
33
- "zod": ["zod@4.4.3", "https://registry.npmmirror.com/zod/-/zod-4.4.3.tgz", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
34
- }
35
- }
package/tsconfig.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "Bundler",
6
- "strict": true,
7
- "noEmit": true,
8
- "allowImportingTsExtensions": true,
9
- "types": ["bun"]
10
- },
11
- "include": ["src", "test"]
12
- }