Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to Envoy are documented in this file. Format is based on [Ke

## [Unreleased]

### Changed
- The whole app now talks like a person. Every emoji and dingbat is gone from buttons and status messages, along with the em dashes and the mission-briefing jargon. Mission Briefing is now just Apply, the Data Vault is the Vault, Browser Nexus is Browser, LLM Nexus is AI Models, and Codename is Full Name. Status lines read like plain English ("Application submitted." instead of "MISSION ACCOMPLISHED"). The Browser page also drops the fingerprint-evasion sales pitch and now just explains how the DevTools connection works, which matches what the app actually does and has always done.

### Added
- The Scoreboard. A new view, and the first thing you see when Envoy opens: ghosts dodged, hours saved, day streak, and ghosts surfaced, with a receipts list showing every dodge alongside the evidence that was on screen when you passed. Hours saved shows its math (dodges times your own time-per-application estimate, adjustable right on the board) instead of pretending to measure something it can't. Screened and sent counts appear only as context; the board scores the system, it does not grade you.
- Find Jobs feeds the ledger. Every scored result is recorded as a sighting (one per posting per day, so re-running a search inflates nothing), opening a posting records a view, and a new SKIP button on each result records the pass and clears the row. Skipping a flagged posting answers with GHOST DODGED in the status line. Day-by-day sightings are also the listing history the repost-frequency signal needs for its stronger path.
Expand Down
2 changes: 1 addition & 1 deletion src/Envoy.Core/Services/ApplicationOrchestrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ public async Task<ApplicationLog> SubmitApplicationAsync(
if (await _browser.DetectCaptchaAsync(ct))
{
log.Status = ApplicationStatus.RequiresCaptcha;
await onConfirmationRequired("CAPTCHA detected. Please solve it manually in the browser window, then click Resume.");
await onConfirmationRequired("CAPTCHA detected. Envoy doesn't solve those. Finish it in the browser, then run the application again.");
await _logRepo.UpdateAsync(log, ct);
return log;
}
Expand Down
6 changes: 3 additions & 3 deletions src/Envoy.Core/Services/LLMDetectionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ public string GetRecommendation(List<LLMModelInfo> models)
{
var sizeGB = bestLocal.SizeBytes.HasValue ? bestLocal.SizeBytes.Value / 1_073_741_824.0 : 0;
return sizeGB >= 7
? $"Recommended: {bestLocal.DisplayName} large enough for quality resume tailoring"
: $"Available: {bestLocal.DisplayName} works but larger models produce better results";
? $"Recommended: {bestLocal.DisplayName}, large enough for quality resume tailoring"
: $"Available: {bestLocal.DisplayName}. It works, but larger models produce better results";
}

return "No local models loaded. Pull a model in Ollama (e.g., `ollama pull qwen2.5-coder:14b`)";
Expand Down Expand Up @@ -184,6 +184,6 @@ private static string GetModelRecommendation(LLMModelInfo model)
if (sizeGB >= 3)
return "Adequate but may produce lower quality results";

return "Small model — consider upgrading for better results";
return "Small model. Consider a larger one for better results";
}
}
2 changes: 1 addition & 1 deletion src/Envoy.UI/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ private static void ShowCrashError(string context, Exception ex, bool fatal)
$"{head}\n\n{context}: {ex.Message}\n\n" +
$"Details were saved to:\n{CrashLogPath}\n\n" +
"Open the logs folder now?",
fatal ? "Envoyfatal error" : "Envoy error",
fatal ? "Envoy: fatal error" : "Envoy: error",
MessageBoxButton.YesNo,
fatal ? MessageBoxImage.Error : MessageBoxImage.Warning);

Expand Down
26 changes: 13 additions & 13 deletions src/Envoy.UI/ApplyView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@

<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="24">
<TextBlock Text="&#x25C8; MISSION BRIEFING" Style="{StaticResource CyberHeading}" Margin="0,0,0,4"/>
<TextBlock Text="TARGET ACQUISITION AND PAYLOAD DELIVERY" Style="{StaticResource CyberSubheading}" Margin="0,0,0,20"/>
<TextBlock Text="APPLY" Style="{StaticResource CyberHeading}" Margin="0,0,0,4"/>
<TextBlock Text="Paste a job link. Envoy tailors your resume and fills the form. Nothing sends without you." Style="{StaticResource CyberSubheading}" Margin="0,0,0,20"/>

<Border Background="#111827" CornerRadius="3" BorderBrush="#1A3A4A" BorderThickness="1" Padding="20" Margin="0,0,0,16">
<Border.Effect>
<DropShadowEffect Color="#00F0FF" BlurRadius="12" ShadowDepth="0" Opacity="0.15"/>
</Border.Effect>
<StackPanel>
<TextBlock Text="&#x25C8; TARGET URL" Foreground="#00F0FF" FontSize="11" FontFamily="{StaticResource CyberFontBody}" Margin="0,0,0,6"/>
<TextBlock Text="JOB URL" Foreground="#00F0FF" FontSize="11" FontFamily="{StaticResource CyberFontBody}" Margin="0,0,0,6"/>
<TextBox x:Name="TxtJobUrl" Style="{StaticResource CyberTextBox}" Margin="0,0,0,12" AutomationProperties.Name="Job posting URL"/>

<TextBlock Text="&#x25C8; OPERATION MODE" Foreground="#00F0FF" FontSize="11" FontFamily="{StaticResource CyberFontBody}" Margin="0,0,0,6"/>
<TextBlock Text="INPUT MODE" Foreground="#00F0FF" FontSize="11" FontFamily="{StaticResource CyberFontBody}" Margin="0,0,0,6"/>
<ComboBox x:Name="CmbMode" Style="{StaticResource CyberComboBox}" Margin="0,0,0,12" AutomationProperties.Name="Execution mode">
<ComboBoxItem Content="Safe" IsSelected="True"/>
<ComboBoxItem Content="Stealth"/>
</ComboBox>

<Button x:Name="BtnInitiate" Content="&#x25C8; INITIATE SEQUENCE" Click="BtnInitiate_Click" Style="{StaticResource CyberButton}" HorizontalAlignment="Left" Padding="24,10"/>
<Button x:Name="BtnInitiate" Content="TAILOR RESUME" Click="BtnInitiate_Click" Style="{StaticResource CyberButton}" HorizontalAlignment="Left" Padding="24,10"/>
</StackPanel>
</Border>

Expand All @@ -31,7 +31,7 @@
<Border x:Name="GhostRiskPanel" Background="#111827" CornerRadius="3" BorderBrush="#1A3A4A" BorderThickness="1" Padding="16" Visibility="Collapsed" Margin="0,0,0,16">
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,6">
<TextBlock Text="&#x25C8; GHOST RISK " Foreground="#8892A4" FontSize="12" FontFamily="{StaticResource CyberFontBody}" VerticalAlignment="Center"/>
<TextBlock Text="GHOST RISK " Foreground="#8892A4" FontSize="12" FontFamily="{StaticResource CyberFontBody}" VerticalAlignment="Center"/>
<Border x:Name="GhostBadge" CornerRadius="2" Padding="8,3" Background="#39FF14">
<TextBlock x:Name="GhostBandText" Text="OK" Foreground="#0A0E17" FontSize="12" FontWeight="Bold" FontFamily="{StaticResource CyberFontHeading}"/>
</Border>
Expand All @@ -45,19 +45,19 @@
<DropShadowEffect Color="#00F0FF" BlurRadius="12" ShadowDepth="0" Opacity="0.15"/>
</Border.Effect>
<StackPanel>
<TextBlock Text="&#x25C8; TACTICAL ANALYSIS" Foreground="#00F0FF" FontSize="14" FontWeight="Bold" FontFamily="{StaticResource CyberFontHeading}" Margin="0,0,0,8"/>
<TextBlock Text="TAILORING RESULTS" Foreground="#00F0FF" FontSize="14" FontWeight="Bold" FontFamily="{StaticResource CyberFontHeading}" Margin="0,0,0,8"/>
<StackPanel Orientation="Horizontal" Margin="0,0,0,4">
<TextBlock Text="MATCH SCORE: " Foreground="#8892A4" FontSize="12" FontFamily="{StaticResource CyberFontBody}"/>
<TextBlock x:Name="ScoreText" Text="&#x2014;" Foreground="#00F0FF" FontSize="18" FontWeight="Bold" FontFamily="{StaticResource CyberFontHeading}"/>
<TextBlock x:Name="ScoreText" Text="-" Foreground="#00F0FF" FontSize="18" FontWeight="Bold" FontFamily="{StaticResource CyberFontHeading}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,4">
<TextBlock Text="STATUS: " Foreground="#8892A4" FontSize="12" FontFamily="{StaticResource CyberFontBody}"/>
<TextBlock x:Name="SafetyText" Text="&#x2014;" FontSize="12" FontFamily="{StaticResource CyberFontBody}"/>
<TextBlock x:Name="SafetyText" Text="-" FontSize="12" FontFamily="{StaticResource CyberFontBody}"/>
</StackPanel>
<TextBlock x:Name="ViolationsText" Text="" Foreground="#FFE600" FontSize="11" FontFamily="{StaticResource CyberFontBody}" Margin="0,4,0,8" TextWrapping="Wrap"/>
<TextBlock x:Name="ChangesText" Text="" Foreground="#39FF14" FontSize="11" FontFamily="{StaticResource CyberFontBody}" Margin="0,0,0,8" TextWrapping="Wrap"/>

<Button x:Name="BtnExecute" Content="&#x25C8; EXECUTE DROP" Click="BtnExecute_Click" Style="{StaticResource CyberButton}" HorizontalAlignment="Left" Padding="20,10" Visibility="Collapsed"/>
<Button x:Name="BtnExecute" Content="FILL OUT APPLICATION" Click="BtnExecute_Click" Style="{StaticResource CyberButton}" HorizontalAlignment="Left" Padding="20,10" Visibility="Collapsed"/>
</StackPanel>
</Border>

Expand All @@ -66,12 +66,12 @@
<DropShadowEffect Color="#FFE600" BlurRadius="12" ShadowDepth="0" Opacity="0.2"/>
</Border.Effect>
<StackPanel>
<TextBlock Text="&#x26A0; HUMAN CONFIRMATION REQUIRED" Foreground="#FFE600" FontSize="14" FontWeight="Bold" FontFamily="{StaticResource CyberFontHeading}" Margin="0,0,0,8"/>
<TextBlock Text="READY TO SEND?" Foreground="#FFE600" FontSize="14" FontWeight="Bold" FontFamily="{StaticResource CyberFontHeading}" Margin="0,0,0,8"/>
<TextBlock x:Name="ConfirmPrompt" Text="" Foreground="#E0E6F0" FontSize="12" FontFamily="{StaticResource CyberFontBody}" Margin="0,0,0,4" TextWrapping="Wrap"/>
<TextBlock Text="The form is filled and ready. Nothing is submitted until you confirm." Foreground="#8892A4" FontSize="11" FontFamily="{StaticResource CyberFontBody}" Margin="0,0,0,12" TextWrapping="Wrap"/>
<StackPanel Orientation="Horizontal">
<Button x:Name="BtnConfirmSubmit" Content="&#x25C8; CONFIRM SUBMIT" Click="BtnConfirmSubmit_Click" Style="{StaticResource CyberButton}" Padding="20,10" Margin="0,0,12,0" AutomationProperties.Name="Confirm and submit application"/>
<Button x:Name="BtnCancelSubmit" Content="&#x2715; CANCEL" Click="BtnCancelSubmit_Click" Style="{StaticResource CyberButtonDanger}" Padding="20,10" AutomationProperties.Name="Cancel submission"/>
<Button x:Name="BtnConfirmSubmit" Content="CONFIRM AND SEND" Click="BtnConfirmSubmit_Click" Style="{StaticResource CyberButton}" Padding="20,10" Margin="0,0,12,0" AutomationProperties.Name="Confirm and submit application"/>
<Button x:Name="BtnCancelSubmit" Content="CANCEL" Click="BtnCancelSubmit_Click" Style="{StaticResource CyberButtonDanger}" Padding="20,10" AutomationProperties.Name="Cancel submission"/>
</StackPanel>
</StackPanel>
</Border>
Expand Down
48 changes: 25 additions & 23 deletions src/Envoy.UI/ApplyView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,28 +59,28 @@ private async void BtnInitiate_Click(object sender, RoutedEventArgs e)
var jobUrl = TxtJobUrl.Text?.Trim() ?? "";
if (string.IsNullOrWhiteSpace(jobUrl))
{
StatusText.Text = "⚠ TARGET URL REQUIRED";
StatusText.Text = "Paste a job posting link first.";
StatusText.Foreground = Red;
return;
}

if (_profileId == Guid.Empty)
{
StatusText.Text = "⚠ SELECT A PROFILE ON THE DASHBOARD FIRST";
StatusText.Text = "Pick a profile on the Dashboard first.";
StatusText.Foreground = Yellow;
return;
}

if (!Uri.TryCreate(jobUrl, UriKind.Absolute, out var uri)
|| (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps))
{
StatusText.Text = "⚠ TARGET URL MUST BE A FULL http:// OR https:// LINK";
StatusText.Text = "That link doesn't look complete. It needs to start with http:// or https://.";
StatusText.Foreground = Red;
return;
}

BtnInitiate.IsEnabled = false;
StatusText.Text = "ANALYZING TARGET AND OPTIMIZING PAYLOAD...";
StatusText.Text = "Reading the posting and tailoring your resume...";
StatusText.Foreground = Cyan;
ResultPanel.Visibility = Visibility.Collapsed;

Expand All @@ -94,7 +94,7 @@ private async void BtnInitiate_Click(object sender, RoutedEventArgs e)
}
catch (Exception ex)
{
StatusText.Text = $"✕ ERROR: {ex.Message}";
StatusText.Text = $"Something went wrong: {ex.Message}";
StatusText.Foreground = Red;
}
finally
Expand All @@ -103,7 +103,7 @@ private async void BtnInitiate_Click(object sender, RoutedEventArgs e)
}
}

// Scores the posting in front of the user (full scoring network signals OK for a
// Scores the posting in front of the user (full scoring; network signals OK for a
// single job) and surfaces the ghost risk band + evidence before they apply.
private async Task ScoreGhostRiskAsync(string jobUrl)
{
Expand All @@ -120,7 +120,7 @@ private async Task ScoreGhostRiskAsync(string jobUrl)
};

var score = await _ghostScorer.ScoreAsync(posting);
// Held so the score in front of the user travels with the submit
// Held so the score in front of the user travels with the submit;
// the log and the ledger record what the decision was made against.
_ghostScore = score;

Expand All @@ -134,7 +134,7 @@ private async Task ScoreGhostRiskAsync(string jobUrl)
GhostBadge.Background = badge;
GhostBandText.Text = score.Band == RiskBand.Neutral ? "OK" : $"{label} · {score.RiskScore:0}";
GhostEvidenceText.Text = score.TopEvidence.Length > 0
? string.Join("\n", score.TopEvidence.Select(ev => " " + ev))
? string.Join("\n", score.TopEvidence.Select(ev => "- " + ev))
: "No ghost-risk signals detected on this posting.";
GhostRiskPanel.Visibility = Visibility.Visible;
}
Expand All @@ -151,19 +151,21 @@ private void ShowResult()
ResultPanel.Visibility = Visibility.Visible;

ScoreText.Text = $"{_tailored.MatchScore}%";
SafetyText.Text = _tailored.SafetyResult.Passed ? "✓ CLEARED" : "⚠ SAFE MODE REQUIRED";
SafetyText.Text = _tailored.SafetyResult.Passed ? "Cleared" : "Needs your review";
SafetyText.Foreground = _tailored.SafetyResult.Passed ? Green : Red;

ViolationsText.Text = _tailored.SafetyResult.Violations.Any()
? string.Join("\n", _tailored.SafetyResult.Violations.Select(v => $" {v.Type}: {v.Description}"))
? string.Join("\n", _tailored.SafetyResult.Violations.Select(v => $"- {v.Type}: {v.Description}"))
: "";

ChangesText.Text = _tailored.ChangesMade.Any()
? "MODIFICATIONS:\n" + string.Join("\n", _tailored.ChangesMade.Select(c => $" {c}"))
? "Changes made:\n" + string.Join("\n", _tailored.ChangesMade.Select(c => $" - {c}"))
: "";

BtnExecute.Visibility = Visibility.Visible;
StatusText.Text = _tailored.SafetyResult.Passed ? "✓ PAYLOAD OPTIMIZED" : "⚠ SAFETY PROTOCOLS TRIGGERED";
StatusText.Text = _tailored.SafetyResult.Passed
? "Resume tailored and ready."
: "Tailored, but check the flagged items before you send.";
}

private async void BtnExecute_Click(object sender, RoutedEventArgs e)
Expand All @@ -177,7 +179,7 @@ private async void BtnExecute_Click(object sender, RoutedEventArgs e)
var mode = (CmbMode.SelectedItem as ComboBoxItem)?.Content?.ToString() == "Stealth"
? ExecutionMode.Stealth : ExecutionMode.Safe;

StatusText.Text = "ESTABLISHING CONNECTION...";
StatusText.Text = "Connecting to your browser...";
StatusText.Foreground = Cyan;

var snapshot = _ghostScore == null
Expand All @@ -189,14 +191,14 @@ private async void BtnExecute_Click(object sender, RoutedEventArgs e)

StatusText.Text = log.Status switch
{
ApplicationStatus.Completed => "✓ MISSION ACCOMPLISHED",
ApplicationStatus.DeclinedByUser => "⏸ DECLINED — NOTHING WAS SENT",
ApplicationStatus.SafeModeStopped => "⏸ SUBMISSION HELD — NOT SUBMITTED",
ApplicationStatus.RequiresCaptcha => "🧩 CAPTCHA DETECTED — HUMAN INPUT NEEDED",
ApplicationStatus.Failed => $"✕ FAILED: {log.ErrorMessage}",
_ => $"STATUS: {log.Status}"
ApplicationStatus.Completed => "Application submitted.",
ApplicationStatus.DeclinedByUser => "You said no. Nothing was sent.",
ApplicationStatus.SafeModeStopped => "Held for your review. Nothing was sent.",
ApplicationStatus.RequiresCaptcha => "There's a CAPTCHA. Solve it in the browser, then run this again.",
ApplicationStatus.Failed => $"That didn't work: {log.ErrorMessage}",
_ => $"Status: {log.Status}"
};
// Declining is a decision, not a failure don't paint it error-red.
// Declining is a decision, not a failure; don't paint it error-red.
StatusText.Foreground = log.Status switch
{
ApplicationStatus.Completed => Green,
Expand All @@ -206,7 +208,7 @@ private async void BtnExecute_Click(object sender, RoutedEventArgs e)
}
catch (Exception ex)
{
StatusText.Text = $"✕ ERROR: {ex.Message}";
StatusText.Text = $"Something went wrong: {ex.Message}";
StatusText.Foreground = Red;
}
finally
Expand All @@ -217,7 +219,7 @@ private async void BtnExecute_Click(object sender, RoutedEventArgs e)
}

// Presents an inline Confirm/Cancel gate and blocks until the user decides.
// Returns true only on explicit confirmation the submit click never fires
// Returns true only on explicit confirmation; the submit click never fires
// otherwise. Marshalled through the Dispatcher so it is safe regardless of
// which thread the orchestrator resumes the callback on.
private Task<bool> RequestSubmitConfirmationAsync(string message)
Expand All @@ -229,7 +231,7 @@ private Task<bool> RequestSubmitConfirmationAsync(string message)
ConfirmPanel.Visibility = Visibility.Visible;
BtnConfirmSubmit.IsEnabled = true;
BtnCancelSubmit.IsEnabled = true;
StatusText.Text = "⏸ AWAITING YOUR CONFIRMATION...";
StatusText.Text = "Waiting on you. Confirm or cancel below.";
StatusText.Foreground = Yellow;
_confirmTcs = tcs;
});
Expand Down
Loading