@byok-sdk/keys 0.1.0

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.
@@ -0,0 +1,103 @@
1
+ import { AnthropicMessagesClient } from './anthropic-client';
2
+ import type { ProviderFetch } from './http';
3
+ import { OpenAiCompatibleChatClient } from './openai-client';
4
+ import type { ProviderProfileStore } from './profile-store';
5
+ import { type ModelProviderAdapter, type ModelProviderId, type ProviderAuthMode } from './provider-profile';
6
+ import { type ModelProviderSecretName, type SecretStore } from './secret-store';
7
+ /** A transport client for whichever dialect the resolved profile declares. */
8
+ export type ModelProviderClient = AnthropicMessagesClient | OpenAiCompatibleChatClient;
9
+ /**
10
+ * What a caller supplies to {@link ProviderRegistry.configure}: the profile
11
+ * minus the fields the registry owns (`kind`, both timestamps) and minus the
12
+ * secret, which travels as a separate argument so it cannot be mistaken for
13
+ * persisted data.
14
+ */
15
+ export interface ProviderConfiguration {
16
+ adapter: ModelProviderAdapter;
17
+ auth_mode: ProviderAuthMode;
18
+ base_url: string;
19
+ display_name: string;
20
+ /** Defaults to `true`: configuring a provider makes it the default. */
21
+ enabled?: boolean;
22
+ model: string;
23
+ provider_id: ModelProviderId;
24
+ }
25
+ /**
26
+ * The registry's outward projection of a profile.
27
+ *
28
+ * It reports **whether** a secret exists (`secret_configured`) and never the
29
+ * secret itself — the property `registry.golden.test.ts` asserts, mirroring
30
+ * `docs/researches/HANDOFF-byok-keys.md` §4.3's "status JSON contains no
31
+ * plaintext key".
32
+ */
33
+ export interface ProviderStatus {
34
+ adapter: ModelProviderAdapter;
35
+ auth_mode: ProviderAuthMode;
36
+ base_url: string;
37
+ created_at: string;
38
+ display_name: string;
39
+ enabled: boolean;
40
+ model: string;
41
+ provider_id: ModelProviderId;
42
+ /** Whether the credential store currently holds this provider's key. */
43
+ secret_configured: boolean;
44
+ updated_at: string;
45
+ }
46
+ export interface ProviderRegistryOptions {
47
+ fetchImpl?: ProviderFetch;
48
+ /** Injected clock, so tests get deterministic timestamps. */
49
+ now?: () => Date;
50
+ profileStore: ProviderProfileStore;
51
+ secretStore: SecretStore<ModelProviderSecretName>;
52
+ }
53
+ /**
54
+ * The configure/resolve boundary, ported from `providers.ts:1180-1229`
55
+ * (`configure`) and `providers.ts:1331-1354` (`resolveDefaultModelProvider`).
56
+ *
57
+ * Both halves of a provider's configuration are written here and nowhere else:
58
+ * the non-secret profile goes to the injected {@link ProviderProfileStore}, the
59
+ * API key goes to the injected {@link SecretStore}. Splitting them is the whole
60
+ * point of the package, so the registry is the only place that knows both.
61
+ *
62
+ * Two departures from the source, both required by
63
+ * `docs/researches/HANDOFF-byok-keys.md` §4.5:
64
+ *
65
+ * - `resolveDefaultModelProvider` returns a transport client or `undefined`,
66
+ * and throws on a broken configuration. The source returned an
67
+ * `UnavailableNarrativeProvider` null-object carrying an error code, which is
68
+ * a narrative-domain symbol that stays in aip-main-open — and a degradation
69
+ * fallback this package's fail-closed rule does not permit. A caller that
70
+ * wants aip's behaviour catches `ByokKeysError` and reads `.code`, which is
71
+ * the same information the null-object carried.
72
+ * - The source's `#migrateLegacyModelSecret` is not ported (legacy secret
73
+ * migration is out of scope per the plan).
74
+ */
75
+ export declare class ProviderRegistry {
76
+ #private;
77
+ constructor(options: ProviderRegistryOptions);
78
+ close(): void;
79
+ /**
80
+ * Persist a provider's profile and, when supplied, its secret
81
+ * (`providers.ts:1180-1229`).
82
+ *
83
+ * Order matters and is the source's: write the secret first, then require
84
+ * that an authenticating profile actually has one, and only then save the
85
+ * profile. A profile is therefore never persisted in a state that claims
86
+ * authentication it cannot perform.
87
+ */
88
+ configure(configuration: ProviderConfiguration, secret?: string): Promise<ProviderStatus>;
89
+ /** Remove a provider's profile and its secret together. */
90
+ delete(providerId: ModelProviderId): Promise<boolean>;
91
+ get(providerId: ModelProviderId): Promise<ProviderStatus | undefined>;
92
+ list(): Promise<ProviderStatus[]>;
93
+ /**
94
+ * Build a client for the one enabled provider (`providers.ts:1331-1354`).
95
+ *
96
+ * `undefined` means "nothing is configured", which is a legitimate state a
97
+ * caller must handle. A configured-but-broken provider throws instead — a
98
+ * missing secret or an unusable profile is a fault, not an absence.
99
+ */
100
+ resolveDefaultModelProvider(): Promise<ModelProviderClient | undefined>;
101
+ /** Switch which configured provider is the default. */
102
+ setDefaultModelProvider(providerId: ModelProviderId): Promise<ProviderStatus>;
103
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Legal secret-entry names, 3 to 96 characters.
3
+ *
4
+ * The source (`aip-main-open@c6a5385`, `apps/local-agent/src/index.ts:258-268`)
5
+ * closed this set at compile time with the `KeychainSecretName` union, so it
6
+ * needed no runtime check. `SecretStore<TName extends string>` is open by
7
+ * design — aip's closed union stays in aip — so the closure moves to runtime.
8
+ *
9
+ * The exclusion that matters is `.`: every backend composes its storage key as
10
+ * `` `${servicePrefix}.${name}` `` and a scoped store appends
11
+ * `.scope.<namespace>` to that prefix. A name containing a dot could therefore
12
+ * spell out another scope's service string and read that scope's secret. The
13
+ * validator throws rather than sanitizing — a caller that mistyped a name must
14
+ * see the error, not silently address a different entry.
15
+ */
16
+ export declare const SECRET_NAME_PATTERN: RegExp;
17
+ /**
18
+ * Legal scope namespaces, 8 to 96 characters. Ported verbatim from the source's
19
+ * `normalizeSecretNamespace` (`index.ts:748-757`); the 8-character floor keeps
20
+ * a namespace from colliding with a short accidental value.
21
+ */
22
+ export declare const SECRET_NAMESPACE_PATTERN: RegExp;
23
+ /** Fail closed unless `name` matches {@link SECRET_NAME_PATTERN}. */
24
+ export declare function assertSecretName<TName extends string>(name: TName): TName;
25
+ /**
26
+ * Trim and validate a scope namespace, or throw. Trimming is the source's
27
+ * behavior and is safe because the pattern rejects every interior whitespace
28
+ * character anyway.
29
+ */
30
+ export declare function assertSecretNamespace(value: string): string;
@@ -0,0 +1,59 @@
1
+ import { type SecretStore } from './secret-store';
2
+ /**
3
+ * Tenant identity a secret store is partitioned by. Ported from
4
+ * `aip-main-open@c6a5385` `apps/local-agent/src/local-data-scope.ts:19-22`
5
+ * (`LocalAccountDataScope`).
6
+ */
7
+ export interface SecretScope {
8
+ account_id: string;
9
+ workspace_id: string;
10
+ }
11
+ /**
12
+ * Envelope marker, injectable so K4 can pass the source's
13
+ * `aiphabee-scoped-secrets-v1:` value (`local-data-scope.ts:30`).
14
+ */
15
+ export declare const DEFAULT_SECRET_ENVELOPE_PREFIX = "byok-scoped-secrets-v1:";
16
+ /**
17
+ * Derive the opaque namespace a scope's secrets live under
18
+ * (`local-data-scope.ts:32-37`). Hashing means the namespace never leaks a
19
+ * tenant identifier into a keychain service name a user can browse.
20
+ */
21
+ export declare function secretScopeId(scope: SecretScope): string;
22
+ /**
23
+ * Partition `store` by `scope` (`local-data-scope.ts:100-106`).
24
+ *
25
+ * The source read `store.scope?.(id) ?? new EnvelopeScopedSecretStore(...)`:
26
+ * when a store happened not to implement `scope`, it silently switched to a
27
+ * different storage layout. `SecretStore.scope` is required in this package, so
28
+ * that implicit substitution is gone — a caller who wants envelope semantics
29
+ * constructs {@link EnvelopeScopedSecretStore} on purpose.
30
+ */
31
+ export declare function scopeSecretStore<TName extends string>(store: SecretStore<TName>, scope: SecretScope): SecretStore<TName>;
32
+ /**
33
+ * Partition a store by keeping every scope's secret in one underlying entry,
34
+ * as a JSON map of scope id to secret.
35
+ *
36
+ * This is the layout to reach for when the backend cannot cheaply namespace its
37
+ * own key space. It costs a read-modify-write on every mutation and offers no
38
+ * cross-process locking, so prefer a backend's native `scope()` when it has
39
+ * one; this decorator exists for the backends that do not.
40
+ *
41
+ * Reads are fail-closed in a way the source's were not
42
+ * (`local-data-scope.ts:163-194`): the source mapped any unparseable stored
43
+ * value to `undefined`, which made a foreign value look like an absent secret
44
+ * and let the next `set()` overwrite it. Here a stored value that is not a
45
+ * well-formed envelope raises `SECRET_ENVELOPE_INVALID`.
46
+ */
47
+ export declare class EnvelopeScopedSecretStore<TName extends string = string> implements SecretStore<TName> {
48
+ #private;
49
+ readonly providerLabel: string;
50
+ constructor(store: SecretStore<TName>, scopeId: string, options?: {
51
+ envelopePrefix?: string;
52
+ });
53
+ available(): Promise<boolean>;
54
+ delete(name: TName): Promise<boolean>;
55
+ get(name: TName): Promise<string | undefined>;
56
+ has(name: TName): Promise<boolean>;
57
+ scope(namespace: string): SecretStore<TName>;
58
+ set(name: TName, secret: string): Promise<void>;
59
+ }
@@ -0,0 +1,103 @@
1
+ import type { ModelProviderId } from './provider-profile';
2
+ /**
3
+ * Storage contract for a single secret entry in an operating-system credential
4
+ * store.
5
+ *
6
+ * Ported from `aip-main-open@c6a5385` `apps/local-agent/src/index.ts:261-269`
7
+ * with two deliberate changes:
8
+ *
9
+ * 1. `TName` is a generic parameter rather than aip's closed
10
+ * `KeychainSecretName` union. aip's union names device keys, refresh tokens,
11
+ * and market-data entries that this package has no business knowing about,
12
+ * so it stays in aip and consumers pin their own union. The compile-time
13
+ * closure it provided is replaced at runtime by {@link assertSecretName},
14
+ * which every implementation must apply on every name it receives.
15
+ * 2. `scope()` is required, not optional. The source made it optional and its
16
+ * `scopeLocalAgentSecretStore` silently substituted an envelope
17
+ * implementation when a store did not provide one. Scope-envelope prefixing
18
+ * has no installed base, so this package removes the implicit substitution:
19
+ * every store scopes itself, and {@link EnvelopeScopedSecretStore} is a
20
+ * decorator a caller applies on purpose.
21
+ */
22
+ export interface SecretStore<TName extends string = string> {
23
+ /** Human-readable backend name, safe to show a user. Never contains a secret. */
24
+ readonly providerLabel: string;
25
+ /** Whether this backend can be used on the current machine. Must not throw. */
26
+ available(): Promise<boolean>;
27
+ /** Remove `name`; `false` when it was already absent. */
28
+ delete(name: TName): Promise<boolean>;
29
+ /** Read `name`, or `undefined` when it is absent. */
30
+ get(name: TName): Promise<string | undefined>;
31
+ /** Whether `name` currently holds a secret. */
32
+ has(name: TName): Promise<boolean>;
33
+ /** A view of this store isolated under `namespace`. */
34
+ scope(namespace: string): SecretStore<TName>;
35
+ /** Write `secret` at `name`, replacing any existing value. */
36
+ set(name: TName, secret: string): Promise<void>;
37
+ }
38
+ /**
39
+ * Default service prefix for this package's own entries. `servicePrefix` is a
40
+ * constructor option on both OS backends, so K4's aip-main-open swap passes its
41
+ * `com.aiphabee.local-agent` value and keeps existing installs byte-compatible.
42
+ */
43
+ export declare const DEFAULT_SECRET_SERVICE_PREFIX = "com.byok.keys";
44
+ /**
45
+ * Credential-store entry name per model provider, ported verbatim from
46
+ * `providers.ts:1624-1632`. The names carry no vendor branding — the branding
47
+ * lives in the service prefix — so they travel unchanged and K4 needs no
48
+ * migration.
49
+ */
50
+ export declare const MODEL_PROVIDER_SECRET_NAMES: {
51
+ readonly anthropic: 'model-anthropic-api-key';
52
+ readonly custom: 'model-custom-api-key';
53
+ readonly deepseek: 'model-deepseek-api-key';
54
+ readonly openai: 'model-openai-api-key';
55
+ };
56
+ export type ModelProviderSecretName = (typeof MODEL_PROVIDER_SECRET_NAMES)[keyof typeof MODEL_PROVIDER_SECRET_NAMES];
57
+ /** Resolve a provider id to the credential-store entry holding its API key. */
58
+ export declare function modelProviderSecretName(providerId: ModelProviderId): ModelProviderSecretName;
59
+ /**
60
+ * The secret-shape invariant both OS backends share. Their size ceilings differ
61
+ * in both magnitude and unit (macOS counts 16384 characters, Windows counts
62
+ * 2560 UTF-8 bytes) and each reports its own error code, so those checks stay
63
+ * in the backends; only the empty/control-character rule is common.
64
+ */
65
+ export declare function assertSharedSecretValue(secret: string): void;
66
+ /**
67
+ * Decode base64 to UTF-8 text, or return `undefined` — never a repaired
68
+ * approximation.
69
+ *
70
+ * Both decode paths in Node are lenient in ways that matter here: `Buffer.from`
71
+ * silently drops characters outside the base64 alphabet (so `"a!G@k="` would
72
+ * decode to `"hi"`), and `Buffer#toString('utf8')` substitutes U+FFFD for
73
+ * invalid byte sequences. A credential store that "successfully" returns a
74
+ * silently-mangled secret is worse than one that fails, so the alphabet, the
75
+ * canonical padding, and the UTF-8 round trip are all checked explicitly.
76
+ */
77
+ export declare function decodeStrictBase64Utf8(encoded: string): string | undefined;
78
+ /**
79
+ * A {@link SecretStore} held in process memory, for tests and for embedders
80
+ * that supply their own persistence.
81
+ *
82
+ * It keys entries by the same `` `${servicePrefix}.${name}` `` string the OS
83
+ * backends use, and `scope()` extends that prefix exactly as they do, so a test
84
+ * written against this store exercises the same isolation arithmetic as
85
+ * production. It applies the name validator and the shared secret-shape rule;
86
+ * the platform size ceilings deliberately are not simulated, since they differ
87
+ * per backend.
88
+ */
89
+ export declare class InMemorySecretStore<TName extends string = string> implements SecretStore<TName> {
90
+ #private;
91
+ readonly providerLabel = "in-memory";
92
+ constructor(options?: {
93
+ /** Shared backing map; a scoped view keeps the parent's map. */
94
+ entries?: Map<string, string>;
95
+ servicePrefix?: string;
96
+ });
97
+ available(): Promise<boolean>;
98
+ delete(name: TName): Promise<boolean>;
99
+ get(name: TName): Promise<string | undefined>;
100
+ has(name: TName): Promise<boolean>;
101
+ scope(namespace: string): SecretStore<TName>;
102
+ set(name: TName, secret: string): Promise<void>;
103
+ }
@@ -0,0 +1,35 @@
1
+ import { type ProviderProfileStore } from './profile-store';
2
+ import { type ModelProviderId, type ModelProviderProfile } from './provider-profile';
3
+ export interface SqliteProviderProfileStoreOptions {
4
+ /**
5
+ * Database file path. `:memory:` exercises the SQLite code path without a
6
+ * temp file, but defeats the point of this store (restart-safety) exactly as
7
+ * it does for `@byok/server`'s `SqliteTaskStore`.
8
+ */
9
+ path: string;
10
+ }
11
+ /**
12
+ * SQLite-backed {@link ProviderProfileStore}, following `@byok/server`'s
13
+ * `SqliteTaskStore` shape. Holds no secret: the API key lives in the injected
14
+ * `SecretStore`, and `registry.golden.test.ts` asserts the plaintext key never
15
+ * appears in this file's bytes.
16
+ */
17
+ export declare class SqliteProviderProfileStore implements ProviderProfileStore {
18
+ #private;
19
+ constructor(options: SqliteProviderProfileStoreOptions);
20
+ /**
21
+ * Idempotent, as {@link ProviderProfileStore.close} requires: `node:sqlite`
22
+ * throws "database is not open" on a second `close()`, and a store is
23
+ * routinely closed both by the code that finished with it and by a test's
24
+ * teardown.
25
+ */
26
+ close(): void;
27
+ delete(providerId: ModelProviderId): boolean;
28
+ get(providerId: ModelProviderId): ModelProviderProfile | undefined;
29
+ getEnabled(): ModelProviderProfile | undefined;
30
+ list(): ModelProviderProfile[];
31
+ save(profile: ModelProviderProfile): ModelProviderProfile;
32
+ setEnabled(providerId: ModelProviderId): ModelProviderProfile;
33
+ }
34
+ /** Exported for the store's own tests to enumerate the CHECK-constrained ids. */
35
+ export declare const SQLITE_PROFILE_PROVIDER_IDS: readonly ["openai", "deepseek", "anthropic", "custom"];
@@ -0,0 +1,50 @@
1
+ import type { DatabaseSync, DatabaseSyncOptions } from 'node:sqlite';
2
+ interface SqliteModule {
3
+ DatabaseSync: new (path: string, options?: DatabaseSyncOptions) => DatabaseSync;
4
+ }
5
+ /**
6
+ * Load `node:sqlite`, or fail with a `ByokKeysError` that says why.
7
+ *
8
+ * `node:sqlite` shipped in Node.js 22.5.0 and stays marked experimental (an
9
+ * `ExperimentalWarning` on stderr is expected and harmless). Following
10
+ * `@byok/server`'s `sqlite-support.ts`, the SQLite-backed store here depends on
11
+ * nothing else — no `better-sqlite3`, no native module — because zero native
12
+ * dependencies is what keeps this package trivially packageable. The tradeoff
13
+ * is that {@link SqliteProviderProfileStore} does not work below Node 22.5, and
14
+ * this error says so instead of letting a cryptic `Cannot find module` surface
15
+ * from deep inside a query.
16
+ */
17
+ export declare function loadSqliteModule(): SqliteModule;
18
+ /**
19
+ * Whether `node:sqlite` can ACTUALLY be loaded right now.
20
+ *
21
+ * Same predicate as `@byok/server`'s `sqlite-support.ts`, and it exists for the
22
+ * same reason: this package's `engines.node` is `>=20` and CI runs the matrix
23
+ * on 20 and 22, but `node:sqlite` shipped in 22.5 and stayed behind
24
+ * `--experimental-sqlite` for part of the 22.x line. A version-number
25
+ * comparison would therefore be wrong in both directions, so this attempts the
26
+ * real require via {@link loadSqliteModule} and reports whether it succeeded.
27
+ *
28
+ * Callers use it to skip a SQLite-backed path rather than fail it — the
29
+ * package's own SQLite-backed suites gate on it — and anything else should call
30
+ * it before assuming a {@link SqliteProviderProfileStore} can be constructed.
31
+ */
32
+ export declare function isSqliteAvailable(): boolean;
33
+ /**
34
+ * Open a database, creating its parent directory owner-only first. `:memory:`
35
+ * skips every filesystem step, which is how the shared contract suite exercises
36
+ * the SQLite code path without leaving anything on disk.
37
+ */
38
+ export declare function openSqliteDatabase(path: string, options?: DatabaseSyncOptions): DatabaseSync;
39
+ /**
40
+ * Restrict `databasePath` and its WAL/SHM siblings to owner-only read/write.
41
+ *
42
+ * The profile table holds no secret — that is the whole point of splitting the
43
+ * key into the OS credential store — but it does hold every provider endpoint
44
+ * this machine talks to, and the source locked the file down
45
+ * (`providers.ts:158`), so the port keeps that. Call after the schema exists,
46
+ * so the lazily-created WAL/SHM files are already there; a sibling that does
47
+ * not exist is skipped rather than treated as an error. No-op for `:memory:`.
48
+ */
49
+ export declare function secureSqliteFilePermissions(databasePath: string): void;
50
+ export {};
package/dist/url.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Normalize a provider base URL, fail-closed.
3
+ *
4
+ * Ported from `aip-main-open@c6a5385` `providers.ts:1558-1588` plus the two
5
+ * host predicates at `:2216-2242`. Rules, unchanged:
6
+ * - must be an absolute URL;
7
+ * - no embedded credentials, no fragment, no query string;
8
+ * - HTTPS only, except that HTTP is allowed for loopback hosts;
9
+ * - private-network literals are rejected unless they are loopback;
10
+ * - one trailing slash is stripped.
11
+ */
12
+ export declare function normalizeProviderUrl(value: string): string;
13
+ /** Whether an already-parseable provider URL points at a loopback host. */
14
+ export declare function isLoopbackProviderUrl(value: string): boolean;
15
+ export declare function isLoopbackHost(hostname: string): boolean;
16
+ /**
17
+ * Conservative private-network literal check. Any IPv6 literal counts as
18
+ * private (the `:` branch), matching the source: the guard cannot cheaply
19
+ * classify IPv6 ranges, so it refuses all of them and lets hostnames through.
20
+ */
21
+ export declare function isPrivateNetworkLiteral(hostname: string): boolean;
@@ -0,0 +1,31 @@
1
+ import { type CommandRunner } from './command-runner';
2
+ import { type SecretStore } from './secret-store';
3
+ export interface WindowsCredentialManagerSecretStoreOptions {
4
+ account?: string;
5
+ commandRunner?: CommandRunner;
6
+ platform?: NodeJS.Platform;
7
+ servicePrefix?: string;
8
+ }
9
+ /**
10
+ * Windows Credential Manager backend (`index.ts:568-712`).
11
+ *
12
+ * As on macOS there is no plaintext fallback — off win32 every operation throws
13
+ * `CREDENTIAL_MANAGER_UNAVAILABLE`.
14
+ */
15
+ export declare class WindowsCredentialManagerSecretStore<TName extends string = string> implements SecretStore<TName> {
16
+ #private;
17
+ readonly providerLabel = "Windows Credential Manager";
18
+ constructor(options?: WindowsCredentialManagerSecretStoreOptions);
19
+ available(): Promise<boolean>;
20
+ /**
21
+ * Delete, then read back. The source verifies rather than trusting the
22
+ * delete's exit code (`index.ts:651-679`): a credential that survives a
23
+ * "successful" delete is a security failure, so it is reported as one instead
24
+ * of being returned as `true`.
25
+ */
26
+ delete(name: TName): Promise<boolean>;
27
+ get(name: TName): Promise<string | undefined>;
28
+ has(name: TName): Promise<boolean>;
29
+ scope(namespace: string): SecretStore<TName>;
30
+ set(name: TName, secret: string): Promise<void>;
31
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@byok-sdk/keys",
3
+ "version": "0.1.0",
4
+ "description": "BYOK SDK key management: provider profiles, credential-backed auth headers, and direct provider transports",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Ancienttwo/byok-sdk.git",
10
+ "directory": "packages/keys"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/Ancienttwo/byok-sdk/issues"
14
+ },
15
+ "homepage": "https://github.com/Ancienttwo/byok-sdk#readme",
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
19
+ "sideEffects": false,
20
+ "main": "./dist/index.js",
21
+ "module": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "dependencies": {
37
+ "zod": "^4.4.3"
38
+ },
39
+ "scripts": {
40
+ "build": "tsup && tsc -p tsconfig.build.json",
41
+ "dev": "tsup --watch",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest",
44
+ "typecheck": "tsc --noEmit",
45
+ "clean": "rm -rf dist"
46
+ }
47
+ }