From dddf306b986dc60733eb13b134f61b3ea4bde1f3 Mon Sep 17 00:00:00 2001 From: guantou Date: Wed, 29 Jul 2026 11:47:51 +0800 Subject: [PATCH 1/2] feat(windows): platform title bar + PowerShell hooks + WSL integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix titleBarStyle: use 'hidden' + titleBarOverlay on Windows so min/max/close controls remain usable (macOS keeps 'hiddenInset') - Expose process.platform to renderer; platform-aware title bar padding and keyboard shortcut labels (Ctrl+ vs ⌘) - Add PowerShell shell hook (shell-hook.ps1) that captures commands via prompt override + Get-History, fire-and-forget via runspaces - Add PowerShell event sender (redlog-send.ps1) for scripted use - Add WSL hooks: redlog-send.sh (auto-resolves Windows token path) and wsl-redlog-test.sh (diagnostics for WSL→Windows connectivity) - Register PowerShell hook in hooks-manager with manual setup steps; fix bash hook availability on Windows (detects WSL/Git Bash) - Add .gitattributes to enforce LF for shell/python scripts - Add docs/windows-setup.md covering build, hooks, WSL, and isolation Co-Authored-By: Claude Opus 4.6 --- .gitattributes | 4 + README.md | 2 + docs/windows-setup.md | 151 ++++++++++++++++++++++++++++++++++++++ hooks/redlog-send.ps1 | 63 ++++++++++++++++ hooks/redlog-send.sh | 93 +++++++++++++++++++++++ hooks/shell-hook.ps1 | 89 ++++++++++++++++++++++ hooks/wsl-redlog-test.sh | 123 +++++++++++++++++++++++++++++++ src/core/hooks-manager.ts | 25 ++++++- src/main/windows.ts | 13 +++- src/preload/index.ts | 1 + src/renderer/src/App.tsx | 13 ++-- src/renderer/src/env.d.ts | 1 + 12 files changed, 571 insertions(+), 7 deletions(-) create mode 100644 .gitattributes create mode 100644 docs/windows-setup.md create mode 100644 hooks/redlog-send.ps1 create mode 100644 hooks/redlog-send.sh create mode 100644 hooks/shell-hook.ps1 create mode 100644 hooks/wsl-redlog-test.sh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e595c64 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +*.sh text eol=lf +*.zsh text eol=lf +*.bash text eol=lf +*.py text eol=lf diff --git a/README.md b/README.md index 87c4ea0..df3bb5d 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,8 @@ npm run build npx electron-builder --mac # or --win / --linux ``` +> **Windows / WSL users** — see [docs/windows-setup.md](docs/windows-setup.md) for build prerequisites, PowerShell hooks, and WSL integration. + ## Features ### Recording Engine diff --git a/docs/windows-setup.md b/docs/windows-setup.md new file mode 100644 index 0000000..b79af66 --- /dev/null +++ b/docs/windows-setup.md @@ -0,0 +1,151 @@ +# Windows & WSL Setup + +Running RedLog on Windows — build environment, PowerShell hooks, WSL integration, +and operational isolation. + +--- + +## 1. Prerequisites + +RedLog uses `better-sqlite3` (native C++ module), which must be compiled for +Electron's ABI. That requires a C/C++ toolchain. + +| Requirement | Notes | +|---|---| +| **Node.js 20 or 22 (LTS)** | Not 24+ — no prebuilt `better-sqlite3` binary yet. | +| **Visual Studio Build Tools** | Install the **"Desktop development with C++"** workload. | +| **Python 3** | Required by `node-gyp`. | + +```powershell +winget install Microsoft.VisualStudio.2022.BuildTools +winget install --id OpenJS.NodeJS.22 -e +``` + +### PowerShell notes + +- Windows PowerShell 5.1 does **not** support `&&`. Either install PowerShell 7 + (`winget install Microsoft.PowerShell`, run as `pwsh`) or chain with + `cmd1; if ($?) { cmd2 }`. +- If `npm` reports *"running scripts is disabled"*, allow user scripts once: + `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`. + +--- + +## 2. Build & Run + +```powershell +npm install +npm run rebuild # compile better-sqlite3 for Electron's ABI +npm run dev # launch the app +``` + +--- + +## 3. Packaging + +Installers are built by [electron-builder](https://www.electron.build). The +GitHub Actions workflow (`.github/workflows/release.yml`) produces releases on +tags. To build locally: + +```powershell +npm run build +npx electron-builder --win # NSIS + portable → dist\ +``` + +**First-run note:** electron-builder downloads `winCodeSign`, which may need +symlink privileges. Either enable **Developer Mode** (Settings → For developers) +or run the packaging command once from an elevated terminal. + +--- + +## 4. PowerShell Shell Hook + +The PowerShell hook (`hooks/shell-hook.ps1`) captures every command from +PowerShell 5.1+ and pwsh 7+ and sends it to RedLog's timeline. + +### Quick start + +```powershell +# Source in current session +. "C:\path\to\redlog\hooks\shell-hook.ps1" + +# Or add to your profile for every session +Add-Content $PROFILE '. "C:\path\to\redlog\hooks\shell-hook.ps1"' +``` + +The hook: +- Overrides the `prompt` function to capture commands via `Get-History` +- Sends `command_start` + `command_end` events with exit code and duration +- Uses background runspaces so the prompt is never blocked +- No-ops silently when RedLog isn't running + +### Event sender (scripting) + +For custom scripts, use `hooks/redlog-send.ps1`: + +```powershell +. ".\hooks\redlog-send.ps1" +Send-RedLogEvent "invoke-mimikatz" command_start +# ... run tool ... +Send-RedLogEvent "invoke-mimikatz" command_end @{ exit_code = 0 } +``` + +--- + +## 5. WSL Integration + +Pentest tooling often runs in WSL. Two things must line up for WSL → RedLog. + +### 5.1 Token path + +RedLog writes `api-token` and `api-port` to `%USERPROFILE%\.redlog\`. The WSL +hook scripts resolve this via `cmd.exe /c 'echo %USERPROFILE%'` + `wslpath`. + +### 5.2 Networking — mirrored mode + +The API binds `127.0.0.1` on Windows. Under WSL2's default NAT mode, WSL's +`127.0.0.1` is a separate loopback. Enable **mirrored networking**: + +1. Create `%USERPROFILE%\.wslconfig`: + ```ini + [wsl2] + networkingMode=mirrored + ``` +2. `wsl --shutdown` (from Windows PowerShell), then reopen WSL. +3. Verify: `wslinfo --networking-mode` → `mirrored`. + +### 5.3 WSL hooks + +```bash +# Diagnose the WSL → RedLog link +bash /mnt/c/Users//Desktop/REDLOG/hooks/wsl-redlog-test.sh + +# Fire-and-forget event sender +hooks/redlog-send.sh "nmap -sV $TARGET" command_start +nmap -sV "$TARGET" +hooks/redlog-send.sh "nmap -sV $TARGET" command_end "{\"exit_code\":$?}" +``` + +The bash/zsh `shell-preexec-hook.sh` also works inside WSL — source it in your +WSL distro's `~/.bashrc` or `~/.zshrc`. + +--- + +## 6. Operational Privacy + +### 6.1 Workspace isolation (primary control) + +Source hooks **only** in engagement shells. Commands in unhooked shells are never +logged. A clean pattern: do engagement work in a dedicated WSL distro with the +hook in `~/.bashrc`; keep personal work on the Windows host (unhooked). + +### 6.2 Pause limitation + +The status-bar recording toggle only hides events from the live timeline — it +does **not** stop database writes. For genuine isolation, stop the producer +(unsource the hook or close the engagement workspace). + +### 6.3 Per-engagement isolation + +Each project is a separate SQLite DB (`~/.redlog/projects//`). Close / +switch the project when you stop working an engagement. diff --git a/hooks/redlog-send.ps1 b/hooks/redlog-send.ps1 new file mode 100644 index 0000000..903e7a7 --- /dev/null +++ b/hooks/redlog-send.ps1 @@ -0,0 +1,63 @@ +# RedLog Event Sender for PowerShell +# Fire-and-forget event posting from any PowerShell script or tool. +# +# Usage: +# . .\hooks\redlog-send.ps1 +# Send-RedLogEvent "nmap -sV 10.0.0.1" command_start +# nmap -sV 10.0.0.1 +# Send-RedLogEvent "nmap -sV 10.0.0.1" command_end @{ exit_code = $LASTEXITCODE } + +$script:_RedLogPortFile = Join-Path $env:USERPROFILE '.redlog\api-port' +$script:_RedLogTokenFile = Join-Path $env:USERPROFILE '.redlog\api-token' + +function Send-RedLogEvent { + [CmdletBinding()] + param( + [Parameter(Mandatory, Position = 0)] + [string]$Command, + + [Parameter(Mandatory, Position = 1)] + [ValidateSet('command_start', 'command_end', 'note', 'marker')] + [string]$Subtype, + + [Parameter(Position = 2)] + [hashtable]$Extra = @{} + ) + + if (-not (Test-Path $script:_RedLogPortFile) -or + -not (Test-Path $script:_RedLogTokenFile)) { + return + } + + try { + $port = (Get-Content $script:_RedLogPortFile -Raw).Trim() + $token = (Get-Content $script:_RedLogTokenFile -Raw).Trim() + + $data = @{ + subtype = $Subtype + command = $Command + shell = 'powershell' + pid = $PID + } + foreach ($k in $Extra.Keys) { $data[$k] = $Extra[$k] } + + $body = @{ agent_type = 'shell'; data = $data } | ConvertTo-Json -Depth 4 -Compress + $uri = "http://127.0.0.1:${port}/api/events" + $headers = @{ + 'Authorization' = "Bearer $token" + 'Content-Type' = 'application/json' + } + + # Fire-and-forget via background runspace + $rs = [runspacefactory]::CreateRunspace() + $rs.Open() + $ps = [powershell]::Create().AddScript({ + param($uri, $headers, $body) + try { + Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $body -TimeoutSec 2 | Out-Null + } catch {} + }).AddArgument($uri).AddArgument($headers).AddArgument($body) + $ps.Runspace = $rs + $null = $ps.BeginInvoke() + } catch {} +} diff --git a/hooks/redlog-send.sh b/hooks/redlog-send.sh new file mode 100644 index 0000000..e9a77a1 --- /dev/null +++ b/hooks/redlog-send.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# RedLog Event Sender — fire-and-forget from any shell / hook script. +# Works natively on macOS/Linux and inside WSL2 (auto-resolves Windows token path). +# +# Usage: +# hooks/redlog-send.sh "nmap -sV $TARGET" command_start +# nmap -sV "$TARGET" +# hooks/redlog-send.sh "nmap -sV $TARGET" command_end "{\"exit_code\":$?}" + +set -u + +COMMAND="${1:-}" +SUBTYPE="${2:-command_start}" +EXTRA="${3:-}" + +[[ -z "$COMMAND" ]] && exit 0 + +# --- Locate token / port files --- +_resolve_redlog_dir() { + # Native (macOS / Linux / WSL with symlink) + if [[ -f "$HOME/.redlog/api-token" ]]; then + echo "$HOME/.redlog" + return + fi + # WSL: resolve from Windows %USERPROFILE% + if [[ -n "${WSL_DISTRO_NAME:-}" ]]; then + local win_profile + win_profile=$(cmd.exe /c 'echo %USERPROFILE%' 2>/dev/null | tr -d '\r') + if [[ -n "$win_profile" ]]; then + local wsl_path + wsl_path=$(wslpath "$win_profile" 2>/dev/null) + if [[ -f "$wsl_path/.redlog/api-token" ]]; then + echo "$wsl_path/.redlog" + return + fi + fi + fi + return 1 +} + +# Cache the resolved dir for the session +if [[ -z "${_REDLOG_DIR:-}" ]]; then + _REDLOG_DIR=$(_resolve_redlog_dir) || exit 0 + export _REDLOG_DIR +fi + +PORT_FILE="$_REDLOG_DIR/api-port" +TOKEN_FILE="$_REDLOG_DIR/api-token" + +[[ -f "$PORT_FILE" ]] || exit 0 +[[ -f "$TOKEN_FILE" ]] || exit 0 + +PORT=$(<"$PORT_FILE") +TOKEN=$(<"$TOKEN_FILE") + +# --- Resolve reachable host --- +if [[ -z "${_REDLOG_HOST:-}" ]]; then + _REDLOG_HOST="127.0.0.1" + if ! curl -sf --connect-timeout 1 "http://127.0.0.1:${PORT}/api/health" >/dev/null 2>&1; then + # WSL2 NAT mode: try the Windows host gateway + if [[ -n "${WSL_DISTRO_NAME:-}" ]]; then + local gw + gw=$(ip route show default 2>/dev/null | awk '{print $3; exit}') + if [[ -n "$gw" ]] && curl -sf --connect-timeout 1 "http://${gw}:${PORT}/api/health" >/dev/null 2>&1; then + _REDLOG_HOST="$gw" + fi + fi + fi + export _REDLOG_HOST +fi + +# --- Build and send event --- +PAYLOAD=$(python3 -c " +import json, sys, os +d = { + 'agent_type': 'shell', + 'data': { + 'subtype': sys.argv[1], + 'command': sys.argv[2], + 'shell': os.environ.get('SHELL', 'bash').rsplit('/', 1)[-1], + 'pid': os.getppid() + } +} +if sys.argv[3]: + d['data'].update(json.loads(sys.argv[3])) +print(json.dumps(d)) +" "$SUBTYPE" "$COMMAND" "$EXTRA" 2>/dev/null) || exit 0 + +curl -sf -X POST "http://${_REDLOG_HOST}:${PORT}/api/events" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD" \ + --connect-timeout 1 --max-time 2 >/dev/null 2>&1 & diff --git a/hooks/shell-hook.ps1 b/hooks/shell-hook.ps1 new file mode 100644 index 0000000..9ea121d --- /dev/null +++ b/hooks/shell-hook.ps1 @@ -0,0 +1,89 @@ +# RedLog PowerShell Shell Hook +# Captures every command executed in PowerShell and sends to RedLog. +# Works with PowerShell 5.1+ and PowerShell 7+ (pwsh). +# +# Usage — add to your $PROFILE: +# . "C:\path\to\redlog\hooks\shell-hook.ps1" + +$script:_RedLogPortFile = Join-Path $env:USERPROFILE '.redlog\api-port' +$script:_RedLogTokenFile = Join-Path $env:USERPROFILE '.redlog\api-token' +$script:_RedLogLastId = 0 +$script:_RedLogCmdStart = $null + +function _RedLogIsRunning { + (Test-Path $script:_RedLogPortFile) -and (Test-Path $script:_RedLogTokenFile) +} + +function _RedLogSendEvent { + param( + [string]$Subtype, + [string]$Command, + [hashtable]$Extra = @{} + ) + if (-not (_RedLogIsRunning)) { return } + + try { + $port = Get-Content $script:_RedLogPortFile -Raw + $token = Get-Content $script:_RedLogTokenFile -Raw + $port = $port.Trim() + $token = $token.Trim() + + $data = @{ + subtype = $Subtype + command = $Command + shell = 'powershell' + pid = $PID + } + foreach ($k in $Extra.Keys) { $data[$k] = $Extra[$k] } + + $body = @{ agent_type = 'shell'; data = $data } | ConvertTo-Json -Depth 4 -Compress + $uri = "http://127.0.0.1:${port}/api/events" + $headers = @{ + 'Authorization' = "Bearer $token" + 'Content-Type' = 'application/json' + } + + # Fire-and-forget: use a runspace so the prompt is not blocked + $rs = [runspacefactory]::CreateRunspace() + $rs.Open() + $ps = [powershell]::Create().AddScript({ + param($uri, $headers, $body) + try { + Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $body -TimeoutSec 2 | Out-Null + } catch {} + }).AddArgument($uri).AddArgument($headers).AddArgument($body) + $ps.Runspace = $rs + $null = $ps.BeginInvoke() + } catch {} +} + +# Save the original prompt so we can chain +if (-not $script:_RedLogOriginalPrompt) { + $script:_RedLogOriginalPrompt = (Get-Item function:prompt).ScriptBlock +} + +function prompt { + $lastExit = $LASTEXITCODE + $success = $? + + $entry = Get-History -Count 1 -ErrorAction SilentlyContinue + if ($entry -and $entry.Id -ne $script:_RedLogLastId) { + $cmd = $entry.CommandLine + $duration = [int]($entry.EndExecutionTime - $entry.StartExecutionTime).TotalSeconds + $exitCode = if ($success) { 0 } else { if ($lastExit) { $lastExit } else { 1 } } + + _RedLogSendEvent -Subtype 'command_start' -Command $cmd + _RedLogSendEvent -Subtype 'command_end' -Command $cmd -Extra @{ + exit_code = $exitCode + duration_sec = $duration + } + $script:_RedLogLastId = $entry.Id + } + + # Restore LASTEXITCODE so we don't pollute the user's session + $global:LASTEXITCODE = $lastExit + # Chain to the original prompt + & $script:_RedLogOriginalPrompt +} + +Write-Host '[redlog] PowerShell hook active - commands will be logged to RedLog timeline' -ForegroundColor DarkGray diff --git a/hooks/wsl-redlog-test.sh b/hooks/wsl-redlog-test.sh new file mode 100644 index 0000000..5295f10 --- /dev/null +++ b/hooks/wsl-redlog-test.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash +# RedLog WSL Integration Test +# Diagnoses the WSL → Windows RedLog connection: environment, token path, +# API reachability, and a live event round-trip. +# +# Run from inside WSL: +# bash /mnt/c/Users//Desktop/REDLOG/hooks/wsl-redlog-test.sh + +set -u +OK="\033[32m✓\033[0m" +FAIL="\033[31m✗\033[0m" +WARN="\033[33m!\033[0m" +pass=0; fail=0 + +step() { printf "\n\033[1m── %s\033[0m\n" "$1"; } +ok() { printf " $OK %s\n" "$1"; ((pass++)); } +fail() { printf " $FAIL %s\n" "$1"; ((fail++)); } +warn() { printf " $WARN %s\n" "$1"; } + +# 1. WSL environment +step "WSL Environment" +if [[ -n "${WSL_DISTRO_NAME:-}" ]]; then + ok "Running inside WSL ($WSL_DISTRO_NAME)" +else + fail "Not running inside WSL — this script is for WSL→Windows testing" + exit 1 +fi + +MODE=$(wslinfo --networking-mode 2>/dev/null || echo "unknown") +if [[ "$MODE" == "mirrored" ]]; then + ok "Networking mode: mirrored (localhost shared)" +else + warn "Networking mode: $MODE — mirrored mode recommended" + echo " Fix: create %USERPROFILE%\\.wslconfig with [wsl2] networkingMode=mirrored" + echo " then: wsl --shutdown (from Windows PowerShell)" +fi + +# 2. Token path resolution +step "Token Path Resolution" +WIN_PROFILE=$(cmd.exe /c 'echo %USERPROFILE%' 2>/dev/null | tr -d '\r') +if [[ -n "$WIN_PROFILE" ]]; then + ok "Windows USERPROFILE: $WIN_PROFILE" + WSL_PROFILE=$(wslpath "$WIN_PROFILE" 2>/dev/null) + ok "WSL path: $WSL_PROFILE" +else + fail "Cannot resolve Windows USERPROFILE" + exit 1 +fi + +TOKEN_FILE="$WSL_PROFILE/.redlog/api-token" +PORT_FILE="$WSL_PROFILE/.redlog/api-port" + +if [[ -f "$TOKEN_FILE" ]]; then + ok "Token file found: $TOKEN_FILE" +else + fail "Token file missing: $TOKEN_FILE" + echo " → Is RedLog running on Windows with a project open?" +fi + +if [[ -f "$PORT_FILE" ]]; then + PORT=$(<"$PORT_FILE") + ok "Port file found: port=$PORT" +else + fail "Port file missing: $PORT_FILE" + echo " → Is RedLog running on Windows with a project open?" + exit 1 +fi + +TOKEN=$(<"$TOKEN_FILE") + +# 3. API reachability +step "API Reachability" +HOST="" +if curl -sf --connect-timeout 2 "http://127.0.0.1:${PORT}/api/health" >/dev/null 2>&1; then + ok "127.0.0.1:$PORT reachable (mirrored networking)" + HOST="127.0.0.1" +else + warn "127.0.0.1:$PORT not reachable" + GW=$(ip route show default 2>/dev/null | awk '{print $3; exit}') + if [[ -n "$GW" ]] && curl -sf --connect-timeout 2 "http://${GW}:${PORT}/api/health" >/dev/null 2>&1; then + ok "Gateway ${GW}:$PORT reachable (NAT mode)" + HOST="$GW" + else + fail "RedLog API not reachable from WSL" + echo " → Check: is RedLog running? Is mirrored networking enabled?" + exit 1 + fi +fi + +# 4. Event round-trip +step "Event Round-Trip" +BEFORE=$(curl -sf "http://${HOST}:${PORT}/api/events/count" \ + -H "Authorization: Bearer $TOKEN" 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin).get('count',0))" 2>/dev/null) + +PAYLOAD='{"agent_type":"shell","data":{"subtype":"wsl_test","command":"wsl-redlog-test.sh","shell":"bash","pid":'$$'}}' +RESP=$(curl -sf -X POST "http://${HOST}:${PORT}/api/events" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD" --connect-timeout 2 --max-time 3 2>&1) + +if [[ $? -eq 0 ]]; then + ok "Test event sent successfully" +else + fail "Failed to send test event: $RESP" +fi + +AFTER=$(curl -sf "http://${HOST}:${PORT}/api/events/count" \ + -H "Authorization: Bearer $TOKEN" 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin).get('count',0))" 2>/dev/null) + +if [[ "$AFTER" -gt "$BEFORE" ]]; then + ok "Event persisted (count: $BEFORE → $AFTER)" +else + warn "Event count unchanged ($BEFORE → $AFTER) — event may not have been persisted" +fi + +# Summary +step "Summary" +printf " Passed: %d Failed: %d\n" "$pass" "$fail" +if [[ $fail -eq 0 ]]; then + printf "\n $OK WSL → RedLog integration is working.\n\n" +else + printf "\n $FAIL Some checks failed — see above for remediation.\n\n" +fi diff --git a/src/core/hooks-manager.ts b/src/core/hooks-manager.ts index c93098f..cfebc36 100644 --- a/src/core/hooks-manager.ts +++ b/src/core/hooks-manager.ts @@ -97,6 +97,15 @@ const PLUGIN_REGISTRY: PluginManifest[] = [ requires: ['mitmproxy', 'mitmdump'], hookFile: 'hooks/mitmproxy-addon.py', installMethod: 'manual' + }, + { + id: 'shell-powershell', + name: 'PowerShell', + description: 'Captures commands from PowerShell via prompt hook', + agentType: 'shell', + requires: [], + hookFile: 'hooks/shell-hook.ps1', + installMethod: 'manual' } ] @@ -209,8 +218,11 @@ function checkInstalled(plugin: PluginManifest): boolean { function checkAvailable(plugin: PluginManifest): boolean { if (plugin.requires.length === 0) { + if (plugin.id === 'shell-powershell') return process.platform === 'win32' if (plugin.id === 'shell-zsh') return process.env.SHELL?.includes('zsh') || existsSync('/bin/zsh') - if (plugin.id === 'shell-bash') return existsSync('/bin/bash') + if (plugin.id === 'shell-bash') { + return existsSync('/bin/bash') || (process.platform === 'win32' && commandExists('bash')) + } return true } return plugin.requires.some((cmd) => commandExists(cmd)) @@ -254,6 +266,17 @@ function buildManualSteps(pluginId: string, hookFile: string): ManualStep[] | un command: `SHELL="${hookFile}" codex run "scan the target"` } ] + case 'shell-powershell': + return [ + { + label: 'Add to your PowerShell profile so it loads on every session', + command: `Add-Content $PROFILE '. "${hookFile}"'` + }, + { + label: 'Or source it manually in the current session', + command: `. "${hookFile}"` + } + ] default: return undefined } diff --git a/src/main/windows.ts b/src/main/windows.ts index 493445b..8d1545b 100644 --- a/src/main/windows.ts +++ b/src/main/windows.ts @@ -2,6 +2,8 @@ import { BrowserWindow, screen } from 'electron' import { join } from 'path' import { is } from '@electron-toolkit/utils' +const isMac = process.platform === 'darwin' + export function createMainWindow(savedBounds?: Electron.Rectangle): BrowserWindow { const win = new BrowserWindow({ width: savedBounds?.width ?? 1100, @@ -13,7 +15,16 @@ export function createMainWindow(savedBounds?: Electron.Rectangle): BrowserWindo show: false, icon: join(__dirname, '../../resources/icon-256.png'), backgroundColor: '#0a0a0a', - titleBarStyle: 'hiddenInset', + titleBarStyle: isMac ? 'hiddenInset' : 'hidden', + ...(isMac + ? {} + : { + titleBarOverlay: { + color: '#0a0a0a', + symbolColor: '#a1a1aa', + height: 40 + } + }), webPreferences: { preload: join(__dirname, '../preload/index.js'), sandbox: true, diff --git a/src/preload/index.ts b/src/preload/index.ts index be8489a..a77e1af 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -1,6 +1,7 @@ import { contextBridge, ipcRenderer } from 'electron' contextBridge.exposeInMainWorld('redlog', { + platform: process.platform, project: { list: () => ipcRenderer.invoke('project:list'), create: (name: string, initialConfig?: unknown) => ipcRenderer.invoke('project:create', name, initialConfig), diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index adaa678..80dbc93 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -23,6 +23,9 @@ type View = 'dashboard' | 'terminal' | 'timeline' | 'screenshots' | 'targets' | const VIEW_KEYS: View[] = ['dashboard', 'terminal', 'timeline', 'screenshots', 'targets', 'scope', 'loot', 'marks', 'settings'] +const isMac = window.redlog.platform === 'darwin' +const modKey = isMac ? '⌘' : 'Ctrl+' + export default function App(): JSX.Element { const [project, setProject] = useState<{ id: string; name: string } | null>(null) const [view, setView] = useState('dashboard') @@ -81,13 +84,13 @@ export default function App(): JSX.Element { className="h-10 flex items-center px-4 select-none shrink-0 border-b border-redlog-border bg-redlog-bg" style={{ WebkitAppRegion: 'drag' } as React.CSSProperties} > -
+
{t('app.title')} v{__APP_VERSION__}
{project.name} -
+