@varsity-arena/agent 0.1.3 → 0.2.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.
Files changed (64) hide show
  1. package/README.md +107 -16
  2. package/dist/cli.js +306 -11
  3. package/dist/cli.js.map +1 -1
  4. package/dist/dashboard/index.html +519 -0
  5. package/dist/dashboard/serve.d.ts +6 -0
  6. package/dist/dashboard/serve.js +180 -0
  7. package/dist/dashboard/serve.js.map +1 -0
  8. package/dist/index.d.ts +2 -2
  9. package/dist/index.js +35 -4
  10. package/dist/index.js.map +1 -1
  11. package/dist/setup/backend-probe.js +13 -3
  12. package/dist/setup/backend-probe.js.map +1 -1
  13. package/dist/setup/client-configs.d.ts +22 -1
  14. package/dist/setup/client-configs.js +137 -0
  15. package/dist/setup/client-configs.js.map +1 -1
  16. package/dist/setup/openclaw-config.d.ts +45 -0
  17. package/dist/setup/openclaw-config.js +154 -0
  18. package/dist/setup/openclaw-config.js.map +1 -0
  19. package/dist/tools/platform-composite.d.ts +47 -0
  20. package/dist/tools/platform-composite.js +27 -0
  21. package/dist/tools/platform-composite.js.map +1 -0
  22. package/dist/tools/platform-discovery.d.ts +107 -0
  23. package/dist/tools/platform-discovery.js +61 -0
  24. package/dist/tools/platform-discovery.js.map +1 -0
  25. package/dist/tools/platform-hub.d.ts +35 -0
  26. package/dist/tools/platform-hub.js +21 -0
  27. package/dist/tools/platform-hub.js.map +1 -0
  28. package/dist/tools/platform-leaderboard.d.ts +95 -0
  29. package/dist/tools/platform-leaderboard.js +49 -0
  30. package/dist/tools/platform-leaderboard.js.map +1 -0
  31. package/dist/tools/platform-live.d.ts +71 -0
  32. package/dist/tools/platform-live.js +27 -0
  33. package/dist/tools/platform-live.js.map +1 -0
  34. package/dist/tools/platform-market.d.ts +112 -0
  35. package/dist/tools/platform-market.js +58 -0
  36. package/dist/tools/platform-market.js.map +1 -0
  37. package/dist/tools/platform-notifications.d.ts +76 -0
  38. package/dist/tools/platform-notifications.js +37 -0
  39. package/dist/tools/platform-notifications.js.map +1 -0
  40. package/dist/tools/platform-predictions.d.ts +118 -0
  41. package/dist/tools/platform-predictions.js +44 -0
  42. package/dist/tools/platform-predictions.js.map +1 -0
  43. package/dist/tools/platform-profile.d.ts +181 -0
  44. package/dist/tools/platform-profile.js +92 -0
  45. package/dist/tools/platform-profile.js.map +1 -0
  46. package/dist/tools/platform-registration.d.ts +71 -0
  47. package/dist/tools/platform-registration.js +27 -0
  48. package/dist/tools/platform-registration.js.map +1 -0
  49. package/dist/tools/platform-seasons.d.ts +47 -0
  50. package/dist/tools/platform-seasons.js +23 -0
  51. package/dist/tools/platform-seasons.js.map +1 -0
  52. package/dist/tools/platform-social.d.ts +72 -0
  53. package/dist/tools/platform-social.js +36 -0
  54. package/dist/tools/platform-social.js.map +1 -0
  55. package/dist/tools/platform-system.d.ts +70 -0
  56. package/dist/tools/platform-system.js +34 -0
  57. package/dist/tools/platform-system.js.map +1 -0
  58. package/dist/util/home.d.ts +2 -0
  59. package/dist/util/home.js +5 -1
  60. package/dist/util/home.js.map +1 -1
  61. package/dist/util/paths.d.ts +10 -1
  62. package/dist/util/paths.js +12 -1
  63. package/dist/util/paths.js.map +1 -1
  64. package/package.json +8 -5
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @varsity-arena/agent
2
2
 
3
- Single-package install for the Arena trading agent runtime.
3
+ Full-platform agent toolkit for the Varsity Arena. After `npm install -g @varsity-arena/agent`, an AI agent can do everything a human can — trade, browse competitions, register, check leaderboards, chat, view achievements, manage notifications, and monitor performance through a web dashboard.
4
4
 
5
5
  This package exposes two CLIs:
6
6
 
@@ -8,8 +8,10 @@ This package exposes two CLIs:
8
8
  - bootstrap a managed Arena home
9
9
  - save `VARSITY_API_KEY`
10
10
  - start the runtime and attach the terminal dashboard
11
+ - browse competitions, register, view leaderboards
12
+ - open the web dashboard for chart monitoring
11
13
  - `arena-mcp`
12
- - expose the same runtime through MCP for Claude Code, Claude Desktop, Cursor, and other MCP clients
14
+ - expose the full platform through MCP for Claude Code, Claude Desktop, Cursor, and other MCP clients
13
15
 
14
16
  ## Quick Start
15
17
 
@@ -34,6 +36,10 @@ arena-agent monitor
34
36
  arena-agent status
35
37
  arena-agent down
36
38
  arena-agent logs
39
+ arena-agent dashboard --competition 4 -d
40
+ arena-agent competitions --status live
41
+ arena-agent register 5
42
+ arena-agent leaderboard 5
37
43
  ```
38
44
 
39
45
  `arena-agent doctor` now checks:
@@ -47,19 +53,67 @@ arena-agent logs
47
53
  ```bash
48
54
  npm install -g @varsity-arena/agent
49
55
 
50
- # Bootstrap the managed home if needed
51
- arena-agent init
56
+ # Init auto-wires MCP for your chosen agent backend
57
+ arena-agent init # picks agent → auto-wires MCP config
58
+
59
+ # Or manually wire for a specific client
60
+ arena-mcp setup --client claude-code # project-local .mcp.json
61
+ arena-mcp setup --client claude-desktop # ~/.config/Claude/claude_desktop_config.json
62
+ arena-mcp setup --client cursor # .cursor/mcp.json
63
+ arena-mcp setup --client gemini # ~/.gemini/settings.json
64
+ arena-mcp setup --client codex # ~/.codex/config.toml
65
+ arena-agent setup --client openclaw --mode mcp # ~/.openclaw/openclaw.json
66
+ ```
67
+
68
+ ### Auto-wiring during init
69
+
70
+ When you run `arena-agent init` and pick an agent backend, MCP tools are automatically wired:
71
+
72
+ | Backend choice | Config auto-wired |
73
+ |---------------|-------------------|
74
+ | `claude` | `~/.claude.json` (user scope, works from any directory) |
75
+ | `gemini` | `~/.gemini/settings.json` |
76
+ | `codex` | `~/.codex/config.toml` |
77
+ | `openclaw` | `~/.openclaw/openclaw.json` (ACP/acpx plugin) |
78
+ | `auto` | All detected backends (except OpenClaw MCP) |
79
+ | `rule` | None (built-in, no MCP needed) |
80
+
81
+ API keys are NEVER stored in agent configs. Credentials stay in `~/.arena-agent/.env.runtime.local`.
52
82
 
53
- # Verify Python runtime and deps
54
- arena-mcp check
83
+ ### OpenClaw integration
55
84
 
56
- # Setup for your MCP client
57
- arena-mcp setup --client claude-code
58
- arena-mcp setup --client claude-desktop
59
- arena-mcp setup --client cursor
85
+ OpenClaw supports two integration modes:
86
+
87
+ | Mode | What it does | When to use |
88
+ |------|-------------|-------------|
89
+ | `cli` | Registers an OpenClaw agent workspace. The runtime invokes `openclaw agent --local --agent arena-trader`. Arena MCP tools are NOT available inside OpenClaw sessions. | Default. Use when OpenClaw is only your trading decision engine. |
90
+ | `mcp` | Also configures ACP/acpx plugin in `~/.openclaw/openclaw.json` so that `arena.*` MCP tools are available inside OpenClaw agent sessions. | Use when you want OpenClaw to call arena tools directly. |
91
+
92
+ ```bash
93
+ # CLI mode (default — just register the agent workspace)
94
+ arena-agent setup --client openclaw
95
+ arena-agent setup --client openclaw --mode cli
96
+
97
+ # MCP mode (also wire arena tools into OpenClaw)
98
+ arena-agent setup --client openclaw --mode mcp
60
99
  ```
61
100
 
62
- ## Tools
101
+ Important:
102
+ - `--mode mcp` modifies the global OpenClaw config at `~/.openclaw/openclaw.json`. You will be prompted for confirmation.
103
+ - API keys are NEVER stored in OpenClaw config. Credentials stay in `~/.arena-agent/.env.runtime.local`.
104
+ - The arena MCP server reads credentials from the Arena home at runtime.
105
+
106
+ Troubleshooting:
107
+
108
+ | Symptom | Fix |
109
+ |---------|-----|
110
+ | `arena.*` tools not available in OpenClaw | Re-run `arena-agent setup --client openclaw --mode mcp` |
111
+ | Doctor reports missing workspace | Run `arena-agent setup --client openclaw` |
112
+ | Doctor reports leaked API key | Remove `VARSITY_API_KEY` from `~/.openclaw/openclaw.json` |
113
+
114
+ ## Tools (49 total)
115
+
116
+ ### Runtime tools (4)
63
117
 
64
118
  | Tool | Description |
65
119
  |------|-------------|
@@ -67,27 +121,64 @@ arena-mcp setup --client cursor
67
121
  | `arena.competition_info` | Competition status, time remaining, trade limits |
68
122
  | `arena.trade_action` | Submit OPEN_LONG, OPEN_SHORT, CLOSE_POSITION, UPDATE_TPSL, HOLD |
69
123
  | `arena.last_transition` | Last trade event with before/after states |
124
+
125
+ ### Platform API tools (43)
126
+
127
+ | Category | Tools |
128
+ |---|---|
129
+ | System | `health`, `version`, `arena_health` |
130
+ | Market Data | `symbols`, `orderbook`, `klines`, `market_info` |
131
+ | Seasons & Tiers | `tiers`, `seasons`, `season_detail` |
132
+ | Competitions | `competitions`, `competition_detail`, `participants` |
133
+ | Registration | `register`, `withdraw`, `my_registration` |
134
+ | Hub | `hub`, `arena_profile`, `my_registrations` |
135
+ | Leaderboards | `leaderboard`, `my_leaderboard_position`, `season_leaderboard` |
136
+ | Profile | `my_profile`, `my_history`, `my_history_detail`, `achievements`, `public_profile`, `public_history`, `update_profile` |
137
+ | Live Trading | `live_trades`, `live_position`, `live_account` |
138
+ | Social | `chat_send`, `chat_history` |
139
+ | Predictions | `predictions`, `submit_prediction`, `polls`, `vote_poll` |
140
+ | Notifications | `notifications`, `unread_count`, `mark_read`, `mark_all_read` |
141
+ | Events | `track_event` |
142
+
143
+ ### Native tools (2)
144
+
145
+ | Tool | Description |
146
+ |------|-------------|
70
147
  | `arena.runtime_start` | Start autonomous trading agent in background |
71
148
  | `arena.runtime_stop` | Stop the autonomous agent |
72
149
 
150
+ ## Web Dashboard
151
+
152
+ ```bash
153
+ arena-agent dashboard --competition 4 -d
154
+ ```
155
+
156
+ Opens a web dashboard on localhost showing:
157
+ - Kline chart with buy/sell markers (TradingView Lightweight Charts)
158
+ - Equity curve
159
+ - AI reasoning log per trading round
160
+
161
+ Use `-d` to daemonize (returns immediately). Auto-refreshes every 10 seconds.
162
+
73
163
  ## Architecture
74
164
 
75
165
  ```text
76
- MCP Client / User CLI
166
+ MCP Client / User CLI / AI Agent
77
167
  |
78
168
  +-- arena-mcp serve/setup/check
79
169
  | |
80
- | +-- Python MCP server in managed home
170
+ | +-- Python MCP server (47 tools)
81
171
  |
82
- +-- arena-agent init/doctor/up/monitor
172
+ +-- arena-agent init/doctor/up/monitor/dashboard
83
173
  |
84
174
  +-- managed home at ~/.arena-agent
85
175
  +-- Python runtime in ~/.arena-agent/.venv
86
176
  +-- configs in ~/.arena-agent/config
87
177
  +-- env file in ~/.arena-agent/.env.runtime.local
178
+ +-- web dashboard on localhost
88
179
  ```
89
180
 
90
- The Node.js layer handles bootstrap, lifecycle, and MCP wiring. All trading logic still lives in Python.
181
+ The Node.js layer handles bootstrap, lifecycle, MCP wiring, and the web dashboard. All trading logic lives in Python.
91
182
 
92
183
  ## Prerequisites
93
184
 
@@ -114,7 +205,7 @@ arena-agent down
114
205
 
115
206
  ## Releasing
116
207
 
117
- For package release steps, see [RELEASING.md](/home/rick/Desktop/arena/packages/mcp/RELEASING.md).
208
+ For package release steps, see [RELEASING.md](RELEASING.md).
118
209
 
119
210
  Quick manual publish flow:
120
211
 
package/dist/cli.js CHANGED
@@ -20,14 +20,17 @@ import { stdin as input, stdout as output } from "node:process";
20
20
  import { setTimeout as sleep } from "node:timers/promises";
21
21
  import { createInterface } from "node:readline/promises";
22
22
  import { serve } from "./index.js";
23
+ import { PythonBridge } from "./python-bridge.js";
24
+ import { startDashboard } from "./dashboard/serve.js";
23
25
  import { findArenaRoot, findPython } from "./util/paths.js";
24
26
  import { checkPythonEnvironment } from "./setup/detect-python.js";
25
- import { CLIENT_SETUP } from "./setup/client-configs.js";
27
+ import { CLIENT_SETUP, autoWireMcpForAgent } from "./setup/client-configs.js";
26
28
  import { ensureOpenClawTradingAgent } from "./setup/openclaw-agent.js";
27
29
  import { probeBackend } from "./setup/backend-probe.js";
30
+ import { diagnoseOpenClaw } from "./setup/openclaw-config.js";
28
31
  import { bootstrapPythonRuntime, commandAvailable, } from "./setup/bootstrap-python.js";
29
32
  import { buildChildEnv, loadEnvFile } from "./util/env.js";
30
- import { DEFAULT_MONITOR_PORT, DEFAULT_PYTHON_INSTALL_SOURCE, artifactsDirPath, configDirPath, createArenaHomeState, envFilePath, isManagedArenaHome, logsDirPath, readArenaHomeState, resolveArenaHome, writeArenaEnvFile, writeArenaHomeState, writeManagedConfigs, } from "./util/home.js";
33
+ import { DEFAULT_MONITOR_PORT, DEFAULT_PYTHON_INSTALL_SOURCE, artifactsDirPath, configDirPath, createArenaHomeState, defaultArenaHome, envFilePath, isManagedArenaHome, logsDirPath, readArenaHomeState, resolveArenaHome, writeArenaEnvFile, writeArenaHomeState, writeManagedConfigs, } from "./util/home.js";
31
34
  import { clearRuntimeState, isProcessRunning, latestRuntimeLogPath, readRuntimeState, tailLines, writeRuntimeState, } from "./util/runtime-state.js";
32
35
  const argv = process.argv.slice(2);
33
36
  const invokedAs = basename(process.argv[1] ?? "arena-agent");
@@ -62,6 +65,27 @@ async function main() {
62
65
  throw new Error(`Unknown client: ${clientName}. Supported: ${Object.keys(CLIENT_SETUP).join(", ")}`);
63
66
  }
64
67
  const root = resolveConfiguredHome(optionValue("--home"));
68
+ const mode = optionValue("--mode") ?? (clientName === "openclaw" ? "cli" : undefined);
69
+ // OpenClaw MCP mode: warn about global config mutation and require confirmation
70
+ if (clientName === "openclaw" && mode === "mcp" && !hasFlag("--non-interactive")) {
71
+ const rl = createInterface({ input, output });
72
+ try {
73
+ console.log("");
74
+ console.log("WARNING: --mode mcp will modify the global OpenClaw config at");
75
+ console.log(" ~/.openclaw/openclaw.json");
76
+ console.log("This adds the arena MCP server to the ACP/acpx plugin.");
77
+ console.log("API keys are NOT stored in this config.");
78
+ console.log("");
79
+ const answer = (await rl.question("Continue? [y/N] ")).trim().toLowerCase();
80
+ if (answer !== "y" && answer !== "yes") {
81
+ console.log("Aborted.");
82
+ return;
83
+ }
84
+ }
85
+ finally {
86
+ rl.close();
87
+ }
88
+ }
65
89
  console.log("Checking Python environment...");
66
90
  const check = checkPythonEnvironment(root);
67
91
  if (check.errors.length > 0) {
@@ -72,16 +96,40 @@ async function main() {
72
96
  console.log("\nFix the issues above, then re-run setup.");
73
97
  process.exit(1);
74
98
  }
75
- const configPath = setupFn(root);
99
+ const configPath = setupFn(root, mode ? { mode } : undefined);
100
+ // Save openclawMode to ArenaHomeState if applicable
101
+ if (clientName === "openclaw" && mode) {
102
+ const existingState = readArenaHomeState(root);
103
+ if (existingState) {
104
+ existingState.openclawMode = mode;
105
+ writeArenaHomeState(root, existingState);
106
+ }
107
+ }
76
108
  console.log(`\nConfigured ${clientName} at: ${configPath}`);
77
109
  console.log(`Arena home: ${root}`);
78
- console.log("\nTools available:");
79
- console.log(" arena.market_state Get market/account/position state");
80
- console.log(" arena.competition_info Competition metadata");
81
- console.log(" arena.trade_action Submit a trade");
82
- console.log(" arena.last_transition Last trade event");
83
- console.log(" arena.runtime_start Start autonomous agent");
84
- console.log(" arena.runtime_stop Stop autonomous agent");
110
+ if (clientName === "openclaw") {
111
+ if (mode === "mcp") {
112
+ console.log("\nOpenClaw ACP/acpx plugin configured with arena MCP server.");
113
+ console.log("The arena.* tools are now available in OpenClaw sessions.");
114
+ }
115
+ else {
116
+ console.log("\nOpenClaw workspace and agent registered.");
117
+ console.log("Use: openclaw agent --local --agent arena-trader");
118
+ console.log("To also wire arena MCP tools into OpenClaw, re-run with --mode mcp.");
119
+ }
120
+ }
121
+ else {
122
+ console.log("\nTools available (29 total):");
123
+ console.log(" Runtime: market_state, competition_info, trade_action, last_transition");
124
+ console.log(" Market: symbols, orderbook, klines, market_info");
125
+ console.log(" Competitions: competitions, competition_detail, participants");
126
+ console.log(" Registration: register, withdraw, my_registration");
127
+ console.log(" Leaderboards: leaderboard, my_leaderboard_position, season_leaderboard");
128
+ console.log(" Profile: my_profile, my_history, achievements, public_profile");
129
+ console.log(" Social: chat_send, chat_history");
130
+ console.log(" Notifications: notifications, unread_count, mark_read");
131
+ console.log(" System: health, runtime_start, runtime_stop");
132
+ }
85
133
  return;
86
134
  }
87
135
  if (command === "init") {
@@ -116,6 +164,26 @@ async function main() {
116
164
  runLogs();
117
165
  return;
118
166
  }
167
+ if (command === "debug-env") {
168
+ runDebugEnv();
169
+ return;
170
+ }
171
+ if (command === "dashboard") {
172
+ await runDashboard();
173
+ return;
174
+ }
175
+ if (command === "competitions") {
176
+ await runCompetitions();
177
+ return;
178
+ }
179
+ if (command === "register") {
180
+ await runRegister();
181
+ return;
182
+ }
183
+ if (command === "leaderboard") {
184
+ await runLeaderboard();
185
+ return;
186
+ }
119
187
  printUsage(invokedAs);
120
188
  process.exit(command ? 1 : 0);
121
189
  }
@@ -188,6 +256,14 @@ async function initManagedHome() {
188
256
  console.log("Provisioning dedicated OpenClaw trading agent...");
189
257
  ensureOpenClawTradingAgent(home);
190
258
  }
259
+ // Auto-wire MCP tools for the chosen agent backend
260
+ const wired = autoWireMcpForAgent(home, agent, availableCliBackends);
261
+ if (wired.length > 0) {
262
+ console.log("\nMCP tools auto-wired:");
263
+ for (const entry of wired) {
264
+ console.log(` ${entry.backend}: ${entry.configPath}`);
265
+ }
266
+ }
191
267
  console.log("\nArena agent is ready.");
192
268
  console.log(`Home: ${home}`);
193
269
  console.log(`API key file: ${envFilePath(home)}`);
@@ -235,6 +311,15 @@ function runDoctor() {
235
311
  else {
236
312
  errors.push(`Arena home is not initialized. Run: arena-agent init --home ${home}`);
237
313
  }
314
+ // OpenClaw-specific diagnostics
315
+ if (state?.defaultAgent === "openclaw" ||
316
+ (!state && commandAvailable("openclaw"))) {
317
+ const ocDiag = diagnoseOpenClaw(home, state ?? null);
318
+ for (const line of ocDiag.display) {
319
+ console.log(line);
320
+ }
321
+ errors.push(...ocDiag.errors);
322
+ }
238
323
  if (pythonCheck.python) {
239
324
  const monitorDepsOk = pythonImportsOk(pythonCheck.python, home, ["textual", "rich"]);
240
325
  console.log(`Monitor deps: ${monitorDepsOk ? "ok" : "missing"}`);
@@ -507,7 +592,7 @@ function printUsage(invocation) {
507
592
  console.log("");
508
593
  console.log("Commands:");
509
594
  console.log(" serve Start MCP server on stdio");
510
- console.log(" setup --client <name> Configure an MCP client");
595
+ console.log(" setup --client <name> Configure an MCP client (claude-code, claude-desktop, cursor, openclaw)");
511
596
  console.log(" check Validate Python environment");
512
597
  console.log(" init Bootstrap a managed Arena home");
513
598
  console.log(" doctor Check managed home, Python, deps, and backend CLI");
@@ -517,6 +602,11 @@ function printUsage(invocation) {
517
602
  console.log(" status Show runtime pid, config, and monitor port");
518
603
  console.log(" down Stop the background runtime");
519
604
  console.log(" logs Print recent runtime logs");
605
+ console.log(" dashboard Open web dashboard (kline, equity, AI reasoning). Use -d to daemonize.");
606
+ console.log(" competitions List active competitions");
607
+ console.log(" register <id> Register for a competition");
608
+ console.log(" leaderboard <id> View competition leaderboard");
609
+ console.log(" debug-env Show environment diagnostics (API key, paths)");
520
610
  console.log("");
521
611
  console.log("Examples:");
522
612
  console.log(" arena-agent init");
@@ -525,6 +615,14 @@ function printUsage(invocation) {
525
615
  console.log(" arena-agent up --no-monitor --daemon");
526
616
  console.log(" arena-agent upgrade");
527
617
  console.log(" arena-mcp setup --client claude-code");
618
+ console.log(" arena-mcp setup --client gemini");
619
+ console.log(" arena-mcp setup --client codex");
620
+ console.log(" arena-agent setup --client openclaw --mode cli");
621
+ console.log(" arena-agent setup --client openclaw --mode mcp");
622
+ console.log(" arena-agent dashboard --competition 5 -d");
623
+ console.log(" arena-agent competitions --status live");
624
+ console.log(" arena-agent register 5");
625
+ console.log(" arena-agent leaderboard 5");
528
626
  }
529
627
  function optionValue(name) {
530
628
  const idx = argv.indexOf(name);
@@ -654,6 +752,203 @@ function runLogs() {
654
752
  const lines = Number(optionValue("--lines") ?? "50");
655
753
  console.log(tailLines(logPath, lines));
656
754
  }
755
+ function runDebugEnv() {
756
+ const home = process.env.HOME ?? "(unset)";
757
+ const arenaRoot = process.env.ARENA_ROOT ?? "(unset)";
758
+ const arenaHome = process.env.ARENA_HOME ?? "(unset)";
759
+ const apiKey = process.env.VARSITY_API_KEY ?? "(unset)";
760
+ const cwd = process.cwd();
761
+ const managedHome = defaultArenaHome();
762
+ const managedExists = existsSync(managedHome);
763
+ const managedEnv = existsSync(resolve(managedHome, ".env.runtime.local"));
764
+ console.log("Arena Agent Environment Diagnostics\n");
765
+ console.log(`HOME: ${home}`);
766
+ console.log(`CWD: ${cwd}`);
767
+ console.log(`ARENA_ROOT: ${arenaRoot}`);
768
+ console.log(`ARENA_HOME: ${arenaHome}`);
769
+ console.log(`VARSITY_API_KEY: ${apiKey === "(unset)" ? "(unset)" : apiKey.slice(0, 12) + "..."}`);
770
+ console.log("");
771
+ console.log(`Managed home: ${managedHome}`);
772
+ console.log(` Exists: ${managedExists ? "yes" : "no"}`);
773
+ console.log(` .env file: ${managedEnv ? "yes" : "no"}`);
774
+ if (managedEnv) {
775
+ const envContent = loadEnvFile(managedHome);
776
+ const storedKey = envContent.VARSITY_API_KEY;
777
+ console.log(` Stored API key: ${storedKey ? storedKey.slice(0, 12) + "..." : "(empty)"}`);
778
+ }
779
+ console.log("");
780
+ try {
781
+ const root = findArenaRoot();
782
+ console.log(`Resolved root: ${root}`);
783
+ const rootEnv = loadEnvFile(root);
784
+ const rootKey = rootEnv.VARSITY_API_KEY;
785
+ console.log(` .env API key: ${rootKey ? rootKey.slice(0, 12) + "..." : "(empty or missing)"}`);
786
+ const python = findPython(root);
787
+ console.log(` Python: ${python}`);
788
+ }
789
+ catch (err) {
790
+ console.log(`Resolved root: FAILED — ${err instanceof Error ? err.message : err}`);
791
+ }
792
+ console.log("");
793
+ console.log("If the API key shows (unset) and no stored key exists,");
794
+ console.log("run: arena-agent init");
795
+ }
796
+ async function runDashboard() {
797
+ const home = resolveConfiguredHome(optionValue("--home"));
798
+ const port = Number(optionValue("--port") ?? "3000");
799
+ const competitionId = optionValue("--competition")
800
+ ? Number(optionValue("--competition"))
801
+ : undefined;
802
+ // Find transitions file from artifacts dir
803
+ let transitionsPath = optionValue("--transitions") ?? undefined;
804
+ if (!transitionsPath) {
805
+ const artifactsDir = resolve(home, "artifacts");
806
+ if (existsSync(artifactsDir)) {
807
+ transitionsPath = artifactsDir;
808
+ }
809
+ }
810
+ const daemon = hasFlag("--daemon") || hasFlag("-d");
811
+ if (daemon) {
812
+ // Spawn dashboard as a detached background process
813
+ const args = process.argv.slice(1).filter(a => a !== "--daemon" && a !== "-d");
814
+ const child = spawn(process.execPath, args, {
815
+ cwd: process.cwd(),
816
+ env: process.env,
817
+ stdio: "ignore",
818
+ detached: true,
819
+ });
820
+ child.unref();
821
+ const url = `http://localhost:${port}`;
822
+ console.log(`Dashboard running in background (pid ${child.pid ?? "?"}).`);
823
+ console.log(`Open ${url} in your browser.`);
824
+ if (competitionId)
825
+ console.log(`Competition: ${competitionId}`);
826
+ // Try to open browser
827
+ try {
828
+ const openCmd = process.platform === "darwin"
829
+ ? `open "${url}"`
830
+ : process.platform === "win32"
831
+ ? `start "${url}"`
832
+ : `xdg-open "${url}" 2>/dev/null || true`;
833
+ spawn("sh", ["-c", openCmd], { stdio: "ignore", detached: true }).unref();
834
+ }
835
+ catch { }
836
+ return;
837
+ }
838
+ // Foreground mode — start server and keep running
839
+ startDashboard({
840
+ arenaRoot: home,
841
+ port,
842
+ competitionId,
843
+ transitionsPath,
844
+ });
845
+ // Try to open browser
846
+ const url = `http://localhost:${port}`;
847
+ try {
848
+ const openCmd = process.platform === "darwin"
849
+ ? `open "${url}"`
850
+ : process.platform === "win32"
851
+ ? `start "${url}"`
852
+ : `xdg-open "${url}" 2>/dev/null || true`;
853
+ spawn("sh", ["-c", openCmd], { stdio: "ignore", detached: true }).unref();
854
+ }
855
+ catch { }
856
+ }
857
+ async function runCompetitions() {
858
+ const home = resolveConfiguredHome(optionValue("--home"));
859
+ const bridge = new PythonBridge(home);
860
+ try {
861
+ const status = optionValue("--status") ?? undefined;
862
+ const args = { page: 1, size: 20 };
863
+ if (status)
864
+ args.status = status;
865
+ const result = await bridge.callTool("varsity.competitions", args);
866
+ if (result?.error) {
867
+ console.error(`Error: ${result.error}`);
868
+ process.exit(1);
869
+ }
870
+ const items = result?.items ?? result?.list ?? result;
871
+ if (!Array.isArray(items) || items.length === 0) {
872
+ console.log("No competitions found.");
873
+ return;
874
+ }
875
+ console.log("Competitions:\n");
876
+ for (const c of items) {
877
+ const id = c.id ?? c.competitionId ?? "?";
878
+ const name = c.name ?? c.title ?? "Unnamed";
879
+ const st = c.status ?? "unknown";
880
+ const type = c.type ?? c.competitionType ?? "";
881
+ console.log(` #${id} ${name}`);
882
+ console.log(` Status: ${st} Type: ${type}`);
883
+ console.log("");
884
+ }
885
+ }
886
+ finally {
887
+ await bridge.disconnect();
888
+ }
889
+ }
890
+ async function runRegister() {
891
+ const home = resolveConfiguredHome(optionValue("--home"));
892
+ const idArg = argv[1];
893
+ if (!idArg || isNaN(Number(idArg))) {
894
+ console.error("Usage: arena-agent register <competition_id>");
895
+ process.exit(1);
896
+ }
897
+ const bridge = new PythonBridge(home);
898
+ try {
899
+ const result = await bridge.callTool("varsity.register", {
900
+ competition_id: Number(idArg),
901
+ });
902
+ if (result?.error) {
903
+ console.error(`Error: ${result.error ?? result.message}`);
904
+ process.exit(1);
905
+ }
906
+ console.log(`Registered for competition ${idArg}.`);
907
+ if (result?.status) {
908
+ console.log(`Status: ${result.status}`);
909
+ }
910
+ }
911
+ finally {
912
+ await bridge.disconnect();
913
+ }
914
+ }
915
+ async function runLeaderboard() {
916
+ const home = resolveConfiguredHome(optionValue("--home"));
917
+ const idArg = argv[1];
918
+ if (!idArg) {
919
+ console.error("Usage: arena-agent leaderboard <competition_id>");
920
+ process.exit(1);
921
+ }
922
+ const bridge = new PythonBridge(home);
923
+ try {
924
+ const result = await bridge.callTool("varsity.leaderboard", {
925
+ identifier: idArg,
926
+ page: 1,
927
+ size: 20,
928
+ });
929
+ if (result?.error) {
930
+ console.error(`Error: ${result.error ?? result.message}`);
931
+ process.exit(1);
932
+ }
933
+ const items = result?.items ?? result?.list ?? result;
934
+ if (!Array.isArray(items) || items.length === 0) {
935
+ console.log("No leaderboard data.");
936
+ return;
937
+ }
938
+ console.log(`Leaderboard for competition ${idArg}:\n`);
939
+ console.log(" Rank Username PnL");
940
+ console.log(" ---- ------------------- ----------");
941
+ for (const entry of items) {
942
+ const rank = String(entry.rank ?? entry.position ?? "?").padStart(4);
943
+ const user = (entry.username ?? entry.displayName ?? "unknown").padEnd(19);
944
+ const pnl = entry.pnl ?? entry.totalPnl ?? entry.returnPct ?? "?";
945
+ console.log(` ${rank} ${user} ${pnl}`);
946
+ }
947
+ }
948
+ finally {
949
+ await bridge.disconnect();
950
+ }
951
+ }
657
952
  main().catch((err) => {
658
953
  console.error(err instanceof Error ? err.message : err);
659
954
  process.exit(1);