From 4fa7d8b6d0bb79b7f816702ac8b067f430ece13d Mon Sep 17 00:00:00 2001 From: asepharyana Date: Mon, 21 Sep 2026 15:11:36 +0700 Subject: [PATCH] fix(cli): fallback to user-local private key + record analytics - cli.ts: key resolution now falls back to ~/.hermes/keys/pr-agent-key.pem when /opt/pr-agent-server/private-key.pem is EACCES/ENOENT (CLI as non-root user works out of the box) - cli.ts+index.ts: export logReviewEvent; CLI now records an analytics event (describe/improve/review) in the legacy pr-agent.*.log format, best-effort (never fails the CLI on a log write), honoring PR_AGENT_ANALYTICS_DIR - Verified: 16/16 tests, tsc clean, describe publishes, review publishes (comment 5757282509), webhook 403/ping/ignored paths correct --- server/src/cli.ts | 36 ++++++++++++++++++++++++++++++++++-- server/src/index.ts | 2 +- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/server/src/cli.ts b/server/src/cli.ts index 8d82e10..abb1950 100644 --- a/server/src/cli.ts +++ b/server/src/cli.ts @@ -5,6 +5,26 @@ import { loadConfig } from "./config"; import { runReview } from "./review"; import { runDescribe } from "./describe"; import { runImprove } from "./improve"; +import { logReviewEvent } from "./index"; +import { join } from "node:path"; + +const ANALYTICS_DIR = process.env.PR_AGENT_ANALYTICS_DIR || "/var/lib/pr-agent-server/analytics"; + +function recordAnalytics(command: string, result: { status?: string; model?: string }): void { + try { + logReviewEvent(ANALYTICS_DIR, { + message: result.status === "success" ? "Generated code suggestions" : `Failed to generate (${result.status ?? "error"})`, + extra: { + command, + pr_url_short: "", + model: result.model ?? "", + error: result.status === "success" ? "" : (result.status ?? "error"), + }, + }); + } catch { + // analytics is best-effort; never fail the CLI on a log write + } +} async function main() { const args = process.argv.slice(2); @@ -32,9 +52,18 @@ async function main() { const tool = getArg("--tool") || "review"; const cfg = loadConfig(); - const keyPath = getArg("--private-key") || process.env.PRIVATE_KEY_PATH || "/opt/pr-agent-server/private-key.pem"; + const keyPath = + getArg("--private-key") || + process.env.PRIVATE_KEY_PATH || + "/opt/pr-agent-server/private-key.pem"; const fs = await import("node:fs"); - const privateKey = fs.readFileSync(keyPath, "utf-8"); + const fallbackKey = join(process.env.HOME ?? "/home/code", ".hermes", "keys", "pr-agent-key.pem"); + let privateKey: string; + try { + privateKey = fs.readFileSync(keyPath, "utf-8"); + } catch { + privateKey = fs.readFileSync(fallbackKey, "utf-8"); // EACCES/ENOENT on /opt → user-local copy + } const publish = !has("--no-publish"); if (tool === "describe") { @@ -54,6 +83,7 @@ async function main() { )); console.log("\n--- MARKDOWN ---\n"); console.log(result.markdown); + recordAnalytics("describe", result); return; } @@ -74,6 +104,7 @@ async function main() { )); console.log("\n--- MARKDOWN ---\n"); console.log(result.markdown); + recordAnalytics("improve", result); return; } @@ -95,6 +126,7 @@ async function main() { )); console.log("\n--- MARKDOWN ---\n"); console.log(result.markdown); + recordAnalytics("review", result); } main().catch((e) => { diff --git a/server/src/index.ts b/server/src/index.ts index 39715aa..4bae19f 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -384,7 +384,7 @@ export function readAnalyticsLogs(dir: string, maxFiles = 5): AnalyticsRecord[] /** Append an analytics event in the legacy pr-agent JSONL shape so external * dashboards that parse pr-agent.*.log keep working. */ -function logReviewEvent(dir: string, event: Record): void { +export function logReviewEvent(dir: string, event: Record): void { if (!dir) return; const fs = require("node:fs") as typeof import("node:fs"); const path = require("node:path") as typeof import("node:path");