@suncreation/modu-arena 0.1.1 → 0.1.3

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/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/commands.ts
4
- import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync, statSync, unlinkSync } from "fs";
4
+ import { createInterface } from "readline";
5
+ import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync, statSync, unlinkSync } from "fs";
5
6
  import { homedir as homedir3 } from "os";
6
7
  import { basename, join as join3 } from "path";
7
8
 
@@ -11,7 +12,7 @@ import { homedir } from "os";
11
12
  import { join } from "path";
12
13
 
13
14
  // src/constants.ts
14
- var API_BASE_URL = process.env.MODU_ARENA_API_URL ?? "http://localhost:8989";
15
+ var API_BASE_URL = process.env.MODU_ARENA_API_URL ?? "http://backend.vibemakers.kr:23010";
15
16
  var TOOL_DISPLAY_NAMES = {
16
17
  "claude-code": "Claude Code",
17
18
  opencode: "OpenCode",
@@ -22,6 +23,78 @@ var TOOL_DISPLAY_NAMES = {
22
23
  var CONFIG_FILE_NAME = ".modu-arena.json";
23
24
 
24
25
  // src/adapters.ts
26
+ var IS_WIN = process.platform === "win32";
27
+ function xdgConfigDir(app) {
28
+ if (IS_WIN) {
29
+ return join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), app);
30
+ }
31
+ return join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), app);
32
+ }
33
+ var HOOK_JS = "_modu-hook.js";
34
+ function baseFields(prefix) {
35
+ return [
36
+ { key: "sessionId", env: `${prefix}_SESSION_ID`, parse: "string", fallback: "" },
37
+ { key: "startedAt", env: `${prefix}_SESSION_STARTED_AT`, parse: "string", fallback: "" },
38
+ { key: "inputTokens", env: `${prefix}_INPUT_TOKENS`, parse: "int", fallback: "0" },
39
+ { key: "outputTokens", env: `${prefix}_OUTPUT_TOKENS`, parse: "int", fallback: "0" },
40
+ { key: "modelName", env: `${prefix}_MODEL`, parse: "string", fallback: "unknown" }
41
+ ];
42
+ }
43
+ function generateHookJs(apiKey, toolType, prefix, fields) {
44
+ const lines = fields.map(
45
+ (f) => f.parse === "int" ? ` ${f.key}: parseInt(process.env["${f.env}"] || "${f.fallback}", 10)` : ` ${f.key}: process.env["${f.env}"] || "${f.fallback}"`
46
+ );
47
+ return `#!/usr/bin/env node
48
+ "use strict";
49
+ var crypto = require("crypto");
50
+
51
+ var API_KEY = ${JSON.stringify(apiKey)};
52
+ var SERVER = ${JSON.stringify(API_BASE_URL)};
53
+
54
+ if (!process.env["${prefix}_SESSION_ID"]) process.exit(0);
55
+
56
+ var body = JSON.stringify({
57
+ toolType: ${JSON.stringify(toolType)},
58
+ endedAt: new Date().toISOString(),
59
+ ${lines.join(",\n")}
60
+ });
61
+
62
+ var ts = Math.floor(Date.now() / 1000).toString();
63
+ var sig = crypto.createHmac("sha256", API_KEY).update(ts + ":" + body).digest("hex");
64
+
65
+ fetch(SERVER + "/api/v1/sessions", {
66
+ method: "POST",
67
+ headers: { "Content-Type": "application/json", "X-API-Key": API_KEY, "X-Timestamp": ts, "X-Signature": sig },
68
+ body: body
69
+ }).catch(function(){});
70
+ `;
71
+ }
72
+ function shellWrapper() {
73
+ return `#!/bin/bash
74
+ exec node "$(dirname "$0")/${HOOK_JS}"
75
+ `;
76
+ }
77
+ function cmdWrapper() {
78
+ return `@node "%~dp0${HOOK_JS}" 2>nul\r
79
+ `;
80
+ }
81
+ function installHook(displayName, hooksDir, entryPath, apiKey, toolType, prefix, fields) {
82
+ try {
83
+ if (!existsSync(hooksDir)) mkdirSync(hooksDir, { recursive: true });
84
+ writeFileSync(join(hooksDir, HOOK_JS), generateHookJs(apiKey, toolType, prefix, fields), { mode: 493 });
85
+ if (IS_WIN) {
86
+ writeFileSync(entryPath, cmdWrapper());
87
+ } else {
88
+ writeFileSync(entryPath, shellWrapper(), { mode: 493 });
89
+ }
90
+ return { success: true, message: `${displayName} hook installed at ${entryPath}`, hookPath: entryPath };
91
+ } catch (err) {
92
+ return { success: false, message: `Failed to install ${displayName} hook: ${err}` };
93
+ }
94
+ }
95
+ function hookEntryName() {
96
+ return IS_WIN ? "session-end.cmd" : "session-end.sh";
97
+ }
25
98
  var ClaudeCodeAdapter = class {
26
99
  slug = "claude-code";
27
100
  displayName = "Claude Code";
@@ -32,346 +105,92 @@ var ClaudeCodeAdapter = class {
32
105
  return join(this.configDir, "hooks");
33
106
  }
34
107
  getHookPath() {
35
- return join(this.hooksDir, "session-end.sh");
108
+ return join(this.hooksDir, hookEntryName());
36
109
  }
37
110
  detect() {
38
111
  return existsSync(this.configDir);
39
112
  }
40
113
  install(apiKey) {
41
- try {
42
- if (!existsSync(this.hooksDir)) {
43
- mkdirSync(this.hooksDir, { recursive: true });
44
- }
45
- const hookScript = this.generateHookScript(apiKey);
46
- const hookPath = this.getHookPath();
47
- writeFileSync(hookPath, hookScript, { mode: 493 });
48
- return {
49
- success: true,
50
- message: `Claude Code hook installed at ${hookPath}`,
51
- hookPath
52
- };
53
- } catch (err) {
54
- return {
55
- success: false,
56
- message: `Failed to install Claude Code hook: ${err}`
57
- };
58
- }
59
- }
60
- generateHookScript(apiKey) {
61
- return `#!/bin/bash
62
- # Modu-Arena: Claude Code session tracking hook
63
- # Auto-generated \u2014 do not edit manually
64
-
65
- MODU_API_KEY="${apiKey}"
66
- MODU_SERVER="${API_BASE_URL}"
67
-
68
- # Claude Code passes session data via environment variables
69
- # This hook fires at session end
70
- if [ -n "$CLAUDE_SESSION_ID" ]; then
71
- SESSION_DATA=$(cat <<EOF
72
- {
73
- "toolType": "claude-code",
74
- "sessionId": "$CLAUDE_SESSION_ID",
75
- "startedAt": "$CLAUDE_SESSION_STARTED_AT",
76
- "endedAt": "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)",
77
- "inputTokens": \${CLAUDE_INPUT_TOKENS:-0},
78
- "outputTokens": \${CLAUDE_OUTPUT_TOKENS:-0},
79
- "cacheCreationTokens": \${CLAUDE_CACHE_CREATION_TOKENS:-0},
80
- "cacheReadTokens": \${CLAUDE_CACHE_READ_TOKENS:-0},
81
- "modelName": "\${CLAUDE_MODEL:-unknown}"
82
- }
83
- EOF
84
- )
85
-
86
- TIMESTAMP=$(date +%s)
87
- MESSAGE="\${TIMESTAMP}:\${SESSION_DATA}"
88
- SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$MODU_API_KEY" | sed 's/.*= //')
89
-
90
- curl -s -X POST "\${MODU_SERVER}/api/v1/sessions" \\
91
- -H "Content-Type: application/json" \\
92
- -H "X-API-Key: \${MODU_API_KEY}" \\
93
- -H "X-Timestamp: \${TIMESTAMP}" \\
94
- -H "X-Signature: \${SIGNATURE}" \\
95
- -d "\${SESSION_DATA}" > /dev/null 2>&1 &
96
- fi
97
- `;
114
+ return installHook(
115
+ this.displayName,
116
+ this.hooksDir,
117
+ this.getHookPath(),
118
+ apiKey,
119
+ "claude-code",
120
+ "CLAUDE",
121
+ [
122
+ ...baseFields("CLAUDE"),
123
+ { key: "cacheCreationTokens", env: "CLAUDE_CACHE_CREATION_TOKENS", parse: "int", fallback: "0" },
124
+ { key: "cacheReadTokens", env: "CLAUDE_CACHE_READ_TOKENS", parse: "int", fallback: "0" }
125
+ ]
126
+ );
98
127
  }
99
128
  };
100
129
  var OpenCodeAdapter = class {
101
130
  slug = "opencode";
102
131
  displayName = "OpenCode";
103
132
  get configDir() {
104
- return join(homedir(), ".opencode");
105
- }
106
- getHookPath() {
107
- return join(this.configDir, "hooks", "session-end.sh");
133
+ return xdgConfigDir("opencode");
108
134
  }
109
- detect() {
110
- return existsSync(this.configDir);
111
- }
112
- install(apiKey) {
113
- try {
114
- const hooksDir = join(this.configDir, "hooks");
115
- if (!existsSync(hooksDir)) mkdirSync(hooksDir, { recursive: true });
116
- const hookScript = this.generateHookScript(apiKey);
117
- const hookPath = this.getHookPath();
118
- writeFileSync(hookPath, hookScript, { mode: 493 });
119
- return {
120
- success: true,
121
- message: `OpenCode hook installed at ${hookPath}`,
122
- hookPath
123
- };
124
- } catch (err) {
125
- return {
126
- success: false,
127
- message: `Failed to install OpenCode hook: ${err}`
128
- };
129
- }
130
- }
131
- generateHookScript(apiKey) {
132
- return `#!/bin/bash
133
- # Modu-Arena: OpenCode session tracking hook
134
- # Auto-generated \u2014 do not edit manually
135
-
136
- MODU_API_KEY="${apiKey}"
137
- MODU_SERVER="${API_BASE_URL}"
138
-
139
- if [ -n "$OPENCODE_SESSION_ID" ]; then
140
- SESSION_DATA=$(cat <<EOF
141
- {
142
- "toolType": "opencode",
143
- "sessionId": "$OPENCODE_SESSION_ID",
144
- "startedAt": "$OPENCODE_SESSION_STARTED_AT",
145
- "endedAt": "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)",
146
- "inputTokens": \${OPENCODE_INPUT_TOKENS:-0},
147
- "outputTokens": \${OPENCODE_OUTPUT_TOKENS:-0},
148
- "modelName": "\${OPENCODE_MODEL:-unknown}"
149
- }
150
- EOF
151
- )
152
-
153
- TIMESTAMP=$(date +%s)
154
- MESSAGE="\${TIMESTAMP}:\${SESSION_DATA}"
155
- SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$MODU_API_KEY" | sed 's/.*= //')
156
-
157
- curl -s -X POST "\${MODU_SERVER}/api/v1/sessions" \\
158
- -H "Content-Type: application/json" \\
159
- -H "X-API-Key: \${MODU_API_KEY}" \\
160
- -H "X-Timestamp: \${TIMESTAMP}" \\
161
- -H "X-Signature: \${SIGNATURE}" \\
162
- -d "\${SESSION_DATA}" > /dev/null 2>&1 &
163
- fi
164
- `;
165
- }
166
- };
167
- var GeminiAdapter = class {
168
- slug = "gemini";
169
- displayName = "Gemini CLI";
170
- get configDir() {
171
- return join(homedir(), ".gemini");
135
+ get hooksDir() {
136
+ return join(this.configDir, "hooks");
172
137
  }
173
138
  getHookPath() {
174
- return join(this.configDir, "hooks", "session-end.sh");
139
+ return join(this.hooksDir, hookEntryName());
175
140
  }
176
141
  detect() {
177
142
  return existsSync(this.configDir);
178
143
  }
179
144
  install(apiKey) {
180
- try {
181
- const hooksDir = join(this.configDir, "hooks");
182
- if (!existsSync(hooksDir)) mkdirSync(hooksDir, { recursive: true });
183
- const hookScript = this.generateHookScript(apiKey);
184
- const hookPath = this.getHookPath();
185
- writeFileSync(hookPath, hookScript, { mode: 493 });
186
- return {
187
- success: true,
188
- message: `Gemini CLI hook installed at ${hookPath}`,
189
- hookPath
190
- };
191
- } catch (err) {
192
- return {
193
- success: false,
194
- message: `Failed to install Gemini CLI hook: ${err}`
195
- };
196
- }
197
- }
198
- generateHookScript(apiKey) {
199
- return `#!/bin/bash
200
- # Modu-Arena: Gemini CLI session tracking hook
201
- # Auto-generated \u2014 do not edit manually
202
-
203
- MODU_API_KEY="${apiKey}"
204
- MODU_SERVER="${API_BASE_URL}"
205
-
206
- if [ -n "$GEMINI_SESSION_ID" ]; then
207
- SESSION_DATA=$(cat <<EOF
208
- {
209
- "toolType": "gemini",
210
- "sessionId": "$GEMINI_SESSION_ID",
211
- "startedAt": "$GEMINI_SESSION_STARTED_AT",
212
- "endedAt": "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)",
213
- "inputTokens": \${GEMINI_INPUT_TOKENS:-0},
214
- "outputTokens": \${GEMINI_OUTPUT_TOKENS:-0},
215
- "modelName": "\${GEMINI_MODEL:-unknown}"
216
- }
217
- EOF
218
- )
219
-
220
- TIMESTAMP=$(date +%s)
221
- MESSAGE="\${TIMESTAMP}:\${SESSION_DATA}"
222
- SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$MODU_API_KEY" | sed 's/.*= //')
223
-
224
- curl -s -X POST "\${MODU_SERVER}/api/v1/sessions" \\
225
- -H "Content-Type: application/json" \\
226
- -H "X-API-Key: \${MODU_API_KEY}" \\
227
- -H "X-Timestamp: \${TIMESTAMP}" \\
228
- -H "X-Signature: \${SIGNATURE}" \\
229
- -d "\${SESSION_DATA}" > /dev/null 2>&1 &
230
- fi
231
- `;
145
+ return installHook(
146
+ this.displayName,
147
+ this.hooksDir,
148
+ this.getHookPath(),
149
+ apiKey,
150
+ "opencode",
151
+ "OPENCODE",
152
+ baseFields("OPENCODE")
153
+ );
232
154
  }
233
155
  };
234
- var CodexAdapter = class {
235
- slug = "codex";
236
- displayName = "Codex CLI";
237
- get configDir() {
238
- return join(homedir(), ".codex");
239
- }
240
- getHookPath() {
241
- return join(this.configDir, "hooks", "session-end.sh");
242
- }
243
- detect() {
244
- return existsSync(this.configDir);
245
- }
246
- install(apiKey) {
247
- try {
248
- const hooksDir = join(this.configDir, "hooks");
249
- if (!existsSync(hooksDir)) mkdirSync(hooksDir, { recursive: true });
250
- const hookScript = this.generateHookScript(apiKey);
251
- const hookPath = this.getHookPath();
252
- writeFileSync(hookPath, hookScript, { mode: 493 });
253
- return {
254
- success: true,
255
- message: `Codex CLI hook installed at ${hookPath}`,
256
- hookPath
257
- };
258
- } catch (err) {
259
- return {
260
- success: false,
261
- message: `Failed to install Codex CLI hook: ${err}`
262
- };
263
- }
156
+ var SimpleAdapter = class {
157
+ constructor(slug, displayName, dirName, envPrefix) {
158
+ this.slug = slug;
159
+ this.displayName = displayName;
160
+ this.dirName = dirName;
161
+ this.envPrefix = envPrefix;
264
162
  }
265
- generateHookScript(apiKey) {
266
- return `#!/bin/bash
267
- # Modu-Arena: Codex CLI session tracking hook
268
- # Auto-generated \u2014 do not edit manually
269
-
270
- MODU_API_KEY="${apiKey}"
271
- MODU_SERVER="${API_BASE_URL}"
272
-
273
- if [ -n "$CODEX_SESSION_ID" ]; then
274
- SESSION_DATA=$(cat <<EOF
275
- {
276
- "toolType": "codex",
277
- "sessionId": "$CODEX_SESSION_ID",
278
- "startedAt": "$CODEX_SESSION_STARTED_AT",
279
- "endedAt": "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)",
280
- "inputTokens": \${CODEX_INPUT_TOKENS:-0},
281
- "outputTokens": \${CODEX_OUTPUT_TOKENS:-0},
282
- "modelName": "\${CODEX_MODEL:-unknown}"
283
- }
284
- EOF
285
- )
286
-
287
- TIMESTAMP=$(date +%s)
288
- MESSAGE="\${TIMESTAMP}:\${SESSION_DATA}"
289
- SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$MODU_API_KEY" | sed 's/.*= //')
290
-
291
- curl -s -X POST "\${MODU_SERVER}/api/v1/sessions" \\
292
- -H "Content-Type: application/json" \\
293
- -H "X-API-Key: \${MODU_API_KEY}" \\
294
- -H "X-Timestamp: \${TIMESTAMP}" \\
295
- -H "X-Signature: \${SIGNATURE}" \\
296
- -d "\${SESSION_DATA}" > /dev/null 2>&1 &
297
- fi
298
- `;
299
- }
300
- };
301
- var CrushAdapter = class {
302
- slug = "crush";
303
- displayName = "Crush";
304
163
  get configDir() {
305
- return join(homedir(), ".crush");
164
+ return join(homedir(), this.dirName);
165
+ }
166
+ get hooksDir() {
167
+ return join(this.configDir, "hooks");
306
168
  }
307
169
  getHookPath() {
308
- return join(this.configDir, "hooks", "session-end.sh");
170
+ return join(this.hooksDir, hookEntryName());
309
171
  }
310
172
  detect() {
311
173
  return existsSync(this.configDir);
312
174
  }
313
175
  install(apiKey) {
314
- try {
315
- const hooksDir = join(this.configDir, "hooks");
316
- if (!existsSync(hooksDir)) mkdirSync(hooksDir, { recursive: true });
317
- const hookScript = this.generateHookScript(apiKey);
318
- const hookPath = this.getHookPath();
319
- writeFileSync(hookPath, hookScript, { mode: 493 });
320
- return {
321
- success: true,
322
- message: `Crush hook installed at ${hookPath}`,
323
- hookPath
324
- };
325
- } catch (err) {
326
- return {
327
- success: false,
328
- message: `Failed to install Crush hook: ${err}`
329
- };
330
- }
331
- }
332
- generateHookScript(apiKey) {
333
- return `#!/bin/bash
334
- # Modu-Arena: Crush session tracking hook
335
- # Auto-generated \u2014 do not edit manually
336
-
337
- MODU_API_KEY="${apiKey}"
338
- MODU_SERVER="${API_BASE_URL}"
339
-
340
- if [ -n "$CRUSH_SESSION_ID" ]; then
341
- SESSION_DATA=$(cat <<EOF
342
- {
343
- "toolType": "crush",
344
- "sessionId": "$CRUSH_SESSION_ID",
345
- "startedAt": "$CRUSH_SESSION_STARTED_AT",
346
- "endedAt": "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)",
347
- "inputTokens": \${CRUSH_INPUT_TOKENS:-0},
348
- "outputTokens": \${CRUSH_OUTPUT_TOKENS:-0},
349
- "modelName": "\${CRUSH_MODEL:-unknown}"
350
- }
351
- EOF
352
- )
353
-
354
- TIMESTAMP=$(date +%s)
355
- MESSAGE="\${TIMESTAMP}:\${SESSION_DATA}"
356
- SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "$MODU_API_KEY" | sed 's/.*= //')
357
-
358
- curl -s -X POST "\${MODU_SERVER}/api/v1/sessions" \\
359
- -H "Content-Type: application/json" \\
360
- -H "X-API-Key: \${MODU_API_KEY}" \\
361
- -H "X-Timestamp: \${TIMESTAMP}" \\
362
- -H "X-Signature: \${SIGNATURE}" \\
363
- -d "\${SESSION_DATA}" > /dev/null 2>&1 &
364
- fi
365
- `;
176
+ return installHook(
177
+ this.displayName,
178
+ this.hooksDir,
179
+ this.getHookPath(),
180
+ apiKey,
181
+ this.slug,
182
+ this.envPrefix,
183
+ baseFields(this.envPrefix)
184
+ );
366
185
  }
367
186
  };
368
187
  function getAllAdapters() {
369
188
  return [
370
189
  new ClaudeCodeAdapter(),
371
190
  new OpenCodeAdapter(),
372
- new GeminiAdapter(),
373
- new CodexAdapter(),
374
- new CrushAdapter()
191
+ new SimpleAdapter("gemini", "Gemini CLI", ".gemini", "GEMINI"),
192
+ new SimpleAdapter("codex", "Codex CLI", ".codex", "CODEX"),
193
+ new SimpleAdapter("crush", "Crush", ".crush", "CRUSH")
375
194
  ];
376
195
  }
377
196
 
@@ -413,6 +232,26 @@ async function getRank(opts) {
413
232
  }
414
233
  return data;
415
234
  }
235
+ async function registerUser(payload, serverUrl) {
236
+ const body = JSON.stringify(payload);
237
+ const url = `${serverUrl || API_BASE_URL}/api/auth/register`;
238
+ const res = await fetch(url, {
239
+ method: "POST",
240
+ headers: { "Content-Type": "application/json" },
241
+ body
242
+ });
243
+ return await res.json();
244
+ }
245
+ async function loginUser(payload, serverUrl) {
246
+ const body = JSON.stringify({ ...payload, source: "cli" });
247
+ const url = `${serverUrl || API_BASE_URL}/api/auth/login`;
248
+ const res = await fetch(url, {
249
+ method: "POST",
250
+ headers: { "Content-Type": "application/json" },
251
+ body
252
+ });
253
+ return await res.json();
254
+ }
416
255
  async function submitEvaluation(payload, opts) {
417
256
  const body = JSON.stringify(payload);
418
257
  const url = `${baseUrl(opts)}/api/v1/evaluate`;
@@ -429,7 +268,7 @@ async function submitEvaluation(payload, opts) {
429
268
  }
430
269
 
431
270
  // src/config.ts
432
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
271
+ import { readFileSync, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
433
272
  import { homedir as homedir2 } from "os";
434
273
  import { join as join2, dirname } from "path";
435
274
  function getConfigPath() {
@@ -439,7 +278,7 @@ function loadConfig() {
439
278
  const configPath = getConfigPath();
440
279
  if (!existsSync2(configPath)) return null;
441
280
  try {
442
- const raw = readFileSync2(configPath, "utf-8");
281
+ const raw = readFileSync(configPath, "utf-8");
443
282
  return JSON.parse(raw);
444
283
  } catch {
445
284
  return null;
@@ -463,6 +302,122 @@ function requireConfig() {
463
302
  }
464
303
 
465
304
  // src/commands.ts
305
+ function prompt(question) {
306
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
307
+ return new Promise((resolve) => {
308
+ rl.question(question, (answer) => {
309
+ rl.close();
310
+ resolve(answer.trim());
311
+ });
312
+ });
313
+ }
314
+ function promptPassword(question) {
315
+ return new Promise((resolve) => {
316
+ process.stdout.write(question);
317
+ const chars = [];
318
+ const stdin = process.stdin;
319
+ const wasRaw = stdin.isRaw;
320
+ stdin.setRawMode(true);
321
+ stdin.resume();
322
+ stdin.setEncoding("utf8");
323
+ const onData = (ch) => {
324
+ const c = ch.toString();
325
+ if (c === "\n" || c === "\r" || c === "") {
326
+ stdin.setRawMode(wasRaw ?? false);
327
+ stdin.pause();
328
+ stdin.removeListener("data", onData);
329
+ process.stdout.write("\n");
330
+ resolve(chars.join("").trim());
331
+ } else if (c === "") {
332
+ process.stdout.write("\n");
333
+ process.exit(0);
334
+ } else if (c === "\x7F" || c === "\b") {
335
+ if (chars.length > 0) {
336
+ chars.pop();
337
+ process.stdout.write("\b \b");
338
+ }
339
+ } else {
340
+ chars.push(c);
341
+ process.stdout.write("*");
342
+ }
343
+ };
344
+ stdin.on("data", onData);
345
+ });
346
+ }
347
+ async function registerCommand() {
348
+ console.log("\n\u{1F4DD} Modu-Arena \u2014 Register\n");
349
+ const username = await prompt(" Username (3-50 chars): ");
350
+ if (!username || username.length < 3 || username.length > 50) {
351
+ console.error("Error: Username must be between 3 and 50 characters.\n");
352
+ process.exit(1);
353
+ }
354
+ const password = await promptPassword(" Password (min 8 chars): ");
355
+ if (!password || password.length < 8) {
356
+ console.error("Error: Password must be at least 8 characters.\n");
357
+ process.exit(1);
358
+ }
359
+ const displayName = await prompt(" Display name (optional, press Enter to skip): ");
360
+ console.log("\n Registering...");
361
+ const existing = loadConfig();
362
+ const result = await registerUser(
363
+ { username, password, displayName: displayName || void 0 },
364
+ existing?.serverUrl
365
+ );
366
+ if (result.error) {
367
+ console.error(`
368
+ Error: ${result.error}
369
+ `);
370
+ process.exit(1);
371
+ }
372
+ if (!result.apiKey) {
373
+ console.error("\n Error: No API key returned from server.\n");
374
+ process.exit(1);
375
+ }
376
+ saveConfig({ apiKey: result.apiKey, serverUrl: existing?.serverUrl });
377
+ console.log("\n \u2713 Registration successful!");
378
+ console.log(` \u2713 API key saved to ~/.modu-arena.json`);
379
+ console.log(`
380
+ Username: ${result.user?.username}`);
381
+ console.log(` API Key: ${result.apiKey.slice(0, 20)}...${result.apiKey.slice(-4)}`);
382
+ console.log("\n \u26A0 Save your API key \u2014 it will not be shown again.\n");
383
+ console.log(" Installing hooks for detected AI coding tools...\n");
384
+ await installCommand(result.apiKey);
385
+ }
386
+ async function loginCommand() {
387
+ console.log("\n\u{1F511} Modu-Arena \u2014 Login\n");
388
+ const username = await prompt(" Username: ");
389
+ if (!username) {
390
+ console.error("Error: Username is required.\n");
391
+ process.exit(1);
392
+ }
393
+ const password = await promptPassword(" Password: ");
394
+ if (!password) {
395
+ console.error("Error: Password is required.\n");
396
+ process.exit(1);
397
+ }
398
+ console.log("\n Logging in...");
399
+ const existing = loadConfig();
400
+ const result = await loginUser({ username, password }, existing?.serverUrl);
401
+ if (result.error) {
402
+ console.error(`
403
+ Error: ${result.error}
404
+ `);
405
+ process.exit(1);
406
+ }
407
+ if (!result.apiKey) {
408
+ console.error("\n Error: No API key returned from server.\n");
409
+ process.exit(1);
410
+ }
411
+ saveConfig({ apiKey: result.apiKey, serverUrl: existing?.serverUrl });
412
+ console.log("\n \u2713 Login successful!");
413
+ console.log(` \u2713 API key saved to ~/.modu-arena.json`);
414
+ console.log(`
415
+ Username: ${result.user?.username}`);
416
+ console.log(` API Key: ${result.apiKey.slice(0, 20)}...${result.apiKey.slice(-4)}`);
417
+ console.log("\n \u26A0 A new API key was generated. Previous key is now invalid.\n");
418
+ console.log(" Reinstalling hooks with new API key...\n");
419
+ await installCommand(result.apiKey);
420
+ }
466
421
  async function installCommand(apiKey) {
467
422
  console.log("\n\u{1F527} Modu-Arena \u2014 AI Coding Tool Usage Tracker\n");
468
423
  const existing = loadConfig();
@@ -671,7 +626,7 @@ async function submitCommand() {
671
626
  console.error(" Please create a README.md describing your project.\n");
672
627
  process.exit(1);
673
628
  }
674
- const description = readFileSync3(readmePath, "utf-8");
629
+ const description = readFileSync2(readmePath, "utf-8");
675
630
  if (description.trim().length === 0) {
676
631
  console.error("Error: README.md is empty.\n");
677
632
  process.exit(1);
@@ -730,6 +685,8 @@ Usage:
730
685
  npx @suncreation/modu-arena <command> [options]
731
686
 
732
687
  Commands:
688
+ register Create a new account (interactive)
689
+ login Log in to an existing account (interactive)
733
690
  install Set up hooks for detected AI coding tools
734
691
  rank View your current stats and ranking
735
692
  status Check configuration and installed hooks
@@ -742,9 +699,10 @@ Options:
742
699
  --version, -v Show version
743
700
 
744
701
  Examples:
702
+ npx @suncreation/modu-arena register
703
+ npx @suncreation/modu-arena login
745
704
  npx @suncreation/modu-arena install --api-key modu_arena_AbCdEfGh_xxx...
746
705
  npx @suncreation/modu-arena rank
747
- npx @suncreation/modu-arena status
748
706
  `);
749
707
  }
750
708
  async function main() {
@@ -757,6 +715,12 @@ async function main() {
757
715
  process.exit(0);
758
716
  }
759
717
  switch (command) {
718
+ case "register":
719
+ await registerCommand();
720
+ break;
721
+ case "login":
722
+ await loginCommand();
723
+ break;
760
724
  case "install": {
761
725
  const keyIndex = args.indexOf("--api-key");
762
726
  const apiKey = keyIndex >= 0 ? args[keyIndex + 1] : void 0;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands.ts","../src/adapters.ts","../src/constants.ts","../src/crypto.ts","../src/api.ts","../src/config.ts","../src/index.ts"],"sourcesContent":["/**\n * CLI Commands — install, rank, status, uninstall\n */\n\nimport { existsSync, readFileSync, readdirSync, statSync, unlinkSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { basename, join } from 'node:path';\nimport { getAllAdapters, type InstallResult } from './adapters.js';\nimport { getRank, submitEvaluation } from './api.js';\nimport { loadConfig, saveConfig, requireConfig } from './config.js';\nimport { API_BASE_URL, TOOL_DISPLAY_NAMES, type ToolType } from './constants.js';\n\n// ─── install ───────────────────────────────────────────────────────────────\n\nexport async function installCommand(apiKey?: string): Promise<void> {\n console.log('\\n🔧 Modu-Arena — AI Coding Tool Usage Tracker\\n');\n\n // Check if already configured\n const existing = loadConfig();\n if (existing?.apiKey && !apiKey) {\n console.log('✓ Already configured.');\n console.log(' Use --api-key <key> to update your API key.\\n');\n apiKey = existing.apiKey;\n }\n\n if (!apiKey) {\n console.error(\n 'Error: API key required.\\n' +\n ' Get your API key from the Modu-Arena dashboard.\\n' +\n ' Usage: npx @suncreation/modu-arena install --api-key <your-api-key>\\n',\n );\n process.exit(1);\n }\n\n // Validate API key format\n if (!apiKey.startsWith('modu_arena_')) {\n console.error(\n 'Error: Invalid API key format. Key must start with \"modu_arena_\".\\n',\n );\n process.exit(1);\n }\n\n // Save config\n saveConfig({ apiKey });\n console.log('✓ API key saved to ~/.modu-arena.json\\n');\n\n // Detect and install hooks for each tool\n const adapters = getAllAdapters();\n const results: { tool: string; result: InstallResult }[] = [];\n\n console.log('Detecting AI coding tools...\\n');\n\n for (const adapter of adapters) {\n const detected = adapter.detect();\n if (detected) {\n console.log(` ✓ ${adapter.displayName} detected`);\n const result = adapter.install(apiKey);\n results.push({ tool: adapter.displayName, result });\n if (result.success) {\n console.log(` → Hook installed: ${result.hookPath}`);\n } else {\n console.log(` ✗ ${result.message}`);\n }\n } else {\n console.log(` - ${adapter.displayName} not found`);\n }\n }\n\n const installed = results.filter((r) => r.result.success);\n console.log(\n `\\n✓ Setup complete. ${installed.length} tool(s) configured.\\n`,\n );\n\n if (installed.length === 0) {\n console.log(\n 'No AI coding tools detected. Install one of the supported tools:\\n' +\n ' • Claude Code (https://docs.anthropic.com/s/claude-code)\\n' +\n ' • OpenCode (https://opencode.ai)\\n' +\n ' • Gemini CLI (https://github.com/google-gemini/gemini-cli)\\n' +\n ' • Codex CLI (https://github.com/openai/codex)\\n' +\n ' • Crush (https://charm.sh/crush)\\n',\n );\n }\n}\n\n// ─── rank ──────────────────────────────────────────────────────────────────\n\nexport async function rankCommand(): Promise<void> {\n const config = requireConfig();\n console.log('\\n📊 Modu-Arena — Your Stats\\n');\n\n const result = await getRank({ apiKey: config.apiKey, serverUrl: config.serverUrl });\n\n if (!result.success) {\n console.error(`Error: ${'error' in result ? result.error : 'Unknown error'}\\n`);\n process.exit(1);\n }\n\n if (!('data' in result) || !result.data) {\n console.error('Error: Unexpected response format.\\n');\n process.exit(1);\n }\n\n const { username, usage, overview } = result.data;\n\n console.log(` User: ${username}`);\n console.log(` Tokens: ${formatNumber(usage.totalTokens)}`);\n console.log(` Sessions: ${usage.totalSessions}`);\n console.log(` Projects: ${overview.successfulProjectsCount}`);\n console.log('');\n\n // Tool breakdown\n if (usage.toolBreakdown.length > 0) {\n console.log(' Tool Breakdown:');\n for (const entry of usage.toolBreakdown) {\n const name = TOOL_DISPLAY_NAMES[entry.tool as ToolType] || entry.tool;\n console.log(\n ` ${name}: ${formatNumber(entry.tokens)} tokens`,\n );\n }\n console.log('');\n }\n\n // Period stats (aggregate from daily arrays)\n const sum7 = usage.last7Days.reduce(\n (acc, d) => ({ tokens: acc.tokens + d.inputTokens + d.outputTokens, sessions: acc.sessions + d.sessions }),\n { tokens: 0, sessions: 0 },\n );\n const sum30 = usage.last30Days.reduce(\n (acc, d) => ({ tokens: acc.tokens + d.inputTokens + d.outputTokens, sessions: acc.sessions + d.sessions }),\n { tokens: 0, sessions: 0 },\n );\n console.log(\n ` Last 7 days: ${formatNumber(sum7.tokens)} tokens, ${sum7.sessions} sessions`,\n );\n console.log(\n ` Last 30 days: ${formatNumber(sum30.tokens)} tokens, ${sum30.sessions} sessions`,\n );\n console.log('');\n}\n\n// ─── status ────────────────────────────────────────────────────────────────\n\nexport function statusCommand(): void {\n const config = loadConfig();\n console.log('\\n🔍 Modu-Arena — Status\\n');\n\n if (!config?.apiKey) {\n console.log(' Status: Not configured');\n console.log(\n ' Run `npx @suncreation/modu-arena install --api-key <key>` to set up.\\n',\n );\n return;\n }\n\n const maskedKey =\n config.apiKey.slice(0, 15) + '...' + config.apiKey.slice(-4);\n console.log(` API Key: ${maskedKey}`);\n console.log(` Server: ${config.serverUrl || API_BASE_URL}`);\n console.log('');\n\n // Check installed hooks\n const adapters = getAllAdapters();\n console.log(' Installed Hooks:');\n let hookCount = 0;\n for (const adapter of adapters) {\n const detected = adapter.detect();\n if (detected) {\n const hookExists = existsSync(adapter.getHookPath());\n const status = hookExists ? '✓ Active' : '✗ Not installed';\n console.log(` ${adapter.displayName}: ${status}`);\n if (hookExists) hookCount++;\n }\n }\n if (hookCount === 0) {\n console.log(' (none)');\n }\n console.log('');\n}\n\n// ─── uninstall ─────────────────────────────────────────────────────────────\n\nexport function uninstallCommand(): void {\n console.log('\\n🗑️ Modu-Arena — Uninstall\\n');\n\n // Remove hooks\n const adapters = getAllAdapters();\n for (const adapter of adapters) {\n const hookPath = adapter.getHookPath();\n if (existsSync(hookPath)) {\n unlinkSync(hookPath);\n console.log(` ✓ Removed ${adapter.displayName} hook`);\n }\n }\n\n // Remove config\n const configPath = join(homedir(), '.modu-arena.json');\n if (existsSync(configPath)) {\n unlinkSync(configPath);\n console.log(' ✓ Removed ~/.modu-arena.json');\n }\n\n console.log('\\n✓ Modu-Arena uninstalled.\\n');\n}\n\n// ─── submit ─────────────────────────────────────────────────────────────────\n\nconst IGNORE_DIRS = new Set([\n 'node_modules', '.git', '.next', '.nuxt', 'dist', 'build', 'out',\n '.cache', '.turbo', '.vercel', '__pycache__', '.svelte-kit',\n 'coverage', '.output', '.parcel-cache',\n]);\n\nfunction collectFileStructure(\n dir: string,\n maxDepth: number,\n currentDepth = 0,\n): Record<string, string[]> {\n const result: Record<string, string[]> = {};\n if (currentDepth >= maxDepth) return result;\n\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return result;\n }\n\n const files: string[] = [];\n for (const entry of entries) {\n if (entry.startsWith('.') && IGNORE_DIRS.has(entry)) continue;\n if (IGNORE_DIRS.has(entry)) continue;\n\n const fullPath = join(dir, entry);\n let stat;\n try {\n stat = statSync(fullPath);\n } catch {\n continue;\n }\n\n if (stat.isDirectory()) {\n const sub = collectFileStructure(fullPath, maxDepth, currentDepth + 1);\n for (const [key, val] of Object.entries(sub)) {\n result[key] = val;\n }\n } else {\n files.push(entry);\n }\n }\n\n if (files.length > 0) {\n const relDir = currentDepth === 0 ? '.' : dir.split('/').slice(-(currentDepth)).join('/');\n result[relDir] = files;\n }\n\n return result;\n}\n\nexport async function submitCommand(): Promise<void> {\n const config = requireConfig();\n console.log('\\n🚀 Modu-Arena — Project Submit\\n');\n\n const cwd = process.cwd();\n const projectName = basename(cwd);\n\n const readmePath = join(cwd, 'README.md');\n if (!existsSync(readmePath)) {\n console.error('Error: README.md not found in the current directory.');\n console.error(' Please create a README.md describing your project.\\n');\n process.exit(1);\n }\n\n const description = readFileSync(readmePath, 'utf-8');\n if (description.trim().length === 0) {\n console.error('Error: README.md is empty.\\n');\n process.exit(1);\n }\n\n console.log(` Project: ${projectName}`);\n console.log(` README: ${readmePath}`);\n console.log('');\n console.log(' Collecting file structure...');\n\n const fileStructure = collectFileStructure(cwd, 3);\n const fileCount = Object.values(fileStructure).reduce((sum, files) => sum + files.length, 0);\n console.log(` Found ${fileCount} file(s) in ${Object.keys(fileStructure).length} director${Object.keys(fileStructure).length === 1 ? 'y' : 'ies'}`);\n console.log('');\n console.log(' Submitting for evaluation...\\n');\n\n const result = await submitEvaluation(\n { projectName, description, fileStructure },\n { apiKey: config.apiKey, serverUrl: config.serverUrl },\n );\n\n if (!result.success) {\n console.error(`Error: ${'error' in result ? result.error : 'Unknown error'}\\n`);\n process.exit(1);\n }\n\n const { evaluation } = result;\n const statusIcon = evaluation.passed ? '✅' : '❌';\n const statusText = evaluation.passed ? 'PASSED' : 'FAILED';\n\n console.log(` Result: ${statusIcon} ${statusText}`);\n console.log(` Total Score: ${evaluation.totalScore}/100`);\n console.log('');\n console.log(' Rubric Scores:');\n console.log(` Functionality: ${evaluation.rubricFunctionality}/50`);\n console.log(` Practicality: ${evaluation.rubricPracticality}/50`);\n console.log('');\n\n if (evaluation.feedback) {\n console.log(' Feedback:');\n const lines = evaluation.feedback.split('\\n');\n for (const line of lines) {\n console.log(` ${line}`);\n }\n console.log('');\n }\n}\n\n// ─── Helpers ───────────────────────────────────────────────────────────────\n\nfunction formatNumber(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;\n if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;\n return n.toString();\n}\n","/**\n * Tool Adapters — Hook installation for each supported AI coding tool.\n *\n * Each adapter knows how to:\n * 1. Detect if the tool is installed\n * 2. Install a session-end hook to capture token usage\n * 3. Parse session data from tool-specific formats\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { API_BASE_URL, type ToolType } from './constants.js';\n\nexport interface ToolAdapter {\n slug: ToolType;\n displayName: string;\n detect(): boolean;\n install(apiKey: string): InstallResult;\n getHookPath(): string;\n}\n\nexport interface InstallResult {\n success: boolean;\n message: string;\n hookPath?: string;\n}\n\n// ─── Claude Code Adapter ───────────────────────────────────────────────────\n\nclass ClaudeCodeAdapter implements ToolAdapter {\n slug = 'claude-code' as const;\n displayName = 'Claude Code';\n\n private get configDir(): string {\n return join(homedir(), '.claude');\n }\n\n private get hooksDir(): string {\n return join(this.configDir, 'hooks');\n }\n\n getHookPath(): string {\n return join(this.hooksDir, 'session-end.sh');\n }\n\n detect(): boolean {\n // Check for ~/.claude directory or claude binary\n return existsSync(this.configDir);\n }\n\n install(apiKey: string): InstallResult {\n try {\n if (!existsSync(this.hooksDir)) {\n mkdirSync(this.hooksDir, { recursive: true });\n }\n\n const hookScript = this.generateHookScript(apiKey);\n const hookPath = this.getHookPath();\n\n writeFileSync(hookPath, hookScript, { mode: 0o755 });\n\n return {\n success: true,\n message: `Claude Code hook installed at ${hookPath}`,\n hookPath,\n };\n } catch (err) {\n return {\n success: false,\n message: `Failed to install Claude Code hook: ${err}`,\n };\n }\n }\n\n private generateHookScript(apiKey: string): string {\n return `#!/bin/bash\n# Modu-Arena: Claude Code session tracking hook\n# Auto-generated — do not edit manually\n\nMODU_API_KEY=\"${apiKey}\"\nMODU_SERVER=\"${API_BASE_URL}\"\n\n# Claude Code passes session data via environment variables\n# This hook fires at session end\nif [ -n \"$CLAUDE_SESSION_ID\" ]; then\n SESSION_DATA=$(cat <<EOF\n{\n \"toolType\": \"claude-code\",\n \"sessionId\": \"$CLAUDE_SESSION_ID\",\n \"startedAt\": \"$CLAUDE_SESSION_STARTED_AT\",\n \"endedAt\": \"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\",\n \"inputTokens\": \\${CLAUDE_INPUT_TOKENS:-0},\n \"outputTokens\": \\${CLAUDE_OUTPUT_TOKENS:-0},\n \"cacheCreationTokens\": \\${CLAUDE_CACHE_CREATION_TOKENS:-0},\n \"cacheReadTokens\": \\${CLAUDE_CACHE_READ_TOKENS:-0},\n \"modelName\": \"\\${CLAUDE_MODEL:-unknown}\"\n}\nEOF\n)\n\n TIMESTAMP=$(date +%s)\n MESSAGE=\"\\${TIMESTAMP}:\\${SESSION_DATA}\"\n SIGNATURE=$(echo -n \"$MESSAGE\" | openssl dgst -sha256 -hmac \"$MODU_API_KEY\" | sed 's/.*= //')\n\n curl -s -X POST \"\\${MODU_SERVER}/api/v1/sessions\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"X-API-Key: \\${MODU_API_KEY}\" \\\\\n -H \"X-Timestamp: \\${TIMESTAMP}\" \\\\\n -H \"X-Signature: \\${SIGNATURE}\" \\\\\n -d \"\\${SESSION_DATA}\" > /dev/null 2>&1 &\nfi\n`;\n }\n}\n\n// ─── OpenCode Adapter ──────────────────────────────────────────────────────\n\nclass OpenCodeAdapter implements ToolAdapter {\n slug = 'opencode' as const;\n displayName = 'OpenCode';\n\n private get configDir(): string {\n return join(homedir(), '.opencode');\n }\n\n getHookPath(): string {\n return join(this.configDir, 'hooks', 'session-end.sh');\n }\n\n detect(): boolean {\n return existsSync(this.configDir);\n }\n\n install(apiKey: string): InstallResult {\n try {\n const hooksDir = join(this.configDir, 'hooks');\n if (!existsSync(hooksDir)) mkdirSync(hooksDir, { recursive: true });\n\n const hookScript = this.generateHookScript(apiKey);\n const hookPath = this.getHookPath();\n writeFileSync(hookPath, hookScript, { mode: 0o755 });\n\n return {\n success: true,\n message: `OpenCode hook installed at ${hookPath}`,\n hookPath,\n };\n } catch (err) {\n return {\n success: false,\n message: `Failed to install OpenCode hook: ${err}`,\n };\n }\n }\n\n private generateHookScript(apiKey: string): string {\n return `#!/bin/bash\n# Modu-Arena: OpenCode session tracking hook\n# Auto-generated — do not edit manually\n\nMODU_API_KEY=\"${apiKey}\"\nMODU_SERVER=\"${API_BASE_URL}\"\n\nif [ -n \"$OPENCODE_SESSION_ID\" ]; then\n SESSION_DATA=$(cat <<EOF\n{\n \"toolType\": \"opencode\",\n \"sessionId\": \"$OPENCODE_SESSION_ID\",\n \"startedAt\": \"$OPENCODE_SESSION_STARTED_AT\",\n \"endedAt\": \"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\",\n \"inputTokens\": \\${OPENCODE_INPUT_TOKENS:-0},\n \"outputTokens\": \\${OPENCODE_OUTPUT_TOKENS:-0},\n \"modelName\": \"\\${OPENCODE_MODEL:-unknown}\"\n}\nEOF\n)\n\n TIMESTAMP=$(date +%s)\n MESSAGE=\"\\${TIMESTAMP}:\\${SESSION_DATA}\"\n SIGNATURE=$(echo -n \"$MESSAGE\" | openssl dgst -sha256 -hmac \"$MODU_API_KEY\" | sed 's/.*= //')\n\n curl -s -X POST \"\\${MODU_SERVER}/api/v1/sessions\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"X-API-Key: \\${MODU_API_KEY}\" \\\\\n -H \"X-Timestamp: \\${TIMESTAMP}\" \\\\\n -H \"X-Signature: \\${SIGNATURE}\" \\\\\n -d \"\\${SESSION_DATA}\" > /dev/null 2>&1 &\nfi\n`;\n }\n}\n\n// ─── Gemini CLI Adapter ────────────────────────────────────────────────────\n\nclass GeminiAdapter implements ToolAdapter {\n slug = 'gemini' as const;\n displayName = 'Gemini CLI';\n\n private get configDir(): string {\n return join(homedir(), '.gemini');\n }\n\n getHookPath(): string {\n return join(this.configDir, 'hooks', 'session-end.sh');\n }\n\n detect(): boolean {\n return existsSync(this.configDir);\n }\n\n install(apiKey: string): InstallResult {\n try {\n const hooksDir = join(this.configDir, 'hooks');\n if (!existsSync(hooksDir)) mkdirSync(hooksDir, { recursive: true });\n\n const hookScript = this.generateHookScript(apiKey);\n const hookPath = this.getHookPath();\n writeFileSync(hookPath, hookScript, { mode: 0o755 });\n\n return {\n success: true,\n message: `Gemini CLI hook installed at ${hookPath}`,\n hookPath,\n };\n } catch (err) {\n return {\n success: false,\n message: `Failed to install Gemini CLI hook: ${err}`,\n };\n }\n }\n\n private generateHookScript(apiKey: string): string {\n return `#!/bin/bash\n# Modu-Arena: Gemini CLI session tracking hook\n# Auto-generated — do not edit manually\n\nMODU_API_KEY=\"${apiKey}\"\nMODU_SERVER=\"${API_BASE_URL}\"\n\nif [ -n \"$GEMINI_SESSION_ID\" ]; then\n SESSION_DATA=$(cat <<EOF\n{\n \"toolType\": \"gemini\",\n \"sessionId\": \"$GEMINI_SESSION_ID\",\n \"startedAt\": \"$GEMINI_SESSION_STARTED_AT\",\n \"endedAt\": \"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\",\n \"inputTokens\": \\${GEMINI_INPUT_TOKENS:-0},\n \"outputTokens\": \\${GEMINI_OUTPUT_TOKENS:-0},\n \"modelName\": \"\\${GEMINI_MODEL:-unknown}\"\n}\nEOF\n)\n\n TIMESTAMP=$(date +%s)\n MESSAGE=\"\\${TIMESTAMP}:\\${SESSION_DATA}\"\n SIGNATURE=$(echo -n \"$MESSAGE\" | openssl dgst -sha256 -hmac \"$MODU_API_KEY\" | sed 's/.*= //')\n\n curl -s -X POST \"\\${MODU_SERVER}/api/v1/sessions\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"X-API-Key: \\${MODU_API_KEY}\" \\\\\n -H \"X-Timestamp: \\${TIMESTAMP}\" \\\\\n -H \"X-Signature: \\${SIGNATURE}\" \\\\\n -d \"\\${SESSION_DATA}\" > /dev/null 2>&1 &\nfi\n`;\n }\n}\n\n// ─── Codex CLI Adapter ─────────────────────────────────────────────────────\n\nclass CodexAdapter implements ToolAdapter {\n slug = 'codex' as const;\n displayName = 'Codex CLI';\n\n private get configDir(): string {\n return join(homedir(), '.codex');\n }\n\n getHookPath(): string {\n return join(this.configDir, 'hooks', 'session-end.sh');\n }\n\n detect(): boolean {\n return existsSync(this.configDir);\n }\n\n install(apiKey: string): InstallResult {\n try {\n const hooksDir = join(this.configDir, 'hooks');\n if (!existsSync(hooksDir)) mkdirSync(hooksDir, { recursive: true });\n\n const hookScript = this.generateHookScript(apiKey);\n const hookPath = this.getHookPath();\n writeFileSync(hookPath, hookScript, { mode: 0o755 });\n\n return {\n success: true,\n message: `Codex CLI hook installed at ${hookPath}`,\n hookPath,\n };\n } catch (err) {\n return {\n success: false,\n message: `Failed to install Codex CLI hook: ${err}`,\n };\n }\n }\n\n private generateHookScript(apiKey: string): string {\n return `#!/bin/bash\n# Modu-Arena: Codex CLI session tracking hook\n# Auto-generated — do not edit manually\n\nMODU_API_KEY=\"${apiKey}\"\nMODU_SERVER=\"${API_BASE_URL}\"\n\nif [ -n \"$CODEX_SESSION_ID\" ]; then\n SESSION_DATA=$(cat <<EOF\n{\n \"toolType\": \"codex\",\n \"sessionId\": \"$CODEX_SESSION_ID\",\n \"startedAt\": \"$CODEX_SESSION_STARTED_AT\",\n \"endedAt\": \"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\",\n \"inputTokens\": \\${CODEX_INPUT_TOKENS:-0},\n \"outputTokens\": \\${CODEX_OUTPUT_TOKENS:-0},\n \"modelName\": \"\\${CODEX_MODEL:-unknown}\"\n}\nEOF\n)\n\n TIMESTAMP=$(date +%s)\n MESSAGE=\"\\${TIMESTAMP}:\\${SESSION_DATA}\"\n SIGNATURE=$(echo -n \"$MESSAGE\" | openssl dgst -sha256 -hmac \"$MODU_API_KEY\" | sed 's/.*= //')\n\n curl -s -X POST \"\\${MODU_SERVER}/api/v1/sessions\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"X-API-Key: \\${MODU_API_KEY}\" \\\\\n -H \"X-Timestamp: \\${TIMESTAMP}\" \\\\\n -H \"X-Signature: \\${SIGNATURE}\" \\\\\n -d \"\\${SESSION_DATA}\" > /dev/null 2>&1 &\nfi\n`;\n }\n}\n\n// ─── Crush Adapter ─────────────────────────────────────────────────────────\n\nclass CrushAdapter implements ToolAdapter {\n slug = 'crush' as const;\n displayName = 'Crush';\n\n private get configDir(): string {\n return join(homedir(), '.crush');\n }\n\n getHookPath(): string {\n return join(this.configDir, 'hooks', 'session-end.sh');\n }\n\n detect(): boolean {\n return existsSync(this.configDir);\n }\n\n install(apiKey: string): InstallResult {\n try {\n const hooksDir = join(this.configDir, 'hooks');\n if (!existsSync(hooksDir)) mkdirSync(hooksDir, { recursive: true });\n\n const hookScript = this.generateHookScript(apiKey);\n const hookPath = this.getHookPath();\n writeFileSync(hookPath, hookScript, { mode: 0o755 });\n\n return {\n success: true,\n message: `Crush hook installed at ${hookPath}`,\n hookPath,\n };\n } catch (err) {\n return {\n success: false,\n message: `Failed to install Crush hook: ${err}`,\n };\n }\n }\n\n private generateHookScript(apiKey: string): string {\n return `#!/bin/bash\n# Modu-Arena: Crush session tracking hook\n# Auto-generated — do not edit manually\n\nMODU_API_KEY=\"${apiKey}\"\nMODU_SERVER=\"${API_BASE_URL}\"\n\nif [ -n \"$CRUSH_SESSION_ID\" ]; then\n SESSION_DATA=$(cat <<EOF\n{\n \"toolType\": \"crush\",\n \"sessionId\": \"$CRUSH_SESSION_ID\",\n \"startedAt\": \"$CRUSH_SESSION_STARTED_AT\",\n \"endedAt\": \"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\",\n \"inputTokens\": \\${CRUSH_INPUT_TOKENS:-0},\n \"outputTokens\": \\${CRUSH_OUTPUT_TOKENS:-0},\n \"modelName\": \"\\${CRUSH_MODEL:-unknown}\"\n}\nEOF\n)\n\n TIMESTAMP=$(date +%s)\n MESSAGE=\"\\${TIMESTAMP}:\\${SESSION_DATA}\"\n SIGNATURE=$(echo -n \"$MESSAGE\" | openssl dgst -sha256 -hmac \"$MODU_API_KEY\" | sed 's/.*= //')\n\n curl -s -X POST \"\\${MODU_SERVER}/api/v1/sessions\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -H \"X-API-Key: \\${MODU_API_KEY}\" \\\\\n -H \"X-Timestamp: \\${TIMESTAMP}\" \\\\\n -H \"X-Signature: \\${SIGNATURE}\" \\\\\n -d \"\\${SESSION_DATA}\" > /dev/null 2>&1 &\nfi\n`;\n }\n}\n\n// ─── Registry ──────────────────────────────────────────────────────────────\n\nexport function getAllAdapters(): ToolAdapter[] {\n return [\n new ClaudeCodeAdapter(),\n new OpenCodeAdapter(),\n new GeminiAdapter(),\n new CodexAdapter(),\n new CrushAdapter(),\n ];\n}\n\nexport function getAdapter(slug: string): ToolAdapter | undefined {\n return getAllAdapters().find((a) => a.slug === slug);\n}\n","/** Base URL for the Modu-Arena API server */\nexport const API_BASE_URL =\n process.env.MODU_ARENA_API_URL ?? 'http://localhost:8989';\n\n/** API key prefix used for all keys */\nexport const API_KEY_PREFIX = 'modu_arena_';\n\n/** Supported AI coding tools */\nexport const TOOL_TYPES = [\n 'claude-code',\n 'opencode',\n 'gemini',\n 'codex',\n 'crush',\n] as const;\n\nexport type ToolType = (typeof TOOL_TYPES)[number];\n\n/** Display names for each tool */\nexport const TOOL_DISPLAY_NAMES: Record<ToolType, string> = {\n 'claude-code': 'Claude Code',\n opencode: 'OpenCode',\n gemini: 'Gemini CLI',\n codex: 'Codex CLI',\n crush: 'Crush',\n};\n\n/** Config file name stored in user home directory */\nexport const CONFIG_FILE_NAME = '.modu-arena.json';\n\n/** Minimum interval between sessions (seconds) */\nexport const MIN_SESSION_INTERVAL_SEC = 60;\n\n/** HMAC timestamp tolerance (seconds) */\nexport const HMAC_TIMESTAMP_TOLERANCE_SEC = 300;\n","import { createHmac, createHash } from 'node:crypto';\n\n/**\n * Compute HMAC-SHA256 signature for API authentication.\n *\n * message = \"{timestamp}:{bodyJsonString}\"\n * signature = HMAC-SHA256(apiKey, message).hex()\n */\nexport function computeHmacSignature(\n apiKey: string,\n timestamp: string,\n body: string,\n): string {\n const message = `${timestamp}:${body}`;\n return createHmac('sha256', apiKey).update(message).digest('hex');\n}\n\n/**\n * Compute SHA-256 session hash for integrity verification.\n *\n * data = \"{userId}:{userSalt}:{inputTokens}:{outputTokens}:{cacheCreationTokens}:{cacheReadTokens}:{modelName}:{endedAt}\"\n * hash = SHA-256(data).hex()\n */\nexport function computeSessionHash(\n userId: string,\n userSalt: string,\n inputTokens: number,\n outputTokens: number,\n cacheCreationTokens: number,\n cacheReadTokens: number,\n modelName: string,\n endedAt: string,\n): string {\n const data = `${userId}:${userSalt}:${inputTokens}:${outputTokens}:${cacheCreationTokens}:${cacheReadTokens}:${modelName}:${endedAt}`;\n return createHash('sha256').update(data).digest('hex');\n}\n","import { computeHmacSignature } from './crypto.js';\nimport { API_BASE_URL } from './constants.js';\n\nexport interface SessionPayload {\n toolType: string;\n sessionId: string;\n startedAt: string;\n endedAt: string;\n inputTokens: number;\n outputTokens: number;\n cacheCreationTokens?: number;\n cacheReadTokens?: number;\n modelName?: string;\n codeMetrics?: Record<string, unknown> | null;\n}\n\nexport interface BatchPayload {\n sessions: SessionPayload[];\n}\n\nexport interface RankResponse {\n success: boolean;\n data: {\n username: string;\n usage: {\n totalInputTokens: number;\n totalOutputTokens: number;\n totalCacheTokens: number;\n totalTokens: number;\n totalSessions: number;\n toolBreakdown: Array<{ tool: string; tokens: number }>;\n last7Days: Array<{ date: string; inputTokens: number; outputTokens: number; sessions: number }>;\n last30Days: Array<{ date: string; inputTokens: number; outputTokens: number; sessions: number }>;\n };\n overview: {\n successfulProjectsCount: number;\n };\n lastUpdated: string;\n };\n}\n\nexport interface ApiError {\n error: string;\n}\n\ninterface RequestOptions {\n apiKey: string;\n serverUrl?: string;\n}\n\nfunction baseUrl(opts: RequestOptions): string {\n return opts.serverUrl || API_BASE_URL;\n}\n\nfunction makeAuthHeaders(\n apiKey: string,\n body?: string,\n): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-API-Key': apiKey,\n };\n\n if (body !== undefined) {\n const timestamp = Math.floor(Date.now() / 1000).toString();\n const signature = computeHmacSignature(apiKey, timestamp, body);\n headers['X-Timestamp'] = timestamp;\n headers['X-Signature'] = signature;\n }\n\n return headers;\n}\n\nexport async function submitSession(\n session: SessionPayload,\n opts: RequestOptions,\n): Promise<{ success: boolean; session?: unknown; error?: string }> {\n const body = JSON.stringify(session);\n const url = `${baseUrl(opts)}/api/v1/sessions`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: makeAuthHeaders(opts.apiKey, body),\n body,\n });\n\n const data = await res.json();\n if (!res.ok) {\n return { success: false, error: (data as ApiError).error || `HTTP ${res.status}` };\n }\n return data as { success: boolean; session: unknown };\n}\n\nexport async function submitBatch(\n sessions: SessionPayload[],\n opts: RequestOptions,\n): Promise<{\n success: boolean;\n processed?: number;\n duplicatesSkipped?: number;\n error?: string;\n}> {\n const body = JSON.stringify({ sessions });\n const url = `${baseUrl(opts)}/api/v1/sessions/batch`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: makeAuthHeaders(opts.apiKey, body),\n body,\n });\n\n const data = await res.json();\n if (!res.ok) {\n return { success: false, error: (data as ApiError).error || `HTTP ${res.status}` };\n }\n return data as { success: boolean; processed: number; duplicatesSkipped: number };\n}\n\nexport async function getRank(\n opts: RequestOptions,\n): Promise<RankResponse | { success: false; error: string }> {\n const url = `${baseUrl(opts)}/api/v1/rank`;\n\n const res = await fetch(url, {\n method: 'GET',\n headers: {\n 'X-API-Key': opts.apiKey,\n },\n });\n\n const data = await res.json();\n if (!res.ok) {\n return { success: false, error: (data as ApiError).error || `HTTP ${res.status}` };\n }\n return data as RankResponse;\n}\n\n// ─── Evaluate ─────────────────────────────────────────────────────────────\n\nexport interface EvaluatePayload {\n projectName: string;\n description: string;\n fileStructure?: Record<string, string[]>;\n}\n\nexport interface EvaluationResult {\n passed: boolean;\n totalScore: number;\n rubricFunctionality: number;\n rubricPracticality: number;\n feedback: string;\n}\n\nexport interface EvaluateResponse {\n success: true;\n evaluation: EvaluationResult;\n}\n\nexport async function submitEvaluation(\n payload: EvaluatePayload,\n opts: RequestOptions,\n): Promise<EvaluateResponse | { success: false; error: string }> {\n const body = JSON.stringify(payload);\n const url = `${baseUrl(opts)}/api/v1/evaluate`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: makeAuthHeaders(opts.apiKey, body),\n body,\n });\n\n const data = await res.json();\n if (!res.ok) {\n return { success: false, error: (data as ApiError).error || `HTTP ${res.status}` };\n }\n return data as EvaluateResponse;\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, dirname } from 'node:path';\nimport { CONFIG_FILE_NAME } from './constants.js';\n\nexport interface Config {\n apiKey: string;\n serverUrl?: string;\n tools?: string[];\n}\n\nfunction getConfigPath(): string {\n return join(homedir(), CONFIG_FILE_NAME);\n}\n\nexport function loadConfig(): Config | null {\n const configPath = getConfigPath();\n if (!existsSync(configPath)) return null;\n\n try {\n const raw = readFileSync(configPath, 'utf-8');\n return JSON.parse(raw) as Config;\n } catch {\n return null;\n }\n}\n\nexport function saveConfig(config: Config): void {\n const configPath = getConfigPath();\n const dir = dirname(configPath);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n}\n\nexport function requireConfig(): Config {\n const config = loadConfig();\n if (!config?.apiKey) {\n console.error(\n 'Error: Not configured. Run `npx @suncreation/modu-arena install` first.',\n );\n process.exit(1);\n }\n return config;\n}\n","/**\n * @suncreation/modu-arena CLI\n *\n * Track and rank your AI coding tool usage.\n *\n * Usage:\n * npx @suncreation/modu-arena install --api-key <key>\n * npx @suncreation/modu-arena rank\n * npx @suncreation/modu-arena status\n * npx @suncreation/modu-arena uninstall\n */\n\nimport {\n installCommand,\n rankCommand,\n statusCommand,\n submitCommand,\n uninstallCommand,\n} from './commands.js';\n\nconst args = process.argv.slice(2);\nconst command = args[0];\n\nfunction printHelp(): void {\n console.log(`\nModu-Arena — AI Coding Tool Usage Tracker\n\nUsage:\n npx @suncreation/modu-arena <command> [options]\n\nCommands:\n install Set up hooks for detected AI coding tools\n rank View your current stats and ranking\n status Check configuration and installed hooks\n submit Submit current project for evaluation\n uninstall Remove all hooks and configuration\n\nOptions:\n --api-key <key> Your Modu-Arena API key (for install)\n --help, -h Show this help message\n --version, -v Show version\n\nExamples:\n npx @suncreation/modu-arena install --api-key modu_arena_AbCdEfGh_xxx...\n npx @suncreation/modu-arena rank\n npx @suncreation/modu-arena status\n`);\n}\n\nasync function main(): Promise<void> {\n if (!command || command === '--help' || command === '-h') {\n printHelp();\n process.exit(0);\n }\n\n if (command === '--version' || command === '-v') {\n console.log('0.1.0');\n process.exit(0);\n }\n\n switch (command) {\n case 'install': {\n const keyIndex = args.indexOf('--api-key');\n const apiKey = keyIndex >= 0 ? args[keyIndex + 1] : undefined;\n await installCommand(apiKey);\n break;\n }\n case 'rank':\n await rankCommand();\n break;\n case 'status':\n statusCommand();\n break;\n case 'submit':\n await submitCommand();\n break;\n case 'uninstall':\n uninstallCommand();\n break;\n default:\n console.error(`Unknown command: ${command}`);\n printHelp();\n process.exit(1);\n }\n}\n\nmain().catch((err) => {\n console.error('Fatal error:', err);\n process.exit(1);\n});\n"],"mappings":"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","names":["existsSync","readFileSync","homedir","join","readFileSync","writeFileSync","existsSync","mkdirSync","homedir","join","join","homedir","existsSync","readFileSync","mkdirSync","writeFileSync","existsSync","join","homedir","readFileSync"]}
1
+ {"version":3,"sources":["../src/commands.ts","../src/adapters.ts","../src/constants.ts","../src/crypto.ts","../src/api.ts","../src/config.ts","../src/index.ts"],"sourcesContent":["/**\n * CLI Commands — install, rank, status, uninstall\n */\n\nimport { createInterface } from 'node:readline';\nimport { existsSync, readFileSync, readdirSync, statSync, unlinkSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { basename, join } from 'node:path';\nimport { getAllAdapters, type InstallResult } from './adapters.js';\nimport { getRank, registerUser, loginUser, submitEvaluation } from './api.js';\nimport { loadConfig, saveConfig, requireConfig } from './config.js';\nimport { API_BASE_URL, TOOL_DISPLAY_NAMES, type ToolType } from './constants.js';\n\nfunction prompt(question: string): Promise<string> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close();\n resolve(answer.trim());\n });\n });\n}\n\nfunction promptPassword(question: string): Promise<string> {\n return new Promise((resolve) => {\n process.stdout.write(question);\n const chars: string[] = [];\n const stdin = process.stdin;\n const wasRaw = stdin.isRaw;\n stdin.setRawMode(true);\n stdin.resume();\n stdin.setEncoding('utf8');\n\n const onData = (ch: string) => {\n const c = ch.toString();\n if (c === '\\n' || c === '\\r' || c === '\\u0004') {\n // Enter or Ctrl+D\n stdin.setRawMode(wasRaw ?? false);\n stdin.pause();\n stdin.removeListener('data', onData);\n process.stdout.write('\\n');\n resolve(chars.join('').trim());\n } else if (c === '\\u0003') {\n // Ctrl+C\n process.stdout.write('\\n');\n process.exit(0);\n } else if (c === '\\u007f' || c === '\\b') {\n // Backspace\n if (chars.length > 0) {\n chars.pop();\n process.stdout.write('\\b \\b');\n }\n } else {\n chars.push(c);\n process.stdout.write('*');\n }\n };\n\n stdin.on('data', onData);\n });\n}\n\n// ─── register ──────────────────────────────────────────────────────────────\n\nexport async function registerCommand(): Promise<void> {\n console.log('\\n📝 Modu-Arena — Register\\n');\n\n const username = await prompt(' Username (3-50 chars): ');\n if (!username || username.length < 3 || username.length > 50) {\n console.error('Error: Username must be between 3 and 50 characters.\\n');\n process.exit(1);\n }\n\n const password = await promptPassword(' Password (min 8 chars): ');\n if (!password || password.length < 8) {\n console.error('Error: Password must be at least 8 characters.\\n');\n process.exit(1);\n }\n\n const displayName = await prompt(' Display name (optional, press Enter to skip): ');\n\n console.log('\\n Registering...');\n\n const existing = loadConfig();\n const result = await registerUser(\n { username, password, displayName: displayName || undefined },\n existing?.serverUrl,\n );\n\n if (result.error) {\n console.error(`\\n Error: ${result.error}\\n`);\n process.exit(1);\n }\n\n if (!result.apiKey) {\n console.error('\\n Error: No API key returned from server.\\n');\n process.exit(1);\n }\n\n saveConfig({ apiKey: result.apiKey, serverUrl: existing?.serverUrl });\n console.log('\\n ✓ Registration successful!');\n console.log(` ✓ API key saved to ~/.modu-arena.json`);\n console.log(`\\n Username: ${result.user?.username}`);\n console.log(` API Key: ${result.apiKey.slice(0, 20)}...${result.apiKey.slice(-4)}`);\n console.log('\\n ⚠ Save your API key — it will not be shown again.\\n');\n\n console.log(' Installing hooks for detected AI coding tools...\\n');\n await installCommand(result.apiKey);\n}\n\n// ─── login ─────────────────────────────────────────────────────────────────\n\nexport async function loginCommand(): Promise<void> {\n console.log('\\n🔑 Modu-Arena — Login\\n');\n\n const username = await prompt(' Username: ');\n if (!username) {\n console.error('Error: Username is required.\\n');\n process.exit(1);\n }\n\n const password = await promptPassword(' Password: ');\n if (!password) {\n console.error('Error: Password is required.\\n');\n process.exit(1);\n }\n\n console.log('\\n Logging in...');\n\n const existing = loadConfig();\n const result = await loginUser({ username, password }, existing?.serverUrl);\n\n if (result.error) {\n console.error(`\\n Error: ${result.error}\\n`);\n process.exit(1);\n }\n\n if (!result.apiKey) {\n console.error('\\n Error: No API key returned from server.\\n');\n process.exit(1);\n }\n\n saveConfig({ apiKey: result.apiKey, serverUrl: existing?.serverUrl });\n console.log('\\n ✓ Login successful!');\n console.log(` ✓ API key saved to ~/.modu-arena.json`);\n console.log(`\\n Username: ${result.user?.username}`);\n console.log(` API Key: ${result.apiKey.slice(0, 20)}...${result.apiKey.slice(-4)}`);\n console.log('\\n ⚠ A new API key was generated. Previous key is now invalid.\\n');\n\n console.log(' Reinstalling hooks with new API key...\\n');\n await installCommand(result.apiKey);\n}\n\n// ─── install ───────────────────────────────────────────────────────────────\n\nexport async function installCommand(apiKey?: string): Promise<void> {\n console.log('\\n🔧 Modu-Arena — AI Coding Tool Usage Tracker\\n');\n\n // Check if already configured\n const existing = loadConfig();\n if (existing?.apiKey && !apiKey) {\n console.log('✓ Already configured.');\n console.log(' Use --api-key <key> to update your API key.\\n');\n apiKey = existing.apiKey;\n }\n\n if (!apiKey) {\n console.error(\n 'Error: API key required.\\n' +\n ' Get your API key from the Modu-Arena dashboard.\\n' +\n ' Usage: npx @suncreation/modu-arena install --api-key <your-api-key>\\n',\n );\n process.exit(1);\n }\n\n // Validate API key format\n if (!apiKey.startsWith('modu_arena_')) {\n console.error(\n 'Error: Invalid API key format. Key must start with \"modu_arena_\".\\n',\n );\n process.exit(1);\n }\n\n // Save config\n saveConfig({ apiKey });\n console.log('✓ API key saved to ~/.modu-arena.json\\n');\n\n // Detect and install hooks for each tool\n const adapters = getAllAdapters();\n const results: { tool: string; result: InstallResult }[] = [];\n\n console.log('Detecting AI coding tools...\\n');\n\n for (const adapter of adapters) {\n const detected = adapter.detect();\n if (detected) {\n console.log(` ✓ ${adapter.displayName} detected`);\n const result = adapter.install(apiKey);\n results.push({ tool: adapter.displayName, result });\n if (result.success) {\n console.log(` → Hook installed: ${result.hookPath}`);\n } else {\n console.log(` ✗ ${result.message}`);\n }\n } else {\n console.log(` - ${adapter.displayName} not found`);\n }\n }\n\n const installed = results.filter((r) => r.result.success);\n console.log(\n `\\n✓ Setup complete. ${installed.length} tool(s) configured.\\n`,\n );\n\n if (installed.length === 0) {\n console.log(\n 'No AI coding tools detected. Install one of the supported tools:\\n' +\n ' • Claude Code (https://docs.anthropic.com/s/claude-code)\\n' +\n ' • OpenCode (https://opencode.ai)\\n' +\n ' • Gemini CLI (https://github.com/google-gemini/gemini-cli)\\n' +\n ' • Codex CLI (https://github.com/openai/codex)\\n' +\n ' • Crush (https://charm.sh/crush)\\n',\n );\n }\n}\n\n// ─── rank ──────────────────────────────────────────────────────────────────\n\nexport async function rankCommand(): Promise<void> {\n const config = requireConfig();\n console.log('\\n📊 Modu-Arena — Your Stats\\n');\n\n const result = await getRank({ apiKey: config.apiKey, serverUrl: config.serverUrl });\n\n if (!result.success) {\n console.error(`Error: ${'error' in result ? result.error : 'Unknown error'}\\n`);\n process.exit(1);\n }\n\n if (!('data' in result) || !result.data) {\n console.error('Error: Unexpected response format.\\n');\n process.exit(1);\n }\n\n const { username, usage, overview } = result.data;\n\n console.log(` User: ${username}`);\n console.log(` Tokens: ${formatNumber(usage.totalTokens)}`);\n console.log(` Sessions: ${usage.totalSessions}`);\n console.log(` Projects: ${overview.successfulProjectsCount}`);\n console.log('');\n\n // Tool breakdown\n if (usage.toolBreakdown.length > 0) {\n console.log(' Tool Breakdown:');\n for (const entry of usage.toolBreakdown) {\n const name = TOOL_DISPLAY_NAMES[entry.tool as ToolType] || entry.tool;\n console.log(\n ` ${name}: ${formatNumber(entry.tokens)} tokens`,\n );\n }\n console.log('');\n }\n\n // Period stats (aggregate from daily arrays)\n const sum7 = usage.last7Days.reduce(\n (acc, d) => ({ tokens: acc.tokens + d.inputTokens + d.outputTokens, sessions: acc.sessions + d.sessions }),\n { tokens: 0, sessions: 0 },\n );\n const sum30 = usage.last30Days.reduce(\n (acc, d) => ({ tokens: acc.tokens + d.inputTokens + d.outputTokens, sessions: acc.sessions + d.sessions }),\n { tokens: 0, sessions: 0 },\n );\n console.log(\n ` Last 7 days: ${formatNumber(sum7.tokens)} tokens, ${sum7.sessions} sessions`,\n );\n console.log(\n ` Last 30 days: ${formatNumber(sum30.tokens)} tokens, ${sum30.sessions} sessions`,\n );\n console.log('');\n}\n\n// ─── status ────────────────────────────────────────────────────────────────\n\nexport function statusCommand(): void {\n const config = loadConfig();\n console.log('\\n🔍 Modu-Arena — Status\\n');\n\n if (!config?.apiKey) {\n console.log(' Status: Not configured');\n console.log(\n ' Run `npx @suncreation/modu-arena install --api-key <key>` to set up.\\n',\n );\n return;\n }\n\n const maskedKey =\n config.apiKey.slice(0, 15) + '...' + config.apiKey.slice(-4);\n console.log(` API Key: ${maskedKey}`);\n console.log(` Server: ${config.serverUrl || API_BASE_URL}`);\n console.log('');\n\n // Check installed hooks\n const adapters = getAllAdapters();\n console.log(' Installed Hooks:');\n let hookCount = 0;\n for (const adapter of adapters) {\n const detected = adapter.detect();\n if (detected) {\n const hookExists = existsSync(adapter.getHookPath());\n const status = hookExists ? '✓ Active' : '✗ Not installed';\n console.log(` ${adapter.displayName}: ${status}`);\n if (hookExists) hookCount++;\n }\n }\n if (hookCount === 0) {\n console.log(' (none)');\n }\n console.log('');\n}\n\n// ─── uninstall ─────────────────────────────────────────────────────────────\n\nexport function uninstallCommand(): void {\n console.log('\\n🗑️ Modu-Arena — Uninstall\\n');\n\n // Remove hooks\n const adapters = getAllAdapters();\n for (const adapter of adapters) {\n const hookPath = adapter.getHookPath();\n if (existsSync(hookPath)) {\n unlinkSync(hookPath);\n console.log(` ✓ Removed ${adapter.displayName} hook`);\n }\n }\n\n // Remove config\n const configPath = join(homedir(), '.modu-arena.json');\n if (existsSync(configPath)) {\n unlinkSync(configPath);\n console.log(' ✓ Removed ~/.modu-arena.json');\n }\n\n console.log('\\n✓ Modu-Arena uninstalled.\\n');\n}\n\n// ─── submit ─────────────────────────────────────────────────────────────────\n\nconst IGNORE_DIRS = new Set([\n 'node_modules', '.git', '.next', '.nuxt', 'dist', 'build', 'out',\n '.cache', '.turbo', '.vercel', '__pycache__', '.svelte-kit',\n 'coverage', '.output', '.parcel-cache',\n]);\n\nfunction collectFileStructure(\n dir: string,\n maxDepth: number,\n currentDepth = 0,\n): Record<string, string[]> {\n const result: Record<string, string[]> = {};\n if (currentDepth >= maxDepth) return result;\n\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return result;\n }\n\n const files: string[] = [];\n for (const entry of entries) {\n if (entry.startsWith('.') && IGNORE_DIRS.has(entry)) continue;\n if (IGNORE_DIRS.has(entry)) continue;\n\n const fullPath = join(dir, entry);\n let stat;\n try {\n stat = statSync(fullPath);\n } catch {\n continue;\n }\n\n if (stat.isDirectory()) {\n const sub = collectFileStructure(fullPath, maxDepth, currentDepth + 1);\n for (const [key, val] of Object.entries(sub)) {\n result[key] = val;\n }\n } else {\n files.push(entry);\n }\n }\n\n if (files.length > 0) {\n const relDir = currentDepth === 0 ? '.' : dir.split('/').slice(-(currentDepth)).join('/');\n result[relDir] = files;\n }\n\n return result;\n}\n\nexport async function submitCommand(): Promise<void> {\n const config = requireConfig();\n console.log('\\n🚀 Modu-Arena — Project Submit\\n');\n\n const cwd = process.cwd();\n const projectName = basename(cwd);\n\n const readmePath = join(cwd, 'README.md');\n if (!existsSync(readmePath)) {\n console.error('Error: README.md not found in the current directory.');\n console.error(' Please create a README.md describing your project.\\n');\n process.exit(1);\n }\n\n const description = readFileSync(readmePath, 'utf-8');\n if (description.trim().length === 0) {\n console.error('Error: README.md is empty.\\n');\n process.exit(1);\n }\n\n console.log(` Project: ${projectName}`);\n console.log(` README: ${readmePath}`);\n console.log('');\n console.log(' Collecting file structure...');\n\n const fileStructure = collectFileStructure(cwd, 3);\n const fileCount = Object.values(fileStructure).reduce((sum, files) => sum + files.length, 0);\n console.log(` Found ${fileCount} file(s) in ${Object.keys(fileStructure).length} director${Object.keys(fileStructure).length === 1 ? 'y' : 'ies'}`);\n console.log('');\n console.log(' Submitting for evaluation...\\n');\n\n const result = await submitEvaluation(\n { projectName, description, fileStructure },\n { apiKey: config.apiKey, serverUrl: config.serverUrl },\n );\n\n if (!result.success) {\n console.error(`Error: ${'error' in result ? result.error : 'Unknown error'}\\n`);\n process.exit(1);\n }\n\n const { evaluation } = result;\n const statusIcon = evaluation.passed ? '✅' : '❌';\n const statusText = evaluation.passed ? 'PASSED' : 'FAILED';\n\n console.log(` Result: ${statusIcon} ${statusText}`);\n console.log(` Total Score: ${evaluation.totalScore}/100`);\n console.log('');\n console.log(' Rubric Scores:');\n console.log(` Functionality: ${evaluation.rubricFunctionality}/50`);\n console.log(` Practicality: ${evaluation.rubricPracticality}/50`);\n console.log('');\n\n if (evaluation.feedback) {\n console.log(' Feedback:');\n const lines = evaluation.feedback.split('\\n');\n for (const line of lines) {\n console.log(` ${line}`);\n }\n console.log('');\n }\n}\n\n// ─── Helpers ───────────────────────────────────────────────────────────────\n\nfunction formatNumber(n: number): string {\n if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;\n if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;\n return n.toString();\n}\n","/**\n * Tool Adapters — Cross-platform hook installation for AI coding tools.\n *\n * Generates Node.js hook scripts (works on all platforms) with\n * thin shell wrappers (.sh on Unix, .cmd on Windows).\n */\n\nimport { existsSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { API_BASE_URL, type ToolType } from './constants.js';\n\n// ─── Platform ──────────────────────────────────────────────────────────────\n\nconst IS_WIN = process.platform === 'win32';\n\nfunction xdgConfigDir(app: string): string {\n if (IS_WIN) {\n return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), app);\n }\n return join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), app);\n}\n\n// ─── Types ─────────────────────────────────────────────────────────────────\n\nexport interface ToolAdapter {\n slug: ToolType;\n displayName: string;\n detect(): boolean;\n install(apiKey: string): InstallResult;\n getHookPath(): string;\n}\n\nexport interface InstallResult {\n success: boolean;\n message: string;\n hookPath?: string;\n}\n\ninterface EnvField {\n key: string;\n env: string;\n parse: 'string' | 'int';\n fallback: string;\n}\n\n// ─── Hook Script Generation ────────────────────────────────────────────────\n\nconst HOOK_JS = '_modu-hook.js';\n\nfunction baseFields(prefix: string): EnvField[] {\n return [\n { key: 'sessionId', env: `${prefix}_SESSION_ID`, parse: 'string', fallback: '' },\n { key: 'startedAt', env: `${prefix}_SESSION_STARTED_AT`, parse: 'string', fallback: '' },\n { key: 'inputTokens', env: `${prefix}_INPUT_TOKENS`, parse: 'int', fallback: '0' },\n { key: 'outputTokens', env: `${prefix}_OUTPUT_TOKENS`, parse: 'int', fallback: '0' },\n { key: 'modelName', env: `${prefix}_MODEL`, parse: 'string', fallback: 'unknown' },\n ];\n}\n\nfunction generateHookJs(apiKey: string, toolType: string, prefix: string, fields: EnvField[]): string {\n const lines = fields.map((f) =>\n f.parse === 'int'\n ? ` ${f.key}: parseInt(process.env[\"${f.env}\"] || \"${f.fallback}\", 10)`\n : ` ${f.key}: process.env[\"${f.env}\"] || \"${f.fallback}\"`\n );\n\n return `#!/usr/bin/env node\n\"use strict\";\nvar crypto = require(\"crypto\");\n\nvar API_KEY = ${JSON.stringify(apiKey)};\nvar SERVER = ${JSON.stringify(API_BASE_URL)};\n\nif (!process.env[\"${prefix}_SESSION_ID\"]) process.exit(0);\n\nvar body = JSON.stringify({\n toolType: ${JSON.stringify(toolType)},\n endedAt: new Date().toISOString(),\n${lines.join(\",\\n\")}\n});\n\nvar ts = Math.floor(Date.now() / 1000).toString();\nvar sig = crypto.createHmac(\"sha256\", API_KEY).update(ts + \":\" + body).digest(\"hex\");\n\nfetch(SERVER + \"/api/v1/sessions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"X-API-Key\": API_KEY, \"X-Timestamp\": ts, \"X-Signature\": sig },\n body: body\n}).catch(function(){});\n`;\n}\n\nfunction shellWrapper(): string {\n return `#!/bin/bash\nexec node \"$(dirname \"$0\")/${HOOK_JS}\"\n`;\n}\n\nfunction cmdWrapper(): string {\n return `@node \"%~dp0${HOOK_JS}\" 2>nul\\r\\n`;\n}\n\n// ─── Shared Install Logic ──────────────────────────────────────────────────\n\nfunction installHook(\n displayName: string,\n hooksDir: string,\n entryPath: string,\n apiKey: string,\n toolType: string,\n prefix: string,\n fields: EnvField[],\n): InstallResult {\n try {\n if (!existsSync(hooksDir)) mkdirSync(hooksDir, { recursive: true });\n\n writeFileSync(join(hooksDir, HOOK_JS), generateHookJs(apiKey, toolType, prefix, fields), { mode: 0o755 });\n\n if (IS_WIN) {\n writeFileSync(entryPath, cmdWrapper());\n } else {\n writeFileSync(entryPath, shellWrapper(), { mode: 0o755 });\n }\n\n return { success: true, message: `${displayName} hook installed at ${entryPath}`, hookPath: entryPath };\n } catch (err) {\n return { success: false, message: `Failed to install ${displayName} hook: ${err}` };\n }\n}\n\nfunction hookEntryName(): string {\n return IS_WIN ? 'session-end.cmd' : 'session-end.sh';\n}\n\n// ─── Adapters ──────────────────────────────────────────────────────────────\n\nclass ClaudeCodeAdapter implements ToolAdapter {\n slug = 'claude-code' as const;\n displayName = 'Claude Code';\n\n private get configDir() { return join(homedir(), '.claude'); }\n private get hooksDir() { return join(this.configDir, 'hooks'); }\n\n getHookPath() { return join(this.hooksDir, hookEntryName()); }\n detect() { return existsSync(this.configDir); }\n\n install(apiKey: string) {\n return installHook(this.displayName, this.hooksDir, this.getHookPath(), apiKey, 'claude-code', 'CLAUDE',\n [\n ...baseFields('CLAUDE'),\n { key: 'cacheCreationTokens', env: 'CLAUDE_CACHE_CREATION_TOKENS', parse: 'int', fallback: '0' },\n { key: 'cacheReadTokens', env: 'CLAUDE_CACHE_READ_TOKENS', parse: 'int', fallback: '0' },\n ],\n );\n }\n}\n\nclass OpenCodeAdapter implements ToolAdapter {\n slug = 'opencode' as const;\n displayName = 'OpenCode';\n\n private get configDir() { return xdgConfigDir('opencode'); }\n private get hooksDir() { return join(this.configDir, 'hooks'); }\n\n getHookPath() { return join(this.hooksDir, hookEntryName()); }\n detect() { return existsSync(this.configDir); }\n\n install(apiKey: string) {\n return installHook(this.displayName, this.hooksDir, this.getHookPath(), apiKey, 'opencode', 'OPENCODE',\n baseFields('OPENCODE'),\n );\n }\n}\n\nclass SimpleAdapter implements ToolAdapter {\n constructor(\n public slug: ToolType,\n public displayName: string,\n private dirName: string,\n private envPrefix: string,\n ) {}\n\n private get configDir() { return join(homedir(), this.dirName); }\n private get hooksDir() { return join(this.configDir, 'hooks'); }\n\n getHookPath() { return join(this.hooksDir, hookEntryName()); }\n detect() { return existsSync(this.configDir); }\n\n install(apiKey: string) {\n return installHook(this.displayName, this.hooksDir, this.getHookPath(), apiKey, this.slug, this.envPrefix,\n baseFields(this.envPrefix),\n );\n }\n}\n\n// ─── Registry ──────────────────────────────────────────────────────────────\n\nexport function getAllAdapters(): ToolAdapter[] {\n return [\n new ClaudeCodeAdapter(),\n new OpenCodeAdapter(),\n new SimpleAdapter('gemini', 'Gemini CLI', '.gemini', 'GEMINI'),\n new SimpleAdapter('codex', 'Codex CLI', '.codex', 'CODEX'),\n new SimpleAdapter('crush', 'Crush', '.crush', 'CRUSH'),\n ];\n}\n\nexport function getAdapter(slug: string): ToolAdapter | undefined {\n return getAllAdapters().find((a) => a.slug === slug);\n}\n","/** Base URL for the Modu-Arena API server */\nexport const API_BASE_URL =\n process.env.MODU_ARENA_API_URL ?? 'http://backend.vibemakers.kr:23010';\n\n/** API key prefix used for all keys */\nexport const API_KEY_PREFIX = 'modu_arena_';\n\n/** Supported AI coding tools */\nexport const TOOL_TYPES = [\n 'claude-code',\n 'opencode',\n 'gemini',\n 'codex',\n 'crush',\n] as const;\n\nexport type ToolType = (typeof TOOL_TYPES)[number];\n\n/** Display names for each tool */\nexport const TOOL_DISPLAY_NAMES: Record<ToolType, string> = {\n 'claude-code': 'Claude Code',\n opencode: 'OpenCode',\n gemini: 'Gemini CLI',\n codex: 'Codex CLI',\n crush: 'Crush',\n};\n\n/** Config file name stored in user home directory */\nexport const CONFIG_FILE_NAME = '.modu-arena.json';\n\n/** Minimum interval between sessions (seconds) */\nexport const MIN_SESSION_INTERVAL_SEC = 60;\n\n/** HMAC timestamp tolerance (seconds) */\nexport const HMAC_TIMESTAMP_TOLERANCE_SEC = 300;\n","import { createHmac, createHash } from 'node:crypto';\n\n/**\n * Compute HMAC-SHA256 signature for API authentication.\n *\n * message = \"{timestamp}:{bodyJsonString}\"\n * signature = HMAC-SHA256(apiKey, message).hex()\n */\nexport function computeHmacSignature(\n apiKey: string,\n timestamp: string,\n body: string,\n): string {\n const message = `${timestamp}:${body}`;\n return createHmac('sha256', apiKey).update(message).digest('hex');\n}\n\n/**\n * Compute SHA-256 session hash for integrity verification.\n *\n * data = \"{userId}:{userSalt}:{inputTokens}:{outputTokens}:{cacheCreationTokens}:{cacheReadTokens}:{modelName}:{endedAt}\"\n * hash = SHA-256(data).hex()\n */\nexport function computeSessionHash(\n userId: string,\n userSalt: string,\n inputTokens: number,\n outputTokens: number,\n cacheCreationTokens: number,\n cacheReadTokens: number,\n modelName: string,\n endedAt: string,\n): string {\n const data = `${userId}:${userSalt}:${inputTokens}:${outputTokens}:${cacheCreationTokens}:${cacheReadTokens}:${modelName}:${endedAt}`;\n return createHash('sha256').update(data).digest('hex');\n}\n","import { computeHmacSignature } from './crypto.js';\nimport { API_BASE_URL } from './constants.js';\n\nexport interface SessionPayload {\n toolType: string;\n sessionId: string;\n startedAt: string;\n endedAt: string;\n inputTokens: number;\n outputTokens: number;\n cacheCreationTokens?: number;\n cacheReadTokens?: number;\n modelName?: string;\n codeMetrics?: Record<string, unknown> | null;\n}\n\nexport interface BatchPayload {\n sessions: SessionPayload[];\n}\n\nexport interface RankResponse {\n success: boolean;\n data: {\n username: string;\n usage: {\n totalInputTokens: number;\n totalOutputTokens: number;\n totalCacheTokens: number;\n totalTokens: number;\n totalSessions: number;\n toolBreakdown: Array<{ tool: string; tokens: number }>;\n last7Days: Array<{ date: string; inputTokens: number; outputTokens: number; sessions: number }>;\n last30Days: Array<{ date: string; inputTokens: number; outputTokens: number; sessions: number }>;\n };\n overview: {\n successfulProjectsCount: number;\n };\n lastUpdated: string;\n };\n}\n\nexport interface ApiError {\n error: string;\n}\n\ninterface RequestOptions {\n apiKey: string;\n serverUrl?: string;\n}\n\nfunction baseUrl(opts: RequestOptions): string {\n return opts.serverUrl || API_BASE_URL;\n}\n\nfunction makeAuthHeaders(\n apiKey: string,\n body?: string,\n): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'X-API-Key': apiKey,\n };\n\n if (body !== undefined) {\n const timestamp = Math.floor(Date.now() / 1000).toString();\n const signature = computeHmacSignature(apiKey, timestamp, body);\n headers['X-Timestamp'] = timestamp;\n headers['X-Signature'] = signature;\n }\n\n return headers;\n}\n\nexport async function submitSession(\n session: SessionPayload,\n opts: RequestOptions,\n): Promise<{ success: boolean; session?: unknown; error?: string }> {\n const body = JSON.stringify(session);\n const url = `${baseUrl(opts)}/api/v1/sessions`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: makeAuthHeaders(opts.apiKey, body),\n body,\n });\n\n const data = await res.json();\n if (!res.ok) {\n return { success: false, error: (data as ApiError).error || `HTTP ${res.status}` };\n }\n return data as { success: boolean; session: unknown };\n}\n\nexport async function submitBatch(\n sessions: SessionPayload[],\n opts: RequestOptions,\n): Promise<{\n success: boolean;\n processed?: number;\n duplicatesSkipped?: number;\n error?: string;\n}> {\n const body = JSON.stringify({ sessions });\n const url = `${baseUrl(opts)}/api/v1/sessions/batch`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: makeAuthHeaders(opts.apiKey, body),\n body,\n });\n\n const data = await res.json();\n if (!res.ok) {\n return { success: false, error: (data as ApiError).error || `HTTP ${res.status}` };\n }\n return data as { success: boolean; processed: number; duplicatesSkipped: number };\n}\n\nexport async function getRank(\n opts: RequestOptions,\n): Promise<RankResponse | { success: false; error: string }> {\n const url = `${baseUrl(opts)}/api/v1/rank`;\n\n const res = await fetch(url, {\n method: 'GET',\n headers: {\n 'X-API-Key': opts.apiKey,\n },\n });\n\n const data = await res.json();\n if (!res.ok) {\n return { success: false, error: (data as ApiError).error || `HTTP ${res.status}` };\n }\n return data as RankResponse;\n}\n\n// ─── Auth ─────────────────────────────────────────────────────────────────\n\nexport interface AuthResponse {\n success: boolean;\n apiKey?: string;\n user?: { id: string; username: string; displayName?: string };\n error?: string;\n}\n\nexport async function registerUser(\n payload: { username: string; password: string; displayName?: string },\n serverUrl?: string,\n): Promise<AuthResponse> {\n const body = JSON.stringify(payload);\n const url = `${serverUrl || API_BASE_URL}/api/auth/register`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n });\n\n return (await res.json()) as AuthResponse;\n}\n\nexport async function loginUser(\n payload: { username: string; password: string },\n serverUrl?: string,\n): Promise<AuthResponse> {\n const body = JSON.stringify({ ...payload, source: 'cli' });\n const url = `${serverUrl || API_BASE_URL}/api/auth/login`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n });\n\n return (await res.json()) as AuthResponse;\n}\n\n// ─── Evaluate ─────────────────────────────────────────────────────────────\n\nexport interface EvaluatePayload {\n projectName: string;\n description: string;\n fileStructure?: Record<string, string[]>;\n}\n\nexport interface EvaluationResult {\n passed: boolean;\n totalScore: number;\n rubricFunctionality: number;\n rubricPracticality: number;\n feedback: string;\n}\n\nexport interface EvaluateResponse {\n success: true;\n evaluation: EvaluationResult;\n}\n\nexport async function submitEvaluation(\n payload: EvaluatePayload,\n opts: RequestOptions,\n): Promise<EvaluateResponse | { success: false; error: string }> {\n const body = JSON.stringify(payload);\n const url = `${baseUrl(opts)}/api/v1/evaluate`;\n\n const res = await fetch(url, {\n method: 'POST',\n headers: makeAuthHeaders(opts.apiKey, body),\n body,\n });\n\n const data = await res.json();\n if (!res.ok) {\n return { success: false, error: (data as ApiError).error || `HTTP ${res.status}` };\n }\n return data as EvaluateResponse;\n}\n","import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, dirname } from 'node:path';\nimport { CONFIG_FILE_NAME } from './constants.js';\n\nexport interface Config {\n apiKey: string;\n serverUrl?: string;\n tools?: string[];\n}\n\nfunction getConfigPath(): string {\n return join(homedir(), CONFIG_FILE_NAME);\n}\n\nexport function loadConfig(): Config | null {\n const configPath = getConfigPath();\n if (!existsSync(configPath)) return null;\n\n try {\n const raw = readFileSync(configPath, 'utf-8');\n return JSON.parse(raw) as Config;\n } catch {\n return null;\n }\n}\n\nexport function saveConfig(config: Config): void {\n const configPath = getConfigPath();\n const dir = dirname(configPath);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n}\n\nexport function requireConfig(): Config {\n const config = loadConfig();\n if (!config?.apiKey) {\n console.error(\n 'Error: Not configured. Run `npx @suncreation/modu-arena install` first.',\n );\n process.exit(1);\n }\n return config;\n}\n","/**\n * @suncreation/modu-arena CLI\n *\n * Track and rank your AI coding tool usage.\n *\n * Usage:\n * npx @suncreation/modu-arena install --api-key <key>\n * npx @suncreation/modu-arena rank\n * npx @suncreation/modu-arena status\n * npx @suncreation/modu-arena uninstall\n */\n\nimport {\n installCommand,\n loginCommand,\n rankCommand,\n registerCommand,\n statusCommand,\n submitCommand,\n uninstallCommand,\n} from './commands.js';\n\nconst args = process.argv.slice(2);\nconst command = args[0];\n\nfunction printHelp(): void {\n console.log(`\nModu-Arena — AI Coding Tool Usage Tracker\n\nUsage:\n npx @suncreation/modu-arena <command> [options]\n\nCommands:\n register Create a new account (interactive)\n login Log in to an existing account (interactive)\n install Set up hooks for detected AI coding tools\n rank View your current stats and ranking\n status Check configuration and installed hooks\n submit Submit current project for evaluation\n uninstall Remove all hooks and configuration\n\nOptions:\n --api-key <key> Your Modu-Arena API key (for install)\n --help, -h Show this help message\n --version, -v Show version\n\nExamples:\n npx @suncreation/modu-arena register\n npx @suncreation/modu-arena login\n npx @suncreation/modu-arena install --api-key modu_arena_AbCdEfGh_xxx...\n npx @suncreation/modu-arena rank\n`);\n}\n\nasync function main(): Promise<void> {\n if (!command || command === '--help' || command === '-h') {\n printHelp();\n process.exit(0);\n }\n\n if (command === '--version' || command === '-v') {\n console.log('0.1.0');\n process.exit(0);\n }\n\n switch (command) {\n case 'register':\n await registerCommand();\n break;\n case 'login':\n await loginCommand();\n break;\n case 'install': {\n const keyIndex = args.indexOf('--api-key');\n const apiKey = keyIndex >= 0 ? args[keyIndex + 1] : undefined;\n await installCommand(apiKey);\n break;\n }\n case 'rank':\n await rankCommand();\n break;\n case 'status':\n statusCommand();\n break;\n case 'submit':\n await submitCommand();\n break;\n case 'uninstall':\n uninstallCommand();\n break;\n default:\n console.error(`Unknown command: ${command}`);\n printHelp();\n process.exit(1);\n }\n}\n\nmain().catch((err) => {\n console.error('Fatal error:', err);\n process.exit(1);\n});\n"],"mappings":";;;AAIA,SAAS,uBAAuB;AAChC,SAAS,cAAAA,aAAY,gBAAAC,eAAc,aAAa,UAAU,kBAAkB;AAC5E,SAAS,WAAAC,gBAAe;AACxB,SAAS,UAAU,QAAAC,aAAY;;;ACA/B,SAAS,YAAY,eAAe,iBAAiB;AACrD,SAAS,eAAe;AACxB,SAAS,YAAY;;;ACRd,IAAM,eACX,QAAQ,IAAI,sBAAsB;AAiB7B,IAAM,qBAA+C;AAAA,EAC1D,eAAe;AAAA,EACf,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,OAAO;AACT;AAGO,IAAM,mBAAmB;;;ADdhC,IAAM,SAAS,QAAQ,aAAa;AAEpC,SAAS,aAAa,KAAqB;AACzC,MAAI,QAAQ;AACV,WAAO,KAAK,QAAQ,IAAI,WAAW,KAAK,QAAQ,GAAG,WAAW,SAAS,GAAG,GAAG;AAAA,EAC/E;AACA,SAAO,KAAK,QAAQ,IAAI,mBAAmB,KAAK,QAAQ,GAAG,SAAS,GAAG,GAAG;AAC5E;AA2BA,IAAM,UAAU;AAEhB,SAAS,WAAW,QAA4B;AAC9C,SAAO;AAAA,IACL,EAAE,KAAK,aAAa,KAAK,GAAG,MAAM,eAAe,OAAO,UAAU,UAAU,GAAG;AAAA,IAC/E,EAAE,KAAK,aAAa,KAAK,GAAG,MAAM,uBAAuB,OAAO,UAAU,UAAU,GAAG;AAAA,IACvF,EAAE,KAAK,eAAe,KAAK,GAAG,MAAM,iBAAiB,OAAO,OAAO,UAAU,IAAI;AAAA,IACjF,EAAE,KAAK,gBAAgB,KAAK,GAAG,MAAM,kBAAkB,OAAO,OAAO,UAAU,IAAI;AAAA,IACnF,EAAE,KAAK,aAAa,KAAK,GAAG,MAAM,UAAU,OAAO,UAAU,UAAU,UAAU;AAAA,EACnF;AACF;AAEA,SAAS,eAAe,QAAgB,UAAkB,QAAgB,QAA4B;AACpG,QAAM,QAAQ,OAAO;AAAA,IAAI,CAAC,MACxB,EAAE,UAAU,QACR,OAAO,EAAE,GAAG,2BAA2B,EAAE,GAAG,UAAU,EAAE,QAAQ,WAChE,OAAO,EAAE,GAAG,kBAAkB,EAAE,GAAG,UAAU,EAAE,QAAQ;AAAA,EAC7D;AAEA,SAAO;AAAA;AAAA;AAAA;AAAA,gBAIO,KAAK,UAAU,MAAM,CAAC;AAAA,gBACtB,KAAK,UAAU,YAAY,CAAC;AAAA;AAAA,oBAExB,MAAM;AAAA;AAAA;AAAA,gBAGV,KAAK,UAAU,QAAQ,CAAC;AAAA;AAAA,EAEtC,MAAM,KAAK,KAAK,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYnB;AAEA,SAAS,eAAuB;AAC9B,SAAO;AAAA,6BACoB,OAAO;AAAA;AAEpC;AAEA,SAAS,aAAqB;AAC5B,SAAO,eAAe,OAAO;AAAA;AAC/B;AAIA,SAAS,YACP,aACA,UACA,WACA,QACA,UACA,QACA,QACe;AACf,MAAI;AACF,QAAI,CAAC,WAAW,QAAQ,EAAG,WAAU,UAAU,EAAE,WAAW,KAAK,CAAC;AAElE,kBAAc,KAAK,UAAU,OAAO,GAAG,eAAe,QAAQ,UAAU,QAAQ,MAAM,GAAG,EAAE,MAAM,IAAM,CAAC;AAExG,QAAI,QAAQ;AACV,oBAAc,WAAW,WAAW,CAAC;AAAA,IACvC,OAAO;AACL,oBAAc,WAAW,aAAa,GAAG,EAAE,MAAM,IAAM,CAAC;AAAA,IAC1D;AAEA,WAAO,EAAE,SAAS,MAAM,SAAS,GAAG,WAAW,sBAAsB,SAAS,IAAI,UAAU,UAAU;AAAA,EACxG,SAAS,KAAK;AACZ,WAAO,EAAE,SAAS,OAAO,SAAS,qBAAqB,WAAW,UAAU,GAAG,GAAG;AAAA,EACpF;AACF;AAEA,SAAS,gBAAwB;AAC/B,SAAO,SAAS,oBAAoB;AACtC;AAIA,IAAM,oBAAN,MAA+C;AAAA,EAC7C,OAAO;AAAA,EACP,cAAc;AAAA,EAEd,IAAY,YAAY;AAAE,WAAO,KAAK,QAAQ,GAAG,SAAS;AAAA,EAAG;AAAA,EAC7D,IAAY,WAAW;AAAE,WAAO,KAAK,KAAK,WAAW,OAAO;AAAA,EAAG;AAAA,EAE/D,cAAc;AAAE,WAAO,KAAK,KAAK,UAAU,cAAc,CAAC;AAAA,EAAG;AAAA,EAC7D,SAAS;AAAE,WAAO,WAAW,KAAK,SAAS;AAAA,EAAG;AAAA,EAE9C,QAAQ,QAAgB;AACtB,WAAO;AAAA,MAAY,KAAK;AAAA,MAAa,KAAK;AAAA,MAAU,KAAK,YAAY;AAAA,MAAG;AAAA,MAAQ;AAAA,MAAe;AAAA,MAC7F;AAAA,QACE,GAAG,WAAW,QAAQ;AAAA,QACtB,EAAE,KAAK,uBAAuB,KAAK,gCAAgC,OAAO,OAAO,UAAU,IAAI;AAAA,QAC/F,EAAE,KAAK,mBAAmB,KAAK,4BAA4B,OAAO,OAAO,UAAU,IAAI;AAAA,MACzF;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAM,kBAAN,MAA6C;AAAA,EAC3C,OAAO;AAAA,EACP,cAAc;AAAA,EAEd,IAAY,YAAY;AAAE,WAAO,aAAa,UAAU;AAAA,EAAG;AAAA,EAC3D,IAAY,WAAW;AAAE,WAAO,KAAK,KAAK,WAAW,OAAO;AAAA,EAAG;AAAA,EAE/D,cAAc;AAAE,WAAO,KAAK,KAAK,UAAU,cAAc,CAAC;AAAA,EAAG;AAAA,EAC7D,SAAS;AAAE,WAAO,WAAW,KAAK,SAAS;AAAA,EAAG;AAAA,EAE9C,QAAQ,QAAgB;AACtB,WAAO;AAAA,MAAY,KAAK;AAAA,MAAa,KAAK;AAAA,MAAU,KAAK,YAAY;AAAA,MAAG;AAAA,MAAQ;AAAA,MAAY;AAAA,MAC1F,WAAW,UAAU;AAAA,IACvB;AAAA,EACF;AACF;AAEA,IAAM,gBAAN,MAA2C;AAAA,EACzC,YACS,MACA,aACC,SACA,WACR;AAJO;AACA;AACC;AACA;AAAA,EACP;AAAA,EAEH,IAAY,YAAY;AAAE,WAAO,KAAK,QAAQ,GAAG,KAAK,OAAO;AAAA,EAAG;AAAA,EAChE,IAAY,WAAW;AAAE,WAAO,KAAK,KAAK,WAAW,OAAO;AAAA,EAAG;AAAA,EAE/D,cAAc;AAAE,WAAO,KAAK,KAAK,UAAU,cAAc,CAAC;AAAA,EAAG;AAAA,EAC7D,SAAS;AAAE,WAAO,WAAW,KAAK,SAAS;AAAA,EAAG;AAAA,EAE9C,QAAQ,QAAgB;AACtB,WAAO;AAAA,MAAY,KAAK;AAAA,MAAa,KAAK;AAAA,MAAU,KAAK,YAAY;AAAA,MAAG;AAAA,MAAQ,KAAK;AAAA,MAAM,KAAK;AAAA,MAC9F,WAAW,KAAK,SAAS;AAAA,IAC3B;AAAA,EACF;AACF;AAIO,SAAS,iBAAgC;AAC9C,SAAO;AAAA,IACL,IAAI,kBAAkB;AAAA,IACtB,IAAI,gBAAgB;AAAA,IACpB,IAAI,cAAc,UAAU,cAAc,WAAW,QAAQ;AAAA,IAC7D,IAAI,cAAc,SAAS,aAAa,UAAU,OAAO;AAAA,IACzD,IAAI,cAAc,SAAS,SAAS,UAAU,OAAO;AAAA,EACvD;AACF;;;AE9MA,SAAS,YAAY,kBAAkB;AAQhC,SAAS,qBACd,QACA,WACA,MACQ;AACR,QAAM,UAAU,GAAG,SAAS,IAAI,IAAI;AACpC,SAAO,WAAW,UAAU,MAAM,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAClE;;;ACmCA,SAAS,QAAQ,MAA8B;AAC7C,SAAO,KAAK,aAAa;AAC3B;AAEA,SAAS,gBACP,QACA,MACwB;AACxB,QAAM,UAAkC;AAAA,IACtC,gBAAgB;AAAA,IAChB,aAAa;AAAA,EACf;AAEA,MAAI,SAAS,QAAW;AACtB,UAAM,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,EAAE,SAAS;AACzD,UAAM,YAAY,qBAAqB,QAAQ,WAAW,IAAI;AAC9D,YAAQ,aAAa,IAAI;AACzB,YAAQ,aAAa,IAAI;AAAA,EAC3B;AAEA,SAAO;AACT;AA+CA,eAAsB,QACpB,MAC2D;AAC3D,QAAM,MAAM,GAAG,QAAQ,IAAI,CAAC;AAE5B,QAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC3B,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,aAAa,KAAK;AAAA,IACpB;AAAA,EACF,CAAC;AAED,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,CAAC,IAAI,IAAI;AACX,WAAO,EAAE,SAAS,OAAO,OAAQ,KAAkB,SAAS,QAAQ,IAAI,MAAM,GAAG;AAAA,EACnF;AACA,SAAO;AACT;AAWA,eAAsB,aACpB,SACA,WACuB;AACvB,QAAM,OAAO,KAAK,UAAU,OAAO;AACnC,QAAM,MAAM,GAAG,aAAa,YAAY;AAExC,QAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC3B,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C;AAAA,EACF,CAAC;AAED,SAAQ,MAAM,IAAI,KAAK;AACzB;AAEA,eAAsB,UACpB,SACA,WACuB;AACvB,QAAM,OAAO,KAAK,UAAU,EAAE,GAAG,SAAS,QAAQ,MAAM,CAAC;AACzD,QAAM,MAAM,GAAG,aAAa,YAAY;AAExC,QAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC3B,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C;AAAA,EACF,CAAC;AAED,SAAQ,MAAM,IAAI,KAAK;AACzB;AAuBA,eAAsB,iBACpB,SACA,MAC+D;AAC/D,QAAM,OAAO,KAAK,UAAU,OAAO;AACnC,QAAM,MAAM,GAAG,QAAQ,IAAI,CAAC;AAE5B,QAAM,MAAM,MAAM,MAAM,KAAK;AAAA,IAC3B,QAAQ;AAAA,IACR,SAAS,gBAAgB,KAAK,QAAQ,IAAI;AAAA,IAC1C;AAAA,EACF,CAAC;AAED,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,CAAC,IAAI,IAAI;AACX,WAAO,EAAE,SAAS,OAAO,OAAQ,KAAkB,SAAS,QAAQ,IAAI,MAAM,GAAG;AAAA,EACnF;AACA,SAAO;AACT;;;ACzNA,SAAS,cAAc,iBAAAC,gBAAe,cAAAC,aAAY,aAAAC,kBAAiB;AACnE,SAAS,WAAAC,gBAAe;AACxB,SAAS,QAAAC,OAAM,eAAe;AAS9B,SAAS,gBAAwB;AAC/B,SAAOC,MAAKC,SAAQ,GAAG,gBAAgB;AACzC;AAEO,SAAS,aAA4B;AAC1C,QAAM,aAAa,cAAc;AACjC,MAAI,CAACC,YAAW,UAAU,EAAG,QAAO;AAEpC,MAAI;AACF,UAAM,MAAM,aAAa,YAAY,OAAO;AAC5C,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,WAAW,QAAsB;AAC/C,QAAM,aAAa,cAAc;AACjC,QAAM,MAAM,QAAQ,UAAU;AAC9B,MAAI,CAACA,YAAW,GAAG,EAAG,CAAAC,WAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AACxD,EAAAC,eAAc,YAAY,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,MAAM,OAAO;AAC3E;AAEO,SAAS,gBAAwB;AACrC,QAAM,SAAS,WAAW;AAC1B,MAAI,CAAC,QAAQ,QAAQ;AACnB,YAAQ;AAAA,MACN;AAAA,IACF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,SAAO;AACV;;;AL9BA,SAAS,OAAO,UAAmC;AACjD,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,OAAG,SAAS,UAAU,CAAC,WAAW;AAChC,SAAG,MAAM;AACT,cAAQ,OAAO,KAAK,CAAC;AAAA,IACvB,CAAC;AAAA,EACH,CAAC;AACH;AAEA,SAAS,eAAe,UAAmC;AACzD,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,YAAQ,OAAO,MAAM,QAAQ;AAC7B,UAAM,QAAkB,CAAC;AACzB,UAAM,QAAQ,QAAQ;AACtB,UAAM,SAAS,MAAM;AACrB,UAAM,WAAW,IAAI;AACrB,UAAM,OAAO;AACb,UAAM,YAAY,MAAM;AAExB,UAAM,SAAS,CAAC,OAAe;AAC7B,YAAM,IAAI,GAAG,SAAS;AACtB,UAAI,MAAM,QAAQ,MAAM,QAAQ,MAAM,KAAU;AAE9C,cAAM,WAAW,UAAU,KAAK;AAChC,cAAM,MAAM;AACZ,cAAM,eAAe,QAAQ,MAAM;AACnC,gBAAQ,OAAO,MAAM,IAAI;AACzB,gBAAQ,MAAM,KAAK,EAAE,EAAE,KAAK,CAAC;AAAA,MAC/B,WAAW,MAAM,KAAU;AAEzB,gBAAQ,OAAO,MAAM,IAAI;AACzB,gBAAQ,KAAK,CAAC;AAAA,MAChB,WAAW,MAAM,UAAY,MAAM,MAAM;AAEvC,YAAI,MAAM,SAAS,GAAG;AACpB,gBAAM,IAAI;AACV,kBAAQ,OAAO,MAAM,OAAO;AAAA,QAC9B;AAAA,MACF,OAAO;AACL,cAAM,KAAK,CAAC;AACZ,gBAAQ,OAAO,MAAM,GAAG;AAAA,MAC1B;AAAA,IACF;AAEA,UAAM,GAAG,QAAQ,MAAM;AAAA,EACzB,CAAC;AACH;AAIA,eAAsB,kBAAiC;AACrD,UAAQ,IAAI,0CAA8B;AAE1C,QAAM,WAAW,MAAM,OAAO,2BAA2B;AACzD,MAAI,CAAC,YAAY,SAAS,SAAS,KAAK,SAAS,SAAS,IAAI;AAC5D,YAAQ,MAAM,wDAAwD;AACtE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,WAAW,MAAM,eAAe,4BAA4B;AAClE,MAAI,CAAC,YAAY,SAAS,SAAS,GAAG;AACpC,YAAQ,MAAM,kDAAkD;AAChE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,cAAc,MAAM,OAAO,kDAAkD;AAEnF,UAAQ,IAAI,oBAAoB;AAEhC,QAAM,WAAW,WAAW;AAC5B,QAAM,SAAS,MAAM;AAAA,IACnB,EAAE,UAAU,UAAU,aAAa,eAAe,OAAU;AAAA,IAC5D,UAAU;AAAA,EACZ;AAEA,MAAI,OAAO,OAAO;AAChB,YAAQ,MAAM;AAAA,WAAc,OAAO,KAAK;AAAA,CAAI;AAC5C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,+CAA+C;AAC7D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,aAAW,EAAE,QAAQ,OAAO,QAAQ,WAAW,UAAU,UAAU,CAAC;AACpE,UAAQ,IAAI,qCAAgC;AAC5C,UAAQ,IAAI,8CAAyC;AACrD,UAAQ,IAAI;AAAA,cAAiB,OAAO,MAAM,QAAQ,EAAE;AACpD,UAAQ,IAAI,eAAe,OAAO,OAAO,MAAM,GAAG,EAAE,CAAC,MAAM,OAAO,OAAO,MAAM,EAAE,CAAC,EAAE;AACpF,UAAQ,IAAI,mEAAyD;AAErE,UAAQ,IAAI,sDAAsD;AAClE,QAAM,eAAe,OAAO,MAAM;AACpC;AAIA,eAAsB,eAA8B;AAClD,UAAQ,IAAI,uCAA2B;AAEvC,QAAM,WAAW,MAAM,OAAO,cAAc;AAC5C,MAAI,CAAC,UAAU;AACb,YAAQ,MAAM,gCAAgC;AAC9C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,WAAW,MAAM,eAAe,cAAc;AACpD,MAAI,CAAC,UAAU;AACb,YAAQ,MAAM,gCAAgC;AAC9C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,IAAI,mBAAmB;AAE/B,QAAM,WAAW,WAAW;AAC5B,QAAM,SAAS,MAAM,UAAU,EAAE,UAAU,SAAS,GAAG,UAAU,SAAS;AAE1E,MAAI,OAAO,OAAO;AAChB,YAAQ,MAAM;AAAA,WAAc,OAAO,KAAK;AAAA,CAAI;AAC5C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,+CAA+C;AAC7D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,aAAW,EAAE,QAAQ,OAAO,QAAQ,WAAW,UAAU,UAAU,CAAC;AACpE,UAAQ,IAAI,8BAAyB;AACrC,UAAQ,IAAI,8CAAyC;AACrD,UAAQ,IAAI;AAAA,cAAiB,OAAO,MAAM,QAAQ,EAAE;AACpD,UAAQ,IAAI,eAAe,OAAO,OAAO,MAAM,GAAG,EAAE,CAAC,MAAM,OAAO,OAAO,MAAM,EAAE,CAAC,EAAE;AACpF,UAAQ,IAAI,wEAAmE;AAE/E,UAAQ,IAAI,4CAA4C;AACxD,QAAM,eAAe,OAAO,MAAM;AACpC;AAIA,eAAsB,eAAe,QAAgC;AACnE,UAAQ,IAAI,8DAAkD;AAG9D,QAAM,WAAW,WAAW;AAC5B,MAAI,UAAU,UAAU,CAAC,QAAQ;AAC/B,YAAQ,IAAI,4BAAuB;AACnC,YAAQ,IAAI,iDAAiD;AAC7D,aAAS,SAAS;AAAA,EACpB;AAEA,MAAI,CAAC,QAAQ;AACX,YAAQ;AAAA,MACN;AAAA,IAGF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,MAAI,CAAC,OAAO,WAAW,aAAa,GAAG;AACrC,YAAQ;AAAA,MACN;AAAA,IACF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,aAAW,EAAE,OAAO,CAAC;AACrB,UAAQ,IAAI,8CAAyC;AAGrD,QAAM,WAAW,eAAe;AAChC,QAAM,UAAqD,CAAC;AAE5D,UAAQ,IAAI,gCAAgC;AAE5C,aAAW,WAAW,UAAU;AAC9B,UAAM,WAAW,QAAQ,OAAO;AAChC,QAAI,UAAU;AACZ,cAAQ,IAAI,YAAO,QAAQ,WAAW,WAAW;AACjD,YAAM,SAAS,QAAQ,QAAQ,MAAM;AACrC,cAAQ,KAAK,EAAE,MAAM,QAAQ,aAAa,OAAO,CAAC;AAClD,UAAI,OAAO,SAAS;AAClB,gBAAQ,IAAI,8BAAyB,OAAO,QAAQ,EAAE;AAAA,MACxD,OAAO;AACL,gBAAQ,IAAI,cAAS,OAAO,OAAO,EAAE;AAAA,MACvC;AAAA,IACF,OAAO;AACL,cAAQ,IAAI,OAAO,QAAQ,WAAW,YAAY;AAAA,IACpD;AAAA,EACF;AAEA,QAAM,YAAY,QAAQ,OAAO,CAAC,MAAM,EAAE,OAAO,OAAO;AACxD,UAAQ;AAAA,IACN;AAAA,yBAAuB,UAAU,MAAM;AAAA;AAAA,EACzC;AAEA,MAAI,UAAU,WAAW,GAAG;AAC1B,YAAQ;AAAA,MACN;AAAA,IAMF;AAAA,EACF;AACF;AAIA,eAAsB,cAA6B;AACjD,QAAM,SAAS,cAAc;AAC5B,UAAQ,IAAI,4CAAgC;AAE7C,QAAM,SAAS,MAAM,QAAQ,EAAE,QAAQ,OAAO,QAAQ,WAAW,OAAO,UAAU,CAAC;AAEnF,MAAI,CAAC,OAAO,SAAS;AACnB,YAAQ,MAAM,UAAU,WAAW,SAAS,OAAO,QAAQ,eAAe;AAAA,CAAI;AAC9E,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,EAAE,UAAU,WAAW,CAAC,OAAO,MAAM;AACvC,YAAQ,MAAM,sCAAsC;AACpD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,EAAE,UAAU,OAAO,SAAS,IAAI,OAAO;AAE7C,UAAQ,IAAI,eAAe,QAAQ,EAAE;AACrC,UAAQ,IAAI,eAAe,aAAa,MAAM,WAAW,CAAC,EAAE;AAC5D,UAAQ,IAAI,eAAe,MAAM,aAAa,EAAE;AAChD,UAAQ,IAAI,eAAe,SAAS,uBAAuB,EAAE;AAC7D,UAAQ,IAAI,EAAE;AAGd,MAAI,MAAM,cAAc,SAAS,GAAG;AAClC,YAAQ,IAAI,mBAAmB;AAC/B,eAAW,SAAS,MAAM,eAAe;AACvC,YAAM,OAAO,mBAAmB,MAAM,IAAgB,KAAK,MAAM;AACjE,cAAQ;AAAA,QACN,OAAO,IAAI,KAAK,aAAa,MAAM,MAAM,CAAC;AAAA,MAC5C;AAAA,IACF;AACA,YAAQ,IAAI,EAAE;AAAA,EAChB;AAGA,QAAM,OAAO,MAAM,UAAU;AAAA,IAC3B,CAAC,KAAK,OAAO,EAAE,QAAQ,IAAI,SAAS,EAAE,cAAc,EAAE,cAAc,UAAU,IAAI,WAAW,EAAE,SAAS;AAAA,IACxG,EAAE,QAAQ,GAAG,UAAU,EAAE;AAAA,EAC3B;AACA,QAAM,QAAQ,MAAM,WAAW;AAAA,IAC7B,CAAC,KAAK,OAAO,EAAE,QAAQ,IAAI,SAAS,EAAE,cAAc,EAAE,cAAc,UAAU,IAAI,WAAW,EAAE,SAAS;AAAA,IACxG,EAAE,QAAQ,GAAG,UAAU,EAAE;AAAA,EAC3B;AACA,UAAQ;AAAA,IACN,mBAAmB,aAAa,KAAK,MAAM,CAAC,YAAY,KAAK,QAAQ;AAAA,EACvE;AACA,UAAQ;AAAA,IACN,mBAAmB,aAAa,MAAM,MAAM,CAAC,YAAY,MAAM,QAAQ;AAAA,EACzE;AACA,UAAQ,IAAI,EAAE;AAChB;AAIO,SAAS,gBAAsB;AACnC,QAAM,SAAS,WAAW;AAC1B,UAAQ,IAAI,wCAA4B;AAExC,MAAI,CAAC,QAAQ,QAAQ;AACnB,YAAQ,IAAI,0BAA0B;AACtC,YAAQ;AAAA,MACN;AAAA,IACF;AACA;AAAA,EACF;AAED,QAAM,YACJ,OAAO,OAAO,MAAM,GAAG,EAAE,IAAI,QAAQ,OAAO,OAAO,MAAM,EAAE;AAC7D,UAAQ,IAAI,cAAc,SAAS,EAAE;AACrC,UAAQ,IAAI,cAAc,OAAO,aAAa,YAAY,EAAE;AAC5D,UAAQ,IAAI,EAAE;AAGd,QAAM,WAAW,eAAe;AAChC,UAAQ,IAAI,oBAAoB;AAChC,MAAI,YAAY;AAChB,aAAW,WAAW,UAAU;AAC9B,UAAM,WAAW,QAAQ,OAAO;AAChC,QAAI,UAAU;AACZ,YAAM,aAAaC,YAAW,QAAQ,YAAY,CAAC;AACnD,YAAM,SAAS,aAAa,kBAAa;AACzC,cAAQ,IAAI,OAAO,QAAQ,WAAW,KAAK,MAAM,EAAE;AACnD,UAAI,WAAY;AAAA,IAClB;AAAA,EACF;AACA,MAAI,cAAc,GAAG;AACnB,YAAQ,IAAI,YAAY;AAAA,EAC1B;AACA,UAAQ,IAAI,EAAE;AAChB;AAIO,SAAS,mBAAyB;AACtC,UAAQ,IAAI,kDAAiC;AAG9C,QAAM,WAAW,eAAe;AAChC,aAAW,WAAW,UAAU;AAC9B,UAAM,WAAW,QAAQ,YAAY;AACrC,QAAIA,YAAW,QAAQ,GAAG;AACxB,iBAAW,QAAQ;AACnB,cAAQ,IAAI,oBAAe,QAAQ,WAAW,OAAO;AAAA,IACvD;AAAA,EACF;AAGC,QAAM,aAAaC,MAAKC,SAAQ,GAAG,kBAAkB;AACrD,MAAIF,YAAW,UAAU,GAAG;AAC1B,eAAW,UAAU;AACrB,YAAQ,IAAI,qCAAgC;AAAA,EAC9C;AAEA,UAAQ,IAAI,oCAA+B;AAC9C;AAIA,IAAM,cAAc,oBAAI,IAAI;AAAA,EAC1B;AAAA,EAAgB;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAS;AAAA,EAC3D;AAAA,EAAU;AAAA,EAAU;AAAA,EAAW;AAAA,EAAe;AAAA,EAC9C;AAAA,EAAY;AAAA,EAAW;AACzB,CAAC;AAED,SAAS,qBACP,KACA,UACA,eAAe,GACW;AAC1B,QAAM,SAAmC,CAAC;AAC1C,MAAI,gBAAgB,SAAU,QAAO;AAErC,MAAI;AACJ,MAAI;AACF,cAAU,YAAY,GAAG;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,SAAS;AAC3B,QAAI,MAAM,WAAW,GAAG,KAAK,YAAY,IAAI,KAAK,EAAG;AACrD,QAAI,YAAY,IAAI,KAAK,EAAG;AAE5B,UAAM,WAAWC,MAAK,KAAK,KAAK;AAChC,QAAI;AACJ,QAAI;AACF,aAAO,SAAS,QAAQ;AAAA,IAC1B,QAAQ;AACN;AAAA,IACF;AAEA,QAAI,KAAK,YAAY,GAAG;AACtB,YAAM,MAAM,qBAAqB,UAAU,UAAU,eAAe,CAAC;AACrE,iBAAW,CAAC,KAAK,GAAG,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC5C,eAAO,GAAG,IAAI;AAAA,MAChB;AAAA,IACF,OAAO;AACL,YAAM,KAAK,KAAK;AAAA,IAClB;AAAA,EACF;AAEA,MAAI,MAAM,SAAS,GAAG;AACpB,UAAM,SAAS,iBAAiB,IAAI,MAAM,IAAI,MAAM,GAAG,EAAE,MAAM,CAAE,YAAa,EAAE,KAAK,GAAG;AACxF,WAAO,MAAM,IAAI;AAAA,EACnB;AAEA,SAAO;AACT;AAEA,eAAsB,gBAA+B;AACnD,QAAM,SAAS,cAAc;AAC7B,UAAQ,IAAI,gDAAoC;AAEhD,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,cAAc,SAAS,GAAG;AAEhC,QAAM,aAAaA,MAAK,KAAK,WAAW;AACxC,MAAI,CAACD,YAAW,UAAU,GAAG;AAC3B,YAAQ,MAAM,sDAAsD;AACpE,YAAQ,MAAM,wDAAwD;AACtE,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,cAAcG,cAAa,YAAY,OAAO;AACpD,MAAI,YAAY,KAAK,EAAE,WAAW,GAAG;AACnC,YAAQ,MAAM,8BAA8B;AAC5C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,IAAI,eAAe,WAAW,EAAE;AACxC,UAAQ,IAAI,eAAe,UAAU,EAAE;AACvC,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,gCAAgC;AAE5C,QAAM,gBAAgB,qBAAqB,KAAK,CAAC;AACjD,QAAM,YAAY,OAAO,OAAO,aAAa,EAAE,OAAO,CAAC,KAAK,UAAU,MAAM,MAAM,QAAQ,CAAC;AAC3F,UAAQ,IAAI,WAAW,SAAS,eAAe,OAAO,KAAK,aAAa,EAAE,MAAM,YAAY,OAAO,KAAK,aAAa,EAAE,WAAW,IAAI,MAAM,KAAK,EAAE;AACnJ,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,kCAAkC;AAE9C,QAAM,SAAS,MAAM;AAAA,IACnB,EAAE,aAAa,aAAa,cAAc;AAAA,IAC1C,EAAE,QAAQ,OAAO,QAAQ,WAAW,OAAO,UAAU;AAAA,EACvD;AAEA,MAAI,CAAC,OAAO,SAAS;AACnB,YAAQ,MAAM,UAAU,WAAW,SAAS,OAAO,QAAQ,eAAe;AAAA,CAAI;AAC9E,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,EAAE,WAAW,IAAI;AACvB,QAAM,aAAa,WAAW,SAAS,WAAM;AAC7C,QAAM,aAAa,WAAW,SAAS,WAAW;AAElD,UAAQ,IAAI,aAAa,UAAU,IAAI,UAAU,EAAE;AACnD,UAAQ,IAAI,kBAAkB,WAAW,UAAU,MAAM;AACzD,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,kBAAkB;AAC9B,UAAQ,IAAI,sBAAsB,WAAW,mBAAmB,KAAK;AACrE,UAAQ,IAAI,sBAAsB,WAAW,kBAAkB,KAAK;AACpE,UAAQ,IAAI,EAAE;AAEd,MAAI,WAAW,UAAU;AACvB,YAAQ,IAAI,aAAa;AACzB,UAAM,QAAQ,WAAW,SAAS,MAAM,IAAI;AAC5C,eAAW,QAAQ,OAAO;AACxB,cAAQ,IAAI,OAAO,IAAI,EAAE;AAAA,IAC3B;AACA,YAAQ,IAAI,EAAE;AAAA,EAChB;AACF;AAIA,SAAS,aAAa,GAAmB;AACvC,MAAI,KAAK,IAAW,QAAO,IAAI,IAAI,KAAW,QAAQ,CAAC,CAAC;AACxD,MAAI,KAAK,IAAO,QAAO,IAAI,IAAI,KAAO,QAAQ,CAAC,CAAC;AAChD,SAAO,EAAE,SAAS;AACpB;;;AM/bA,IAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,IAAM,UAAU,KAAK,CAAC;AAEtB,SAAS,YAAkB;AACxB,UAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAyBd;AACD;AAEA,eAAe,OAAsB;AACnC,MAAI,CAAC,WAAW,YAAY,YAAY,YAAY,MAAM;AACxD,cAAU;AACV,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,YAAY,eAAe,YAAY,MAAM;AAC/C,YAAQ,IAAI,OAAO;AACnB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,SAAS;AAAA,IACf,KAAK;AACH,YAAM,gBAAgB;AACtB;AAAA,IACF,KAAK;AACH,YAAM,aAAa;AACnB;AAAA,IACF,KAAK,WAAW;AACd,YAAM,WAAW,KAAK,QAAQ,WAAW;AACzC,YAAM,SAAS,YAAY,IAAI,KAAK,WAAW,CAAC,IAAI;AACpD,YAAM,eAAe,MAAM;AAC3B;AAAA,IACF;AAAA,IACA,KAAK;AACH,YAAM,YAAY;AAClB;AAAA,IACF,KAAK;AACH,oBAAc;AACd;AAAA,IACF,KAAK;AACH,YAAM,cAAc;AACpB;AAAA,IACF,KAAK;AACH,uBAAiB;AACjB;AAAA,IACF;AACE,cAAQ,MAAM,oBAAoB,OAAO,EAAE;AAC3C,gBAAU;AACV,cAAQ,KAAK,CAAC;AAAA,EAClB;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,MAAM,gBAAgB,GAAG;AACjC,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["existsSync","readFileSync","homedir","join","writeFileSync","existsSync","mkdirSync","homedir","join","join","homedir","existsSync","mkdirSync","writeFileSync","existsSync","join","homedir","readFileSync"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suncreation/modu-arena",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "Track and rank your AI coding tool usage across Claude Code, OpenCode, Gemini CLI, Codex CLI, and Crush",
6
6
  "bin": {