dorkfuncli 0.0.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.
Files changed (110) hide show
  1. package/.env.example +6 -0
  2. package/dist/commands/agent.d.ts +2 -0
  3. package/dist/commands/agent.js +163 -0
  4. package/dist/commands/agent.js.map +1 -0
  5. package/dist/commands/config.d.ts +3 -0
  6. package/dist/commands/config.js +124 -0
  7. package/dist/commands/config.js.map +1 -0
  8. package/dist/config/configFile.d.ts +6 -0
  9. package/dist/config/configFile.js +43 -0
  10. package/dist/config/configFile.js.map +1 -0
  11. package/dist/config/defaults.d.ts +10 -0
  12. package/dist/config/defaults.js +19 -0
  13. package/dist/config/defaults.js.map +1 -0
  14. package/dist/config/index.d.ts +4 -0
  15. package/dist/config/index.js +5 -0
  16. package/dist/config/index.js.map +1 -0
  17. package/dist/config/resolve.d.ts +3 -0
  18. package/dist/config/resolve.js +24 -0
  19. package/dist/config/resolve.js.map +1 -0
  20. package/dist/config/runtime.d.ts +3 -0
  21. package/dist/config/runtime.js +13 -0
  22. package/dist/config/runtime.js.map +1 -0
  23. package/dist/config.d.ts +6 -0
  24. package/dist/config.js +6 -0
  25. package/dist/config.js.map +1 -0
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.js +230 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/transport/httpClient.d.ts +13 -0
  30. package/dist/transport/httpClient.js +91 -0
  31. package/dist/transport/httpClient.js.map +1 -0
  32. package/dist/transport/wsClient.d.ts +30 -0
  33. package/dist/transport/wsClient.js +196 -0
  34. package/dist/transport/wsClient.js.map +1 -0
  35. package/dist/tui/App.d.ts +6 -0
  36. package/dist/tui/App.js +40 -0
  37. package/dist/tui/App.js.map +1 -0
  38. package/dist/tui/components/ChatPanel.d.ts +12 -0
  39. package/dist/tui/components/ChatPanel.js +18 -0
  40. package/dist/tui/components/ChatPanel.js.map +1 -0
  41. package/dist/tui/components/ColoredBoard.d.ts +7 -0
  42. package/dist/tui/components/ColoredBoard.js +73 -0
  43. package/dist/tui/components/ColoredBoard.js.map +1 -0
  44. package/dist/tui/components/PlayerInfo.d.ts +10 -0
  45. package/dist/tui/components/PlayerInfo.js +10 -0
  46. package/dist/tui/components/PlayerInfo.js.map +1 -0
  47. package/dist/tui/components/StatusBar.d.ts +7 -0
  48. package/dist/tui/components/StatusBar.js +13 -0
  49. package/dist/tui/components/StatusBar.js.map +1 -0
  50. package/dist/tui/components/TicTacToeBoard.d.ts +6 -0
  51. package/dist/tui/components/TicTacToeBoard.js +19 -0
  52. package/dist/tui/components/TicTacToeBoard.js.map +1 -0
  53. package/dist/tui/hooks/useEnsNames.d.ts +5 -0
  54. package/dist/tui/hooks/useEnsNames.js +33 -0
  55. package/dist/tui/hooks/useEnsNames.js.map +1 -0
  56. package/dist/tui/screens/GameBoard.d.ts +10 -0
  57. package/dist/tui/screens/GameBoard.js +245 -0
  58. package/dist/tui/screens/GameBoard.js.map +1 -0
  59. package/dist/tui/screens/GameOver.d.ts +10 -0
  60. package/dist/tui/screens/GameOver.js +21 -0
  61. package/dist/tui/screens/GameOver.js.map +1 -0
  62. package/dist/tui/screens/Leaderboard.d.ts +5 -0
  63. package/dist/tui/screens/Leaderboard.js +102 -0
  64. package/dist/tui/screens/Leaderboard.js.map +1 -0
  65. package/dist/tui/screens/Lobby.d.ts +8 -0
  66. package/dist/tui/screens/Lobby.js +113 -0
  67. package/dist/tui/screens/Lobby.js.map +1 -0
  68. package/dist/tui/screens/Matchmaking.d.ts +9 -0
  69. package/dist/tui/screens/Matchmaking.js +66 -0
  70. package/dist/tui/screens/Matchmaking.js.map +1 -0
  71. package/dist/tui/screens/WatchGame.d.ts +7 -0
  72. package/dist/tui/screens/WatchGame.js +99 -0
  73. package/dist/tui/screens/WatchGame.js.map +1 -0
  74. package/dist/tui/screens/WatchList.d.ts +6 -0
  75. package/dist/tui/screens/WatchList.js +49 -0
  76. package/dist/tui/screens/WatchList.js.map +1 -0
  77. package/dist/tui/theme.d.ts +30 -0
  78. package/dist/tui/theme.js +31 -0
  79. package/dist/tui/theme.js.map +1 -0
  80. package/dist/wallet/signer.d.ts +13 -0
  81. package/dist/wallet/signer.js +41 -0
  82. package/dist/wallet/signer.js.map +1 -0
  83. package/package.json +43 -0
  84. package/play-agents.cjs +444 -0
  85. package/src/commands/agent.ts +175 -0
  86. package/src/commands/config.ts +162 -0
  87. package/src/config/configFile.ts +55 -0
  88. package/src/config/defaults.ts +28 -0
  89. package/src/config/index.ts +15 -0
  90. package/src/config/resolve.ts +33 -0
  91. package/src/config/runtime.ts +18 -0
  92. package/src/index.ts +237 -0
  93. package/src/transport/httpClient.ts +104 -0
  94. package/src/transport/wsClient.ts +214 -0
  95. package/src/tui/App.tsx +130 -0
  96. package/src/tui/components/ChatPanel.tsx +53 -0
  97. package/src/tui/components/ColoredBoard.tsx +98 -0
  98. package/src/tui/components/PlayerInfo.tsx +31 -0
  99. package/src/tui/components/StatusBar.tsx +35 -0
  100. package/src/tui/hooks/useEnsNames.ts +35 -0
  101. package/src/tui/screens/GameBoard.tsx +434 -0
  102. package/src/tui/screens/GameOver.tsx +83 -0
  103. package/src/tui/screens/Leaderboard.tsx +196 -0
  104. package/src/tui/screens/Lobby.tsx +197 -0
  105. package/src/tui/screens/Matchmaking.tsx +107 -0
  106. package/src/tui/screens/WatchGame.tsx +182 -0
  107. package/src/tui/screens/WatchList.tsx +99 -0
  108. package/src/tui/theme.ts +31 -0
  109. package/src/wallet/signer.ts +54 -0
  110. package/tsconfig.json +17 -0
@@ -0,0 +1,175 @@
1
+ import { Command } from "commander";
2
+ import { parseEther, formatEther } from "ethers";
3
+ import { DorkAgent, Strategy, GameContext, Action, EscrowInfo } from "@dorkfun/agent-sdk";
4
+ import { initConfig, getConfig } from "../config/index.js";
5
+ import { getAddress, signMessage, sendEscrowDeposit } from "../wallet/signer.js";
6
+
7
+ function formatBoard(board: (string | null)[]): string {
8
+ if (!board || board.length !== 9) return "";
9
+ const rows: string[] = [];
10
+ for (let r = 0; r < 3; r++) {
11
+ rows.push(
12
+ board
13
+ .slice(r * 3, r * 3 + 3)
14
+ .map((c) => c || ".")
15
+ .join(" ")
16
+ );
17
+ }
18
+ return rows.join(" | ");
19
+ }
20
+
21
+ function log(tag: string, message: string): void {
22
+ const ts = new Date().toISOString().slice(11, 19);
23
+ console.log(`${ts} [${tag.padEnd(5)}] ${message}`);
24
+ }
25
+
26
+ class RandomStrategy implements Strategy {
27
+ private delay: number;
28
+ private gameId: string;
29
+
30
+ constructor(gameId: string, delay: number) {
31
+ this.delay = delay;
32
+ this.gameId = gameId;
33
+ }
34
+
35
+ chooseAction(ctx: GameContext): Action {
36
+ const idx = Math.floor(Math.random() * ctx.legalActions.length);
37
+ return ctx.legalActions[idx];
38
+ }
39
+
40
+ onStateUpdate(ctx: GameContext): void {
41
+ const pub = ctx.observation.publicData as Record<string, unknown>;
42
+ const board = pub.board as (string | null)[] | undefined;
43
+ const turnLabel = ctx.yourTurn ? "Your turn" : "Opponent's turn";
44
+
45
+ if (board) {
46
+ log("state", `Turn ${ctx.turnNumber} | ${turnLabel} | ${formatBoard(board)}`);
47
+ } else {
48
+ log("state", `Turn ${ctx.turnNumber} | ${turnLabel}`);
49
+ }
50
+ }
51
+ }
52
+
53
+ export function registerAgentCommand(program: Command): void {
54
+ program
55
+ .command("agent")
56
+ .description("Play games headlessly with a built-in random agent")
57
+ .option("-g, --game <gameId>", "Game to play", "tictactoe")
58
+ .option("-n, --count <N>", "Number of games to play", "1")
59
+ .option("--delay <ms>", "Delay between moves in ms", "0")
60
+ .option("--stake <ETH>", "Stake amount in ETH (e.g. 0.01 for 0.01 ETH, 1 for 1 ETH)")
61
+ .option("--private", "Create a private match")
62
+ .option("--invite <code>", "Join a private match by invite code")
63
+ .action(async (opts) => {
64
+ await initConfig();
65
+
66
+ const config = getConfig();
67
+ const address = getAddress();
68
+ const count = parseInt(opts.count, 10) || 1;
69
+ const delay = parseInt(opts.delay, 10) || 0;
70
+
71
+ // Convert ETH input to wei for the protocol
72
+ let stakeWei: string | undefined;
73
+ if (opts.stake) {
74
+ try {
75
+ stakeWei = parseEther(opts.stake).toString();
76
+ } catch {
77
+ log("error", `Invalid stake amount: "${opts.stake}". Use a decimal ETH value (e.g. 0.01, 1.5)`);
78
+ process.exit(1);
79
+ }
80
+ }
81
+
82
+ log("init", `Player: ${address}`);
83
+ log("init", `Server: ${config.serverUrl}`);
84
+ log("init", `Game: ${opts.game} | Count: ${count} | Delay: ${delay}ms`);
85
+ if (stakeWei) {
86
+ log("init", `Stake: ${opts.stake} ETH (${stakeWei} wei)`);
87
+ }
88
+
89
+ const agent = new DorkAgent({
90
+ serverUrl: config.serverUrl,
91
+ wsUrl: config.wsUrl,
92
+ playerId: address,
93
+ signMessage,
94
+ });
95
+
96
+ // Clean shutdown on Ctrl+C
97
+ process.on("SIGINT", () => {
98
+ log("exit", "Shutting down...");
99
+ agent.close();
100
+ process.exit(0);
101
+ });
102
+
103
+ let wins = 0;
104
+ let losses = 0;
105
+ let draws = 0;
106
+
107
+ for (let i = 0; i < count; i++) {
108
+ if (count > 1) {
109
+ log("match", `--- Game ${i + 1} of ${count} ---`);
110
+ }
111
+
112
+ const strategy = new RandomStrategy(opts.game, delay);
113
+
114
+ try {
115
+ // Check for active match to reconnect to before starting a new one
116
+ const reconnected = await agent.reconnect(strategy, {
117
+ moveDelay: delay,
118
+ onLog: log,
119
+ });
120
+ if (reconnected) {
121
+ if (reconnected.draw) draws++;
122
+ else if (reconnected.didWin) wins++;
123
+ else losses++;
124
+ continue;
125
+ }
126
+
127
+ // Build deposit handler for staked matches
128
+ const depositHandler = stakeWei
129
+ ? async (escrow: EscrowInfo) => {
130
+ log("stake", `Depositing ${formatEther(escrow.stakeWei)} ETH to escrow ${escrow.address}...`);
131
+ return sendEscrowDeposit(escrow);
132
+ }
133
+ : undefined;
134
+
135
+ let result;
136
+ if (opts.invite) {
137
+ result = await agent.playPrivate(opts.game, strategy, {
138
+ inviteCode: opts.invite,
139
+ moveDelay: delay,
140
+ onLog: log,
141
+ stakeWei,
142
+ sendDeposit: depositHandler,
143
+ });
144
+ } else if (opts.private) {
145
+ result = await agent.playPrivate(opts.game, strategy, {
146
+ moveDelay: delay,
147
+ onLog: log,
148
+ stakeWei,
149
+ sendDeposit: depositHandler,
150
+ });
151
+ } else {
152
+ result = await agent.play(opts.game, strategy, {
153
+ moveDelay: delay,
154
+ onLog: log,
155
+ stakeWei,
156
+ sendDeposit: depositHandler,
157
+ });
158
+ }
159
+
160
+ if (result.draw) draws++;
161
+ else if (result.didWin) wins++;
162
+ else losses++;
163
+ } catch (err: any) {
164
+ log("error", err.message);
165
+ break;
166
+ }
167
+ }
168
+
169
+ if (count > 1) {
170
+ log("done", `Results: ${wins}W / ${losses}L / ${draws}D (${count} games)`);
171
+ }
172
+
173
+ process.exit(0);
174
+ });
175
+ }
@@ -0,0 +1,162 @@
1
+ import { Command } from "commander";
2
+ import { createInterface, Interface as RLInterface } from "node:readline";
3
+ import { randomBytes } from "node:crypto";
4
+ import {
5
+ resolveConfig,
6
+ readConfigFile,
7
+ updateConfigFile,
8
+ writeConfigFile,
9
+ getConfigPath,
10
+ CONFIG_KEYS,
11
+ DEFAULTS,
12
+ ENV_MAP,
13
+ ConfigData,
14
+ } from "../config/index.js";
15
+
16
+ function createPrompter() {
17
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
18
+ let closed = false;
19
+ rl.once("close", () => {
20
+ closed = true;
21
+ });
22
+
23
+ return {
24
+ ask(prompt: string): Promise<string> {
25
+ if (closed) return Promise.resolve("");
26
+ return new Promise((resolve) => {
27
+ rl.question(prompt, (answer) => resolve(answer));
28
+ rl.once("close", () => resolve(""));
29
+ });
30
+ },
31
+ close() {
32
+ if (!closed) rl.close();
33
+ },
34
+ };
35
+ }
36
+
37
+ export function registerConfigCommand(program: Command): void {
38
+ const configCmd = program
39
+ .command("config")
40
+ .description("Manage CLI configuration (~/.dork/config.json)");
41
+
42
+ configCmd.action(async () => {
43
+ await runWizard();
44
+ });
45
+
46
+ configCmd
47
+ .command("set <key> <value>")
48
+ .description("Set a config value")
49
+ .action(async (key: string, value: string) => {
50
+ if (!isValidKey(key)) {
51
+ console.error(
52
+ `Unknown config key: "${key}". Valid keys: ${CONFIG_KEYS.join(", ")}`,
53
+ );
54
+ process.exit(1);
55
+ }
56
+ await updateConfigFile(key as keyof ConfigData, value);
57
+ const display = key === "privateKey" ? maskKey(value) : value;
58
+ console.log(`Set ${key} = ${display}`);
59
+ });
60
+
61
+ configCmd
62
+ .command("get <key>")
63
+ .description("Get a config value")
64
+ .action(async (key: string) => {
65
+ if (!isValidKey(key)) {
66
+ console.error(
67
+ `Unknown config key: "${key}". Valid keys: ${CONFIG_KEYS.join(", ")}`,
68
+ );
69
+ process.exit(1);
70
+ }
71
+ const resolved = await resolveConfig();
72
+ console.log(resolved[key as keyof ConfigData]);
73
+ });
74
+
75
+ configCmd
76
+ .command("list")
77
+ .description("List all config values with sources")
78
+ .action(async () => {
79
+ await printConfigList();
80
+ });
81
+ }
82
+
83
+ export async function runWizard(): Promise<void> {
84
+ const existing = await readConfigFile();
85
+ const prompter = createPrompter();
86
+
87
+ console.log("\nDork CLI Configuration");
88
+ console.log("──────────────────────\n");
89
+
90
+ try {
91
+ const serverUrl = await prompter.ask(
92
+ `Server URL [${existing.serverUrl || DEFAULTS.serverUrl}]: `,
93
+ );
94
+ const wsUrl = await prompter.ask(
95
+ `WebSocket URL [${existing.wsUrl || DEFAULTS.wsUrl}]: `,
96
+ );
97
+ const privateKeyInput = await prompter.ask(
98
+ `Private key [enter to generate new]: `,
99
+ );
100
+
101
+ const data: Partial<ConfigData> = {
102
+ ...existing,
103
+ serverUrl: serverUrl.trim() || existing.serverUrl || DEFAULTS.serverUrl,
104
+ wsUrl: wsUrl.trim() || existing.wsUrl || DEFAULTS.wsUrl,
105
+ };
106
+
107
+ if (privateKeyInput.trim()) {
108
+ data.privateKey = privateKeyInput.trim();
109
+ } else if (!existing.privateKey) {
110
+ data.privateKey = "0x" + randomBytes(32).toString("hex");
111
+ console.log(`\nGenerated new private key: ${maskKey(data.privateKey)}`);
112
+ }
113
+
114
+ await writeConfigFile(data);
115
+ console.log(`\nConfig saved to ${getConfigPath()}\n`);
116
+
117
+ const resolved = await resolveConfig();
118
+ for (const key of CONFIG_KEYS) {
119
+ const value =
120
+ key === "privateKey" ? maskKey(resolved[key]) : resolved[key];
121
+ console.log(` ${key}: ${value}`);
122
+ }
123
+ console.log("");
124
+ } finally {
125
+ prompter.close();
126
+ }
127
+ }
128
+
129
+ async function printConfigList(): Promise<void> {
130
+ const resolved = await resolveConfig();
131
+ const fileData = await readConfigFile();
132
+
133
+ console.log(`\nConfig file: ${getConfigPath()}`);
134
+ console.log("──────────────────────────────────────");
135
+
136
+ for (const key of CONFIG_KEYS) {
137
+ const value =
138
+ key === "privateKey" ? maskKey(resolved[key]) : resolved[key];
139
+ const source = getSource(key, fileData);
140
+ console.log(` ${key}: ${value} (${source})`);
141
+ }
142
+ console.log("");
143
+ }
144
+
145
+ function isValidKey(key: string): key is keyof ConfigData {
146
+ return CONFIG_KEYS.includes(key as keyof ConfigData);
147
+ }
148
+
149
+ function maskKey(key: string): string {
150
+ if (!key || key.length < 10) return key ? "****" : "(not set)";
151
+ return key.slice(0, 6) + "..." + key.slice(-4);
152
+ }
153
+
154
+ function getSource(
155
+ key: keyof ConfigData,
156
+ fileData: Partial<ConfigData>,
157
+ ): string {
158
+ const envVal = process.env[ENV_MAP[key]];
159
+ if (envVal !== undefined && envVal !== "") return `env: ${ENV_MAP[key]}`;
160
+ if (fileData[key] !== undefined && fileData[key] !== "") return "config file";
161
+ return "default";
162
+ }
@@ -0,0 +1,55 @@
1
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { ConfigData } from "./defaults.js";
5
+
6
+ const CONFIG_DIR = join(homedir(), ".dork");
7
+ const CONFIG_PATH = join(CONFIG_DIR, "config.json");
8
+
9
+ export function getConfigDir(): string {
10
+ return CONFIG_DIR;
11
+ }
12
+
13
+ export function getConfigPath(): string {
14
+ return CONFIG_PATH;
15
+ }
16
+
17
+ export async function readConfigFile(): Promise<Partial<ConfigData>> {
18
+ try {
19
+ const raw = await readFile(CONFIG_PATH, "utf-8");
20
+ const parsed = JSON.parse(raw);
21
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
22
+ return {};
23
+ }
24
+ return parsed as Partial<ConfigData>;
25
+ } catch (err: any) {
26
+ if (err.code === "ENOENT") {
27
+ return {};
28
+ }
29
+ if (err instanceof SyntaxError) {
30
+ console.error(
31
+ `Warning: ${CONFIG_PATH} is malformed and was ignored. ` +
32
+ `Run "dork config" to recreate it.`,
33
+ );
34
+ return {};
35
+ }
36
+ throw err;
37
+ }
38
+ }
39
+
40
+ export async function writeConfigFile(
41
+ data: Partial<ConfigData>,
42
+ ): Promise<void> {
43
+ await mkdir(CONFIG_DIR, { recursive: true });
44
+ await writeFile(CONFIG_PATH, JSON.stringify(data, null, 2) + "\n", "utf-8");
45
+ }
46
+
47
+ export async function updateConfigFile(
48
+ key: keyof ConfigData,
49
+ value: string,
50
+ ): Promise<Partial<ConfigData>> {
51
+ const existing = await readConfigFile();
52
+ existing[key] = value;
53
+ await writeConfigFile(existing);
54
+ return existing;
55
+ }
@@ -0,0 +1,28 @@
1
+ export interface ConfigData {
2
+ serverUrl: string;
3
+ wsUrl: string;
4
+ privateKey: string;
5
+ /** RPC URL for on-chain deposit transactions */
6
+ rpcUrl: string;
7
+ }
8
+
9
+ export const CONFIG_KEYS: (keyof ConfigData)[] = [
10
+ "serverUrl",
11
+ "wsUrl",
12
+ "privateKey",
13
+ "rpcUrl",
14
+ ];
15
+
16
+ export const DEFAULTS: ConfigData = {
17
+ serverUrl: "https://engine.dork.fun",
18
+ wsUrl: "wss://engine.dork.fun",
19
+ privateKey: "",
20
+ rpcUrl: "https://eth.llamarpc.com",
21
+ };
22
+
23
+ export const ENV_MAP: Record<keyof ConfigData, string> = {
24
+ serverUrl: "SERVER_URL",
25
+ wsUrl: "SERVER_WS_URL",
26
+ privateKey: "PRIVATE_KEY",
27
+ rpcUrl: "RPC_URL",
28
+ };
@@ -0,0 +1,15 @@
1
+ export {
2
+ ConfigData,
3
+ CONFIG_KEYS,
4
+ DEFAULTS,
5
+ ENV_MAP,
6
+ } from "./defaults.js";
7
+ export {
8
+ readConfigFile,
9
+ writeConfigFile,
10
+ updateConfigFile,
11
+ getConfigDir,
12
+ getConfigPath,
13
+ } from "./configFile.js";
14
+ export { resolveConfig, setCliOverride } from "./resolve.js";
15
+ export { initConfig, getConfig } from "./runtime.js";
@@ -0,0 +1,33 @@
1
+ import { ConfigData, DEFAULTS, ENV_MAP, CONFIG_KEYS } from "./defaults.js";
2
+ import { readConfigFile } from "./configFile.js";
3
+
4
+ const cliOverrides: Partial<ConfigData> = {};
5
+
6
+ export function setCliOverride<K extends keyof ConfigData>(
7
+ key: K,
8
+ value: ConfigData[K],
9
+ ): void {
10
+ cliOverrides[key] = value;
11
+ }
12
+
13
+ export async function resolveConfig(): Promise<ConfigData> {
14
+ const fileConfig = await readConfigFile();
15
+ const resolved: ConfigData = { ...DEFAULTS };
16
+
17
+ for (const key of CONFIG_KEYS) {
18
+ if (fileConfig[key] !== undefined && fileConfig[key] !== "") {
19
+ resolved[key] = fileConfig[key]!;
20
+ }
21
+
22
+ const envVal = process.env[ENV_MAP[key]];
23
+ if (envVal !== undefined && envVal !== "") {
24
+ resolved[key] = envVal;
25
+ }
26
+
27
+ if (cliOverrides[key] !== undefined && cliOverrides[key] !== "") {
28
+ resolved[key] = cliOverrides[key]!;
29
+ }
30
+ }
31
+
32
+ return resolved;
33
+ }
@@ -0,0 +1,18 @@
1
+ import { ConfigData } from "./defaults.js";
2
+ import { resolveConfig } from "./resolve.js";
3
+
4
+ let _config: ConfigData | null = null;
5
+
6
+ export async function initConfig(): Promise<ConfigData> {
7
+ _config = await resolveConfig();
8
+ return _config;
9
+ }
10
+
11
+ export function getConfig(): ConfigData {
12
+ if (!_config) {
13
+ throw new Error(
14
+ "Config not initialized. Call initConfig() before accessing config.",
15
+ );
16
+ }
17
+ return _config;
18
+ }