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,182 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+ import { WsMessage, Observation, formatAddress } from "@dorkfun/core";
4
+ import { getGameUI } from "@dorkfun/game-ui";
5
+ import { colors } from "../theme.js";
6
+ import { ColoredBoard } from "../components/ColoredBoard.js";
7
+ import { useEnsNames } from "../hooks/useEnsNames.js";
8
+ import { GameWebSocket } from "../../transport/wsClient.js";
9
+
10
+ interface WatchGameProps {
11
+ matchId: string;
12
+ gameId: string;
13
+ onBack: () => void;
14
+ }
15
+
16
+ export function WatchGame({ matchId, gameId, onBack }: WatchGameProps) {
17
+ const [publicData, setPublicData] = useState<Record<string, unknown>>({});
18
+ const [currentPlayer, setCurrentPlayer] = useState("");
19
+ const [players, setPlayers] = useState<string[]>([]);
20
+ const [turnNumber, setTurnNumber] = useState(0);
21
+ const [status, setStatus] = useState<string>("connecting");
22
+ const [gameOver, setGameOver] = useState(false);
23
+ const [winner, setWinner] = useState<string | null>(null);
24
+ const [reason, setReason] = useState("");
25
+ const [error, setError] = useState("");
26
+ const [wsRef] = useState(() => new GameWebSocket());
27
+
28
+ const allAddresses = [...players, winner, currentPlayer].filter(Boolean) as string[];
29
+ const ensNames = useEnsNames(allAddresses);
30
+
31
+ const ui = getGameUI(gameId);
32
+ const shortId = matchId.slice(0, 8);
33
+
34
+ useEffect(() => {
35
+ const connect = async () => {
36
+ try {
37
+ await wsRef.connect(matchId, "spectate");
38
+ setStatus("connected");
39
+
40
+ wsRef.sendSpectateJoin(matchId, "spectator");
41
+
42
+ wsRef.on("SPECTATE_STATE", (msg: WsMessage) => {
43
+ const payload = msg.payload as any;
44
+ if (payload.observation) {
45
+ applyObservation(payload.observation);
46
+ }
47
+ if (payload.players) setPlayers(payload.players);
48
+ if (payload.status) setStatus(payload.status);
49
+ });
50
+
51
+ wsRef.on("STEP_RESULT", (msg: WsMessage) => {
52
+ const payload = msg.payload as any;
53
+ if (payload.observation) {
54
+ applyObservation(payload.observation);
55
+ }
56
+ if (payload.nextPlayer) {
57
+ setCurrentPlayer(payload.nextPlayer);
58
+ }
59
+ });
60
+
61
+ wsRef.on("GAME_STATE", (msg: WsMessage) => {
62
+ const payload = msg.payload as any;
63
+ if (payload.observation) {
64
+ applyObservation(payload.observation);
65
+ }
66
+ });
67
+
68
+ wsRef.on("GAME_OVER", (msg: WsMessage) => {
69
+ const payload = msg.payload as { winner: string | null; reason: string; draw?: boolean };
70
+ setGameOver(true);
71
+ setWinner(payload.winner);
72
+ setReason(payload.reason);
73
+ setStatus("finished");
74
+ });
75
+
76
+ wsRef.on("close", () => {
77
+ setStatus("disconnected");
78
+ });
79
+
80
+ wsRef.on("reconnecting", () => {
81
+ setStatus("reconnecting");
82
+ });
83
+ } catch (err: any) {
84
+ setError(`Connection failed: ${err.message}`);
85
+ setStatus("disconnected");
86
+ }
87
+ };
88
+
89
+ connect();
90
+ return () => wsRef.close();
91
+ }, [matchId]);
92
+
93
+ function applyObservation(obs: Observation) {
94
+ if (obs.publicData) setPublicData(obs.publicData);
95
+ if (obs.currentPlayer) setCurrentPlayer(obs.currentPlayer);
96
+ if (obs.players?.length) setPlayers(obs.players);
97
+ if (obs.turnNumber !== undefined) setTurnNumber(obs.turnNumber);
98
+ }
99
+
100
+ useInput((_input, key) => {
101
+ if (key.escape) onBack();
102
+ });
103
+
104
+ const boardHtml = ui?.renderBoard(publicData) || `[No renderer for ${gameId}]`;
105
+ const statusStr = ui?.renderStatus(publicData);
106
+ const turnDisplay = ui?.maxTurns ? `${turnNumber}/${ui.maxTurns}` : `${turnNumber}`;
107
+
108
+ return (
109
+ <Box flexDirection="column" paddingX={2} paddingY={1}>
110
+ <Text>
111
+ <Text color={colors.primary}>{"$ "}</Text>
112
+ <Text>watch {shortId}</Text>
113
+ </Text>
114
+
115
+ <Text>
116
+ <Text color={colors.dimmed}>{"> game: "}</Text>
117
+ <Text>{gameId}</Text>
118
+ </Text>
119
+
120
+ {players.length > 0 && ui && (
121
+ <Text>
122
+ <Text color={colors.dimmed}>{"> players: "}</Text>
123
+ <Text color={colors.cyan}>
124
+ {formatAddress(players[0], ensNames[players[0]], "medium")} ({ui.getPlayerLabel(players[0], publicData)})
125
+ </Text>
126
+ <Text color={colors.dimmed}>{" vs "}</Text>
127
+ <Text color={colors.secondary}>
128
+ {formatAddress(players[1], ensNames[players[1]], "medium")} ({ui.getPlayerLabel(players[1], publicData)})
129
+ </Text>
130
+ </Text>
131
+ )}
132
+
133
+ <Text>
134
+ <Text color={colors.dimmed}>{"> move: "}</Text>
135
+ <Text>{turnDisplay}</Text>
136
+ </Text>
137
+
138
+ <Text>{""}</Text>
139
+
140
+ {error ? (
141
+ <Text color={colors.error}>{error}</Text>
142
+ ) : (
143
+ <Box flexDirection="column">
144
+ <ColoredBoard html={boardHtml} />
145
+
146
+ {statusStr && (
147
+ <Text color={colors.warning} bold>{statusStr}</Text>
148
+ )}
149
+
150
+ <Text>{""}</Text>
151
+
152
+ {gameOver ? (
153
+ <Text>
154
+ <Text color={colors.primary}>{"> "}</Text>
155
+ <Text color={colors.secondary} bold>
156
+ {"GAME OVER - "}
157
+ {winner
158
+ ? `Winner: ${formatAddress(winner, ensNames[winner], "medium")} (${reason})`
159
+ : `Draw (${reason})`}
160
+ </Text>
161
+ </Text>
162
+ ) : status === "connected" || status === "ACTIVE" ? (
163
+ <Text>
164
+ <Text color={colors.primary}>{"> "}</Text>
165
+ <Text color={colors.dimmed}>
166
+ {"waiting for "}
167
+ {currentPlayer ? formatAddress(currentPlayer, ensNames[currentPlayer], "medium") : "..."}
168
+ {"... "}
169
+ </Text>
170
+ <Text>{"█"}</Text>
171
+ </Text>
172
+ ) : (
173
+ <Text color={colors.error}>{"connecting..."}</Text>
174
+ )}
175
+ </Box>
176
+ )}
177
+
178
+ <Text>{""}</Text>
179
+ <Text color={colors.dimmed}>{"Press Esc to return to lobby"}</Text>
180
+ </Box>
181
+ );
182
+ }
@@ -0,0 +1,99 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+ import { formatAddress } from "@dorkfun/core";
4
+ import { colors } from "../theme.js";
5
+ import { useEnsNames } from "../hooks/useEnsNames.js";
6
+ import * as api from "../../transport/httpClient.js";
7
+
8
+ interface MatchSummary {
9
+ matchId: string;
10
+ gameId: string;
11
+ status: string;
12
+ players: string[];
13
+ createdAt: string;
14
+ }
15
+
16
+ interface WatchListProps {
17
+ onSelect: (matchId: string, gameId: string) => void;
18
+ onBack: () => void;
19
+ }
20
+
21
+ export function WatchList({ onSelect, onBack }: WatchListProps) {
22
+ const [matches, setMatches] = useState<MatchSummary[]>([]);
23
+ const [selected, setSelected] = useState(0);
24
+ const [loading, setLoading] = useState(true);
25
+ const [error, setError] = useState("");
26
+
27
+ useEffect(() => {
28
+ api
29
+ .listMatches()
30
+ .then((res) => {
31
+ setMatches(res.matches);
32
+ setLoading(false);
33
+ })
34
+ .catch((err: Error) => {
35
+ setError(err.message);
36
+ setLoading(false);
37
+ });
38
+ }, []);
39
+
40
+ useInput((_input, key) => {
41
+ if (key.escape) {
42
+ onBack();
43
+ return;
44
+ }
45
+ if (matches.length === 0) return;
46
+ if (key.upArrow) setSelected((s) => Math.max(0, s - 1));
47
+ if (key.downArrow) setSelected((s) => Math.min(matches.length - 1, s + 1));
48
+ if (key.return) {
49
+ const m = matches[selected];
50
+ if (m) onSelect(m.matchId, m.gameId);
51
+ }
52
+ });
53
+
54
+ const allPlayers = matches.flatMap((m) => m.players);
55
+ const ensNames = useEnsNames(allPlayers);
56
+ const formatPlayer = (addr: string) => formatAddress(addr, ensNames[addr]);
57
+
58
+ return (
59
+ <Box flexDirection="column" paddingX={2} paddingY={1}>
60
+ <Text color={colors.primary} bold>
61
+ {"═══ LIVE GAMES ═══"}
62
+ </Text>
63
+ <Text color={colors.dimmed}>{""}</Text>
64
+
65
+ {loading && <Text color={colors.dimmed}>Loading matches...</Text>}
66
+
67
+ {error && <Text color={colors.error}>Error: {error}</Text>}
68
+
69
+ {!loading && !error && matches.length === 0 && (
70
+ <Text color={colors.warning}>No active games to watch right now.</Text>
71
+ )}
72
+
73
+ {matches.map((m, i) => (
74
+ <Box key={m.matchId} flexDirection="row">
75
+ <Text color={i === selected ? colors.primary : colors.dimmed}>
76
+ {i === selected ? " ▸ " : " "}
77
+ </Text>
78
+ <Text color={i === selected ? colors.primary : colors.text}>
79
+ {m.gameId}
80
+ </Text>
81
+ <Text color={colors.dimmed}>
82
+ {" "}
83
+ {m.players.map(formatPlayer).join(" vs ")}
84
+ </Text>
85
+ <Text color={colors.dimmed}>
86
+ {" "}[{m.status}]
87
+ </Text>
88
+ </Box>
89
+ ))}
90
+
91
+ <Text color={colors.dimmed}>{""}</Text>
92
+ <Text color={colors.dimmed}>
93
+ {matches.length > 0
94
+ ? "Use ↑↓ to select, Enter to watch, Esc to go back"
95
+ : "Press Esc to go back"}
96
+ </Text>
97
+ </Box>
98
+ );
99
+ }
@@ -0,0 +1,31 @@
1
+ export const colors = {
2
+ primary: "#00ff41", // Matrix green
3
+ secondary: "#ffb000", // Amber
4
+ dimmed: "#666666",
5
+ error: "#ff3333",
6
+ warning: "#ffaa00",
7
+ text: "#cccccc",
8
+ bg: "#0a0a0a",
9
+ border: "#333333",
10
+ white: "#ffffff",
11
+ cyan: "#00ffff",
12
+ };
13
+
14
+ export const symbols = {
15
+ topLeft: "┌",
16
+ topRight: "┐",
17
+ bottomLeft: "└",
18
+ bottomRight: "┘",
19
+ horizontal: "─",
20
+ vertical: "│",
21
+ cross: "┼",
22
+ tee: "├",
23
+ teeRight: "┤",
24
+ teeDown: "┬",
25
+ teeUp: "┴",
26
+ bullet: "●",
27
+ arrow: "▸",
28
+ check: "✔",
29
+ x: "✘",
30
+ spinner: ["◐", "◓", "◑", "◒"],
31
+ };
@@ -0,0 +1,54 @@
1
+ import { Wallet, JsonRpcProvider, Contract } from "ethers";
2
+ import { getConfig } from "../config/runtime.js";
3
+
4
+ const ESCROW_ABI = [
5
+ "function depositStake(bytes32 matchId) external payable",
6
+ ];
7
+
8
+ let wallet: Wallet | null = null;
9
+
10
+ export function getWallet(): Wallet {
11
+ if (!wallet) {
12
+ const { privateKey } = getConfig();
13
+ if (!privateKey) {
14
+ throw new Error(
15
+ "Private key not set. Run 'dork config' to set one up.",
16
+ );
17
+ }
18
+ wallet = new Wallet(privateKey);
19
+ }
20
+ return wallet;
21
+ }
22
+
23
+ export function getAddress(): string {
24
+ return getWallet().address;
25
+ }
26
+
27
+ export async function signMessage(message: string): Promise<string> {
28
+ return getWallet().signMessage(message);
29
+ }
30
+
31
+ /**
32
+ * Submit an escrow deposit transaction on-chain.
33
+ * Returns the transaction hash.
34
+ */
35
+ export async function sendEscrowDeposit(escrow: {
36
+ address: string;
37
+ stakeWei: string;
38
+ matchIdBytes32: string;
39
+ }): Promise<string> {
40
+ const { rpcUrl, privateKey } = getConfig();
41
+ if (!privateKey) {
42
+ throw new Error("Private key not set. Run 'dork config' to set one up.");
43
+ }
44
+
45
+ const provider = new JsonRpcProvider(rpcUrl);
46
+ const signer = new Wallet(privateKey, provider);
47
+ const contract = new Contract(escrow.address, ESCROW_ABI, signer);
48
+
49
+ const tx = await contract.depositStake(escrow.matchIdBytes32, {
50
+ value: escrow.stakeWei,
51
+ });
52
+ const receipt = await tx.wait();
53
+ return receipt.hash as string;
54
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "target": "esnext",
5
+ "module": "nodenext",
6
+ "moduleResolution": "nodenext",
7
+ "declaration": true,
8
+ "outDir": "dist",
9
+ "rootDir": "./src",
10
+ "strict": true,
11
+ "jsx": "react-jsx",
12
+ "allowSyntheticDefaultImports": true,
13
+ "esModuleInterop": true,
14
+ "skipLibCheck": true,
15
+ "forceConsistentCasingInFileNames": true
16
+ }
17
+ }