@mupt-ai/dari-gateway 0.3.4 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,7 +36,9 @@ The gateway is the same runtime that serves Dari's hosted routing service. Choos
36
36
 
37
37
  ## Run
38
38
 
39
- Requires [Bun](https://bun.sh) 1.3 or later. Write a `dari-gateway.yaml` (start from [`dari-gateway.example.yaml`](dari-gateway.example.yaml)), then:
39
+ The easiest way to run it on your own machine is the [Dari CLI](https://github.com/mupt-ai/dari-cli): choose **Fully local** in `dari gateway setup` (or on your first `dari claude`), and it installs this gateway's standalone binary, runs it in the background, and connects your agents.
40
+
41
+ To run it yourself, use [Bun](https://bun.sh) 1.3 or later, or the standalone binary for your platform from the [releases](https://github.com/mupt-ai/dari-gateway/releases). A config file is optional: without `DARI_GATEWAY_CONFIG` or a `dari-gateway.yaml` in the working directory, the gateway serves a default router of current Anthropic and OpenAI models, each once its provider has a subscription or API key. To choose models, write a `dari-gateway.yaml` (start from [`dari-gateway.example.yaml`](dari-gateway.example.yaml)). Then:
40
42
 
41
43
  ```bash
42
44
  export DARI_GATEWAY_KEYS="$(openssl rand -hex 24)" # keys your clients send
@@ -0,0 +1,2 @@
1
+ /** The canonical model ids Dari serves, from the monorepo's router catalog. */
2
+ export declare const CANONICAL_MODEL_IDS: readonly string[];
@@ -0,0 +1,3 @@
1
+ import canonicalIds from "./canonical_model_ids.json" with { type: "json" };
2
+ /** The canonical model ids Dari serves, from the monorepo's router catalog. */
3
+ export const CANONICAL_MODEL_IDS = canonicalIds;
@@ -0,0 +1,33 @@
1
+ [
2
+ "openai/gpt-6-astra",
3
+ "openai/gpt-6.1-terra",
4
+ "openai/gpt-6.1-sol",
5
+ "openai/gpt-6-sol",
6
+ "openai/gpt-6-luna",
7
+ "openai/gpt-5.6-sol",
8
+ "openai/gpt-5.6-terra",
9
+ "openai/gpt-5.6-luna",
10
+ "anthropic/claude-opus-5-5",
11
+ "anthropic/claude-opus-5",
12
+ "anthropic/claude-sonnet-5-5",
13
+ "anthropic/claude-sonnet-5",
14
+ "anthropic/claude-fable-5-2",
15
+ "anthropic/claude-fable-5-1",
16
+ "anthropic/claude-fable-5",
17
+ "anthropic/claude-haiku-5-5",
18
+ "anthropic/claude-haiku-4-5",
19
+ "meta/muse-wonder-1.0",
20
+ "meta/muse-spark-1.3",
21
+ "meta/muse-ember",
22
+ "meta/muse-spark-1.2",
23
+ "moonshotai/Kimi-K3",
24
+ "zai-org/GLM-5.3",
25
+ "zai-org/GLM-5.3-Flash",
26
+ "deepseek-ai/DeepSeek-V4-Pro-0813",
27
+ "zai-org/GLM-5.2",
28
+ "deepseek-ai/DeepSeek-V4-Flash-0731",
29
+ "deepseek-ai/DeepSeek-V4.1-Flash-1017",
30
+ "deepseek-ai/DeepSeek-V4.1-Flash",
31
+ "xai/grok-4.6",
32
+ "xai/grok-4.7"
33
+ ]
@@ -26,6 +26,10 @@ const MODEL_COMPAT_OVERRIDES = {
26
26
  forceAdaptiveThinking: true,
27
27
  supportsTemperature: false,
28
28
  },
29
+ "anthropic/claude-fable-5-2": {
30
+ forceAdaptiveThinking: true,
31
+ supportsTemperature: false,
32
+ },
29
33
  "anthropic/claude-opus-5-5": {
30
34
  forceAdaptiveThinking: true,
31
35
  supportsTemperature: false,
@@ -65,6 +69,14 @@ const MODEL_COMPAT_OVERRIDES = {
65
69
  supportsExplicitPromptCacheMode: true,
66
70
  supportsReasoningConfigurationUpdate: true,
67
71
  },
72
+ "openai/gpt-6.1-terra": {
73
+ supportsStrictMode: true,
74
+ supportsOpenAIGrammarTools: true,
75
+ supportsAdditionalTools: true,
76
+ supportsToolSearch: true,
77
+ supportsExplicitPromptCacheMode: true,
78
+ supportsReasoningConfigurationUpdate: true,
79
+ },
68
80
  "openai/gpt-6-sol": {
69
81
  supportsStrictMode: true,
70
82
  supportsOpenAIGrammarTools: true,
@@ -138,7 +150,9 @@ const NATIVE_REASONING_COMPAT_KEYS = new Set([
138
150
  const MODEL_INPUT_OVERRIDES = {
139
151
  "anthropic/claude-haiku-5-5": ["text", "image"],
140
152
  "anthropic/claude-sonnet-5-5": ["text", "image"],
153
+ "meta/muse-ember": ["text", "image"],
141
154
  "openai/gpt-6.1-sol": ["text", "image"],
155
+ "openai/gpt-6.1-terra": ["text", "image"],
142
156
  "openai/gpt-6-sol": ["text", "image"],
143
157
  "openai/gpt-6-luna": ["text", "image"],
144
158
  "openai/gpt-5.6-luna": ["text", "image"],
@@ -41,5 +41,11 @@ export type RouterModelConfig = {
41
41
  id: string;
42
42
  thinking?: ReasoningEffort[];
43
43
  };
44
- export declare function loadGatewayConfig(path: string): Promise<GatewayConfig>;
44
+ export declare const DEFAULT_GATEWAY_CONFIG: GatewayConfig;
45
+ /**
46
+ * Load the config at `path`, which must exist. Without a path, load
47
+ * `dari-gateway.yaml` from the working directory when present, and otherwise
48
+ * serve DEFAULT_GATEWAY_CONFIG.
49
+ */
50
+ export declare function loadGatewayConfig(path: string | undefined): Promise<GatewayConfig>;
45
51
  export declare function parseGatewayConfig(raw: unknown): GatewayConfig;
@@ -2,11 +2,40 @@ import { isReasoningEffort, isRecord, } from "@mupt-ai/dari-router/policy-engine
2
2
  const ROUTER_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
3
3
  const PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9.-]*$/;
4
4
  const MODEL_ID_PATTERN = /^[a-z0-9][a-z0-9.-]*\/\S+$/;
5
+ // Served when no config file is set or found. A model is served once its
6
+ // provider has a credential (a connected subscription or the provider's API
7
+ // key); dari/routing without Auto Routing serves the first available one.
8
+ export const DEFAULT_GATEWAY_CONFIG = parseGatewayConfig({
9
+ routers: {
10
+ default: {
11
+ models: [
12
+ "anthropic/claude-sonnet-5-5",
13
+ "anthropic/claude-opus-5-5",
14
+ "anthropic/claude-fable-5-2",
15
+ "anthropic/claude-haiku-5-5",
16
+ "openai/gpt-6.1-sol",
17
+ "openai/gpt-6.1-terra",
18
+ ],
19
+ },
20
+ },
21
+ });
22
+ const DEFAULT_CONFIG_PATH = "dari-gateway.yaml";
23
+ /**
24
+ * Load the config at `path`, which must exist. Without a path, load
25
+ * `dari-gateway.yaml` from the working directory when present, and otherwise
26
+ * serve DEFAULT_GATEWAY_CONFIG.
27
+ */
5
28
  export async function loadGatewayConfig(path) {
29
+ if (path === undefined) {
30
+ if (!(await Bun.file(DEFAULT_CONFIG_PATH).exists()))
31
+ return DEFAULT_GATEWAY_CONFIG;
32
+ path = DEFAULT_CONFIG_PATH;
33
+ }
6
34
  const file = Bun.file(path);
7
35
  if (!(await file.exists())) {
8
36
  throw new Error(`Gateway config not found at ${path}. Write one from dari-gateway.example.yaml, `
9
- + "or set DARI_GATEWAY_CONFIG (in the container image, mount it at /config/dari-gateway.yaml).");
37
+ + "or unset DARI_GATEWAY_CONFIG to serve the default router "
38
+ + "(in the container image, mount it at /config/dari-gateway.yaml).");
10
39
  }
11
40
  const text = await file.text();
12
41
  const raw = path.endsWith(".json") ? JSON.parse(text) : Bun.YAML.parse(text);
@@ -3,7 +3,8 @@
3
3
  * Start a self-hosted Dari Gateway from environment variables:
4
4
  *
5
5
  * - `DARI_GATEWAY_KEYS` (required): comma-separated bearer keys clients use.
6
- * - `DARI_GATEWAY_CONFIG`: router config file (default `dari-gateway.yaml`).
6
+ * - `DARI_GATEWAY_CONFIG`: router config file. Unset, `dari-gateway.yaml`
7
+ * serves when present, and otherwise the default router.
7
8
  * - `DARI_GATEWAY_DB`: SQLite database for subscriptions and prefix-cache
8
9
  * state (default `dari-gateway.db`). It holds subscription tokens.
9
10
  * - `DARI_AUTO_ROUTING_API_KEY`: choose among a router's models with Dari
@@ -15,3 +16,5 @@ export declare function startSelfHostedGateway(env?: Record<string, string>): Pr
15
16
  server: import("../http/http2_server.js").Http2RoutingServer | Bun.Server<undefined>;
16
17
  close: () => void;
17
18
  }>;
19
+ /** Run the gateway as a process: the `dari-gateway` bin and compiled binary. */
20
+ export declare function runSelfHostedGateway(): Promise<void>;
@@ -11,7 +11,8 @@ import { createPrioritySelectorBackend } from "../strategies/priority_selector.j
11
11
  import { createSelfHostedPlatform } from "./platform.js";
12
12
  import { providerForModel } from "@mupt-ai/dari-router/policy-engine";
13
13
  import { checkProviderKey } from "../providers/key_check.js";
14
- import { loadGatewayConfig } from "./config.js";
14
+ import { CANONICAL_MODEL_IDS } from "../models/canonical.js";
15
+ import { DEFAULT_GATEWAY_CONFIG, loadGatewayConfig } from "./config.js";
15
16
  import { configuredProviderApiKey, gatewayKeyIndex, providersWithoutCredentials } from "./resolve.js";
16
17
  import { PrefixStore } from "./prefix_store.js";
17
18
  import { SessionPinStore } from "./session_pin_store.js";
@@ -21,7 +22,8 @@ const SUBSCRIPTION_ROUTES = "/v1/organizations/current/credentials";
21
22
  * Start a self-hosted Dari Gateway from environment variables:
22
23
  *
23
24
  * - `DARI_GATEWAY_KEYS` (required): comma-separated bearer keys clients use.
24
- * - `DARI_GATEWAY_CONFIG`: router config file (default `dari-gateway.yaml`).
25
+ * - `DARI_GATEWAY_CONFIG`: router config file. Unset, `dari-gateway.yaml`
26
+ * serves when present, and otherwise the default router.
25
27
  * - `DARI_GATEWAY_DB`: SQLite database for subscriptions and prefix-cache
26
28
  * state (default `dari-gateway.db`). It holds subscription tokens.
27
29
  * - `DARI_AUTO_ROUTING_API_KEY`: choose among a router's models with Dari
@@ -37,7 +39,7 @@ export async function startSelfHostedGateway(env = process.env) {
37
39
  if (gatewayKeys.length === 0) {
38
40
  throw new Error("Set DARI_GATEWAY_KEYS to the comma-separated keys clients authenticate with.");
39
41
  }
40
- const config = await loadGatewayConfig(env.DARI_GATEWAY_CONFIG ?? "dari-gateway.yaml");
42
+ const config = await loadGatewayConfig(env.DARI_GATEWAY_CONFIG?.trim() || undefined);
41
43
  const databasePath = env.DARI_GATEWAY_DB ?? "dari-gateway.db";
42
44
  const created = databasePath !== ":memory:" && !existsSync(databasePath);
43
45
  const db = new Database(databasePath, { create: true });
@@ -134,7 +136,30 @@ async function checkConfiguredProviderKeys(config, providerApiKey) {
134
136
  console.warn("dari-gateway could not check these provider API keys", JSON.stringify({ providers: unchecked }));
135
137
  }
136
138
  }
137
- if (import.meta.main) {
139
+ /** Run the gateway as a process: the `dari-gateway` bin and compiled binary. */
140
+ export async function runSelfHostedGateway() {
141
+ // `--print-config` prints the router config the gateway would serve, as
142
+ // JSON, and exits; `--print-default-config` prints the built-in default.
143
+ // The Dari CLI edits a local gateway's models from them.
144
+ if (process.argv.includes("--print-canonical-models")) {
145
+ console.log(JSON.stringify(CANONICAL_MODEL_IDS));
146
+ return;
147
+ }
148
+ if (process.argv.includes("--print-default-config")) {
149
+ console.log(JSON.stringify(DEFAULT_GATEWAY_CONFIG));
150
+ return;
151
+ }
152
+ if (process.argv.includes("--print-config")) {
153
+ try {
154
+ const config = await loadGatewayConfig(process.env.DARI_GATEWAY_CONFIG?.trim() || undefined);
155
+ console.log(JSON.stringify(config));
156
+ return;
157
+ }
158
+ catch (error) {
159
+ console.error(`dari-gateway: ${error instanceof Error ? error.message : String(error)}`);
160
+ process.exit(1);
161
+ }
162
+ }
138
163
  // Startup errors are configuration mistakes: print the message, not a stack.
139
164
  const started = await startSelfHostedGateway().catch((error) => {
140
165
  console.error(`dari-gateway: ${error instanceof Error ? error.message : String(error)}`);
@@ -142,3 +167,5 @@ if (import.meta.main) {
142
167
  });
143
168
  installShutdownHandlers(started.server, async () => started.close());
144
169
  }
170
+ if (import.meta.main)
171
+ await runSelfHostedGateway();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mupt-ai/dari-gateway",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Self-hostable LLM gateway for coding agents and APIs: one endpoint for Anthropic Messages, OpenAI Chat Completions, and OpenAI Responses, with subscription pooling, failover, and prompt-cache-aware routing.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -42,6 +42,7 @@
42
42
  ],
43
43
  "scripts": {
44
44
  "start": "bun run src/self_host/main.ts",
45
+ "compile": "bun scripts/compile.ts",
45
46
  "build": "rm -rf dist && tsc -p tsconfig.build.json",
46
47
  "typecheck": "tsc --noEmit",
47
48
  "test": "bun test",
@@ -0,0 +1,4 @@
1
+ import canonicalIds from "./canonical_model_ids.json" with { type: "json" };
2
+
3
+ /** The canonical model ids Dari serves, from the monorepo's router catalog. */
4
+ export const CANONICAL_MODEL_IDS: readonly string[] = canonicalIds;
@@ -0,0 +1,33 @@
1
+ [
2
+ "openai/gpt-6-astra",
3
+ "openai/gpt-6.1-terra",
4
+ "openai/gpt-6.1-sol",
5
+ "openai/gpt-6-sol",
6
+ "openai/gpt-6-luna",
7
+ "openai/gpt-5.6-sol",
8
+ "openai/gpt-5.6-terra",
9
+ "openai/gpt-5.6-luna",
10
+ "anthropic/claude-opus-5-5",
11
+ "anthropic/claude-opus-5",
12
+ "anthropic/claude-sonnet-5-5",
13
+ "anthropic/claude-sonnet-5",
14
+ "anthropic/claude-fable-5-2",
15
+ "anthropic/claude-fable-5-1",
16
+ "anthropic/claude-fable-5",
17
+ "anthropic/claude-haiku-5-5",
18
+ "anthropic/claude-haiku-4-5",
19
+ "meta/muse-wonder-1.0",
20
+ "meta/muse-spark-1.3",
21
+ "meta/muse-ember",
22
+ "meta/muse-spark-1.2",
23
+ "moonshotai/Kimi-K3",
24
+ "zai-org/GLM-5.3",
25
+ "zai-org/GLM-5.3-Flash",
26
+ "deepseek-ai/DeepSeek-V4-Pro-0813",
27
+ "zai-org/GLM-5.2",
28
+ "deepseek-ai/DeepSeek-V4-Flash-0731",
29
+ "deepseek-ai/DeepSeek-V4.1-Flash-1017",
30
+ "deepseek-ai/DeepSeek-V4.1-Flash",
31
+ "xai/grok-4.6",
32
+ "xai/grok-4.7"
33
+ ]
@@ -29,6 +29,10 @@ const MODEL_COMPAT_OVERRIDES: Record<string, Record<string, unknown>> = {
29
29
  forceAdaptiveThinking: true,
30
30
  supportsTemperature: false,
31
31
  },
32
+ "anthropic/claude-fable-5-2": {
33
+ forceAdaptiveThinking: true,
34
+ supportsTemperature: false,
35
+ },
32
36
  "anthropic/claude-opus-5-5": {
33
37
  forceAdaptiveThinking: true,
34
38
  supportsTemperature: false,
@@ -68,6 +72,14 @@ const MODEL_COMPAT_OVERRIDES: Record<string, Record<string, unknown>> = {
68
72
  supportsExplicitPromptCacheMode: true,
69
73
  supportsReasoningConfigurationUpdate: true,
70
74
  },
75
+ "openai/gpt-6.1-terra": {
76
+ supportsStrictMode: true,
77
+ supportsOpenAIGrammarTools: true,
78
+ supportsAdditionalTools: true,
79
+ supportsToolSearch: true,
80
+ supportsExplicitPromptCacheMode: true,
81
+ supportsReasoningConfigurationUpdate: true,
82
+ },
71
83
  "openai/gpt-6-sol": {
72
84
  supportsStrictMode: true,
73
85
  supportsOpenAIGrammarTools: true,
@@ -144,7 +156,9 @@ const NATIVE_REASONING_COMPAT_KEYS: ReadonlySet<string> = new Set([
144
156
  const MODEL_INPUT_OVERRIDES: Record<string, string[]> = {
145
157
  "anthropic/claude-haiku-5-5": ["text", "image"],
146
158
  "anthropic/claude-sonnet-5-5": ["text", "image"],
159
+ "meta/muse-ember": ["text", "image"],
147
160
  "openai/gpt-6.1-sol": ["text", "image"],
161
+ "openai/gpt-6.1-terra": ["text", "image"],
148
162
  "openai/gpt-6-sol": ["text", "image"],
149
163
  "openai/gpt-6-luna": ["text", "image"],
150
164
  "openai/gpt-5.6-luna": ["text", "image"],
@@ -62,12 +62,42 @@ const ROUTER_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
62
62
  const PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9.-]*$/;
63
63
  const MODEL_ID_PATTERN = /^[a-z0-9][a-z0-9.-]*\/\S+$/;
64
64
 
65
- export async function loadGatewayConfig(path: string): Promise<GatewayConfig> {
65
+ // Served when no config file is set or found. A model is served once its
66
+ // provider has a credential (a connected subscription or the provider's API
67
+ // key); dari/routing without Auto Routing serves the first available one.
68
+ export const DEFAULT_GATEWAY_CONFIG: GatewayConfig = parseGatewayConfig({
69
+ routers: {
70
+ default: {
71
+ models: [
72
+ "anthropic/claude-sonnet-5-5",
73
+ "anthropic/claude-opus-5-5",
74
+ "anthropic/claude-fable-5-2",
75
+ "anthropic/claude-haiku-5-5",
76
+ "openai/gpt-6.1-sol",
77
+ "openai/gpt-6.1-terra",
78
+ ],
79
+ },
80
+ },
81
+ });
82
+
83
+ const DEFAULT_CONFIG_PATH = "dari-gateway.yaml";
84
+
85
+ /**
86
+ * Load the config at `path`, which must exist. Without a path, load
87
+ * `dari-gateway.yaml` from the working directory when present, and otherwise
88
+ * serve DEFAULT_GATEWAY_CONFIG.
89
+ */
90
+ export async function loadGatewayConfig(path: string | undefined): Promise<GatewayConfig> {
91
+ if (path === undefined) {
92
+ if (!(await Bun.file(DEFAULT_CONFIG_PATH).exists())) return DEFAULT_GATEWAY_CONFIG;
93
+ path = DEFAULT_CONFIG_PATH;
94
+ }
66
95
  const file = Bun.file(path);
67
96
  if (!(await file.exists())) {
68
97
  throw new Error(
69
98
  `Gateway config not found at ${path}. Write one from dari-gateway.example.yaml, `
70
- + "or set DARI_GATEWAY_CONFIG (in the container image, mount it at /config/dari-gateway.yaml).",
99
+ + "or unset DARI_GATEWAY_CONFIG to serve the default router "
100
+ + "(in the container image, mount it at /config/dari-gateway.yaml).",
71
101
  );
72
102
  }
73
103
  const text = await file.text();
@@ -13,7 +13,8 @@ import type { SelectorBackend } from "../strategies/routing.js";
13
13
  import { createSelfHostedPlatform } from "./platform.js";
14
14
  import { providerForModel } from "@mupt-ai/dari-router/policy-engine";
15
15
  import { checkProviderKey } from "../providers/key_check.js";
16
- import { loadGatewayConfig, type GatewayConfig } from "./config.js";
16
+ import { CANONICAL_MODEL_IDS } from "../models/canonical.js";
17
+ import { DEFAULT_GATEWAY_CONFIG, loadGatewayConfig, type GatewayConfig } from "./config.js";
17
18
  import { configuredProviderApiKey, gatewayKeyIndex, providersWithoutCredentials } from "./resolve.js";
18
19
  import { PrefixStore } from "./prefix_store.js";
19
20
  import { SessionPinStore } from "./session_pin_store.js";
@@ -25,7 +26,8 @@ const SUBSCRIPTION_ROUTES = "/v1/organizations/current/credentials";
25
26
  * Start a self-hosted Dari Gateway from environment variables:
26
27
  *
27
28
  * - `DARI_GATEWAY_KEYS` (required): comma-separated bearer keys clients use.
28
- * - `DARI_GATEWAY_CONFIG`: router config file (default `dari-gateway.yaml`).
29
+ * - `DARI_GATEWAY_CONFIG`: router config file. Unset, `dari-gateway.yaml`
30
+ * serves when present, and otherwise the default router.
29
31
  * - `DARI_GATEWAY_DB`: SQLite database for subscriptions and prefix-cache
30
32
  * state (default `dari-gateway.db`). It holds subscription tokens.
31
33
  * - `DARI_AUTO_ROUTING_API_KEY`: choose among a router's models with Dari
@@ -41,7 +43,7 @@ export async function startSelfHostedGateway(env: Record<string, string> = proce
41
43
  if (gatewayKeys.length === 0) {
42
44
  throw new Error("Set DARI_GATEWAY_KEYS to the comma-separated keys clients authenticate with.");
43
45
  }
44
- const config = await loadGatewayConfig(env.DARI_GATEWAY_CONFIG ?? "dari-gateway.yaml");
46
+ const config = await loadGatewayConfig(env.DARI_GATEWAY_CONFIG?.trim() || undefined);
45
47
  const databasePath = env.DARI_GATEWAY_DB ?? "dari-gateway.db";
46
48
  const created = databasePath !== ":memory:" && !existsSync(databasePath);
47
49
  const db = new Database(databasePath, { create: true });
@@ -153,7 +155,29 @@ async function checkConfiguredProviderKeys(
153
155
  }
154
156
  }
155
157
 
156
- if (import.meta.main) {
158
+ /** Run the gateway as a process: the `dari-gateway` bin and compiled binary. */
159
+ export async function runSelfHostedGateway(): Promise<void> {
160
+ // `--print-config` prints the router config the gateway would serve, as
161
+ // JSON, and exits; `--print-default-config` prints the built-in default.
162
+ // The Dari CLI edits a local gateway's models from them.
163
+ if (process.argv.includes("--print-canonical-models")) {
164
+ console.log(JSON.stringify(CANONICAL_MODEL_IDS));
165
+ return;
166
+ }
167
+ if (process.argv.includes("--print-default-config")) {
168
+ console.log(JSON.stringify(DEFAULT_GATEWAY_CONFIG));
169
+ return;
170
+ }
171
+ if (process.argv.includes("--print-config")) {
172
+ try {
173
+ const config = await loadGatewayConfig(process.env.DARI_GATEWAY_CONFIG?.trim() || undefined);
174
+ console.log(JSON.stringify(config));
175
+ return;
176
+ } catch (error) {
177
+ console.error(`dari-gateway: ${error instanceof Error ? error.message : String(error)}`);
178
+ process.exit(1);
179
+ }
180
+ }
157
181
  // Startup errors are configuration mistakes: print the message, not a stack.
158
182
  const started = await startSelfHostedGateway().catch((error: unknown) => {
159
183
  console.error(`dari-gateway: ${error instanceof Error ? error.message : String(error)}`);
@@ -161,3 +185,5 @@ if (import.meta.main) {
161
185
  });
162
186
  installShutdownHandlers(started.server, async () => started.close());
163
187
  }
188
+
189
+ if (import.meta.main) await runSelfHostedGateway();