@suncreation/modu-arena 0.1.5 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,16 @@
1
1
  #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
2
8
 
3
9
  // src/commands.ts
4
10
  import { createInterface } from "readline";
5
- import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync, statSync, unlinkSync } from "fs";
6
- import { homedir as homedir3 } from "os";
7
- import { basename, join as join3 } from "path";
11
+ import { existsSync as existsSync5, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync, unlinkSync as unlinkSync2 } from "fs";
12
+ import { homedir as homedir5 } from "os";
13
+ import { basename, join as join5 } from "path";
8
14
 
9
15
  // src/adapters.ts
10
16
  import { existsSync, writeFileSync, mkdirSync } from "fs";
@@ -22,6 +28,8 @@ var TOOL_DISPLAY_NAMES = {
22
28
  crush: "Crush"
23
29
  };
24
30
  var CONFIG_FILE_NAME = ".modu-arena.json";
31
+ var DAEMON_STATE_FILE = ".modu-arena-daemon.json";
32
+ var DAEMON_SYNC_INTERVAL_SEC = 300;
25
33
 
26
34
  // src/adapters.ts
27
35
  var IS_WIN = process.platform === "win32";
@@ -64,33 +72,31 @@ fetch(SERVER + "/api/v1/sessions", {
64
72
  }).catch(function(){});
65
73
  `;
66
74
  }
67
- function shellWrapper(suffix = "") {
75
+ function shellWrapper() {
68
76
  return `#!/bin/bash
69
- exec node "$(dirname "$0")/_modu-hook${suffix}.js"
77
+ exec node "$(dirname "$0")/${HOOK_JS}"
70
78
  `;
71
79
  }
72
- function cmdWrapper(suffix = "") {
73
- return `@node "%~dp0_modu-hook${suffix}.js" 2>nul\r
80
+ function cmdWrapper() {
81
+ return `@node "%~dp0${HOOK_JS}" 2>nul\r
74
82
  `;
75
83
  }
76
- function installHook(displayName, hooksDir, entryPath, apiKey, toolType, prefix, fields, suffix = "") {
84
+ function installHook(displayName, hooksDir, entryPath, apiKey, toolType, prefix, fields) {
77
85
  try {
78
86
  if (!existsSync(hooksDir)) mkdirSync(hooksDir, { recursive: true });
79
- const hookJsName = suffix ? `_modu-hook-${suffix}.js` : HOOK_JS;
80
- writeFileSync(join(hooksDir, hookJsName), generateHookJs(apiKey, toolType, prefix, fields), { mode: 493 });
87
+ writeFileSync(join(hooksDir, HOOK_JS), generateHookJs(apiKey, toolType, prefix, fields), { mode: 493 });
81
88
  if (IS_WIN) {
82
- writeFileSync(entryPath, cmdWrapper(suffix));
89
+ writeFileSync(entryPath, cmdWrapper());
83
90
  } else {
84
- writeFileSync(entryPath, shellWrapper(suffix), { mode: 493 });
91
+ writeFileSync(entryPath, shellWrapper(), { mode: 493 });
85
92
  }
86
93
  return { success: true, message: `${displayName} hook installed at ${entryPath}`, hookPath: entryPath };
87
94
  } catch (err) {
88
95
  return { success: false, message: `Failed to install ${displayName} hook: ${err}` };
89
96
  }
90
97
  }
91
- function hookEntryName(suffix = "") {
92
- const name = suffix ? `session-end-${suffix}` : "session-end";
93
- return IS_WIN ? `${name}.cmd` : `${name}.sh`;
98
+ function hookEntryName() {
99
+ return IS_WIN ? "session-end.cmd" : "session-end.sh";
94
100
  }
95
101
  var ClaudeCodeAdapter = class {
96
102
  slug = "claude-code";
@@ -123,38 +129,6 @@ var ClaudeCodeAdapter = class {
123
129
  );
124
130
  }
125
131
  };
126
- var ClaudeDesktopAdapter = class {
127
- slug = "claude-desktop";
128
- displayName = "Claude Desktop";
129
- get configDir() {
130
- return join(homedir(), ".claude");
131
- }
132
- get hooksDir() {
133
- return join(this.configDir, "hooks");
134
- }
135
- getHookPath() {
136
- return join(this.hooksDir, hookEntryName("desktop"));
137
- }
138
- detect() {
139
- return existsSync(this.configDir);
140
- }
141
- install(apiKey) {
142
- return installHook(
143
- this.displayName,
144
- this.hooksDir,
145
- this.getHookPath(),
146
- apiKey,
147
- "claude-desktop",
148
- "CLAUDE",
149
- [
150
- ...baseFields("CLAUDE"),
151
- { key: "cacheCreationTokens", env: "CLAUDE_CACHE_CREATION_TOKENS", parse: "int", fallback: "0" },
152
- { key: "cacheReadTokens", env: "CLAUDE_CACHE_READ_TOKENS", parse: "int", fallback: "0" }
153
- ],
154
- "desktop"
155
- );
156
- }
157
- };
158
132
  var OpenCodeAdapter = class {
159
133
  slug = "opencode";
160
134
  displayName = "OpenCode";
@@ -218,7 +192,6 @@ var SimpleAdapter = class {
218
192
  function getAllAdapters() {
219
193
  return [
220
194
  new ClaudeCodeAdapter(),
221
- new ClaudeDesktopAdapter(),
222
195
  new OpenCodeAdapter(),
223
196
  new SimpleAdapter("gemini", "Gemini CLI", ".gemini", "GEMINI"),
224
197
  new SimpleAdapter("codex", "Codex CLI", ".codex", "CODEX"),
@@ -333,6 +306,312 @@ function requireConfig() {
333
306
  return config;
334
307
  }
335
308
 
309
+ // src/daemon.ts
310
+ import { writeFileSync as writeFileSync3, existsSync as existsSync3, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
311
+ import { homedir as homedir3 } from "os";
312
+ import { join as join3 } from "path";
313
+ import { execSync } from "child_process";
314
+ var IS_WIN2 = process.platform === "win32";
315
+ var DAEMON_NAME = "com.modu-arena.sync";
316
+ function getDaemonLogDir() {
317
+ const dir = join3(homedir3(), ".modu-arena", "logs");
318
+ if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
319
+ return dir;
320
+ }
321
+ function getNodePath() {
322
+ try {
323
+ return execSync("which node", { encoding: "utf-8" }).trim();
324
+ } catch {
325
+ return "node";
326
+ }
327
+ }
328
+ function getCliPath() {
329
+ return __require.resolve("./index.js").replace(/index\.js$/, "index.js");
330
+ }
331
+ function installDaemon() {
332
+ if (IS_WIN2) {
333
+ return installWindowsDaemon();
334
+ }
335
+ return installMacosDaemon();
336
+ }
337
+ function uninstallDaemon() {
338
+ if (IS_WIN2) {
339
+ return uninstallWindowsDaemon();
340
+ }
341
+ return uninstallMacosDaemon();
342
+ }
343
+ function isDaemonInstalled() {
344
+ if (IS_WIN2) {
345
+ try {
346
+ execSync(`schtasks /Query /TN "${DAEMON_NAME}"`, { encoding: "utf-8" });
347
+ return true;
348
+ } catch {
349
+ return false;
350
+ }
351
+ }
352
+ const plistPath = join3(homedir3(), "Library", "LaunchAgents", `${DAEMON_NAME}.plist`);
353
+ return existsSync3(plistPath);
354
+ }
355
+ function installMacosDaemon() {
356
+ const launchAgentsDir = join3(homedir3(), "Library", "LaunchAgents");
357
+ const plistPath = join3(launchAgentsDir, `${DAEMON_NAME}.plist`);
358
+ const logDir = getDaemonLogDir();
359
+ const nodePath = getNodePath();
360
+ const cliPath = getCliPath();
361
+ const intervalMinutes = Math.floor(DAEMON_SYNC_INTERVAL_SEC / 60);
362
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
363
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
364
+ <plist version="1.0">
365
+ <dict>
366
+ <key>Label</key>
367
+ <string>${DAEMON_NAME}</string>
368
+ <key>ProgramArguments</key>
369
+ <array>
370
+ <string>${nodePath}</string>
371
+ <string>${cliPath}</string>
372
+ <string>daemon-sync</string>
373
+ </array>
374
+ <key>StartInterval</key>
375
+ <integer>${DAEMON_SYNC_INTERVAL_SEC}</integer>
376
+ <key>StandardOutPath</key>
377
+ <string>${logDir}/daemon.log</string>
378
+ <key>StandardErrorPath</key>
379
+ <string>${logDir}/daemon-error.log</string>
380
+ <key>RunAtLoad</key>
381
+ <true/>
382
+ </dict>
383
+ </plist>`;
384
+ try {
385
+ if (!existsSync3(launchAgentsDir)) {
386
+ mkdirSync3(launchAgentsDir, { recursive: true });
387
+ }
388
+ writeFileSync3(plistPath, plist);
389
+ execSync(`launchctl load ${plistPath}`, { encoding: "utf-8" });
390
+ return { success: true, message: `Daemon installed. Syncs every ${intervalMinutes} minutes.` };
391
+ } catch (e) {
392
+ return { success: false, message: `Failed to install daemon: ${e}` };
393
+ }
394
+ }
395
+ function uninstallMacosDaemon() {
396
+ const plistPath = join3(homedir3(), "Library", "LaunchAgents", `${DAEMON_NAME}.plist`);
397
+ try {
398
+ if (existsSync3(plistPath)) {
399
+ execSync(`launchctl unload ${plistPath}`, { encoding: "utf-8" });
400
+ unlinkSync(plistPath);
401
+ }
402
+ return { success: true, message: "Daemon uninstalled." };
403
+ } catch (e) {
404
+ return { success: false, message: `Failed to uninstall daemon: ${e}` };
405
+ }
406
+ }
407
+ function installWindowsDaemon() {
408
+ const nodePath = getNodePath();
409
+ const cliPath = getCliPath();
410
+ const intervalMinutes = Math.floor(DAEMON_SYNC_INTERVAL_SEC / 60);
411
+ try {
412
+ const cmd = `schtasks /Create /TN "${DAEMON_NAME}" /TR "\\"${nodePath}\\" \\"${cliPath}\\" daemon-sync" /SC MINUTE /MO ${intervalMinutes} /F`;
413
+ execSync(cmd, { encoding: "utf-8" });
414
+ return { success: true, message: `Daemon installed. Syncs every ${intervalMinutes} minutes.` };
415
+ } catch (e) {
416
+ return { success: false, message: `Failed to install daemon: ${e}` };
417
+ }
418
+ }
419
+ function uninstallWindowsDaemon() {
420
+ try {
421
+ execSync(`schtasks /Delete /TN "${DAEMON_NAME}" /F`, { encoding: "utf-8" });
422
+ return { success: true, message: "Daemon uninstalled." };
423
+ } catch (e) {
424
+ return { success: false, message: `Failed to uninstall daemon: ${e}` };
425
+ }
426
+ }
427
+ function getDaemonStatus() {
428
+ return {
429
+ installed: isDaemonInstalled(),
430
+ platform: IS_WIN2 ? "windows" : "macos",
431
+ interval: DAEMON_SYNC_INTERVAL_SEC
432
+ };
433
+ }
434
+
435
+ // src/claude-desktop.ts
436
+ import { readdirSync, readFileSync as readFileSync2, existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
437
+ import { homedir as homedir4 } from "os";
438
+ import { join as join4 } from "path";
439
+ import { createHash as createHash2 } from "crypto";
440
+ var IS_WIN3 = process.platform === "win32";
441
+ function getClaudeDesktopDataDir() {
442
+ if (IS_WIN3) {
443
+ return join4(process.env.APPDATA || join4(homedir4(), "AppData", "Roaming"), "Claude");
444
+ }
445
+ return join4(homedir4(), "Library", "Application Support", "Claude");
446
+ }
447
+ function getSessionDirs() {
448
+ const dataDir = getClaudeDesktopDataDir();
449
+ const sessionsDir = join4(dataDir, "local-agent-mode-sessions");
450
+ if (!existsSync4(sessionsDir)) return [];
451
+ const orgDirs = [];
452
+ for (const entry of readdirSync(sessionsDir, { withFileTypes: true })) {
453
+ if (entry.isDirectory() && entry.name !== "skills-plugin") {
454
+ orgDirs.push(join4(sessionsDir, entry.name));
455
+ }
456
+ }
457
+ return orgDirs;
458
+ }
459
+ function findJsonlFiles(baseDir) {
460
+ const files = [];
461
+ function walk(dir) {
462
+ if (!existsSync4(dir)) return;
463
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
464
+ const full = join4(dir, entry.name);
465
+ if (entry.isDirectory()) {
466
+ walk(full);
467
+ } else if (entry.name.endsWith(".jsonl") && !entry.name.includes("audit")) {
468
+ files.push(full);
469
+ }
470
+ }
471
+ }
472
+ walk(baseDir);
473
+ return files;
474
+ }
475
+ function parseJsonlFile(filePath) {
476
+ const content = readFileSync2(filePath, "utf-8");
477
+ const lines = content.trim().split("\n");
478
+ let sessionId = "";
479
+ let inputTokens = 0;
480
+ let outputTokens = 0;
481
+ let cacheCreationTokens = 0;
482
+ let cacheReadTokens = 0;
483
+ let model = "unknown";
484
+ let startedAt = "";
485
+ let endedAt = "";
486
+ let messageCount = 0;
487
+ for (const line of lines) {
488
+ try {
489
+ const msg = JSON.parse(line);
490
+ if (msg.sessionId && !sessionId) {
491
+ sessionId = msg.sessionId;
492
+ }
493
+ if (msg.timestamp) {
494
+ if (!startedAt || msg.timestamp < startedAt) {
495
+ startedAt = msg.timestamp;
496
+ }
497
+ if (!endedAt || msg.timestamp > endedAt) {
498
+ endedAt = msg.timestamp;
499
+ }
500
+ }
501
+ if (msg.message?.usage) {
502
+ const usage = msg.message.usage;
503
+ inputTokens += usage.input_tokens || 0;
504
+ outputTokens += usage.output_tokens || 0;
505
+ cacheCreationTokens += usage.cache_creation_input_tokens || 0;
506
+ cacheReadTokens += usage.cache_read_input_tokens || 0;
507
+ messageCount++;
508
+ if (msg.message.model) {
509
+ model = msg.message.model;
510
+ }
511
+ }
512
+ } catch {
513
+ }
514
+ }
515
+ if (!sessionId || messageCount === 0) return null;
516
+ return {
517
+ sessionId,
518
+ inputTokens,
519
+ outputTokens,
520
+ cacheCreationTokens,
521
+ cacheReadTokens,
522
+ model,
523
+ startedAt,
524
+ endedAt,
525
+ messageCount
526
+ };
527
+ }
528
+ function getDaemonStatePath() {
529
+ return join4(homedir4(), DAEMON_STATE_FILE);
530
+ }
531
+ function loadDaemonState() {
532
+ const path = getDaemonStatePath();
533
+ if (!existsSync4(path)) {
534
+ return { lastSync: (/* @__PURE__ */ new Date(0)).toISOString(), syncedSessions: [] };
535
+ }
536
+ try {
537
+ return JSON.parse(readFileSync2(path, "utf-8"));
538
+ } catch {
539
+ return { lastSync: (/* @__PURE__ */ new Date(0)).toISOString(), syncedSessions: [] };
540
+ }
541
+ }
542
+ function saveDaemonState(state) {
543
+ const path = getDaemonStatePath();
544
+ writeFileSync4(path, JSON.stringify(state, null, 2));
545
+ }
546
+ function computeSessionHash(session) {
547
+ const data = `${session.sessionId}:${session.inputTokens}:${session.outputTokens}:${session.endedAt}`;
548
+ return createHash2("sha256").update(data).digest("hex").substring(0, 16);
549
+ }
550
+ async function syncClaudeDesktop(apiKey) {
551
+ const state = loadDaemonState();
552
+ const errors = [];
553
+ let synced = 0;
554
+ let skipped = 0;
555
+ const sessionDirs = getSessionDirs();
556
+ for (const orgDir of sessionDirs) {
557
+ const jsonlFiles = findJsonlFiles(orgDir);
558
+ for (const file of jsonlFiles) {
559
+ const session = parseJsonlFile(file);
560
+ if (!session) continue;
561
+ const hash = computeSessionHash(session);
562
+ if (state.syncedSessions.includes(hash)) {
563
+ skipped++;
564
+ continue;
565
+ }
566
+ if (session.inputTokens === 0 && session.outputTokens === 0) {
567
+ skipped++;
568
+ continue;
569
+ }
570
+ try {
571
+ const body = JSON.stringify({
572
+ toolType: "claude-desktop",
573
+ endedAt: session.endedAt,
574
+ startedAt: session.startedAt,
575
+ inputTokens: session.inputTokens,
576
+ outputTokens: session.outputTokens,
577
+ cacheCreationTokens: session.cacheCreationTokens,
578
+ cacheReadTokens: session.cacheReadTokens,
579
+ modelName: session.model
580
+ });
581
+ const ts = Math.floor(Date.now() / 1e3).toString();
582
+ const sig = createHash2("sha256").update(ts + ":" + body).update(apiKey).digest("hex");
583
+ const res = await fetch(`${API_BASE_URL}/api/v1/sessions`, {
584
+ method: "POST",
585
+ headers: {
586
+ "Content-Type": "application/json",
587
+ "X-API-Key": apiKey,
588
+ "X-Timestamp": ts,
589
+ "X-Signature": sig
590
+ },
591
+ body
592
+ });
593
+ if (res.ok) {
594
+ state.syncedSessions.push(hash);
595
+ synced++;
596
+ } else {
597
+ const err = await res.text();
598
+ errors.push(`${session.sessionId}: ${err}`);
599
+ }
600
+ } catch (e) {
601
+ errors.push(`${session.sessionId}: ${e}`);
602
+ }
603
+ }
604
+ }
605
+ state.lastSync = (/* @__PURE__ */ new Date()).toISOString();
606
+ saveDaemonState(state);
607
+ return { synced, skipped, errors };
608
+ }
609
+ function hasClaudeDesktopData() {
610
+ const dataDir = getClaudeDesktopDataDir();
611
+ const sessionsDir = join4(dataDir, "local-agent-mode-sessions");
612
+ return existsSync4(sessionsDir);
613
+ }
614
+
336
615
  // src/commands.ts
337
616
  function prompt(question) {
338
617
  const rl = createInterface({ input: process.stdin, output: process.stdout });
@@ -567,7 +846,7 @@ function statusCommand() {
567
846
  for (const adapter of adapters) {
568
847
  const detected = adapter.detect();
569
848
  if (detected) {
570
- const hookExists = existsSync3(adapter.getHookPath());
849
+ const hookExists = existsSync5(adapter.getHookPath());
571
850
  const status = hookExists ? "\u2713 Active" : "\u2717 Not installed";
572
851
  console.log(` ${adapter.displayName}: ${status}`);
573
852
  if (hookExists) hookCount++;
@@ -583,14 +862,14 @@ function uninstallCommand() {
583
862
  const adapters = getAllAdapters();
584
863
  for (const adapter of adapters) {
585
864
  const hookPath = adapter.getHookPath();
586
- if (existsSync3(hookPath)) {
587
- unlinkSync(hookPath);
865
+ if (existsSync5(hookPath)) {
866
+ unlinkSync2(hookPath);
588
867
  console.log(` \u2713 Removed ${adapter.displayName} hook`);
589
868
  }
590
869
  }
591
- const configPath = join3(homedir3(), ".modu-arena.json");
592
- if (existsSync3(configPath)) {
593
- unlinkSync(configPath);
870
+ const configPath = join5(homedir5(), ".modu-arena.json");
871
+ if (existsSync5(configPath)) {
872
+ unlinkSync2(configPath);
594
873
  console.log(" \u2713 Removed ~/.modu-arena.json");
595
874
  }
596
875
  console.log("\n\u2713 Modu-Arena uninstalled.\n");
@@ -617,7 +896,7 @@ function collectFileStructure(dir, maxDepth, currentDepth = 0) {
617
896
  if (currentDepth >= maxDepth) return result;
618
897
  let entries;
619
898
  try {
620
- entries = readdirSync(dir);
899
+ entries = readdirSync2(dir);
621
900
  } catch {
622
901
  return result;
623
902
  }
@@ -625,7 +904,7 @@ function collectFileStructure(dir, maxDepth, currentDepth = 0) {
625
904
  for (const entry of entries) {
626
905
  if (entry.startsWith(".") && IGNORE_DIRS.has(entry)) continue;
627
906
  if (IGNORE_DIRS.has(entry)) continue;
628
- const fullPath = join3(dir, entry);
907
+ const fullPath = join5(dir, entry);
629
908
  let stat;
630
909
  try {
631
910
  stat = statSync(fullPath);
@@ -652,13 +931,13 @@ async function submitCommand() {
652
931
  console.log("\n\u{1F680} Modu-Arena \u2014 Project Submit\n");
653
932
  const cwd = process.cwd();
654
933
  const projectName = basename(cwd);
655
- const readmePath = join3(cwd, "README.md");
656
- if (!existsSync3(readmePath)) {
934
+ const readmePath = join5(cwd, "README.md");
935
+ if (!existsSync5(readmePath)) {
657
936
  console.error("Error: README.md not found in the current directory.");
658
937
  console.error(" Please create a README.md describing your project.\n");
659
938
  process.exit(1);
660
939
  }
661
- const description = readFileSync2(readmePath, "utf-8");
940
+ const description = readFileSync3(readmePath, "utf-8");
662
941
  if (description.trim().length === 0) {
663
942
  console.error("Error: README.md is empty.\n");
664
943
  process.exit(1);
@@ -705,6 +984,71 @@ function formatNumber(n) {
705
984
  if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
706
985
  return n.toString();
707
986
  }
987
+ function daemonInstallCommand() {
988
+ console.log("\n\u{1F504} Modu-Arena \u2014 Claude Desktop Daemon\n");
989
+ if (!hasClaudeDesktopData()) {
990
+ console.log(" \u2717 Claude Desktop data not found.");
991
+ console.log(" Make sure Claude Desktop is installed and has been used.\n");
992
+ process.exit(1);
993
+ }
994
+ const result = installDaemon();
995
+ if (result.success) {
996
+ console.log(` \u2713 ${result.message}`);
997
+ console.log(" \u2713 Daemon will sync Claude Desktop usage automatically.\n");
998
+ } else {
999
+ console.error(` \u2717 ${result.message}
1000
+ `);
1001
+ process.exit(1);
1002
+ }
1003
+ }
1004
+ function daemonUninstallCommand() {
1005
+ console.log("\n\u{1F504} Modu-Arena \u2014 Claude Desktop Daemon\n");
1006
+ const result = uninstallDaemon();
1007
+ if (result.success) {
1008
+ console.log(` \u2713 ${result.message}
1009
+ `);
1010
+ } else {
1011
+ console.error(` \u2717 ${result.message}
1012
+ `);
1013
+ process.exit(1);
1014
+ }
1015
+ }
1016
+ function daemonStatusCommand() {
1017
+ console.log("\n\u{1F504} Modu-Arena \u2014 Claude Desktop Daemon\n");
1018
+ const status = getDaemonStatus();
1019
+ console.log(` Platform: ${status.platform}`);
1020
+ console.log(` Installed: ${status.installed ? "Yes" : "No"}`);
1021
+ if (status.installed) {
1022
+ console.log(` Sync Interval: ${Math.floor(status.interval / 60)} minutes`);
1023
+ }
1024
+ if (hasClaudeDesktopData()) {
1025
+ console.log(" Claude Desktop Data: Found");
1026
+ } else {
1027
+ console.log(" Claude Desktop Data: Not found");
1028
+ }
1029
+ console.log("");
1030
+ }
1031
+ async function daemonSyncCommand() {
1032
+ const config = requireConfig();
1033
+ if (!hasClaudeDesktopData()) {
1034
+ console.log("Claude Desktop data not found. Nothing to sync.\n");
1035
+ return;
1036
+ }
1037
+ console.log("Syncing Claude Desktop usage...");
1038
+ const result = await syncClaudeDesktop(config.apiKey);
1039
+ console.log(` Synced: ${result.synced} sessions`);
1040
+ console.log(` Skipped: ${result.skipped} sessions (already synced)`);
1041
+ if (result.errors.length > 0) {
1042
+ console.log(` Errors: ${result.errors.length}`);
1043
+ for (const err of result.errors.slice(0, 3)) {
1044
+ console.log(` - ${err}`);
1045
+ }
1046
+ if (result.errors.length > 3) {
1047
+ console.log(` ... and ${result.errors.length - 3} more`);
1048
+ }
1049
+ }
1050
+ console.log("");
1051
+ }
708
1052
 
709
1053
  // src/index.ts
710
1054
  var args = process.argv.slice(2);
@@ -717,13 +1061,17 @@ Usage:
717
1061
  npx @suncreation/modu-arena <command> [options]
718
1062
 
719
1063
  Commands:
720
- register Create a new account (interactive)
721
- login Log in to an existing account (interactive)
722
- install Set up hooks for detected AI coding tools
723
- rank View your current stats and ranking
724
- status Check configuration and installed hooks
725
- submit Submit current project for evaluation
726
- uninstall Remove all hooks and configuration
1064
+ register Create a new account (interactive)
1065
+ login Log in to an existing account (interactive)
1066
+ install Set up hooks for detected AI coding tools
1067
+ rank View your current stats and ranking
1068
+ status Check configuration and installed hooks
1069
+ submit Submit current project for evaluation
1070
+ uninstall Remove all hooks and configuration
1071
+ daemon-install Install Claude Desktop sync daemon
1072
+ daemon-status Check daemon status
1073
+ daemon-sync Manually sync Claude Desktop data
1074
+ daemon-remove Remove the daemon
727
1075
 
728
1076
  Options:
729
1077
  --api-key <key> Your Modu-Arena API key (for install)
@@ -735,6 +1083,7 @@ Examples:
735
1083
  npx @suncreation/modu-arena login
736
1084
  npx @suncreation/modu-arena install --api-key modu_arena_AbCdEfGh_xxx...
737
1085
  npx @suncreation/modu-arena rank
1086
+ npx @suncreation/modu-arena daemon-install
738
1087
  `);
739
1088
  }
740
1089
  async function main() {
@@ -771,6 +1120,19 @@ async function main() {
771
1120
  case "uninstall":
772
1121
  uninstallCommand();
773
1122
  break;
1123
+ case "daemon-install":
1124
+ daemonInstallCommand();
1125
+ break;
1126
+ case "daemon-uninstall":
1127
+ case "daemon-remove":
1128
+ daemonUninstallCommand();
1129
+ break;
1130
+ case "daemon-status":
1131
+ daemonStatusCommand();
1132
+ break;
1133
+ case "daemon-sync":
1134
+ await daemonSyncCommand();
1135
+ break;
774
1136
  default:
775
1137
  console.error(`Unknown command: ${command}`);
776
1138
  printHelp();
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 { 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\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(suffix = ''): string {\n return `#!/bin/bash\nexec node \"$(dirname \"$0\")/_modu-hook${suffix}.js\"\n`;\n}\n\nfunction cmdWrapper(suffix = ''): string {\n return `@node \"%~dp0_modu-hook${suffix}.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 suffix = '',\n): InstallResult {\n try {\n if (!existsSync(hooksDir)) mkdirSync(hooksDir, { recursive: true });\n\n const hookJsName = suffix ? `_modu-hook-${suffix}.js` : HOOK_JS;\n writeFileSync(join(hooksDir, hookJsName), generateHookJs(apiKey, toolType, prefix, fields), { mode: 0o755 });\n\n if (IS_WIN) {\n writeFileSync(entryPath, cmdWrapper(suffix));\n } else {\n writeFileSync(entryPath, shellWrapper(suffix), { 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(suffix = ''): string {\n const name = suffix ? `session-end-${suffix}` : 'session-end';\n return IS_WIN ? `${name}.cmd` : `${name}.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 ClaudeDesktopAdapter implements ToolAdapter {\n slug = 'claude-desktop' as const;\n displayName = 'Claude Desktop';\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('desktop')); }\n detect() { return existsSync(this.configDir); }\n\n install(apiKey: string) {\n return installHook(this.displayName, this.hooksDir, this.getHookPath(), apiKey, 'claude-desktop', '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 'desktop',\n );\n }\n}\n\nclass OpenCodeAdapter implements ToolAdapter {\n slug = 'opencode' as const;\n displayName = 'OpenCode';\n\n // OpenCode uses ~/.config/opencode on ALL platforms (including Windows)\n // It uses xdg-basedir which respects XDG_CONFIG_HOME on all platforms\n private get configDir() {\n return join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'opencode');\n }\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 ClaudeDesktopAdapter(),\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 'claude-desktop',\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 'claude-desktop': 'Claude Desktop',\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":"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","names":["existsSync","readFileSync","homedir","join","writeFileSync","existsSync","mkdirSync","homedir","join","join","homedir","existsSync","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/daemon.ts","../src/claude-desktop.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';\nimport { installDaemon, uninstallDaemon, getDaemonStatus } from './daemon.js';\nimport { syncClaudeDesktop, hasClaudeDesktopData } from './claude-desktop.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// ─── daemon install ────────────────────────────────────────────────────────\n\nexport function daemonInstallCommand(): void {\n console.log('\\n🔄 Modu-Arena — Claude Desktop Daemon\\n');\n \n if (!hasClaudeDesktopData()) {\n console.log(' ✗ Claude Desktop data not found.');\n console.log(' Make sure Claude Desktop is installed and has been used.\\n');\n process.exit(1);\n }\n \n const result = installDaemon();\n \n if (result.success) {\n console.log(` ✓ ${result.message}`);\n console.log(' ✓ Daemon will sync Claude Desktop usage automatically.\\n');\n } else {\n console.error(` ✗ ${result.message}\\n`);\n process.exit(1);\n }\n}\n\n// ─── daemon uninstall ──────────────────────────────────────────────────────\n\nexport function daemonUninstallCommand(): void {\n console.log('\\n🔄 Modu-Arena — Claude Desktop Daemon\\n');\n \n const result = uninstallDaemon();\n \n if (result.success) {\n console.log(` ✓ ${result.message}\\n`);\n } else {\n console.error(` ✗ ${result.message}\\n`);\n process.exit(1);\n }\n}\n\n// ─── daemon status ─────────────────────────────────────────────────────────\n\nexport function daemonStatusCommand(): void {\n console.log('\\n🔄 Modu-Arena — Claude Desktop Daemon\\n');\n \n const status = getDaemonStatus();\n \n console.log(` Platform: ${status.platform}`);\n console.log(` Installed: ${status.installed ? 'Yes' : 'No'}`);\n if (status.installed) {\n console.log(` Sync Interval: ${Math.floor(status.interval / 60)} minutes`);\n }\n \n if (hasClaudeDesktopData()) {\n console.log(' Claude Desktop Data: Found');\n } else {\n console.log(' Claude Desktop Data: Not found');\n }\n console.log('');\n}\n\n// ─── daemon sync ───────────────────────────────────────────────────────────\n\nexport async function daemonSyncCommand(): Promise<void> {\n const config = requireConfig();\n \n if (!hasClaudeDesktopData()) {\n console.log('Claude Desktop data not found. Nothing to sync.\\n');\n return;\n }\n \n console.log('Syncing Claude Desktop usage...');\n \n const result = await syncClaudeDesktop(config.apiKey);\n \n console.log(` Synced: ${result.synced} sessions`);\n console.log(` Skipped: ${result.skipped} sessions (already synced)`);\n \n if (result.errors.length > 0) {\n console.log(` Errors: ${result.errors.length}`);\n for (const err of result.errors.slice(0, 3)) {\n console.log(` - ${err}`);\n }\n if (result.errors.length > 3) {\n console.log(` ... and ${result.errors.length - 3} more`);\n }\n }\n console.log('');\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\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 // OpenCode uses ~/.config/opencode on ALL platforms (including Windows)\n // It uses xdg-basedir which respects XDG_CONFIG_HOME on all platforms\n private get configDir() {\n return join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'opencode');\n }\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 'claude-desktop',\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 'claude-desktop': 'Claude Desktop',\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/** Daemon state file for tracking synced sessions */\nexport const DAEMON_STATE_FILE = '.modu-arena-daemon.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\n/** Daemon sync interval in seconds */\nexport const DAEMON_SYNC_INTERVAL_SEC = 300; // 5 minutes\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 * Platform-specific daemon installation for Claude Desktop sync.\n * macOS: launchd (LaunchAgent)\n * Windows: Scheduled Task\n */\nimport { writeFileSync, existsSync, unlinkSync, mkdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { DAEMON_SYNC_INTERVAL_SEC } from './constants.js';\n\nconst IS_WIN = process.platform === 'win32';\nconst DAEMON_NAME = 'com.modu-arena.sync';\n\nfunction getDaemonLogDir(): string {\n const dir = join(homedir(), '.modu-arena', 'logs');\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n return dir;\n}\n\nfunction getNodePath(): string {\n try {\n return execSync('which node', { encoding: 'utf-8' }).trim();\n } catch {\n return 'node';\n }\n}\n\nfunction getCliPath(): string {\n return require.resolve('./index.js').replace(/index\\.js$/, 'index.js');\n}\n\nexport function installDaemon(): { success: boolean; message: string } {\n if (IS_WIN) {\n return installWindowsDaemon();\n }\n return installMacosDaemon();\n}\n\nexport function uninstallDaemon(): { success: boolean; message: string } {\n if (IS_WIN) {\n return uninstallWindowsDaemon();\n }\n return uninstallMacosDaemon();\n}\n\nexport function isDaemonInstalled(): boolean {\n if (IS_WIN) {\n try {\n execSync(`schtasks /Query /TN \"${DAEMON_NAME}\"`, { encoding: 'utf-8' });\n return true;\n } catch {\n return false;\n }\n }\n const plistPath = join(homedir(), 'Library', 'LaunchAgents', `${DAEMON_NAME}.plist`);\n return existsSync(plistPath);\n}\n\nfunction installMacosDaemon(): { success: boolean; message: string } {\n const launchAgentsDir = join(homedir(), 'Library', 'LaunchAgents');\n const plistPath = join(launchAgentsDir, `${DAEMON_NAME}.plist`);\n const logDir = getDaemonLogDir();\n const nodePath = getNodePath();\n const cliPath = getCliPath();\n \n const intervalMinutes = Math.floor(DAEMON_SYNC_INTERVAL_SEC / 60);\n \n const plist = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>${DAEMON_NAME}</string>\n <key>ProgramArguments</key>\n <array>\n <string>${nodePath}</string>\n <string>${cliPath}</string>\n <string>daemon-sync</string>\n </array>\n <key>StartInterval</key>\n <integer>${DAEMON_SYNC_INTERVAL_SEC}</integer>\n <key>StandardOutPath</key>\n <string>${logDir}/daemon.log</string>\n <key>StandardErrorPath</key>\n <string>${logDir}/daemon-error.log</string>\n <key>RunAtLoad</key>\n <true/>\n</dict>\n</plist>`;\n\n try {\n if (!existsSync(launchAgentsDir)) {\n mkdirSync(launchAgentsDir, { recursive: true });\n }\n writeFileSync(plistPath, plist);\n execSync(`launchctl load ${plistPath}`, { encoding: 'utf-8' });\n return { success: true, message: `Daemon installed. Syncs every ${intervalMinutes} minutes.` };\n } catch (e) {\n return { success: false, message: `Failed to install daemon: ${e}` };\n }\n}\n\nfunction uninstallMacosDaemon(): { success: boolean; message: string } {\n const plistPath = join(homedir(), 'Library', 'LaunchAgents', `${DAEMON_NAME}.plist`);\n \n try {\n if (existsSync(plistPath)) {\n execSync(`launchctl unload ${plistPath}`, { encoding: 'utf-8' });\n unlinkSync(plistPath);\n }\n return { success: true, message: 'Daemon uninstalled.' };\n } catch (e) {\n return { success: false, message: `Failed to uninstall daemon: ${e}` };\n }\n}\n\nfunction installWindowsDaemon(): { success: boolean; message: string } {\n const nodePath = getNodePath();\n const cliPath = getCliPath();\n const intervalMinutes = Math.floor(DAEMON_SYNC_INTERVAL_SEC / 60);\n \n try {\n const cmd = `schtasks /Create /TN \"${DAEMON_NAME}\" /TR \"\\\\\"${nodePath}\\\\\" \\\\\"${cliPath}\\\\\" daemon-sync\" /SC MINUTE /MO ${intervalMinutes} /F`;\n execSync(cmd, { encoding: 'utf-8' });\n return { success: true, message: `Daemon installed. Syncs every ${intervalMinutes} minutes.` };\n } catch (e) {\n return { success: false, message: `Failed to install daemon: ${e}` };\n }\n}\n\nfunction uninstallWindowsDaemon(): { success: boolean; message: string } {\n try {\n execSync(`schtasks /Delete /TN \"${DAEMON_NAME}\" /F`, { encoding: 'utf-8' });\n return { success: true, message: 'Daemon uninstalled.' };\n } catch (e) {\n return { success: false, message: `Failed to uninstall daemon: ${e}` };\n }\n}\n\nexport function getDaemonStatus(): { installed: boolean; platform: string; interval: number } {\n return {\n installed: isDaemonInstalled(),\n platform: IS_WIN ? 'windows' : 'macos',\n interval: DAEMON_SYNC_INTERVAL_SEC,\n };\n}\n","/**\n * Claude Desktop session parser and sync logic.\n * \n * Reads JSONL files from ~/Library/Application Support/Claude/local-agent-mode-sessions/\n * on macOS and %APPDATA%\\Claude\\local-agent-mode-sessions\\ on Windows.\n */\nimport { readdirSync, readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport { API_BASE_URL, DAEMON_STATE_FILE } from './constants.js';\n\nconst IS_WIN = process.platform === 'win32';\n\ninterface UsageData {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n}\n\ninterface JsonlMessage {\n type: string;\n sessionId?: string;\n timestamp?: string;\n message?: {\n model?: string;\n usage?: UsageData;\n };\n}\n\ninterface SessionAggregate {\n sessionId: string;\n inputTokens: number;\n outputTokens: number;\n cacheCreationTokens: number;\n cacheReadTokens: number;\n model: string;\n startedAt: string;\n endedAt: string;\n messageCount: number;\n}\n\ninterface DaemonState {\n lastSync: string;\n syncedSessions: string[];\n}\n\nfunction getClaudeDesktopDataDir(): string {\n if (IS_WIN) {\n return join(process.env.APPDATA || join(homedir(), 'AppData', 'Roaming'), 'Claude');\n }\n return join(homedir(), 'Library', 'Application Support', 'Claude');\n}\n\nfunction getSessionDirs(): string[] {\n const dataDir = getClaudeDesktopDataDir();\n const sessionsDir = join(dataDir, 'local-agent-mode-sessions');\n if (!existsSync(sessionsDir)) return [];\n \n const orgDirs: string[] = [];\n for (const entry of readdirSync(sessionsDir, { withFileTypes: true })) {\n if (entry.isDirectory() && entry.name !== 'skills-plugin') {\n orgDirs.push(join(sessionsDir, entry.name));\n }\n }\n return orgDirs;\n}\n\nfunction findJsonlFiles(baseDir: string): string[] {\n const files: string[] = [];\n \n function walk(dir: string) {\n if (!existsSync(dir)) return;\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(full);\n } else if (entry.name.endsWith('.jsonl') && !entry.name.includes('audit')) {\n files.push(full);\n }\n }\n }\n \n walk(baseDir);\n return files;\n}\n\nfunction parseJsonlFile(filePath: string): SessionAggregate | null {\n const content = readFileSync(filePath, 'utf-8');\n const lines = content.trim().split('\\n');\n \n let sessionId = '';\n let inputTokens = 0;\n let outputTokens = 0;\n let cacheCreationTokens = 0;\n let cacheReadTokens = 0;\n let model = 'unknown';\n let startedAt = '';\n let endedAt = '';\n let messageCount = 0;\n \n for (const line of lines) {\n try {\n const msg: JsonlMessage = JSON.parse(line);\n \n if (msg.sessionId && !sessionId) {\n sessionId = msg.sessionId;\n }\n \n if (msg.timestamp) {\n if (!startedAt || msg.timestamp < startedAt) {\n startedAt = msg.timestamp;\n }\n if (!endedAt || msg.timestamp > endedAt) {\n endedAt = msg.timestamp;\n }\n }\n \n if (msg.message?.usage) {\n const usage = msg.message.usage;\n inputTokens += usage.input_tokens || 0;\n outputTokens += usage.output_tokens || 0;\n cacheCreationTokens += usage.cache_creation_input_tokens || 0;\n cacheReadTokens += usage.cache_read_input_tokens || 0;\n messageCount++;\n \n if (msg.message.model) {\n model = msg.message.model;\n }\n }\n } catch {\n }\n }\n \n if (!sessionId || messageCount === 0) return null;\n \n return {\n sessionId,\n inputTokens,\n outputTokens,\n cacheCreationTokens,\n cacheReadTokens,\n model,\n startedAt,\n endedAt,\n messageCount,\n };\n}\n\nfunction getDaemonStatePath(): string {\n return join(homedir(), DAEMON_STATE_FILE);\n}\n\nfunction loadDaemonState(): DaemonState {\n const path = getDaemonStatePath();\n if (!existsSync(path)) {\n return { lastSync: new Date(0).toISOString(), syncedSessions: [] };\n }\n try {\n return JSON.parse(readFileSync(path, 'utf-8'));\n } catch {\n return { lastSync: new Date(0).toISOString(), syncedSessions: [] };\n }\n}\n\nfunction saveDaemonState(state: DaemonState): void {\n const path = getDaemonStatePath();\n writeFileSync(path, JSON.stringify(state, null, 2));\n}\n\nfunction computeSessionHash(session: SessionAggregate): string {\n const data = `${session.sessionId}:${session.inputTokens}:${session.outputTokens}:${session.endedAt}`;\n return createHash('sha256').update(data).digest('hex').substring(0, 16);\n}\n\nexport async function syncClaudeDesktop(apiKey: string): Promise<{ synced: number; skipped: number; errors: string[] }> {\n const state = loadDaemonState();\n const errors: string[] = [];\n let synced = 0;\n let skipped = 0;\n \n const sessionDirs = getSessionDirs();\n \n for (const orgDir of sessionDirs) {\n const jsonlFiles = findJsonlFiles(orgDir);\n \n for (const file of jsonlFiles) {\n const session = parseJsonlFile(file);\n if (!session) continue;\n \n const hash = computeSessionHash(session);\n if (state.syncedSessions.includes(hash)) {\n skipped++;\n continue;\n }\n \n if (session.inputTokens === 0 && session.outputTokens === 0) {\n skipped++;\n continue;\n }\n \n try {\n const body = JSON.stringify({\n toolType: 'claude-desktop',\n endedAt: session.endedAt,\n startedAt: session.startedAt,\n inputTokens: session.inputTokens,\n outputTokens: session.outputTokens,\n cacheCreationTokens: session.cacheCreationTokens,\n cacheReadTokens: session.cacheReadTokens,\n modelName: session.model,\n });\n \n const ts = Math.floor(Date.now() / 1000).toString();\n const sig = createHash('sha256')\n .update(ts + ':' + body)\n .update(apiKey)\n .digest('hex');\n \n const res = await fetch(`${API_BASE_URL}/api/v1/sessions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-API-Key': apiKey,\n 'X-Timestamp': ts,\n 'X-Signature': sig,\n },\n body,\n });\n \n if (res.ok) {\n state.syncedSessions.push(hash);\n synced++;\n } else {\n const err = await res.text();\n errors.push(`${session.sessionId}: ${err}`);\n }\n } catch (e) {\n errors.push(`${session.sessionId}: ${e}`);\n }\n }\n }\n \n state.lastSync = new Date().toISOString();\n saveDaemonState(state);\n \n return { synced, skipped, errors };\n}\n\nexport function hasClaudeDesktopData(): boolean {\n const dataDir = getClaudeDesktopDataDir();\n const sessionsDir = join(dataDir, 'local-agent-mode-sessions');\n return existsSync(sessionsDir);\n}\n\nexport function getClaudeDesktopDataPath(): string {\n return getClaudeDesktopDataDir();\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 daemonInstallCommand,\n daemonUninstallCommand,\n daemonStatusCommand,\n daemonSyncCommand,\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 daemon-install Install Claude Desktop sync daemon\n daemon-status Check daemon status\n daemon-sync Manually sync Claude Desktop data\n daemon-remove Remove the daemon\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 npx @suncreation/modu-arena daemon-install\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 case 'daemon-install':\n daemonInstallCommand();\n break;\n case 'daemon-uninstall':\n case 'daemon-remove':\n daemonUninstallCommand();\n break;\n case 'daemon-status':\n daemonStatusCommand();\n break;\n case 'daemon-sync':\n await daemonSyncCommand();\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","readdirSync","unlinkSync","homedir","join","writeFileSync","existsSync","mkdirSync","homedir","join","join","homedir","existsSync","mkdirSync","writeFileSync","writeFileSync","existsSync","mkdirSync","homedir","join","IS_WIN","join","homedir","existsSync","mkdirSync","writeFileSync","readFileSync","existsSync","writeFileSync","homedir","join","createHash","IS_WIN","join","homedir","existsSync","readFileSync","writeFileSync","createHash","existsSync","unlinkSync","join","homedir","readdirSync","readFileSync"]}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@suncreation/modu-arena",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
- "description": "Track and rank your AI coding tool usage across Claude Code, OpenCode, Gemini CLI, Codex CLI, and Crush",
5
+ "description": "Track and rank your AI coding tool usage across Claude Code, Claude Desktop, OpenCode, Gemini CLI, Codex CLI, and Crush",
6
6
  "bin": {
7
7
  "modu-arena": "dist/index.js"
8
8
  },
@@ -29,6 +29,7 @@
29
29
  "ai",
30
30
  "coding",
31
31
  "claude-code",
32
+ "claude-desktop",
32
33
  "opencode",
33
34
  "gemini",
34
35
  "codex",