@agents-uni/zhenhuan 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,202 @@
1
+ /**
2
+ * Zhen Huan Palace Server - 后宫服务器
3
+ *
4
+ * HTTP API server for the palace competition system.
5
+ * Integrates agents-uni-core Dashboard as the homepage,
6
+ * and injects palace-specific extension panels (ELO, race history, factions, etc.)
7
+ * Uses Hono for lightweight, high-performance routing.
8
+ */
9
+ import { serve } from '@hono/node-server';
10
+ import { Hono } from 'hono';
11
+ import { cors } from 'hono/cors';
12
+ import { logger } from 'hono/logger';
13
+ import { existsSync } from 'node:fs';
14
+ import { resolve } from 'node:path';
15
+ import { PalaceOrchestrator } from '../orchestrator/index.js';
16
+ import { createRoutes } from './routes/index.js';
17
+ import { createDashboardRoutes } from '@agents-uni/core';
18
+ /**
19
+ * Build palace-specific extension panels for the core Dashboard homepage.
20
+ * These show live ELO leaderboard, recent race results, and faction map
21
+ * directly on the dashboard — no need to call raw API.
22
+ */
23
+ function buildPalaceExtension(orchestrator) {
24
+ const extRoutes = new Hono();
25
+ // Extension API: live ELO leaderboard as JSON
26
+ extRoutes.get('/leaderboard', (c) => c.json(orchestrator.getLeaderboard()));
27
+ extRoutes.get('/factions', (c) => c.json(orchestrator.dynamics.getFactions()));
28
+ extRoutes.get('/state', (c) => c.json(orchestrator.getState()));
29
+ const panels = [
30
+ {
31
+ title: '🏆 ELO 排行榜',
32
+ renderHtml: () => {
33
+ const board = orchestrator.getLeaderboard();
34
+ if (board.length === 0) {
35
+ return '<p class="text-gray-500 text-sm">暂无排名数据。先运行一次赛马吧!</p>';
36
+ }
37
+ const rows = board.slice(0, 8).map((r, i) => {
38
+ const medal = i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : `<span class="text-gray-500 w-5 inline-block text-center">${i + 1}</span>`;
39
+ const winRate = r.matchCount > 0 ? Math.round(r.winCount / r.matchCount * 100) + '%' : '-';
40
+ return `<tr class="border-b border-gray-700/30">
41
+ <td class="py-1.5 px-2">${medal}</td>
42
+ <td class="py-1.5 px-2 text-white font-medium">${escapeHtml(r.agentId)}</td>
43
+ <td class="py-1.5 px-2 text-yellow-400 text-right">${r.rating}</td>
44
+ <td class="py-1.5 px-2 text-gray-400 text-right">${r.winCount}W ${r.lossCount}L</td>
45
+ <td class="py-1.5 px-2 text-gray-500 text-right">${winRate}</td>
46
+ </tr>`;
47
+ }).join('');
48
+ return `<table class="w-full text-sm">
49
+ <thead><tr class="text-gray-500 text-xs">
50
+ <th class="px-2 py-1 text-left w-8"></th>
51
+ <th class="px-2 py-1 text-left">Agent</th>
52
+ <th class="px-2 py-1 text-right">ELO</th>
53
+ <th class="px-2 py-1 text-right">战绩</th>
54
+ <th class="px-2 py-1 text-right">胜率</th>
55
+ </tr></thead>
56
+ <tbody>${rows}</tbody>
57
+ </table>`;
58
+ },
59
+ },
60
+ {
61
+ title: '⚔️ 势力格局',
62
+ renderHtml: () => {
63
+ const factions = orchestrator.dynamics.getFactions();
64
+ if (factions.length === 0) {
65
+ return '<p class="text-gray-500 text-sm">暂无势力数据。Agent 结盟后会在此显示。</p>';
66
+ }
67
+ return factions.map(f => `
68
+ <div class="flex items-center gap-3 py-2 border-b border-gray-700/30 last:border-0">
69
+ <span class="text-white font-medium">${escapeHtml(f.leader)}</span>
70
+ <span class="text-gray-500">派系</span>
71
+ <span class="text-gray-300 text-sm">${f.members.map((m) => escapeHtml(m)).join(', ')}</span>
72
+ <span class="ml-auto badge bg-accent/20 text-accent-light">影响力 ${f.influence}</span>
73
+ </div>
74
+ `).join('');
75
+ },
76
+ },
77
+ {
78
+ title: '🏛️ 后宫品级',
79
+ renderHtml: () => {
80
+ const state = orchestrator.getState();
81
+ const sorted = [...state.agents].sort((a, b) => b.rankLevel - a.rankLevel);
82
+ if (sorted.length === 0) {
83
+ return '<p class="text-gray-500 text-sm">暂无 Agent 数据。</p>';
84
+ }
85
+ return `<div class="space-y-1.5">${sorted.map(a => {
86
+ const isActive = a.status === 'active' || a.status === 'idle';
87
+ return `<div class="flex items-center gap-2 text-sm">
88
+ <span class="${isActive ? 'text-green-400' : 'text-red-400'}">●</span>
89
+ <span class="text-white font-medium w-20 truncate">${escapeHtml(a.name)}</span>
90
+ <span class="text-cyan-400 w-12">${escapeHtml(a.rank)}</span>
91
+ <span class="text-yellow-400 text-xs">ELO ${a.elo}</span>
92
+ <span class="ml-auto text-purple-400 text-xs">圣宠 ${a.favor}</span>
93
+ </div>`;
94
+ }).join('')}</div>`;
95
+ },
96
+ },
97
+ ];
98
+ // Add cold palace panel if there are inmates
99
+ const coldPalaceInmates = orchestrator.coldPalace.getInmates();
100
+ if (coldPalaceInmates.length > 0) {
101
+ panels.push({
102
+ title: '🏚️ 冷宫',
103
+ renderHtml: () => {
104
+ return coldPalaceInmates.map(inmate => `
105
+ <div class="flex items-center gap-2 py-1.5 text-sm border-b border-gray-700/30 last:border-0">
106
+ <span class="text-gray-500">○</span>
107
+ <span class="text-gray-400">${escapeHtml(inmate.name ?? inmate.agentId)}</span>
108
+ <span class="ml-auto text-gray-600 text-xs">${escapeHtml(inmate.reason)}</span>
109
+ </div>
110
+ `).join('');
111
+ },
112
+ });
113
+ }
114
+ return {
115
+ uniId: 'zhenhuan-palace',
116
+ routes: extRoutes,
117
+ panels,
118
+ };
119
+ }
120
+ function escapeHtml(str) {
121
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
122
+ .replace(/"/g, '&quot;').replace(/'/g, '&#039;');
123
+ }
124
+ export async function startServer(config) {
125
+ const { port, specPath } = config;
126
+ // ─── Validate spec file exists ──────────────────
127
+ const resolvedSpec = resolve(specPath);
128
+ if (!existsSync(resolvedSpec)) {
129
+ console.error('');
130
+ console.error(' \x1b[31m✗ 找不到规范文件:\x1b[0m ' + resolvedSpec);
131
+ console.error('');
132
+ console.error(' \x1b[90m请先创建 universe.yaml,或指定路径:\x1b[0m');
133
+ console.error(' npm run zhenhuan serve -- --spec /path/to/universe.yaml');
134
+ console.error('');
135
+ console.error(' \x1b[90m或使用脚手架生成:\x1b[0m');
136
+ console.error(' npx uni init');
137
+ console.error('');
138
+ process.exit(1);
139
+ }
140
+ // ─── Initialize orchestrator ────────────────────
141
+ let orchestrator;
142
+ try {
143
+ orchestrator = await PalaceOrchestrator.fromSpec(resolvedSpec);
144
+ }
145
+ catch (err) {
146
+ console.error('');
147
+ console.error(' \x1b[31m✗ 规范文件解析失败:\x1b[0m');
148
+ console.error(` ${err instanceof Error ? err.message : String(err)}`);
149
+ console.error('');
150
+ console.error(' \x1b[90m请检查 YAML 格式是否正确。可运行:\x1b[0m');
151
+ console.error(` npx uni validate ${specPath}`);
152
+ console.error('');
153
+ process.exit(1);
154
+ }
155
+ // ─── Create Hono app ───────────────────────────
156
+ const app = new Hono();
157
+ // Middleware
158
+ app.use('*', cors());
159
+ app.use('*', logger());
160
+ // Health check
161
+ app.get('/health', (c) => c.json({ status: 'ok', universe: 'zhenhuan-palace' }));
162
+ // Mount zhenhuan-specific API routes at /api
163
+ const api = createRoutes(orchestrator);
164
+ app.route('/api', api);
165
+ // Build palace extension panels for Dashboard homepage
166
+ const palaceExtension = buildPalaceExtension(orchestrator);
167
+ // Mount core Dashboard with palace extensions injected
168
+ const dashboard = createDashboardRoutes({
169
+ port,
170
+ openclawDir: config.openclawDir,
171
+ extensions: [palaceExtension],
172
+ });
173
+ app.route('/', dashboard);
174
+ const url = `http://localhost:${port}`;
175
+ console.log('');
176
+ console.log(' \x1b[33m╔══════════════════════════════════════╗\x1b[0m');
177
+ console.log(' \x1b[33m║\x1b[0m \x1b[1m\x1b[35m甄嬛后宫 · 你是皇帝\x1b[0m \x1b[33m║\x1b[0m');
178
+ console.log(' \x1b[33m╚══════════════════════════════════════╝\x1b[0m');
179
+ console.log('');
180
+ console.log(` \x1b[36m首页:\x1b[0m ${url}`);
181
+ console.log(` \x1b[36mAPI:\x1b[0m ${url}/api`);
182
+ console.log(` \x1b[36m管理:\x1b[0m ${url}/manage`);
183
+ console.log(` \x1b[36m手册:\x1b[0m ${url}/guide`);
184
+ console.log(` \x1b[36m规范文件:\x1b[0m ${resolvedSpec}`);
185
+ console.log('');
186
+ console.log(` \x1b[90mAgent 数: ${orchestrator.universe.config.agents.length}\x1b[0m`);
187
+ console.log(' \x1b[90m按 Ctrl+C 停止服务\x1b[0m');
188
+ console.log('');
189
+ serve({ fetch: app.fetch, port });
190
+ }
191
+ // ─── Main Entry Point ──────────────────────────────
192
+ // Only start when this file is executed directly (not imported).
193
+ const isDirectExecution = process.argv[1] &&
194
+ resolve(process.argv[1]) === resolve(import.meta.url.replace('file://', ''));
195
+ if (isDirectExecution) {
196
+ const port = parseInt(process.env.PORT || '8089', 10);
197
+ const specPath = process.env.SPEC_PATH || resolve(process.cwd(), 'universe.yaml');
198
+ startServer({ port, specPath }).catch((err) => {
199
+ console.error('❌ 后宫服务器启动失败:', err instanceof Error ? err.message : err);
200
+ process.exit(1);
201
+ });
202
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * API Routes - 后宫 API 路由
3
+ */
4
+ import { Hono } from 'hono';
5
+ import type { PalaceOrchestrator } from '../../orchestrator/index.js';
6
+ export declare function createRoutes(orchestrator: PalaceOrchestrator): Hono;
@@ -0,0 +1,287 @@
1
+ /**
2
+ * API Routes - 后宫 API 路由
3
+ */
4
+ import { Hono } from 'hono';
5
+ import { registerAgentsInOpenClaw } from '@agents-uni/core';
6
+ // ─── Input Validation Helpers ─────────────────
7
+ function validateRequired(obj, fields) {
8
+ for (const field of fields) {
9
+ if (obj[field] === undefined || obj[field] === null || obj[field] === '') {
10
+ return `缺少必填字段: ${field}`;
11
+ }
12
+ }
13
+ return null;
14
+ }
15
+ function validateAgentInputs(agents) {
16
+ if (!Array.isArray(agents) || agents.length === 0) {
17
+ return '至少需要一个 agent';
18
+ }
19
+ for (let i = 0; i < agents.length; i++) {
20
+ const a = agents[i];
21
+ if (!a.id || typeof a.id !== 'string' || a.id.trim() === '') {
22
+ return `agents[${i}].id 不能为空`;
23
+ }
24
+ if (!a.name || typeof a.name !== 'string' || a.name.trim() === '') {
25
+ return `agents[${i}].name 不能为空`;
26
+ }
27
+ if (a.rank !== undefined && (typeof a.rank !== 'number' || a.rank < 0 || a.rank > 100)) {
28
+ return `agents[${i}].rank 应在 0-100 之间`;
29
+ }
30
+ }
31
+ return null;
32
+ }
33
+ export function createRoutes(orchestrator) {
34
+ const app = new Hono();
35
+ // ─── Global Error Handler ──────────────────
36
+ app.onError((err, c) => {
37
+ console.error('[API Error]', err);
38
+ return c.json({ error: err instanceof Error ? err.message : 'Internal server error' }, 500);
39
+ });
40
+ // ─── State ────────────────────────────────
41
+ /** Get current palace state */
42
+ app.get('/state', (c) => {
43
+ return c.json(orchestrator.getState());
44
+ });
45
+ /** Get ELO leaderboard */
46
+ app.get('/leaderboard', (c) => {
47
+ return c.json(orchestrator.getLeaderboard());
48
+ });
49
+ // ─── Agents ───────────────────────────────
50
+ /** Get all agents */
51
+ app.get('/agents', (c) => {
52
+ const state = orchestrator.getState();
53
+ return c.json(state.agents);
54
+ });
55
+ /** Get agent profile */
56
+ app.get('/agents/:id', (c) => {
57
+ const id = c.req.param('id');
58
+ const profile = orchestrator.getAgentProfile(id);
59
+ if (!profile) {
60
+ return c.json({ error: 'Agent not found' }, 404);
61
+ }
62
+ return c.json(profile);
63
+ });
64
+ // ─── Competition ──────────────────────────
65
+ /** Submit a horse race result for evaluation */
66
+ app.post('/race/evaluate', async (c) => {
67
+ const body = await c.req.json();
68
+ if (!body.task)
69
+ return c.json({ error: '缺少 task 字段' }, 400);
70
+ if (!body.entries || !Array.isArray(body.entries) || body.entries.length === 0) {
71
+ return c.json({ error: '缺少 entries 字段或为空' }, 400);
72
+ }
73
+ // Simple scoring judge: scores based on output length and completion time
74
+ const defaultJudge = async (task, entries) => {
75
+ const judgments = entries.map(entry => {
76
+ const criterionScores = new Map();
77
+ // Placeholder scoring - in production, this would call an LLM judge
78
+ for (const criterion of task.criteria) {
79
+ criterionScores.set(criterion.name, 50 + Math.random() * 50);
80
+ }
81
+ let totalScore = 0;
82
+ let totalWeight = 0;
83
+ for (const criterion of task.criteria) {
84
+ const score = criterionScores.get(criterion.name) ?? 50;
85
+ totalScore += score * criterion.weight;
86
+ totalWeight += criterion.weight;
87
+ }
88
+ if (totalWeight > 0) {
89
+ totalScore /= totalWeight;
90
+ }
91
+ return {
92
+ agentId: entry.agentId,
93
+ criterionScores,
94
+ totalScore,
95
+ feedback: `评分完毕`,
96
+ };
97
+ });
98
+ return judgments;
99
+ };
100
+ const result = await orchestrator.runHorseRace(body.task, body.entries, defaultJudge);
101
+ return c.json(result);
102
+ });
103
+ /**
104
+ * Dispatch a task to OpenClaw workspaces, collect submissions, and run the race.
105
+ * This is the full automated pipeline: dispatch → poll → judge → ELO update.
106
+ */
107
+ app.post('/race/dispatch', async (c) => {
108
+ const body = await c.req.json();
109
+ // Validate required fields
110
+ if (!body.task)
111
+ return c.json({ error: '缺少 task 字段' }, 400);
112
+ const taskErr = validateRequired(body.task, ['id', 'title', 'description', 'timeoutMs', 'participants']);
113
+ if (taskErr)
114
+ return c.json({ error: taskErr }, 400);
115
+ if (!Array.isArray(body.task.participants) || body.task.participants.length < 2) {
116
+ return c.json({ error: '至少需要 2 名参赛者' }, 400);
117
+ }
118
+ // Default judge: placeholder scoring (replace with LLM judge in production)
119
+ const defaultJudge = async (task, entries) => {
120
+ const judgments = entries.map(entry => {
121
+ const criterionScores = new Map();
122
+ for (const criterion of task.criteria) {
123
+ criterionScores.set(criterion.name, 50 + Math.random() * 50);
124
+ }
125
+ let totalScore = 0;
126
+ let totalWeight = 0;
127
+ for (const criterion of task.criteria) {
128
+ const score = criterionScores.get(criterion.name) ?? 50;
129
+ totalScore += score * criterion.weight;
130
+ totalWeight += criterion.weight;
131
+ }
132
+ if (totalWeight > 0)
133
+ totalScore /= totalWeight;
134
+ return { agentId: entry.agentId, criterionScores, totalScore, feedback: '评分完毕' };
135
+ });
136
+ return judgments;
137
+ };
138
+ const result = await orchestrator.dispatchAndRace(body.task, defaultJudge);
139
+ return c.json({
140
+ dispatch: {
141
+ taskId: result.dispatch.taskId,
142
+ submitted: result.dispatch.submissions.length,
143
+ timedOut: result.dispatch.timedOut,
144
+ },
145
+ race: result.race
146
+ ? {
147
+ rankings: result.race.rankings,
148
+ narrative: result.race.narrative,
149
+ }
150
+ : null,
151
+ });
152
+ });
153
+ /** Get race history */
154
+ app.get('/race/history', (c) => {
155
+ return c.json(orchestrator.horseRace.getHistory());
156
+ });
157
+ // ─── Ceremonies ───────────────────────────
158
+ /** Trigger a court assembly */
159
+ app.post('/ceremony/court-assembly', async (c) => {
160
+ await orchestrator.runCourtAssembly();
161
+ return c.json({ ok: true, message: '朝会已举行' });
162
+ });
163
+ /** Get ceremony history */
164
+ app.get('/ceremony/history', (c) => {
165
+ return c.json(orchestrator.ceremonies.getHistory());
166
+ });
167
+ // ─── Agent Registration ──────────────────
168
+ /**
169
+ * Selection ceremony: register new agents into the palace + ELO arena.
170
+ * Also auto-registers them in openclaw.json if the workspace exists.
171
+ */
172
+ app.post('/ceremony/selection', async (c) => {
173
+ const body = await c.req.json();
174
+ // Validate input
175
+ const agentErr = validateAgentInputs(body.agents);
176
+ if (agentErr)
177
+ return c.json({ error: agentErr }, 400);
178
+ // Convert to AgentDefinition format expected by conductSelection
179
+ const agentDefs = body.agents.map(a => ({
180
+ id: a.id,
181
+ name: a.name,
182
+ role: { title: a.role ?? '答应', duties: [], permissions: [] },
183
+ rank: a.rank ?? 10,
184
+ }));
185
+ const result = await orchestrator.ceremonies.conductSelection(agentDefs);
186
+ // Register selected agents in ELO arena
187
+ const selectedIds = result.outcomes
188
+ .filter(o => o.action === 'selected')
189
+ .map(o => o.agentId);
190
+ for (const agentId of selectedIds) {
191
+ orchestrator.arena.register(agentId, 1000);
192
+ }
193
+ return c.json({
194
+ ok: true,
195
+ selected: selectedIds,
196
+ failed: result.outcomes
197
+ .filter(o => o.action === 'selection_failed')
198
+ .map(o => ({ agentId: o.agentId, reason: o.details.reason })),
199
+ narrative: result.narrative,
200
+ });
201
+ });
202
+ /**
203
+ * Register agents in openclaw.json without going through the full deploy pipeline.
204
+ * Useful for adding existing agents to OpenClaw.
205
+ */
206
+ app.post('/agents/register', async (c) => {
207
+ const body = await c.req.json();
208
+ const universe = orchestrator.universe.config;
209
+ // Build a minimal universe config with just the agents to register
210
+ const miniConfig = {
211
+ ...universe,
212
+ agents: body.agents.map(a => ({
213
+ id: a.id,
214
+ name: a.name,
215
+ role: { title: '答应', duties: [], permissions: [] },
216
+ rank: 10,
217
+ })),
218
+ };
219
+ const registered = registerAgentsInOpenClaw(miniConfig, body.openclawDir);
220
+ return c.json({ ok: true, registered });
221
+ });
222
+ // ─── Dynamics ─────────────────────────────
223
+ /** Get factions */
224
+ app.get('/factions', (c) => {
225
+ return c.json(orchestrator.dynamics.getFactions());
226
+ });
227
+ /** Form an alliance */
228
+ app.post('/alliance', async (c) => {
229
+ const { agent1, agent2, reason } = await c.req.json();
230
+ const err = validateRequired({ agent1, agent2, reason }, ['agent1', 'agent2', 'reason']);
231
+ if (err)
232
+ return c.json({ error: err }, 400);
233
+ const success = orchestrator.dynamics.formAlliance(agent1, agent2, reason);
234
+ return c.json({ ok: success });
235
+ });
236
+ // ─── Cold Palace ──────────────────────────
237
+ /** Get cold palace inmates */
238
+ app.get('/cold-palace', (c) => {
239
+ return c.json(orchestrator.coldPalace.getInmates());
240
+ });
241
+ /** Banish an agent */
242
+ app.post('/cold-palace/banish', async (c) => {
243
+ const { agentId, reason, durationMs } = await c.req.json();
244
+ const err = validateRequired({ agentId, reason }, ['agentId', 'reason']);
245
+ if (err)
246
+ return c.json({ error: err }, 400);
247
+ const success = await orchestrator.coldPalace.banish(agentId, reason, durationMs);
248
+ return c.json({ ok: success });
249
+ });
250
+ /** Rehabilitate an agent */
251
+ app.post('/cold-palace/rehabilitate', async (c) => {
252
+ const { agentId } = await c.req.json();
253
+ if (!agentId)
254
+ return c.json({ error: '缺少必填字段: agentId' }, 400);
255
+ const success = await orchestrator.coldPalace.rehabilitate(agentId);
256
+ return c.json({ ok: success });
257
+ });
258
+ // ─── Resources ────────────────────────────
259
+ /** Get resource summary for an agent */
260
+ app.get('/resources/:agentId', (c) => {
261
+ const agentId = c.req.param('agentId');
262
+ return c.json(orchestrator.resources.getResourceSummary(agentId));
263
+ });
264
+ /** Grant favor */
265
+ app.post('/resources/favor', async (c) => {
266
+ const { agentId, amount, reason } = await c.req.json();
267
+ const err = validateRequired({ agentId, amount, reason }, ['agentId', 'amount', 'reason']);
268
+ if (err)
269
+ return c.json({ error: err }, 400);
270
+ if (typeof amount !== 'number' || amount <= 0) {
271
+ return c.json({ error: 'amount 必须是正数' }, 400);
272
+ }
273
+ const success = orchestrator.resources.grantFavor(agentId, amount, reason);
274
+ return c.json({ ok: success });
275
+ });
276
+ // ─── Season ───────────────────────────────
277
+ /** Start a new season */
278
+ app.post('/season/start', (c) => {
279
+ const season = orchestrator.seasonEngine.startSeason();
280
+ return c.json(season);
281
+ });
282
+ /** Get all seasons */
283
+ app.get('/season', (c) => {
284
+ return c.json(orchestrator.seasonEngine.getSeasons());
285
+ });
286
+ return app;
287
+ }
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@agents-uni/zhenhuan",
3
+ "version": "0.1.0",
4
+ "description": "Zhen Huan palace drama themed agent competition system built on @agents-uni/core",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "zhenhuan": "dist/cli/index.ts"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "universe.yaml",
20
+ "README.md",
21
+ "README.en.md",
22
+ "DESIGN.md",
23
+ "LICENSE"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsc",
27
+ "test": "vitest run",
28
+ "dev": "tsx --watch src/cli/index.ts serve",
29
+ "start": "tsx src/cli/index.ts serve",
30
+ "serve": "tsx src/cli/index.ts serve",
31
+ "zhenhuan": "tsx src/cli/index.ts",
32
+ "prepublishOnly": "npm run build"
33
+ },
34
+ "keywords": [
35
+ "agents",
36
+ "multi-agent",
37
+ "competition",
38
+ "elo",
39
+ "horse-race",
40
+ "openclaw",
41
+ "ai-agents"
42
+ ],
43
+ "author": "agents-uni",
44
+ "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/agents-uni/zhenhuan.git"
48
+ },
49
+ "homepage": "https://github.com/agents-uni/zhenhuan#readme",
50
+ "bugs": {
51
+ "url": "https://github.com/agents-uni/zhenhuan/issues"
52
+ },
53
+ "dependencies": {
54
+ "@agents-uni/core": "^0.1.0",
55
+ "@hono/node-server": "^1.13.0",
56
+ "chalk": "^5.3.0",
57
+ "commander": "^12.1.0",
58
+ "hono": "^4.6.0"
59
+ },
60
+ "devDependencies": {
61
+ "@types/node": "^22.10.0",
62
+ "tsx": "^4.19.2",
63
+ "typescript": "^5.7.2",
64
+ "vitest": "^2.1.8"
65
+ }
66
+ }