fix: Allow windows to run CC PR Reviewer (#406)
* fix: Allow windows to run CC PR Reviewer Signed-off-by: Alex Madera <e.a_madera@hotmail.com> * solve auto claude comments * fix linting --------- Signed-off-by: Alex Madera <e.a_madera@hotmail.com>
This commit is contained in:
@@ -7,7 +7,8 @@
|
||||
# Auto Claude uses Claude Code OAuth authentication.
|
||||
# Direct API keys (ANTHROPIC_API_KEY) are NOT supported to prevent silent billing.
|
||||
#
|
||||
# Option 1: Run `claude setup-token` to save token to macOS Keychain (recommended)
|
||||
# Option 1: Run `claude setup-token` to save token to system keychain (recommended)
|
||||
# (macOS: Keychain, Windows: Credential Manager, Linux: secret-service)
|
||||
# Option 2: Set the token explicitly:
|
||||
# CLAUDE_CODE_OAUTH_TOKEN=your-oauth-token-here
|
||||
#
|
||||
|
||||
+66
-17
@@ -34,20 +34,30 @@ SDK_ENV_VARS = [
|
||||
|
||||
def get_token_from_keychain() -> str | None:
|
||||
"""
|
||||
Get authentication token from macOS Keychain.
|
||||
Get authentication token from system credential store.
|
||||
|
||||
Reads Claude Code credentials from macOS Keychain and extracts the OAuth token.
|
||||
Only works on macOS (Darwin platform).
|
||||
Reads Claude Code credentials from:
|
||||
- macOS: Keychain
|
||||
- Windows: Credential Manager
|
||||
- Linux: Not yet supported (use env var)
|
||||
|
||||
Returns:
|
||||
Token string if found in Keychain, None otherwise
|
||||
Token string if found, None otherwise
|
||||
"""
|
||||
# Only attempt on macOS
|
||||
if platform.system() != "Darwin":
|
||||
system = platform.system()
|
||||
|
||||
if system == "Darwin":
|
||||
return _get_token_from_macos_keychain()
|
||||
elif system == "Windows":
|
||||
return _get_token_from_windows_credential_files()
|
||||
else:
|
||||
# Linux: secret-service not yet implemented
|
||||
return None
|
||||
|
||||
|
||||
def _get_token_from_macos_keychain() -> str | None:
|
||||
"""Get token from macOS Keychain."""
|
||||
try:
|
||||
# Query macOS Keychain for Claude Code credentials
|
||||
result = subprocess.run(
|
||||
[
|
||||
"/usr/bin/security",
|
||||
@@ -64,14 +74,11 @@ def get_token_from_keychain() -> str | None:
|
||||
if result.returncode != 0:
|
||||
return None
|
||||
|
||||
# Parse JSON response
|
||||
credentials_json = result.stdout.strip()
|
||||
if not credentials_json:
|
||||
return None
|
||||
|
||||
data = json.loads(credentials_json)
|
||||
|
||||
# Extract OAuth token from nested structure
|
||||
token = data.get("claudeAiOauth", {}).get("accessToken")
|
||||
|
||||
if not token:
|
||||
@@ -84,18 +91,45 @@ def get_token_from_keychain() -> str | None:
|
||||
return token
|
||||
|
||||
except (subprocess.TimeoutExpired, json.JSONDecodeError, KeyError, Exception):
|
||||
# Silently fail - this is a fallback mechanism
|
||||
return None
|
||||
|
||||
|
||||
def _get_token_from_windows_credential_files() -> str | None:
|
||||
"""Get token from Windows credential files.
|
||||
|
||||
Claude Code on Windows stores credentials in ~/.claude/.credentials.json
|
||||
"""
|
||||
try:
|
||||
# Claude Code stores credentials in ~/.claude/.credentials.json
|
||||
cred_paths = [
|
||||
os.path.expandvars(r"%USERPROFILE%\.claude\.credentials.json"),
|
||||
os.path.expandvars(r"%USERPROFILE%\.claude\credentials.json"),
|
||||
os.path.expandvars(r"%LOCALAPPDATA%\Claude\credentials.json"),
|
||||
os.path.expandvars(r"%APPDATA%\Claude\credentials.json"),
|
||||
]
|
||||
|
||||
for cred_path in cred_paths:
|
||||
if os.path.exists(cred_path):
|
||||
with open(cred_path, encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
token = data.get("claudeAiOauth", {}).get("accessToken")
|
||||
if token and token.startswith("sk-ant-oat01-"):
|
||||
return token
|
||||
|
||||
return None
|
||||
|
||||
except (json.JSONDecodeError, KeyError, FileNotFoundError, Exception):
|
||||
return None
|
||||
|
||||
|
||||
def get_auth_token() -> str | None:
|
||||
"""
|
||||
Get authentication token from environment variables or macOS Keychain.
|
||||
Get authentication token from environment variables or system credential store.
|
||||
|
||||
Checks multiple sources in priority order:
|
||||
1. CLAUDE_CODE_OAUTH_TOKEN (env var)
|
||||
2. ANTHROPIC_AUTH_TOKEN (CCR/proxy env var for enterprise setups)
|
||||
3. macOS Keychain (if on Darwin platform)
|
||||
3. System credential store (macOS Keychain, Windows Credential Manager)
|
||||
|
||||
NOTE: ANTHROPIC_API_KEY is intentionally NOT supported to prevent
|
||||
silent billing to user's API credits when OAuth is misconfigured.
|
||||
@@ -109,7 +143,7 @@ def get_auth_token() -> str | None:
|
||||
if token:
|
||||
return token
|
||||
|
||||
# Fallback to macOS Keychain
|
||||
# Fallback to system credential store
|
||||
return get_token_from_keychain()
|
||||
|
||||
|
||||
@@ -120,9 +154,15 @@ def get_auth_token_source() -> str | None:
|
||||
if os.environ.get(var):
|
||||
return var
|
||||
|
||||
# Check if token came from macOS Keychain
|
||||
# Check if token came from system credential store
|
||||
if get_token_from_keychain():
|
||||
return "macOS Keychain"
|
||||
system = platform.system()
|
||||
if system == "Darwin":
|
||||
return "macOS Keychain"
|
||||
elif system == "Windows":
|
||||
return "Windows Credential Files"
|
||||
else:
|
||||
return "System Credential Store"
|
||||
|
||||
return None
|
||||
|
||||
@@ -142,13 +182,22 @@ def require_auth_token() -> str:
|
||||
"Direct API keys (ANTHROPIC_API_KEY) are not supported.\n\n"
|
||||
)
|
||||
# Provide platform-specific guidance
|
||||
if platform.system() == "Darwin":
|
||||
system = platform.system()
|
||||
if system == "Darwin":
|
||||
error_msg += (
|
||||
"To authenticate:\n"
|
||||
" 1. Run: claude setup-token\n"
|
||||
" 2. The token will be saved to macOS Keychain automatically\n\n"
|
||||
"Or set CLAUDE_CODE_OAUTH_TOKEN in your .env file."
|
||||
)
|
||||
elif system == "Windows":
|
||||
error_msg += (
|
||||
"To authenticate:\n"
|
||||
" 1. Run: claude setup-token\n"
|
||||
" 2. The token should be saved to Windows Credential Manager\n\n"
|
||||
"If auto-detection fails, set CLAUDE_CODE_OAUTH_TOKEN in your .env file.\n"
|
||||
"Check: %LOCALAPPDATA%\\Claude\\credentials.json"
|
||||
)
|
||||
else:
|
||||
error_msg += (
|
||||
"To authenticate:\n"
|
||||
|
||||
@@ -46,6 +46,13 @@ import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Fix Windows console encoding for Unicode output (emojis, special chars)
|
||||
if sys.platform == "win32":
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||
if hasattr(sys.stderr, "reconfigure"):
|
||||
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
||||
|
||||
# Add backend to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
||||
|
||||
@@ -79,34 +86,70 @@ def print_progress(callback: ProgressCallback) -> None:
|
||||
|
||||
def get_config(args) -> GitHubRunnerConfig:
|
||||
"""Build config from CLI args and environment."""
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
token = args.token or os.environ.get("GITHUB_TOKEN", "")
|
||||
bot_token = args.bot_token or os.environ.get("GITHUB_BOT_TOKEN")
|
||||
repo = args.repo or os.environ.get("GITHUB_REPO", "")
|
||||
|
||||
if not token:
|
||||
# Find gh CLI - use shutil.which for cross-platform support
|
||||
gh_path = shutil.which("gh")
|
||||
if not gh_path and sys.platform == "win32":
|
||||
# Fallback: check common Windows installation paths
|
||||
common_paths = [
|
||||
r"C:\Program Files\GitHub CLI\gh.exe",
|
||||
r"C:\Program Files (x86)\GitHub CLI\gh.exe",
|
||||
os.path.expandvars(r"%LOCALAPPDATA%\Programs\GitHub CLI\gh.exe"),
|
||||
]
|
||||
for path in common_paths:
|
||||
if os.path.exists(path):
|
||||
gh_path = path
|
||||
break
|
||||
|
||||
if os.environ.get("DEBUG"):
|
||||
print(f"[DEBUG] gh CLI path: {gh_path}", flush=True)
|
||||
print(
|
||||
f"[DEBUG] PATH env: {os.environ.get('PATH', 'NOT SET')[:200]}...",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
if not token and gh_path:
|
||||
# Try to get from gh CLI
|
||||
import subprocess
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[gh_path, "auth", "token"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
token = result.stdout.strip()
|
||||
except FileNotFoundError:
|
||||
pass # gh not installed or not in PATH
|
||||
|
||||
result = subprocess.run(
|
||||
["gh", "auth", "token"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
token = result.stdout.strip()
|
||||
|
||||
if not repo:
|
||||
if not repo and gh_path:
|
||||
# Try to detect from git remote
|
||||
import subprocess
|
||||
|
||||
result = subprocess.run(
|
||||
["gh", "repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"],
|
||||
cwd=args.project,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
repo = result.stdout.strip()
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[
|
||||
gh_path,
|
||||
"repo",
|
||||
"view",
|
||||
"--json",
|
||||
"nameWithOwner",
|
||||
"-q",
|
||||
".nameWithOwner",
|
||||
],
|
||||
cwd=args.project,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
repo = result.stdout.strip()
|
||||
elif os.environ.get("DEBUG"):
|
||||
print(f"[DEBUG] gh repo view failed: {result.stderr}", flush=True)
|
||||
except FileNotFoundError:
|
||||
pass # gh not installed or not in PATH
|
||||
|
||||
if not token:
|
||||
print("Error: No GitHub token found. Set GITHUB_TOKEN or run 'gh auth login'")
|
||||
@@ -133,27 +176,42 @@ async def cmd_review_pr(args) -> int:
|
||||
import sys
|
||||
|
||||
# Force unbuffered output so Electron sees it in real-time
|
||||
sys.stdout.reconfigure(line_buffering=True)
|
||||
sys.stderr.reconfigure(line_buffering=True)
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(line_buffering=True)
|
||||
if hasattr(sys.stderr, "reconfigure"):
|
||||
sys.stderr.reconfigure(line_buffering=True)
|
||||
|
||||
print(f"[DEBUG] Starting PR review for PR #{args.pr_number}", flush=True)
|
||||
print(f"[DEBUG] Project directory: {args.project}", flush=True)
|
||||
debug = os.environ.get("DEBUG")
|
||||
if debug:
|
||||
print(f"[DEBUG] Starting PR review for PR #{args.pr_number}", flush=True)
|
||||
print(f"[DEBUG] Project directory: {args.project}", flush=True)
|
||||
print("[DEBUG] Building config...", flush=True)
|
||||
|
||||
print("[DEBUG] Building config...", flush=True)
|
||||
config = get_config(args)
|
||||
print(f"[DEBUG] Config built: repo={config.repo}, model={config.model}", flush=True)
|
||||
|
||||
print("[DEBUG] Creating orchestrator...", flush=True)
|
||||
if debug:
|
||||
print(
|
||||
f"[DEBUG] Config built: repo={config.repo}, model={config.model}",
|
||||
flush=True,
|
||||
)
|
||||
print("[DEBUG] Creating orchestrator...", flush=True)
|
||||
|
||||
orchestrator = GitHubOrchestrator(
|
||||
project_dir=args.project,
|
||||
config=config,
|
||||
progress_callback=print_progress,
|
||||
)
|
||||
print("[DEBUG] Orchestrator created", flush=True)
|
||||
|
||||
print(f"[DEBUG] Calling orchestrator.review_pr({args.pr_number})...", flush=True)
|
||||
if debug:
|
||||
print("[DEBUG] Orchestrator created", flush=True)
|
||||
print(
|
||||
f"[DEBUG] Calling orchestrator.review_pr({args.pr_number})...", flush=True
|
||||
)
|
||||
|
||||
result = await orchestrator.review_pr(args.pr_number)
|
||||
print(f"[DEBUG] review_pr returned, success={result.success}", flush=True)
|
||||
|
||||
if debug:
|
||||
print(f"[DEBUG] review_pr returned, success={result.success}", flush=True)
|
||||
|
||||
if result.success:
|
||||
print(f"\n{'=' * 60}")
|
||||
@@ -182,28 +240,38 @@ async def cmd_followup_review_pr(args) -> int:
|
||||
import sys
|
||||
|
||||
# Force unbuffered output so Electron sees it in real-time
|
||||
sys.stdout.reconfigure(line_buffering=True)
|
||||
sys.stderr.reconfigure(line_buffering=True)
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(line_buffering=True)
|
||||
if hasattr(sys.stderr, "reconfigure"):
|
||||
sys.stderr.reconfigure(line_buffering=True)
|
||||
|
||||
print(f"[DEBUG] Starting follow-up review for PR #{args.pr_number}", flush=True)
|
||||
print(f"[DEBUG] Project directory: {args.project}", flush=True)
|
||||
debug = os.environ.get("DEBUG")
|
||||
if debug:
|
||||
print(f"[DEBUG] Starting follow-up review for PR #{args.pr_number}", flush=True)
|
||||
print(f"[DEBUG] Project directory: {args.project}", flush=True)
|
||||
print("[DEBUG] Building config...", flush=True)
|
||||
|
||||
print("[DEBUG] Building config...", flush=True)
|
||||
config = get_config(args)
|
||||
print(f"[DEBUG] Config built: repo={config.repo}, model={config.model}", flush=True)
|
||||
|
||||
print("[DEBUG] Creating orchestrator...", flush=True)
|
||||
if debug:
|
||||
print(
|
||||
f"[DEBUG] Config built: repo={config.repo}, model={config.model}",
|
||||
flush=True,
|
||||
)
|
||||
print("[DEBUG] Creating orchestrator...", flush=True)
|
||||
|
||||
orchestrator = GitHubOrchestrator(
|
||||
project_dir=args.project,
|
||||
config=config,
|
||||
progress_callback=print_progress,
|
||||
)
|
||||
print("[DEBUG] Orchestrator created", flush=True)
|
||||
|
||||
print(
|
||||
f"[DEBUG] Calling orchestrator.followup_review_pr({args.pr_number})...",
|
||||
flush=True,
|
||||
)
|
||||
if debug:
|
||||
print("[DEBUG] Orchestrator created", flush=True)
|
||||
print(
|
||||
f"[DEBUG] Calling orchestrator.followup_review_pr({args.pr_number})...",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
try:
|
||||
result = await orchestrator.followup_review_pr(args.pr_number)
|
||||
@@ -211,7 +279,10 @@ async def cmd_followup_review_pr(args) -> int:
|
||||
print(f"\nFollow-up review failed: {e}")
|
||||
return 1
|
||||
|
||||
print(f"[DEBUG] followup_review_pr returned, success={result.success}", flush=True)
|
||||
if debug:
|
||||
print(
|
||||
f"[DEBUG] followup_review_pr returned, success={result.success}", flush=True
|
||||
)
|
||||
|
||||
if result.success:
|
||||
print(f"\n{'=' * 60}")
|
||||
|
||||
@@ -628,7 +628,7 @@ class OrchestratorReviewer:
|
||||
)
|
||||
|
||||
if prompt_file.exists():
|
||||
base_prompt = prompt_file.read_text()
|
||||
base_prompt = prompt_file.read_text(encoding="utf-8")
|
||||
else:
|
||||
logger.warning("Orchestrator prompt not found!")
|
||||
base_prompt = "You are a PR reviewer. Review the provided PR."
|
||||
|
||||
@@ -476,7 +476,7 @@ class PRReviewEngine:
|
||||
/ "pr_structural.md"
|
||||
)
|
||||
if prompt_file.exists():
|
||||
prompt = prompt_file.read_text()
|
||||
prompt = prompt_file.read_text(encoding="utf-8")
|
||||
else:
|
||||
prompt = self.prompt_manager.get_review_pass_prompt(ReviewPass.STRUCTURAL)
|
||||
|
||||
@@ -527,7 +527,7 @@ class PRReviewEngine:
|
||||
/ "pr_ai_triage.md"
|
||||
)
|
||||
if prompt_file.exists():
|
||||
prompt = prompt_file.read_text()
|
||||
prompt = prompt_file.read_text(encoding="utf-8")
|
||||
else:
|
||||
prompt = self.prompt_manager.get_review_pass_prompt(
|
||||
ReviewPass.AI_COMMENT_TRIAGE
|
||||
|
||||
@@ -298,7 +298,7 @@ Output JSON array:
|
||||
"""Get the main PR review prompt."""
|
||||
prompt_file = self.prompts_dir / "pr_reviewer.md"
|
||||
if prompt_file.exists():
|
||||
return prompt_file.read_text()
|
||||
return prompt_file.read_text(encoding="utf-8")
|
||||
return self._get_default_pr_review_prompt()
|
||||
|
||||
def _get_default_pr_review_prompt(self) -> str:
|
||||
@@ -338,7 +338,7 @@ Be specific and actionable. Focus on significant issues, not nitpicks.
|
||||
"""Get the follow-up PR review prompt."""
|
||||
prompt_file = self.prompts_dir / "pr_followup.md"
|
||||
if prompt_file.exists():
|
||||
return prompt_file.read_text()
|
||||
return prompt_file.read_text(encoding="utf-8")
|
||||
return self._get_default_followup_review_prompt()
|
||||
|
||||
def _get_default_followup_review_prompt(self) -> str:
|
||||
@@ -380,7 +380,7 @@ Output JSON:
|
||||
"""Get the issue triage prompt."""
|
||||
prompt_file = self.prompts_dir / "issue_triager.md"
|
||||
if prompt_file.exists():
|
||||
return prompt_file.read_text()
|
||||
return prompt_file.read_text(encoding="utf-8")
|
||||
return self._get_default_triage_prompt()
|
||||
|
||||
def _get_default_triage_prompt(self) -> str:
|
||||
|
||||
@@ -136,7 +136,7 @@ async def spawn_security_review(
|
||||
/ "pr_security_agent.md"
|
||||
)
|
||||
if prompt_file.exists():
|
||||
base_prompt = prompt_file.read_text()
|
||||
base_prompt = prompt_file.read_text(encoding="utf-8")
|
||||
else:
|
||||
logger.warning("Security agent prompt not found, using fallback")
|
||||
base_prompt = _get_fallback_security_prompt()
|
||||
@@ -222,7 +222,7 @@ async def spawn_quality_review(
|
||||
/ "pr_quality_agent.md"
|
||||
)
|
||||
if prompt_file.exists():
|
||||
base_prompt = prompt_file.read_text()
|
||||
base_prompt = prompt_file.read_text(encoding="utf-8")
|
||||
else:
|
||||
logger.warning("Quality agent prompt not found, using fallback")
|
||||
base_prompt = _get_fallback_quality_prompt()
|
||||
@@ -504,7 +504,7 @@ async def get_file_content(
|
||||
try:
|
||||
full_path = project_dir / file_path
|
||||
if full_path.exists():
|
||||
return full_path.read_text()
|
||||
return full_path.read_text(encoding="utf-8")
|
||||
return ""
|
||||
except Exception as e:
|
||||
logger.error(f"[Orchestrator] Failed to read {file_path}: {e}")
|
||||
|
||||
Generated
+26
@@ -69,6 +69,7 @@
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
"autoprefixer": "^10.4.22",
|
||||
"cross-env": "^10.1.0",
|
||||
"electron": "^39.2.7",
|
||||
"electron-builder": "^26.0.12",
|
||||
"electron-vite": "^5.0.0",
|
||||
@@ -1101,6 +1102,13 @@
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@epic-web/invariant": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
|
||||
"integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
|
||||
@@ -6215,6 +6223,24 @@
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz",
|
||||
"integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@epic-web/invariant": "^1.0.0",
|
||||
"cross-spawn": "^7.0.6"
|
||||
},
|
||||
"bin": {
|
||||
"cross-env": "dist/bin/cross-env.js",
|
||||
"cross-env-shell": "dist/bin/cross-env-shell.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"scripts": {
|
||||
"postinstall": "node scripts/postinstall.cjs",
|
||||
"dev": "electron-vite dev",
|
||||
"dev:debug": "DEBUG=true electron-vite dev",
|
||||
"dev:debug": "cross-env DEBUG=true electron-vite dev",
|
||||
"dev:mcp": "electron-vite dev -- --remote-debugging-port=9222",
|
||||
"build": "electron-vite build",
|
||||
"start": "electron .",
|
||||
@@ -107,6 +107,7 @@
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
"autoprefixer": "^10.4.22",
|
||||
"cross-env": "^10.1.0",
|
||||
"electron": "^39.2.7",
|
||||
"electron-builder": "^26.0.12",
|
||||
"electron-vite": "^5.0.0",
|
||||
|
||||
@@ -59,7 +59,9 @@ export function runPythonSubprocess<T = unknown>(
|
||||
// This is safe because: (1) user must explicitly enable via npm run dev:debug,
|
||||
// (2) it only enables our internal debug logging, not third-party framework debugging,
|
||||
// (3) no sensitive values are logged - only LLM reasoning and response text.
|
||||
const safeEnvVars = ['PATH', 'HOME', 'USER', 'SHELL', 'LANG', 'LC_ALL', 'TERM', 'TMPDIR', 'TMP', 'TEMP', 'DEBUG'];
|
||||
// Include platform-specific vars needed for shell commands and CLI tools
|
||||
// Windows: SYSTEMROOT, COMSPEC, PATHEXT, WINDIR for shell; USERPROFILE, APPDATA, LOCALAPPDATA for gh CLI auth
|
||||
const safeEnvVars = ['PATH', 'HOME', 'USER', 'SHELL', 'LANG', 'LC_ALL', 'TERM', 'TMPDIR', 'TMP', 'TEMP', 'DEBUG', 'SYSTEMROOT', 'COMSPEC', 'PATHEXT', 'WINDIR', 'USERPROFILE', 'APPDATA', 'LOCALAPPDATA', 'HOMEDRIVE', 'HOMEPATH'];
|
||||
const filteredEnv: Record<string, string> = {};
|
||||
for (const key of safeEnvVars) {
|
||||
if (process.env[key]) {
|
||||
|
||||
Reference in New Issue
Block a user