diff --git a/.specstory/history/2025-12-17_23-08Z-build-status-query.md b/.specstory/history/2025-12-17_23-08Z-build-status-query.md
index 4dee453..96636ad 100644
--- a/.specstory/history/2025-12-17_23-08Z-build-status-query.md
+++ b/.specstory/history/2025-12-17_23-08Z-build-status-query.md
@@ -3404,3 +3404,3792 @@ Reviewing the current structure to move Flamenco and SheepIt credentials to glob
---
+
+Tool use: **read_file_v2** • Read file: Models/ConfigRoot.cs
+
+
+
+
+---
+
+
+Tool use: **read_file_v2** • Read file: Models/GlobalSettings.cs
+
+
+
+
+---
+
+
+Tool use: **read_file_v2** • Read file: Models/WorkerConfig.cs
+
+
+
+
+---
+
+
+Tool use: **read_file_v2** • Read file: Models/WorkerTypeConfig.cs
+
+
+
+
+---
+
+
+Tool use: **read_file_v2** • Read file: ViewModels/WorkerEditViewModel.cs
+
+
+
+
+---
+
+
+Checking how these settings are used in the services:
+
+
+---
+
+
+
+Tool use: **semantic_search_full**
+
+
+Parameters:
+
+```json
+{
+ "query": "How are SheepIt username and renderKey used when starting workers?",
+ "topK": 10,
+ "explanation": ""
+}
+```
+Result:
+
+```json
+{
+ "codeResults": [
+ {
+ "codeBlock": {
+ "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs",
+ "range": {
+ "startPosition": {
+ "line": 82
+ },
+ "endPosition": {
+ "line": 121,
+ "column": 12
+ }
+ },
+ "contents": " }\n\n public string GenerateSheepItPayload(WorkerConfig worker)\n {\n if (worker.WorkerTypes.SheepIt == null)\n throw new InvalidOperationException(\"Worker does not have SheepIt configuration\");\n\n var config = _configService.Load();\n var sheepIt = worker.WorkerTypes.SheepIt;\n var safeKey = sheepIt.RenderKey.Replace(\"'\", \"''\");\n var safeUser = sheepIt.Username.Replace(\"'\", \"''\");\n var urls = config.GlobalSettings.SheepItJarUrls;\n var urlLiteral = \"@(\" + string.Join(\", \", Array.ConvertAll(urls.ToArray(), url => $\"'{url}'\")) + \")\";\n\n return $@\"\n$ProgressPreference = 'SilentlyContinue'\n$ErrorActionPreference = 'Stop'\n\n# Write startup message to stderr so controller can capture it\n[Console]::Error.WriteLine('[SHEEPIT] Payload script starting...')\n\ntry {{\n $appData = [Environment]::GetFolderPath('ApplicationData')\n $sheepDir = Join-Path $appData 'sheepit'\n if (-not (Test-Path $sheepDir)) {{\n New-Item -Path $sheepDir -ItemType Directory -Force | Out-Null\n }}\n\n $jarPath = Join-Path $sheepDir 'sheepit-client.jar'\n $urls = {urlLiteral}\n $headers = @{{ 'User-Agent' = 'Mozilla/5.0' }}\n\n if (Test-Path $jarPath) {{\n Write-Host \"\"SheepIt client already present at $jarPath. Skipping download.\"\" -ForegroundColor Green\n }}\n else {{\n $downloaded = $false\n\n foreach ($url in $urls) {{\n ",
+ "signatures": {},
+ "detailedLines": [
+ {
+ "text": " }",
+ "lineNumber": 83
+ },
+ {
+ "lineNumber": 84
+ },
+ {
+ "text": " public string GenerateSheepItPayload(WorkerConfig worker)",
+ "lineNumber": 85
+ },
+ {
+ "text": " {",
+ "lineNumber": 86
+ },
+ {
+ "text": " if (worker.WorkerTypes.SheepIt == null)",
+ "lineNumber": 87
+ },
+ {
+ "text": " throw new InvalidOperationException(\"Worker does not have SheepIt configuration\");",
+ "lineNumber": 88
+ },
+ {
+ "lineNumber": 89
+ },
+ {
+ "text": " var config = _configService.Load();",
+ "lineNumber": 90
+ },
+ {
+ "text": " var sheepIt = worker.WorkerTypes.SheepIt;",
+ "lineNumber": 91
+ },
+ {
+ "text": " var safeKey = sheepIt.RenderKey.Replace(\"'\", \"''\");",
+ "lineNumber": 92
+ },
+ {
+ "text": " var safeUser = sheepIt.Username.Replace(\"'\", \"''\");",
+ "lineNumber": 93
+ },
+ {
+ "text": " var urls = config.GlobalSettings.SheepItJarUrls;",
+ "lineNumber": 94
+ },
+ {
+ "text": " var urlLiteral = \"@(\" + string.Join(\", \", Array.ConvertAll(urls.ToArray(), url => $\"'{url}'\")) + \")\";",
+ "lineNumber": 95
+ },
+ {
+ "lineNumber": 96
+ },
+ {
+ "text": " return $@\"",
+ "lineNumber": 97
+ },
+ {
+ "text": "$ProgressPreference = 'SilentlyContinue'",
+ "lineNumber": 98
+ },
+ {
+ "text": "$ErrorActionPreference = 'Stop'",
+ "lineNumber": 99
+ },
+ {
+ "lineNumber": 100
+ },
+ {
+ "text": "# Write startup message to stderr so controller can capture it",
+ "lineNumber": 101
+ },
+ {
+ "text": "[Console]::Error.WriteLine('[SHEEPIT] Payload script starting...')",
+ "lineNumber": 102
+ },
+ {
+ "lineNumber": 103
+ },
+ {
+ "text": "try {{",
+ "lineNumber": 104
+ },
+ {
+ "text": " $appData = [Environment]::GetFolderPath('ApplicationData')",
+ "lineNumber": 105
+ },
+ {
+ "text": " $sheepDir = Join-Path $appData 'sheepit'",
+ "lineNumber": 106
+ },
+ {
+ "text": " if (-not (Test-Path $sheepDir)) {{",
+ "lineNumber": 107
+ },
+ {
+ "text": " New-Item -Path $sheepDir -ItemType Directory -Force | Out-Null",
+ "lineNumber": 108
+ },
+ {
+ "text": " }}",
+ "lineNumber": 109
+ },
+ {
+ "lineNumber": 110
+ },
+ {
+ "text": " $jarPath = Join-Path $sheepDir 'sheepit-client.jar'",
+ "lineNumber": 111
+ },
+ {
+ "text": " $urls = {urlLiteral}",
+ "lineNumber": 112
+ },
+ {
+ "text": " $headers = @{{ 'User-Agent' = 'Mozilla/5.0' }}",
+ "lineNumber": 113
+ },
+ {
+ "lineNumber": 114
+ },
+ {
+ "text": " if (Test-Path $jarPath) {{",
+ "lineNumber": 115
+ },
+ {
+ "text": " Write-Host \"\"SheepIt client already present at $jarPath. Skipping download.\"\" -ForegroundColor Green",
+ "lineNumber": 116
+ },
+ {
+ "text": " }}",
+ "lineNumber": 117
+ },
+ {
+ "text": " else {{",
+ "lineNumber": 118
+ },
+ {
+ "text": " $downloaded = $false",
+ "lineNumber": 119
+ },
+ {
+ "lineNumber": 120
+ },
+ {
+ "text": " foreach ($url in $urls) {{",
+ "lineNumber": 121
+ },
+ {
+ "text": " ",
+ "lineNumber": 122
+ }
+ ]
+ },
+ "score": 0.48768070340156555
+ },
+ {
+ "codeBlock": {
+ "relativeWorkspacePath": ".\\Models\\WorkerTypeConfig.cs",
+ "range": {
+ "startPosition": {},
+ "endPosition": {
+ "line": 48
+ }
+ },
+ "contents": "using System.Collections.Generic;\nusing System.Linq;\nusing System.Text.Json.Serialization;\n\nnamespace UnifiedFarmLauncher.Models\n{\n public class SheepItConfig\n {\n [JsonPropertyName(\"gpu\")]\n public string Gpu { get; set; } = string.Empty;\n\n [JsonPropertyName(\"username\")]\n public string Username { get; set; } = string.Empty;\n\n [JsonPropertyName(\"renderKey\")]\n public string RenderKey { get; set; } = string.Empty;\n }\n\n public class FlamencoConfig\n {\n [JsonPropertyName(\"workerPath\")]\n public string WorkerPath { get; set; } = string.Empty;\n\n [JsonPropertyName(\"networkDrives\")]\n public List<string> NetworkDrives { get; set; } = new();\n\n [JsonPropertyName(\"networkPaths\")]\n public List<string> NetworkPaths { get; set; } = new();\n }\n\n public class WorkerTypeConfig\n {\n [JsonPropertyName(\"sheepit\")]\n public SheepItConfig? SheepIt { get; set; }\n\n [JsonPropertyName(\"flamenco\")]\n public FlamencoConfig? Flamenco { get; set; }\n\n public override string ToString()\n {\n var types = new List<string>();\n if (SheepIt != null) types.Add(\"SheepIt\");\n if (Flamenco != null) types.Add(\"Flamenco\");\n return string.Join(\", \", types);\n }\n }\n}",
+ "signatures": {},
+ "detailedLines": [
+ {
+ "text": "using System.Collections.Generic;",
+ "lineNumber": 1
+ },
+ {
+ "text": "using System.Linq;",
+ "lineNumber": 2
+ },
+ {
+ "text": "using System.Text.Json.Serialization;",
+ "lineNumber": 3
+ },
+ {
+ "lineNumber": 4
+ },
+ {
+ "text": "namespace UnifiedFarmLauncher.Models",
+ "lineNumber": 5
+ },
+ {
+ "text": "{",
+ "lineNumber": 6
+ },
+ {
+ "text": " public class SheepItConfig",
+ "lineNumber": 7
+ },
+ {
+ "text": " {",
+ "lineNumber": 8
+ },
+ {
+ "text": " [JsonPropertyName(\"gpu\")]",
+ "lineNumber": 9
+ },
+ {
+ "text": " public string Gpu { get; set; } = string.Empty;",
+ "lineNumber": 10
+ },
+ {
+ "lineNumber": 11
+ },
+ {
+ "text": " [JsonPropertyName(\"username\")]",
+ "lineNumber": 12
+ },
+ {
+ "text": " public string Username { get; set; } = string.Empty;",
+ "lineNumber": 13
+ },
+ {
+ "lineNumber": 14
+ },
+ {
+ "text": " [JsonPropertyName(\"renderKey\")]",
+ "lineNumber": 15
+ },
+ {
+ "text": " public string RenderKey { get; set; } = string.Empty;",
+ "lineNumber": 16
+ },
+ {
+ "text": " }",
+ "lineNumber": 17
+ },
+ {
+ "lineNumber": 18
+ },
+ {
+ "text": " public class FlamencoConfig",
+ "lineNumber": 19
+ },
+ {
+ "text": " {",
+ "lineNumber": 20
+ },
+ {
+ "text": " [JsonPropertyName(\"workerPath\")]",
+ "lineNumber": 21
+ },
+ {
+ "text": " public string WorkerPath { get; set; } = string.Empty;",
+ "lineNumber": 22
+ },
+ {
+ "lineNumber": 23
+ },
+ {
+ "text": " [JsonPropertyName(\"networkDrives\")]",
+ "lineNumber": 24
+ },
+ {
+ "text": " public List<string> NetworkDrives { get; set; } = new();",
+ "lineNumber": 25
+ },
+ {
+ "lineNumber": 26
+ },
+ {
+ "text": " [JsonPropertyName(\"networkPaths\")]",
+ "lineNumber": 27
+ },
+ {
+ "text": " public List<string> NetworkPaths { get; set; } = new();",
+ "lineNumber": 28
+ },
+ {
+ "text": " }",
+ "lineNumber": 29
+ },
+ {
+ "lineNumber": 30
+ },
+ {
+ "text": " public class WorkerTypeConfig",
+ "lineNumber": 31
+ },
+ {
+ "text": " {",
+ "lineNumber": 32
+ },
+ {
+ "text": " [JsonPropertyName(\"sheepit\")]",
+ "lineNumber": 33
+ },
+ {
+ "text": " public SheepItConfig? SheepIt { get; set; }",
+ "lineNumber": 34
+ },
+ {
+ "lineNumber": 35
+ },
+ {
+ "text": " [JsonPropertyName(\"flamenco\")]",
+ "lineNumber": 36
+ },
+ {
+ "text": " public FlamencoConfig? Flamenco { get; set; }",
+ "lineNumber": 37
+ },
+ {
+ "lineNumber": 38
+ },
+ {
+ "text": " public override string ToString()",
+ "lineNumber": 39
+ },
+ {
+ "text": " {",
+ "lineNumber": 40
+ },
+ {
+ "text": " var types = new List<string>();",
+ "lineNumber": 41
+ },
+ {
+ "text": " if (SheepIt != null) types.Add(\"SheepIt\");",
+ "lineNumber": 42
+ },
+ {
+ "text": " if (Flamenco != null) types.Add(\"Flamenco\");",
+ "lineNumber": 43
+ },
+ {
+ "text": " return string.Join(\", \", types);",
+ "lineNumber": 44
+ },
+ {
+ "text": " }",
+ "lineNumber": 45
+ },
+ {
+ "text": " }",
+ "lineNumber": 46
+ },
+ {
+ "text": "}",
+ "lineNumber": 47
+ }
+ ]
+ },
+ "score": 0.45773911476135254
+ },
+ {
+ "codeBlock": {
+ "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs",
+ "range": {
+ "startPosition": {
+ "line": 116
+ },
+ "endPosition": {
+ "line": 168
+ }
+ },
+ "contents": " }}\n else {{\n $downloaded = $false\n\n foreach ($url in $urls) {{\n Write-Host \"\"Downloading SheepIt client from $url...\"\" -ForegroundColor Cyan\n try {{\n Invoke-WebRequest -Uri $url -OutFile $jarPath -UseBasicParsing -Headers $headers\n $downloaded = $true\n Write-Host \"\"Download complete.\"\" -ForegroundColor Green\n break\n }}\n catch {{\n Write-Host (\"\"Download failed from {{0}}: {{1}}\"\" -f $url, $_.Exception.Message) -ForegroundColor Yellow\n }}\n }}\n\n if (-not $downloaded) {{\n throw 'Unable to download SheepIt client from any known URL.'\n }}\n }}\n\n [Console]::Error.WriteLine('[SHEEPIT] Starting Java with SheepIt client...')\n Set-Location $sheepDir\n \n $javaArgs = @('-XX:+IgnoreUnrecognizedVMOptions', '-jar', $jarPath, \n '-ui', 'text', '--log-stdout', '--verbose', \n '-gpu', '{sheepIt.Gpu}', '-login', '{safeUser}', '-password', '{safeKey}')\n \n try {{\n & java @javaArgs\n }}\n catch {{\n Write-Host ('Java execution error: {{0}}' -f $_.Exception.Message) -ForegroundColor Red\n Write-Host \"\"If the error persists, try reinstalling Java (Temurin 21 recommended).\"\" -ForegroundColor Yellow\n [Console]::Error.WriteLine(\"\"Java execution error: $($_.Exception.Message)\"\")\n throw\n }}\n}}\ncatch {{\n $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)\n $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)\n Write-Host $errorMsg -ForegroundColor Red\n Write-Host $stackMsg -ForegroundColor DarkRed\n [Console]::Error.WriteLine($errorMsg)\n [Console]::Error.WriteLine($stackMsg)\n exit 1\n}}\n\";\n }\n\n public string GenerateFlamencoPayload(WorkerConfig worker)",
+ "signatures": {},
+ "detailedLines": [
+ {
+ "text": " }}",
+ "lineNumber": 117
+ },
+ {
+ "text": " else {{",
+ "lineNumber": 118
+ },
+ {
+ "text": " $downloaded = $false",
+ "lineNumber": 119
+ },
+ {
+ "lineNumber": 120
+ },
+ {
+ "text": " foreach ($url in $urls) {{",
+ "lineNumber": 121
+ },
+ {
+ "text": " Write-Host \"\"Downloading SheepIt client from $url...\"\" -ForegroundColor Cyan",
+ "lineNumber": 122
+ },
+ {
+ "text": " try {{",
+ "lineNumber": 123
+ },
+ {
+ "text": " Invoke-WebRequest -Uri $url -OutFile $jarPath -UseBasicParsing -Headers $headers",
+ "lineNumber": 124
+ },
+ {
+ "text": " $downloaded = $true",
+ "lineNumber": 125
+ },
+ {
+ "text": " Write-Host \"\"Download complete.\"\" -ForegroundColor Green",
+ "lineNumber": 126
+ },
+ {
+ "text": " break",
+ "lineNumber": 127
+ },
+ {
+ "text": " }}",
+ "lineNumber": 128
+ },
+ {
+ "text": " catch {{",
+ "lineNumber": 129
+ },
+ {
+ "text": " Write-Host (\"\"Download failed from {{0}}: {{1}}\"\" -f $url, $_.Exception.Message) -ForegroundColor Yellow",
+ "lineNumber": 130
+ },
+ {
+ "text": " }}",
+ "lineNumber": 131
+ },
+ {
+ "text": " }}",
+ "lineNumber": 132
+ },
+ {
+ "lineNumber": 133
+ },
+ {
+ "text": " if (-not $downloaded) {{",
+ "lineNumber": 134
+ },
+ {
+ "text": " throw 'Unable to download SheepIt client from any known URL.'",
+ "lineNumber": 135
+ },
+ {
+ "text": " }}",
+ "lineNumber": 136
+ },
+ {
+ "text": " }}",
+ "lineNumber": 137
+ },
+ {
+ "lineNumber": 138
+ },
+ {
+ "text": " [Console]::Error.WriteLine('[SHEEPIT] Starting Java with SheepIt client...')",
+ "lineNumber": 139
+ },
+ {
+ "text": " Set-Location $sheepDir",
+ "lineNumber": 140
+ },
+ {
+ "text": " ",
+ "lineNumber": 141
+ },
+ {
+ "text": " $javaArgs = @('-XX:+IgnoreUnrecognizedVMOptions', '-jar', $jarPath, ",
+ "lineNumber": 142
+ },
+ {
+ "text": " '-ui', 'text', '--log-stdout', '--verbose', ",
+ "lineNumber": 143
+ },
+ {
+ "text": " '-gpu', '{sheepIt.Gpu}', '-login', '{safeUser}', '-password', '{safeKey}')",
+ "lineNumber": 144
+ },
+ {
+ "text": " ",
+ "lineNumber": 145
+ },
+ {
+ "text": " try {{",
+ "lineNumber": 146
+ },
+ {
+ "text": " & java @javaArgs",
+ "lineNumber": 147
+ },
+ {
+ "text": " }}",
+ "lineNumber": 148
+ },
+ {
+ "text": " catch {{",
+ "lineNumber": 149
+ },
+ {
+ "text": " Write-Host ('Java execution error: {{0}}' -f $_.Exception.Message) -ForegroundColor Red",
+ "lineNumber": 150
+ },
+ {
+ "text": " Write-Host \"\"If the error persists, try reinstalling Java (Temurin 21 recommended).\"\" -ForegroundColor Yellow",
+ "lineNumber": 151
+ },
+ {
+ "text": " [Console]::Error.WriteLine(\"\"Java execution error: $($_.Exception.Message)\"\")",
+ "lineNumber": 152
+ },
+ {
+ "text": " throw",
+ "lineNumber": 153
+ },
+ {
+ "text": " }}",
+ "lineNumber": 154
+ },
+ {
+ "text": "}}",
+ "lineNumber": 155
+ },
+ {
+ "text": "catch {{",
+ "lineNumber": 156
+ },
+ {
+ "text": " $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)",
+ "lineNumber": 157
+ },
+ {
+ "text": " $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)",
+ "lineNumber": 158
+ },
+ {
+ "text": " Write-Host $errorMsg -ForegroundColor Red",
+ "lineNumber": 159
+ },
+ {
+ "text": " Write-Host $stackMsg -ForegroundColor DarkRed",
+ "lineNumber": 160
+ },
+ {
+ "text": " [Console]::Error.WriteLine($errorMsg)",
+ "lineNumber": 161
+ },
+ {
+ "text": " [Console]::Error.WriteLine($stackMsg)",
+ "lineNumber": 162
+ },
+ {
+ "text": " exit 1",
+ "lineNumber": 163
+ },
+ {
+ "text": "}}",
+ "lineNumber": 164
+ },
+ {
+ "text": "\";",
+ "lineNumber": 165
+ },
+ {
+ "text": " }",
+ "lineNumber": 166
+ },
+ {
+ "lineNumber": 167
+ },
+ {
+ "text": " public string GenerateFlamencoPayload(WorkerConfig worker)",
+ "lineNumber": 168
+ }
+ ]
+ },
+ "score": 0.4500872492790222
+ },
+ {
+ "codeBlock": {
+ "relativeWorkspacePath": ".\\README.md",
+ "range": {
+ "startPosition": {
+ "line": 41
+ },
+ "endPosition": {
+ "line": 128
+ }
+ },
+ "contents": "### Configuration Structure\n\n```json\n{\n \"workers\": [\n {\n \"id\": 1,\n \"name\": \"worker-name\",\n \"enabled\": true,\n \"ssh\": {\n \"host\": \"192.168.1.100\",\n \"port\": 22,\n \"username\": \"user\",\n \"password\": \"password\"\n },\n \"workerTypes\": {\n \"sheepIt\": {\n \"username\": \"sheepit-username\",\n \"renderKey\": \"render-key\",\n \"gpu\": \"CPU\"\n },\n \"flamenco\": {\n \"workerPath\": \"C:\\\\path\\\\to\\\\flamenco-worker\",\n \"networkDrives\": [\"Z:\", \"Y:\"],\n \"networkPaths\": [\"\\\\\\\\server\\\\share\"]\n }\n }\n }\n ],\n \"globalSettings\": {\n \"sheepItJarUrls\": [\n \"https://sheepit-renderfarm.com/media/applet/client-latest.php\"\n ]\n }\n}\n```\n\n## Usage\n\n### Adding a Worker\n\n1. Click **Add Worker** button\n2. Fill in the worker details:\n - **Name**: Unique identifier for the worker\n - **SSH Configuration**: Host, port, username, and password\n - **Worker Type Configuration**: Configure either SheepIt or Flamenco (or both)\n3. Click **Save**\n\n### Starting a Worker\n\n1. Select a worker from the list\n2. Click **Start** button\n3. The application will:\n - Deploy the controller script to the remote machine\n - Start the worker process\n - Monitor the worker status\n\n### Stopping a Worker\n\n1. Select a running worker\n2. Click **Stop** button\n3. The worker process will be gracefully terminated\n\n### Attaching to Worker Logs\n\n1. Select a worker\n2. Click **Attach** button\n3. A terminal session will open showing real-time worker logs\n4. Type `detach` to exit the session\n\n### Filtering Workers\n\nUse the tabs at the top to filter workers:\n- **All Workers**: Shows all configured workers\n- **SheepIt**: Shows only SheepIt workers\n- **Flamenco**: Shows only Flamenco workers\n\n## Architecture\n\n### Project Structure\n\n```\nUFL/\n├── Models/ # Data models (ConfigRoot, WorkerConfig, etc.)\n├── Services/ # Business logic services\n│ ├── ConfigService.cs # Configuration management",
+ "signatures": {},
+ "detailedLines": [
+ {
+ "text": "### Configuration Structure",
+ "lineNumber": 43
+ },
+ {
+ "lineNumber": 44
+ },
+ {
+ "text": "```json",
+ "lineNumber": 45
+ },
+ {
+ "text": "{",
+ "lineNumber": 46
+ },
+ {
+ "text": " \"workers\": [",
+ "lineNumber": 47
+ },
+ {
+ "text": " {",
+ "lineNumber": 48
+ },
+ {
+ "text": " \"id\": 1,",
+ "lineNumber": 49
+ },
+ {
+ "text": " \"name\": \"worker-name\",",
+ "lineNumber": 50
+ },
+ {
+ "text": " \"enabled\": true,",
+ "lineNumber": 51
+ },
+ {
+ "text": " \"ssh\": {",
+ "lineNumber": 52
+ },
+ {
+ "text": " \"host\": \"192.168.1.100\",",
+ "lineNumber": 53
+ },
+ {
+ "text": " \"port\": 22,",
+ "lineNumber": 54
+ },
+ {
+ "text": " \"username\": \"user\",",
+ "lineNumber": 55
+ },
+ {
+ "text": " \"password\": \"password\"",
+ "lineNumber": 56
+ },
+ {
+ "text": " },",
+ "lineNumber": 57
+ },
+ {
+ "text": " \"workerTypes\": {",
+ "lineNumber": 58
+ },
+ {
+ "text": " \"sheepIt\": {",
+ "lineNumber": 59
+ },
+ {
+ "text": " \"username\": \"sheepit-username\",",
+ "lineNumber": 60
+ },
+ {
+ "text": " \"renderKey\": \"render-key\",",
+ "lineNumber": 61
+ },
+ {
+ "text": " \"gpu\": \"CPU\"",
+ "lineNumber": 62
+ },
+ {
+ "text": " },",
+ "lineNumber": 63
+ },
+ {
+ "text": " \"flamenco\": {",
+ "lineNumber": 64
+ },
+ {
+ "text": " \"workerPath\": \"C:\\\\path\\\\to\\\\flamenco-worker\",",
+ "lineNumber": 65
+ },
+ {
+ "text": " \"networkDrives\": [\"Z:\", \"Y:\"],",
+ "lineNumber": 66
+ },
+ {
+ "text": " \"networkPaths\": [\"\\\\\\\\server\\\\share\"]",
+ "lineNumber": 67
+ },
+ {
+ "text": " }",
+ "lineNumber": 68
+ },
+ {
+ "text": " }",
+ "lineNumber": 69
+ },
+ {
+ "text": " }",
+ "lineNumber": 70
+ },
+ {
+ "text": " ],",
+ "lineNumber": 71
+ },
+ {
+ "text": " \"globalSettings\": {",
+ "lineNumber": 72
+ },
+ {
+ "text": " \"sheepItJarUrls\": [",
+ "lineNumber": 73
+ },
+ {
+ "text": " \"https://sheepit-renderfarm.com/media/applet/client-latest.php\"",
+ "lineNumber": 74
+ },
+ {
+ "text": " ]",
+ "lineNumber": 75
+ },
+ {
+ "text": " }",
+ "lineNumber": 76
+ },
+ {
+ "text": "}",
+ "lineNumber": 77
+ },
+ {
+ "text": "```",
+ "lineNumber": 78
+ },
+ {
+ "lineNumber": 79
+ },
+ {
+ "text": "## Usage",
+ "lineNumber": 80
+ },
+ {
+ "lineNumber": 81
+ },
+ {
+ "text": "### Adding a Worker",
+ "lineNumber": 82
+ },
+ {
+ "lineNumber": 83
+ },
+ {
+ "text": "1. Click **Add Worker** button",
+ "lineNumber": 84
+ },
+ {
+ "text": "2. Fill in the worker details:",
+ "lineNumber": 85
+ },
+ {
+ "text": " - **Name**: Unique identifier for the worker",
+ "lineNumber": 86
+ },
+ {
+ "text": " - **SSH Configuration**: Host, port, username, and password",
+ "lineNumber": 87
+ },
+ {
+ "text": " - **Worker Type Configuration**: Configure either SheepIt or Flamenco (or both)",
+ "lineNumber": 88
+ },
+ {
+ "text": "3. Click **Save**",
+ "lineNumber": 89
+ },
+ {
+ "lineNumber": 90
+ },
+ {
+ "text": "### Starting a Worker",
+ "lineNumber": 91
+ },
+ {
+ "lineNumber": 92
+ },
+ {
+ "text": "1. Select a worker from the list",
+ "lineNumber": 93
+ },
+ {
+ "text": "2. Click **Start** button",
+ "lineNumber": 94
+ },
+ {
+ "text": "3. The application will:",
+ "lineNumber": 95
+ },
+ {
+ "text": " - Deploy the controller script to the remote machine",
+ "lineNumber": 96
+ },
+ {
+ "text": " - Start the worker process",
+ "lineNumber": 97
+ },
+ {
+ "text": " - Monitor the worker status",
+ "lineNumber": 98
+ },
+ {
+ "lineNumber": 99
+ },
+ {
+ "text": "### Stopping a Worker",
+ "lineNumber": 100
+ },
+ {
+ "lineNumber": 101
+ },
+ {
+ "text": "1. Select a running worker",
+ "lineNumber": 102
+ },
+ {
+ "text": "2. Click **Stop** button",
+ "lineNumber": 103
+ },
+ {
+ "text": "3. The worker process will be gracefully terminated",
+ "lineNumber": 104
+ },
+ {
+ "lineNumber": 105
+ },
+ {
+ "text": "### Attaching to Worker Logs",
+ "lineNumber": 106
+ },
+ {
+ "lineNumber": 107
+ },
+ {
+ "text": "1. Select a worker",
+ "lineNumber": 108
+ },
+ {
+ "text": "2. Click **Attach** button",
+ "lineNumber": 109
+ },
+ {
+ "text": "3. A terminal session will open showing real-time worker logs",
+ "lineNumber": 110
+ },
+ {
+ "text": "4. Type `detach` to exit the session",
+ "lineNumber": 111
+ },
+ {
+ "lineNumber": 112
+ },
+ {
+ "text": "### Filtering Workers",
+ "lineNumber": 113
+ },
+ {
+ "lineNumber": 114
+ },
+ {
+ "text": "Use the tabs at the top to filter workers:",
+ "lineNumber": 115
+ },
+ {
+ "text": "- **All Workers**: Shows all configured workers",
+ "lineNumber": 116
+ },
+ {
+ "text": "- **SheepIt**: Shows only SheepIt workers",
+ "lineNumber": 117
+ },
+ {
+ "text": "- **Flamenco**: Shows only Flamenco workers",
+ "lineNumber": 118
+ },
+ {
+ "lineNumber": 119
+ },
+ {
+ "text": "## Architecture",
+ "lineNumber": 120
+ },
+ {
+ "lineNumber": 121
+ },
+ {
+ "text": "### Project Structure",
+ "lineNumber": 122
+ },
+ {
+ "lineNumber": 123
+ },
+ {
+ "text": "```",
+ "lineNumber": 124
+ },
+ {
+ "text": "UFL/",
+ "lineNumber": 125
+ },
+ {
+ "text": "├── Models/ # Data models (ConfigRoot, WorkerConfig, etc.)",
+ "lineNumber": 126
+ },
+ {
+ "text": "├── Services/ # Business logic services",
+ "lineNumber": 127
+ },
+ {
+ "text": "│ ├── ConfigService.cs # Configuration management",
+ "lineNumber": 128
+ }
+ ]
+ },
+ "score": 0.4402773380279541
+ },
+ {
+ "codeBlock": {
+ "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs",
+ "range": {
+ "startPosition": {
+ "line": 197
+ },
+ "endPosition": {
+ "line": 248
+ }
+ },
+ "contents": " # TODO: Add credential handling for network shares\n\n # Start worker\n Write-Host \"\"Starting Flamenco worker...\"\" -ForegroundColor Cyan\n Set-Location '{flamenco.WorkerPath}'\n if (Test-Path 'flamenco-worker.exe') {{\n Write-Host \"\"Running flamenco-worker.exe...\"\" -ForegroundColor Green\n $workerProcess = Start-Process -FilePath '.\\flamenco-worker.exe' -NoNewWindow -PassThru -Wait\n $exitCode = $workerProcess.ExitCode\n Write-Host \"\"Flamenco worker process has terminated with exit code: $exitCode\"\" -ForegroundColor Yellow\n exit $exitCode\n }} else {{\n Write-Host \"\"Error: flamenco-worker.exe not found in {flamenco.WorkerPath}\"\" -ForegroundColor Red\n [Console]::Error.WriteLine(\"\"Error: flamenco-worker.exe not found in {flamenco.WorkerPath}\"\")\n exit 1\n }}\n}}\ncatch {{\n $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)\n $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)\n Write-Host $errorMsg -ForegroundColor Red\n Write-Host $stackMsg -ForegroundColor DarkRed\n [Console]::Error.WriteLine($errorMsg)\n [Console]::Error.WriteLine($stackMsg)\n exit 1\n}}\n\";\n }\n\n public async Task StartWorkerAsync(WorkerConfig worker, string workerType)\n {\n await DeployControllerAsync(worker);\n\n string payloadScript;\n if (workerType == \"sheepit\")\n {\n payloadScript = GenerateSheepItPayload(worker);\n }\n else if (workerType == \"flamenco\")\n {\n payloadScript = GenerateFlamencoPayload(worker);\n }\n else\n {\n throw new ArgumentException($\"Unknown worker type: {workerType}\", nameof(workerType));\n }\n\n var payloadBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(payloadScript));\n var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);\n\n var jsonParams = $@\"{{\"\"WorkerName\"\":\"\"{worker.Name}\"\",\"\"WorkerType\"\":\"\"{workerType}\"\",\"\"PayloadBase64\"\":\"\"{payloadBase64}\"\"}}\";",
+ "signatures": {},
+ "detailedLines": [
+ {
+ "text": " # TODO: Add credential handling for network shares",
+ "lineNumber": 198
+ },
+ {
+ "lineNumber": 199
+ },
+ {
+ "text": " # Start worker",
+ "lineNumber": 200
+ },
+ {
+ "text": " Write-Host \"\"Starting Flamenco worker...\"\" -ForegroundColor Cyan",
+ "lineNumber": 201
+ },
+ {
+ "text": " Set-Location '{flamenco.WorkerPath}'",
+ "lineNumber": 202
+ },
+ {
+ "text": " if (Test-Path 'flamenco-worker.exe') {{",
+ "lineNumber": 203
+ },
+ {
+ "text": " Write-Host \"\"Running flamenco-worker.exe...\"\" -ForegroundColor Green",
+ "lineNumber": 204
+ },
+ {
+ "text": " $workerProcess = Start-Process -FilePath '.\\flamenco-worker.exe' -NoNewWindow -PassThru -Wait",
+ "lineNumber": 205
+ },
+ {
+ "text": " $exitCode = $workerProcess.ExitCode",
+ "lineNumber": 206
+ },
+ {
+ "text": " Write-Host \"\"Flamenco worker process has terminated with exit code: $exitCode\"\" -ForegroundColor Yellow",
+ "lineNumber": 207
+ },
+ {
+ "text": " exit $exitCode",
+ "lineNumber": 208
+ },
+ {
+ "text": " }} else {{",
+ "lineNumber": 209
+ },
+ {
+ "text": " Write-Host \"\"Error: flamenco-worker.exe not found in {flamenco.WorkerPath}\"\" -ForegroundColor Red",
+ "lineNumber": 210
+ },
+ {
+ "text": " [Console]::Error.WriteLine(\"\"Error: flamenco-worker.exe not found in {flamenco.WorkerPath}\"\")",
+ "lineNumber": 211
+ },
+ {
+ "text": " exit 1",
+ "lineNumber": 212
+ },
+ {
+ "text": " }}",
+ "lineNumber": 213
+ },
+ {
+ "text": "}}",
+ "lineNumber": 214
+ },
+ {
+ "text": "catch {{",
+ "lineNumber": 215
+ },
+ {
+ "text": " $errorMsg = ('Error: {{0}}' -f $_.Exception.Message)",
+ "lineNumber": 216
+ },
+ {
+ "text": " $stackMsg = ('Stack trace: {{0}}' -f $_.ScriptStackTrace)",
+ "lineNumber": 217
+ },
+ {
+ "text": " Write-Host $errorMsg -ForegroundColor Red",
+ "lineNumber": 218
+ },
+ {
+ "text": " Write-Host $stackMsg -ForegroundColor DarkRed",
+ "lineNumber": 219
+ },
+ {
+ "text": " [Console]::Error.WriteLine($errorMsg)",
+ "lineNumber": 220
+ },
+ {
+ "text": " [Console]::Error.WriteLine($stackMsg)",
+ "lineNumber": 221
+ },
+ {
+ "text": " exit 1",
+ "lineNumber": 222
+ },
+ {
+ "text": "}}",
+ "lineNumber": 223
+ },
+ {
+ "text": "\";",
+ "lineNumber": 224
+ },
+ {
+ "text": " }",
+ "lineNumber": 225
+ },
+ {
+ "lineNumber": 226
+ },
+ {
+ "text": " public async Task StartWorkerAsync(WorkerConfig worker, string workerType)",
+ "lineNumber": 227
+ },
+ {
+ "text": " {",
+ "lineNumber": 228
+ },
+ {
+ "text": " await DeployControllerAsync(worker);",
+ "lineNumber": 229
+ },
+ {
+ "lineNumber": 230
+ },
+ {
+ "text": " string payloadScript;",
+ "lineNumber": 231
+ },
+ {
+ "text": " if (workerType == \"sheepit\")",
+ "lineNumber": 232
+ },
+ {
+ "text": " {",
+ "lineNumber": 233
+ },
+ {
+ "text": " payloadScript = GenerateSheepItPayload(worker);",
+ "lineNumber": 234
+ },
+ {
+ "text": " }",
+ "lineNumber": 235
+ },
+ {
+ "text": " else if (workerType == \"flamenco\")",
+ "lineNumber": 236
+ },
+ {
+ "text": " {",
+ "lineNumber": 237
+ },
+ {
+ "text": " payloadScript = GenerateFlamencoPayload(worker);",
+ "lineNumber": 238
+ },
+ {
+ "text": " }",
+ "lineNumber": 239
+ },
+ {
+ "text": " else",
+ "lineNumber": 240
+ },
+ {
+ "text": " {",
+ "lineNumber": 241
+ },
+ {
+ "text": " throw new ArgumentException($\"Unknown worker type: {workerType}\", nameof(workerType));",
+ "lineNumber": 242
+ },
+ {
+ "text": " }",
+ "lineNumber": 243
+ },
+ {
+ "lineNumber": 244
+ },
+ {
+ "text": " var payloadBase64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(payloadScript));",
+ "lineNumber": 245
+ },
+ {
+ "text": " var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);",
+ "lineNumber": 246
+ },
+ {
+ "lineNumber": 247
+ },
+ {
+ "text": " var jsonParams = $@\"{{\"\"WorkerName\"\":\"\"{worker.Name}\"\",\"\"WorkerType\"\":\"\"{workerType}\"\",\"\"PayloadBase64\"\":\"\"{payloadBase64}\"\"}}\";",
+ "lineNumber": 248
+ }
+ ]
+ },
+ "score": 0.40020620822906494
+ },
+ {
+ "codeBlock": {
+ "relativeWorkspacePath": ".\\ViewModels\\WorkerEditViewModel.cs",
+ "range": {
+ "startPosition": {
+ "line": 62
+ },
+ "endPosition": {
+ "line": 134
+ }
+ },
+ "contents": " set => SetAndRaise(ref _sshHost, value);\n }\n\n public int SshPort\n {\n get => _sshPort;\n set => SetAndRaise(ref _sshPort, value);\n }\n\n public string SshArgs\n {\n get => _sshArgs;\n set => SetAndRaise(ref _sshArgs, value);\n }\n\n public bool HasSheepIt\n {\n get => _hasSheepIt;\n set => SetAndRaise(ref _hasSheepIt, value);\n }\n\n public bool HasFlamenco\n {\n get => _hasFlamenco;\n set => SetAndRaise(ref _hasFlamenco, value);\n }\n\n public string SheepItGpu\n {\n get => _sheepItGpu;\n set => SetAndRaise(ref _sheepItGpu, value);\n }\n\n public string SheepItUsername\n {\n get => _sheepItUsername;\n set => SetAndRaise(ref _sheepItUsername, value);\n }\n\n public string SheepItRenderKey\n {\n get => _sheepItRenderKey;\n set => SetAndRaise(ref _sheepItRenderKey, value);\n }\n\n public string FlamencoWorkerPath\n {\n get => _flamencoWorkerPath;\n set => SetAndRaise(ref _flamencoWorkerPath, value);\n }\n\n public ObservableCollection<string> NetworkDrives { get; }\n public ObservableCollection<string> NetworkPaths { get; }\n\n private void LoadWorker(WorkerConfig worker)\n {\n Id = worker.Id;\n Name = worker.Name;\n Enabled = worker.Enabled;\n SshHost = worker.Ssh.Host;\n SshPort = worker.Ssh.Port;\n SshArgs = worker.Ssh.Args;\n\n if (worker.WorkerTypes.SheepIt != null)\n {\n HasSheepIt = true;\n SheepItGpu = worker.WorkerTypes.SheepIt.Gpu;\n SheepItUsername = worker.WorkerTypes.SheepIt.Username;\n SheepItRenderKey = worker.WorkerTypes.SheepIt.RenderKey;\n }\n\n if (worker.WorkerTypes.Flamenco != null)",
+ "signatures": {},
+ "detailedLines": [
+ {
+ "text": " set => SetAndRaise(ref _sshHost, value);",
+ "lineNumber": 63
+ },
+ {
+ "text": " }",
+ "lineNumber": 64
+ },
+ {
+ "lineNumber": 65
+ },
+ {
+ "text": " public int SshPort",
+ "lineNumber": 66
+ },
+ {
+ "text": " {",
+ "lineNumber": 67
+ },
+ {
+ "text": " get => _sshPort;",
+ "lineNumber": 68
+ },
+ {
+ "text": " set => SetAndRaise(ref _sshPort, value);",
+ "lineNumber": 69
+ },
+ {
+ "text": " }",
+ "lineNumber": 70
+ },
+ {
+ "lineNumber": 71
+ },
+ {
+ "text": " public string SshArgs",
+ "lineNumber": 72
+ },
+ {
+ "text": " {",
+ "lineNumber": 73
+ },
+ {
+ "text": " get => _sshArgs;",
+ "lineNumber": 74
+ },
+ {
+ "text": " set => SetAndRaise(ref _sshArgs, value);",
+ "lineNumber": 75
+ },
+ {
+ "text": " }",
+ "lineNumber": 76
+ },
+ {
+ "lineNumber": 77
+ },
+ {
+ "text": " public bool HasSheepIt",
+ "lineNumber": 78
+ },
+ {
+ "text": " {",
+ "lineNumber": 79
+ },
+ {
+ "text": " get => _hasSheepIt;",
+ "lineNumber": 80
+ },
+ {
+ "text": " set => SetAndRaise(ref _hasSheepIt, value);",
+ "lineNumber": 81
+ },
+ {
+ "text": " }",
+ "lineNumber": 82
+ },
+ {
+ "lineNumber": 83
+ },
+ {
+ "text": " public bool HasFlamenco",
+ "lineNumber": 84
+ },
+ {
+ "text": " {",
+ "lineNumber": 85
+ },
+ {
+ "text": " get => _hasFlamenco;",
+ "lineNumber": 86
+ },
+ {
+ "text": " set => SetAndRaise(ref _hasFlamenco, value);",
+ "lineNumber": 87
+ },
+ {
+ "text": " }",
+ "lineNumber": 88
+ },
+ {
+ "lineNumber": 89
+ },
+ {
+ "text": " public string SheepItGpu",
+ "lineNumber": 90
+ },
+ {
+ "text": " {",
+ "lineNumber": 91
+ },
+ {
+ "text": " get => _sheepItGpu;",
+ "lineNumber": 92
+ },
+ {
+ "text": " set => SetAndRaise(ref _sheepItGpu, value);",
+ "lineNumber": 93
+ },
+ {
+ "text": " }",
+ "lineNumber": 94
+ },
+ {
+ "lineNumber": 95
+ },
+ {
+ "text": " public string SheepItUsername",
+ "lineNumber": 96
+ },
+ {
+ "text": " {",
+ "lineNumber": 97
+ },
+ {
+ "text": " get => _sheepItUsername;",
+ "lineNumber": 98
+ },
+ {
+ "text": " set => SetAndRaise(ref _sheepItUsername, value);",
+ "lineNumber": 99
+ },
+ {
+ "text": " }",
+ "lineNumber": 100
+ },
+ {
+ "lineNumber": 101
+ },
+ {
+ "text": " public string SheepItRenderKey",
+ "lineNumber": 102
+ },
+ {
+ "text": " {",
+ "lineNumber": 103
+ },
+ {
+ "text": " get => _sheepItRenderKey;",
+ "lineNumber": 104
+ },
+ {
+ "text": " set => SetAndRaise(ref _sheepItRenderKey, value);",
+ "lineNumber": 105
+ },
+ {
+ "text": " }",
+ "lineNumber": 106
+ },
+ {
+ "lineNumber": 107
+ },
+ {
+ "text": " public string FlamencoWorkerPath",
+ "lineNumber": 108
+ },
+ {
+ "text": " {",
+ "lineNumber": 109
+ },
+ {
+ "text": " get => _flamencoWorkerPath;",
+ "lineNumber": 110
+ },
+ {
+ "text": " set => SetAndRaise(ref _flamencoWorkerPath, value);",
+ "lineNumber": 111
+ },
+ {
+ "text": " }",
+ "lineNumber": 112
+ },
+ {
+ "lineNumber": 113
+ },
+ {
+ "text": " public ObservableCollection<string> NetworkDrives { get; }",
+ "lineNumber": 114
+ },
+ {
+ "text": " public ObservableCollection<string> NetworkPaths { get; }",
+ "lineNumber": 115
+ },
+ {
+ "lineNumber": 116
+ },
+ {
+ "text": " private void LoadWorker(WorkerConfig worker)",
+ "lineNumber": 117
+ },
+ {
+ "text": " {",
+ "lineNumber": 118
+ },
+ {
+ "text": " Id = worker.Id;",
+ "lineNumber": 119
+ },
+ {
+ "text": " Name = worker.Name;",
+ "lineNumber": 120
+ },
+ {
+ "text": " Enabled = worker.Enabled;",
+ "lineNumber": 121
+ },
+ {
+ "text": " SshHost = worker.Ssh.Host;",
+ "lineNumber": 122
+ },
+ {
+ "text": " SshPort = worker.Ssh.Port;",
+ "lineNumber": 123
+ },
+ {
+ "text": " SshArgs = worker.Ssh.Args;",
+ "lineNumber": 124
+ },
+ {
+ "lineNumber": 125
+ },
+ {
+ "text": " if (worker.WorkerTypes.SheepIt != null)",
+ "lineNumber": 126
+ },
+ {
+ "text": " {",
+ "lineNumber": 127
+ },
+ {
+ "text": " HasSheepIt = true;",
+ "lineNumber": 128
+ },
+ {
+ "text": " SheepItGpu = worker.WorkerTypes.SheepIt.Gpu;",
+ "lineNumber": 129
+ },
+ {
+ "text": " SheepItUsername = worker.WorkerTypes.SheepIt.Username;",
+ "lineNumber": 130
+ },
+ {
+ "text": " SheepItRenderKey = worker.WorkerTypes.SheepIt.RenderKey;",
+ "lineNumber": 131
+ },
+ {
+ "text": " }",
+ "lineNumber": 132
+ },
+ {
+ "lineNumber": 133
+ },
+ {
+ "text": " if (worker.WorkerTypes.Flamenco != null)",
+ "lineNumber": 134
+ }
+ ]
+ },
+ "score": 0.38747286796569824
+ },
+ {
+ "codeBlock": {
+ "relativeWorkspacePath": ".\\Views\\WorkerEditWindow.axaml",
+ "range": {
+ "startPosition": {
+ "line": 27
+ },
+ "endPosition": {
+ "line": 54,
+ "column": 16
+ }
+ },
+ "contents": " </StackPanel>\n </TabItem>\n\n <!-- SheepIt Tab -->\n <TabItem Header=\"SheepIt\">\n <StackPanel Margin=\"10\" Spacing=\"10\">\n <CheckBox Name=\"HasSheepItCheckBox\" Content=\"Enable SheepIt Worker\" IsChecked=\"{Binding HasSheepIt}\" Margin=\"0,0,0,10\"/>\n \n <TextBlock Text=\"GPU:\" IsVisible=\"{Binding HasSheepIt}\"/>\n <ComboBox Name=\"GpuComboBox\" IsVisible=\"{Binding HasSheepIt}\" SelectedItem=\"{Binding SheepItGpu}\">\n <ComboBox.Items>\n <ComboBoxItem Content=\"OPTIX_0\"/>\n <ComboBoxItem Content=\"CUDA_0\"/>\n <ComboBoxItem Content=\"OPENCL_0\"/>\n </ComboBox.Items>\n </ComboBox>\n \n <TextBlock Text=\"Username:\" IsVisible=\"{Binding HasSheepIt}\" Margin=\"0,10,0,0\"/>\n <TextBox Name=\"SheepItUsernameTextBox\" Text=\"{Binding SheepItUsername}\" IsVisible=\"{Binding HasSheepIt}\"/>\n \n <TextBlock Text=\"Render Key:\" IsVisible=\"{Binding HasSheepIt}\" Margin=\"0,10,0,0\"/>\n <TextBox Name=\"SheepItRenderKeyTextBox\" Text=\"{Binding SheepItRenderKey}\" IsVisible=\"{Binding HasSheepIt}\" PasswordChar=\"*\"/>\n </StackPanel>\n </TabItem>\n\n <!-- Flamenco Tab -->\n <TabItem Header=\"Flamenco\">\n ",
+ "signatures": {},
+ "detailedLines": [
+ {
+ "text": " </StackPanel>",
+ "lineNumber": 28
+ },
+ {
+ "text": " </TabItem>",
+ "lineNumber": 29
+ },
+ {
+ "lineNumber": 30
+ },
+ {
+ "text": " <!-- SheepIt Tab -->",
+ "lineNumber": 31
+ },
+ {
+ "text": " <TabItem Header=\"SheepIt\">",
+ "lineNumber": 32
+ },
+ {
+ "text": " <StackPanel Margin=\"10\" Spacing=\"10\">",
+ "lineNumber": 33
+ },
+ {
+ "text": " <CheckBox Name=\"HasSheepItCheckBox\" Content=\"Enable SheepIt Worker\" IsChecked=\"{Binding HasSheepIt}\" Margin=\"0,0,0,10\"/>",
+ "lineNumber": 34
+ },
+ {
+ "text": " ",
+ "lineNumber": 35
+ },
+ {
+ "text": " <TextBlock Text=\"GPU:\" IsVisible=\"{Binding HasSheepIt}\"/>",
+ "lineNumber": 36
+ },
+ {
+ "text": " <ComboBox Name=\"GpuComboBox\" IsVisible=\"{Binding HasSheepIt}\" SelectedItem=\"{Binding SheepItGpu}\">",
+ "lineNumber": 37
+ },
+ {
+ "text": " <ComboBox.Items>",
+ "lineNumber": 38
+ },
+ {
+ "text": " <ComboBoxItem Content=\"OPTIX_0\"/>",
+ "lineNumber": 39
+ },
+ {
+ "text": " <ComboBoxItem Content=\"CUDA_0\"/>",
+ "lineNumber": 40
+ },
+ {
+ "text": " <ComboBoxItem Content=\"OPENCL_0\"/>",
+ "lineNumber": 41
+ },
+ {
+ "text": " </ComboBox.Items>",
+ "lineNumber": 42
+ },
+ {
+ "text": " </ComboBox>",
+ "lineNumber": 43
+ },
+ {
+ "text": " ",
+ "lineNumber": 44
+ },
+ {
+ "text": " <TextBlock Text=\"Username:\" IsVisible=\"{Binding HasSheepIt}\" Margin=\"0,10,0,0\"/>",
+ "lineNumber": 45
+ },
+ {
+ "text": " <TextBox Name=\"SheepItUsernameTextBox\" Text=\"{Binding SheepItUsername}\" IsVisible=\"{Binding HasSheepIt}\"/>",
+ "lineNumber": 46
+ },
+ {
+ "text": " ",
+ "lineNumber": 47
+ },
+ {
+ "text": " <TextBlock Text=\"Render Key:\" IsVisible=\"{Binding HasSheepIt}\" Margin=\"0,10,0,0\"/>",
+ "lineNumber": 48
+ },
+ {
+ "text": " <TextBox Name=\"SheepItRenderKeyTextBox\" Text=\"{Binding SheepItRenderKey}\" IsVisible=\"{Binding HasSheepIt}\" PasswordChar=\"*\"/>",
+ "lineNumber": 49
+ },
+ {
+ "text": " </StackPanel>",
+ "lineNumber": 50
+ },
+ {
+ "text": " </TabItem>",
+ "lineNumber": 51
+ },
+ {
+ "lineNumber": 52
+ },
+ {
+ "text": " <!-- Flamenco Tab -->",
+ "lineNumber": 53
+ },
+ {
+ "text": " <TabItem Header=\"Flamenco\">",
+ "lineNumber": 54
+ },
+ {
+ "text": " ",
+ "lineNumber": 55
+ }
+ ]
+ },
+ "score": 0.3794308006763458
+ },
+ {
+ "codeBlock": {
+ "relativeWorkspacePath": ".\\obj\\Debug\\net8.0\\Avalonia\\resources",
+ "range": {
+ "startPosition": {
+ "line": 97
+ },
+ "endPosition": {
+ "line": 122,
+ "column": 16
+ }
+ },
+ "contents": "\r\n <!-- SheepIt Tab -->\r\n <TabItem Header=\"SheepIt\">\r\n <StackPanel Margin=\"10\" Spacing=\"10\">\r\n <CheckBox Name=\"HasSheepItCheckBox\" Content=\"Enable SheepIt Worker\" IsChecked=\"{Binding HasSheepIt}\" Margin=\"0,0,0,10\"/>\r\n \r\n <TextBlock Text=\"GPU:\" IsVisible=\"{Binding HasSheepIt}\"/>\r\n <ComboBox Name=\"GpuComboBox\" IsVisible=\"{Binding HasSheepIt}\" SelectedItem=\"{Binding SheepItGpu}\">\r\n <ComboBox.Items>\r\n <ComboBoxItem Content=\"OPTIX_0\"/>\r\n <ComboBoxItem Content=\"CUDA_0\"/>\r\n <ComboBoxItem Content=\"OPENCL_0\"/>\r\n </ComboBox.Items>\r\n </ComboBox>\r\n \r\n <TextBlock Text=\"Username:\" IsVisible=\"{Binding HasSheepIt}\" Margin=\"0,10,0,0\"/>\r\n <TextBox Name=\"SheepItUsernameTextBox\" Text=\"{Binding SheepItUsername}\" IsVisible=\"{Binding HasSheepIt}\"/>\r\n \r\n <TextBlock Text=\"Render Key:\" IsVisible=\"{Binding HasSheepIt}\" Margin=\"0,10,0,0\"/>\r\n <TextBox Name=\"SheepItRenderKeyTextBox\" Text=\"{Binding SheepItRenderKey}\" IsVisible=\"{Binding HasSheepIt}\" PasswordChar=\"*\"/>\r\n </StackPanel>\r\n </TabItem>\r\n\r\n <!-- Flamenco Tab -->\r\n <TabItem Header=\"Flamenco\">\r\n ",
+ "signatures": {},
+ "detailedLines": [
+ {
+ "text": "\r",
+ "lineNumber": 98
+ },
+ {
+ "text": " <!-- SheepIt Tab -->\r",
+ "lineNumber": 99
+ },
+ {
+ "text": " <TabItem Header=\"SheepIt\">\r",
+ "lineNumber": 100
+ },
+ {
+ "text": " <StackPanel Margin=\"10\" Spacing=\"10\">\r",
+ "lineNumber": 101
+ },
+ {
+ "text": " <CheckBox Name=\"HasSheepItCheckBox\" Content=\"Enable SheepIt Worker\" IsChecked=\"{Binding HasSheepIt}\" Margin=\"0,0,0,10\"/>\r",
+ "lineNumber": 102
+ },
+ {
+ "text": " \r",
+ "lineNumber": 103
+ },
+ {
+ "text": " <TextBlock Text=\"GPU:\" IsVisible=\"{Binding HasSheepIt}\"/>\r",
+ "lineNumber": 104
+ },
+ {
+ "text": " <ComboBox Name=\"GpuComboBox\" IsVisible=\"{Binding HasSheepIt}\" SelectedItem=\"{Binding SheepItGpu}\">\r",
+ "lineNumber": 105
+ },
+ {
+ "text": " <ComboBox.Items>\r",
+ "lineNumber": 106
+ },
+ {
+ "text": " <ComboBoxItem Content=\"OPTIX_0\"/>\r",
+ "lineNumber": 107
+ },
+ {
+ "text": " <ComboBoxItem Content=\"CUDA_0\"/>\r",
+ "lineNumber": 108
+ },
+ {
+ "text": " <ComboBoxItem Content=\"OPENCL_0\"/>\r",
+ "lineNumber": 109
+ },
+ {
+ "text": " </ComboBox.Items>\r",
+ "lineNumber": 110
+ },
+ {
+ "text": " </ComboBox>\r",
+ "lineNumber": 111
+ },
+ {
+ "text": " \r",
+ "lineNumber": 112
+ },
+ {
+ "text": " <TextBlock Text=\"Username:\" IsVisible=\"{Binding HasSheepIt}\" Margin=\"0,10,0,0\"/>\r",
+ "lineNumber": 113
+ },
+ {
+ "text": " <TextBox Name=\"SheepItUsernameTextBox\" Text=\"{Binding SheepItUsername}\" IsVisible=\"{Binding HasSheepIt}\"/>\r",
+ "lineNumber": 114
+ },
+ {
+ "text": " \r",
+ "lineNumber": 115
+ },
+ {
+ "text": " <TextBlock Text=\"Render Key:\" IsVisible=\"{Binding HasSheepIt}\" Margin=\"0,10,0,0\"/>\r",
+ "lineNumber": 116
+ },
+ {
+ "text": " <TextBox Name=\"SheepItRenderKeyTextBox\" Text=\"{Binding SheepItRenderKey}\" IsVisible=\"{Binding HasSheepIt}\" PasswordChar=\"*\"/>\r",
+ "lineNumber": 117
+ },
+ {
+ "text": " </StackPanel>\r",
+ "lineNumber": 118
+ },
+ {
+ "text": " </TabItem>\r",
+ "lineNumber": 119
+ },
+ {
+ "text": "\r",
+ "lineNumber": 120
+ },
+ {
+ "text": " <!-- Flamenco Tab -->\r",
+ "lineNumber": 121
+ },
+ {
+ "text": " <TabItem Header=\"Flamenco\">\r",
+ "lineNumber": 122
+ },
+ {
+ "text": " ",
+ "lineNumber": 123
+ }
+ ]
+ },
+ "score": 0.3679307997226715
+ },
+ {
+ "codeBlock": {
+ "relativeWorkspacePath": ".\\ViewModels\\WorkerEditViewModel.cs",
+ "range": {
+ "startPosition": {},
+ "endPosition": {
+ "line": 68
+ }
+ },
+ "contents": "using System.Collections.ObjectModel;\nusing System.Linq;\nusing UnifiedFarmLauncher.Models;\nusing UnifiedFarmLauncher.Services;\n\nnamespace UnifiedFarmLauncher.ViewModels\n{\n public class WorkerEditViewModel : ViewModelBase\n {\n private readonly ConfigService _configService;\n private readonly bool _isNew;\n private int _id;\n private string _name = string.Empty;\n private bool _enabled = true;\n private string _sshHost = string.Empty;\n private int _sshPort = 22;\n private string _sshArgs = string.Empty;\n private string _sheepItGpu = \"OPTIX_0\";\n private string _sheepItUsername = string.Empty;\n private string _sheepItRenderKey = string.Empty;\n private string _flamencoWorkerPath = string.Empty;\n private bool _hasSheepIt;\n private bool _hasFlamenco;\n\n public WorkerEditViewModel(ConfigService configService, WorkerConfig? worker = null)\n {\n _configService = configService;\n _isNew = worker == null;\n NetworkDrives = new ObservableCollection<string>();\n NetworkPaths = new ObservableCollection<string>();\n\n if (worker != null)\n {\n LoadWorker(worker);\n }\n else\n {\n _id = _configService.GetNextWorkerId();\n }\n }\n\n public int Id\n {\n get => _id;\n set => SetAndRaise(ref _id, value);\n }\n\n public string Name\n {\n get => _name;\n set => SetAndRaise(ref _name, value);\n }\n\n public bool Enabled\n {\n get => _enabled;\n set => SetAndRaise(ref _enabled, value);\n }\n\n public string SshHost\n {\n get => _sshHost;\n set => SetAndRaise(ref _sshHost, value);\n }\n\n public int SshPort\n {\n get => _sshPort;",
+ "signatures": {},
+ "detailedLines": [
+ {
+ "text": "using System.Collections.ObjectModel;",
+ "lineNumber": 1
+ },
+ {
+ "text": "using System.Linq;",
+ "lineNumber": 2
+ },
+ {
+ "text": "using UnifiedFarmLauncher.Models;",
+ "lineNumber": 3
+ },
+ {
+ "text": "using UnifiedFarmLauncher.Services;",
+ "lineNumber": 4
+ },
+ {
+ "lineNumber": 5
+ },
+ {
+ "text": "namespace UnifiedFarmLauncher.ViewModels",
+ "lineNumber": 6
+ },
+ {
+ "text": "{",
+ "lineNumber": 7
+ },
+ {
+ "text": " public class WorkerEditViewModel : ViewModelBase",
+ "lineNumber": 8
+ },
+ {
+ "text": " {",
+ "lineNumber": 9
+ },
+ {
+ "text": " private readonly ConfigService _configService;",
+ "lineNumber": 10
+ },
+ {
+ "text": " private readonly bool _isNew;",
+ "lineNumber": 11
+ },
+ {
+ "text": " private int _id;",
+ "lineNumber": 12
+ },
+ {
+ "text": " private string _name = string.Empty;",
+ "lineNumber": 13
+ },
+ {
+ "text": " private bool _enabled = true;",
+ "lineNumber": 14
+ },
+ {
+ "text": " private string _sshHost = string.Empty;",
+ "lineNumber": 15
+ },
+ {
+ "text": " private int _sshPort = 22;",
+ "lineNumber": 16
+ },
+ {
+ "text": " private string _sshArgs = string.Empty;",
+ "lineNumber": 17
+ },
+ {
+ "text": " private string _sheepItGpu = \"OPTIX_0\";",
+ "lineNumber": 18
+ },
+ {
+ "text": " private string _sheepItUsername = string.Empty;",
+ "lineNumber": 19
+ },
+ {
+ "text": " private string _sheepItRenderKey = string.Empty;",
+ "lineNumber": 20
+ },
+ {
+ "text": " private string _flamencoWorkerPath = string.Empty;",
+ "lineNumber": 21
+ },
+ {
+ "text": " private bool _hasSheepIt;",
+ "lineNumber": 22
+ },
+ {
+ "text": " private bool _hasFlamenco;",
+ "lineNumber": 23
+ },
+ {
+ "lineNumber": 24
+ },
+ {
+ "text": " public WorkerEditViewModel(ConfigService configService, WorkerConfig? worker = null)",
+ "lineNumber": 25
+ },
+ {
+ "text": " {",
+ "lineNumber": 26
+ },
+ {
+ "text": " _configService = configService;",
+ "lineNumber": 27
+ },
+ {
+ "text": " _isNew = worker == null;",
+ "lineNumber": 28
+ },
+ {
+ "text": " NetworkDrives = new ObservableCollection<string>();",
+ "lineNumber": 29
+ },
+ {
+ "text": " NetworkPaths = new ObservableCollection<string>();",
+ "lineNumber": 30
+ },
+ {
+ "lineNumber": 31
+ },
+ {
+ "text": " if (worker != null)",
+ "lineNumber": 32
+ },
+ {
+ "text": " {",
+ "lineNumber": 33
+ },
+ {
+ "text": " LoadWorker(worker);",
+ "lineNumber": 34
+ },
+ {
+ "text": " }",
+ "lineNumber": 35
+ },
+ {
+ "text": " else",
+ "lineNumber": 36
+ },
+ {
+ "text": " {",
+ "lineNumber": 37
+ },
+ {
+ "text": " _id = _configService.GetNextWorkerId();",
+ "lineNumber": 38
+ },
+ {
+ "text": " }",
+ "lineNumber": 39
+ },
+ {
+ "text": " }",
+ "lineNumber": 40
+ },
+ {
+ "lineNumber": 41
+ },
+ {
+ "text": " public int Id",
+ "lineNumber": 42
+ },
+ {
+ "text": " {",
+ "lineNumber": 43
+ },
+ {
+ "text": " get => _id;",
+ "lineNumber": 44
+ },
+ {
+ "text": " set => SetAndRaise(ref _id, value);",
+ "lineNumber": 45
+ },
+ {
+ "text": " }",
+ "lineNumber": 46
+ },
+ {
+ "lineNumber": 47
+ },
+ {
+ "text": " public string Name",
+ "lineNumber": 48
+ },
+ {
+ "text": " {",
+ "lineNumber": 49
+ },
+ {
+ "text": " get => _name;",
+ "lineNumber": 50
+ },
+ {
+ "text": " set => SetAndRaise(ref _name, value);",
+ "lineNumber": 51
+ },
+ {
+ "text": " }",
+ "lineNumber": 52
+ },
+ {
+ "lineNumber": 53
+ },
+ {
+ "text": " public bool Enabled",
+ "lineNumber": 54
+ },
+ {
+ "text": " {",
+ "lineNumber": 55
+ },
+ {
+ "text": " get => _enabled;",
+ "lineNumber": 56
+ },
+ {
+ "text": " set => SetAndRaise(ref _enabled, value);",
+ "lineNumber": 57
+ },
+ {
+ "text": " }",
+ "lineNumber": 58
+ },
+ {
+ "lineNumber": 59
+ },
+ {
+ "text": " public string SshHost",
+ "lineNumber": 60
+ },
+ {
+ "text": " {",
+ "lineNumber": 61
+ },
+ {
+ "text": " get => _sshHost;",
+ "lineNumber": 62
+ },
+ {
+ "text": " set => SetAndRaise(ref _sshHost, value);",
+ "lineNumber": 63
+ },
+ {
+ "text": " }",
+ "lineNumber": 64
+ },
+ {
+ "lineNumber": 65
+ },
+ {
+ "text": " public int SshPort",
+ "lineNumber": 66
+ },
+ {
+ "text": " {",
+ "lineNumber": 67
+ },
+ {
+ "text": " get => _sshPort;",
+ "lineNumber": 68
+ }
+ ]
+ },
+ "score": 0.35629475116729736
+ },
+ {
+ "codeBlock": {
+ "relativeWorkspacePath": ".\\Services\\WorkerControllerService.cs",
+ "range": {
+ "startPosition": {
+ "line": 277
+ },
+ "endPosition": {
+ "line": 329
+ }
+ },
+ "contents": "if (Test-Path $metaPath) {{\n try {{\n $meta = Get-Content $metaPath -Raw | ConvertFrom-Json\n if ($meta.Status -eq 'running' -and $meta.WorkerPid) {{\n if (Get-Process -Id $meta.WorkerPid -ErrorAction SilentlyContinue) {{\n Write-Host \"\"Worker $workerName already running (PID $($meta.WorkerPid)).\"\"\n $shouldStart = $false\n }}\n }}\n }} catch {{\n Write-Host \"\"Failed to read metadata. Controller will restart worker.\"\" -ForegroundColor Yellow\n }}\n}}\n\nif ($shouldStart) {{\n $initialMeta = [pscustomobject]@{{\n WorkerName = $workerName\n WorkerType = $workerType\n Status = 'launching'\n ControllerPid = $null\n WorkerPid = $null\n Restarts = 0\n LastExitCode = $null\n LogPath = $logPath\n CommandPath = $commandPath\n PayloadPath = $payloadPath\n UpdatedAtUtc = (Get-Date).ToUniversalTime()\n }} | ConvertTo-Json -Depth 5\n $initialMeta | Set-Content -Path $metaPath -Encoding UTF8\n\n $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue\n if ($pwsh) {{\n $psExe = $pwsh.Source\n }}\n else {{\n $psExe = (Get-Command powershell -ErrorAction Stop).Source\n }}\n\n $controllerArgs = @(\n '-NoLogo','-NoProfile','-ExecutionPolicy','Bypass',\n '-File',\"\"$controllerPath\"\",\n '-WorkerName',\"\"$workerName\"\",\n '-WorkerType',\"\"$workerType\"\",\n '-PayloadBase64Path',\"\"$payloadBase64Path\"\"\n )\n\n Start-Process -FilePath $psExe -ArgumentList $controllerArgs -WindowStyle Hidden | Out-Null\n Write-Host \"\"Worker $workerName started under controller.\"\" -ForegroundColor Green\n}}\n\";\n\n await _sshService.ExecuteRemoteCommandAsync(worker, $\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -EncodedCommand {Convert.ToBase64String(Encoding.Unicode.GetBytes(ensureScript))}\");",
+ "signatures": {},
+ "detailedLines": [
+ {
+ "text": "if (Test-Path $metaPath) {{",
+ "lineNumber": 278
+ },
+ {
+ "text": " try {{",
+ "lineNumber": 279
+ },
+ {
+ "text": " $meta = Get-Content $metaPath -Raw | ConvertFrom-Json",
+ "lineNumber": 280
+ },
+ {
+ "text": " if ($meta.Status -eq 'running' -and $meta.WorkerPid) {{",
+ "lineNumber": 281
+ },
+ {
+ "text": " if (Get-Process -Id $meta.WorkerPid -ErrorAction SilentlyContinue) {{",
+ "lineNumber": 282
+ },
+ {
+ "text": " Write-Host \"\"Worker $workerName already running (PID $($meta.WorkerPid)).\"\"",
+ "lineNumber": 283
+ },
+ {
+ "text": " $shouldStart = $false",
+ "lineNumber": 284
+ },
+ {
+ "text": " }}",
+ "lineNumber": 285
+ },
+ {
+ "text": " }}",
+ "lineNumber": 286
+ },
+ {
+ "text": " }} catch {{",
+ "lineNumber": 287
+ },
+ {
+ "text": " Write-Host \"\"Failed to read metadata. Controller will restart worker.\"\" -ForegroundColor Yellow",
+ "lineNumber": 288
+ },
+ {
+ "text": " }}",
+ "lineNumber": 289
+ },
+ {
+ "text": "}}",
+ "lineNumber": 290
+ },
+ {
+ "lineNumber": 291
+ },
+ {
+ "text": "if ($shouldStart) {{",
+ "lineNumber": 292
+ },
+ {
+ "text": " $initialMeta = [pscustomobject]@{{",
+ "lineNumber": 293
+ },
+ {
+ "text": " WorkerName = $workerName",
+ "lineNumber": 294
+ },
+ {
+ "text": " WorkerType = $workerType",
+ "lineNumber": 295
+ },
+ {
+ "text": " Status = 'launching'",
+ "lineNumber": 296
+ },
+ {
+ "text": " ControllerPid = $null",
+ "lineNumber": 297
+ },
+ {
+ "text": " WorkerPid = $null",
+ "lineNumber": 298
+ },
+ {
+ "text": " Restarts = 0",
+ "lineNumber": 299
+ },
+ {
+ "text": " LastExitCode = $null",
+ "lineNumber": 300
+ },
+ {
+ "text": " LogPath = $logPath",
+ "lineNumber": 301
+ },
+ {
+ "text": " CommandPath = $commandPath",
+ "lineNumber": 302
+ },
+ {
+ "text": " PayloadPath = $payloadPath",
+ "lineNumber": 303
+ },
+ {
+ "text": " UpdatedAtUtc = (Get-Date).ToUniversalTime()",
+ "lineNumber": 304
+ },
+ {
+ "text": " }} | ConvertTo-Json -Depth 5",
+ "lineNumber": 305
+ },
+ {
+ "text": " $initialMeta | Set-Content -Path $metaPath -Encoding UTF8",
+ "lineNumber": 306
+ },
+ {
+ "lineNumber": 307
+ },
+ {
+ "text": " $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue",
+ "lineNumber": 308
+ },
+ {
+ "text": " if ($pwsh) {{",
+ "lineNumber": 309
+ },
+ {
+ "text": " $psExe = $pwsh.Source",
+ "lineNumber": 310
+ },
+ {
+ "text": " }}",
+ "lineNumber": 311
+ },
+ {
+ "text": " else {{",
+ "lineNumber": 312
+ },
+ {
+ "text": " $psExe = (Get-Command powershell -ErrorAction Stop).Source",
+ "lineNumber": 313
+ },
+ {
+ "text": " }}",
+ "lineNumber": 314
+ },
+ {
+ "lineNumber": 315
+ },
+ {
+ "text": " $controllerArgs = @(",
+ "lineNumber": 316
+ },
+ {
+ "text": " '-NoLogo','-NoProfile','-ExecutionPolicy','Bypass',",
+ "lineNumber": 317
+ },
+ {
+ "text": " '-File',\"\"$controllerPath\"\",",
+ "lineNumber": 318
+ },
+ {
+ "text": " '-WorkerName',\"\"$workerName\"\",",
+ "lineNumber": 319
+ },
+ {
+ "text": " '-WorkerType',\"\"$workerType\"\",",
+ "lineNumber": 320
+ },
+ {
+ "text": " '-PayloadBase64Path',\"\"$payloadBase64Path\"\"",
+ "lineNumber": 321
+ },
+ {
+ "text": " )",
+ "lineNumber": 322
+ },
+ {
+ "lineNumber": 323
+ },
+ {
+ "text": " Start-Process -FilePath $psExe -ArgumentList $controllerArgs -WindowStyle Hidden | Out-Null",
+ "lineNumber": 324
+ },
+ {
+ "text": " Write-Host \"\"Worker $workerName started under controller.\"\" -ForegroundColor Green",
+ "lineNumber": 325
+ },
+ {
+ "text": "}}",
+ "lineNumber": 326
+ },
+ {
+ "text": "\";",
+ "lineNumber": 327
+ },
+ {
+ "lineNumber": 328
+ },
+ {
+ "text": " await _sshService.ExecuteRemoteCommandAsync(worker, $\"powershell -NoLogo -NoProfile -NonInteractive -OutputFormat Text -ExecutionPolicy Bypass -EncodedCommand {Convert.ToBase64String(Encoding.Unicode.GetBytes(ensureScript))}\");",
+ "lineNumber": 329
+ }
+ ]
+ },
+ "score": 0.3341273069381714
+ },
+ {
+ "codeBlock": {
+ "relativeWorkspacePath": ".\\ViewModels\\WorkerEditViewModel.cs",
+ "range": {
+ "startPosition": {
+ "line": 131
+ },
+ "endPosition": {
+ "line": 191,
+ "column": 12
+ }
+ },
+ "contents": " }\n\n if (worker.WorkerTypes.Flamenco != null)\n {\n HasFlamenco = true;\n FlamencoWorkerPath = worker.WorkerTypes.Flamenco.WorkerPath;\n NetworkDrives.Clear();\n foreach (var drive in worker.WorkerTypes.Flamenco.NetworkDrives)\n {\n NetworkDrives.Add(drive);\n }\n NetworkPaths.Clear();\n foreach (var path in worker.WorkerTypes.Flamenco.NetworkPaths)\n {\n NetworkPaths.Add(path);\n }\n }\n }\n\n public WorkerConfig ToWorkerConfig()\n {\n var worker = new WorkerConfig\n {\n Id = Id,\n Name = Name,\n Enabled = Enabled,\n Ssh = new SshConfig\n {\n Host = SshHost,\n Port = SshPort,\n Args = SshArgs\n },\n WorkerTypes = new WorkerTypeConfig()\n };\n\n if (HasSheepIt)\n {\n worker.WorkerTypes.SheepIt = new SheepItConfig\n {\n Gpu = SheepItGpu,\n Username = SheepItUsername,\n RenderKey = SheepItRenderKey\n };\n }\n\n if (HasFlamenco)\n {\n worker.WorkerTypes.Flamenco = new FlamencoConfig\n {\n WorkerPath = FlamencoWorkerPath,\n NetworkDrives = NetworkDrives.ToList(),\n NetworkPaths = NetworkPaths.ToList()\n };\n }\n\n return worker;\n }\n\n public void Save()\n {\n ",
+ "signatures": {},
+ "detailedLines": [
+ {
+ "text": " }",
+ "lineNumber": 132
+ },
+ {
+ "lineNumber": 133
+ },
+ {
+ "text": " if (worker.WorkerTypes.Flamenco != null)",
+ "lineNumber": 134
+ },
+ {
+ "text": " {",
+ "lineNumber": 135
+ },
+ {
+ "text": " HasFlamenco = true;",
+ "lineNumber": 136
+ },
+ {
+ "text": " FlamencoWorkerPath = worker.WorkerTypes.Flamenco.WorkerPath;",
+ "lineNumber": 137
+ },
+ {
+ "text": " NetworkDrives.Clear();",
+ "lineNumber": 138
+ },
+ {
+ "text": " foreach (var drive in worker.WorkerTypes.Flamenco.NetworkDrives)",
+ "lineNumber": 139
+ },
+ {
+ "text": " {",
+ "lineNumber": 140
+ },
+ {
+ "text": " NetworkDrives.Add(drive);",
+ "lineNumber": 141
+ },
+ {
+ "text": " }",
+ "lineNumber": 142
+ },
+ {
+ "text": " NetworkPaths.Clear();",
+ "lineNumber": 143
+ },
+ {
+ "text": " foreach (var path in worker.WorkerTypes.Flamenco.NetworkPaths)",
+ "lineNumber": 144
+ },
+ {
+ "text": " {",
+ "lineNumber": 145
+ },
+ {
+ "text": " NetworkPaths.Add(path);",
+ "lineNumber": 146
+ },
+ {
+ "text": " }",
+ "lineNumber": 147
+ },
+ {
+ "text": " }",
+ "lineNumber": 148
+ },
+ {
+ "text": " }",
+ "lineNumber": 149
+ },
+ {
+ "lineNumber": 150
+ },
+ {
+ "text": " public WorkerConfig ToWorkerConfig()",
+ "lineNumber": 151
+ },
+ {
+ "text": " {",
+ "lineNumber": 152
+ },
+ {
+ "text": " var worker = new WorkerConfig",
+ "lineNumber": 153
+ },
+ {
+ "text": " {",
+ "lineNumber": 154
+ },
+ {
+ "text": " Id = Id,",
+ "lineNumber": 155
+ },
+ {
+ "text": " Name = Name,",
+ "lineNumber": 156
+ },
+ {
+ "text": " Enabled = Enabled,",
+ "lineNumber": 157
+ },
+ {
+ "text": " Ssh = new SshConfig",
+ "lineNumber": 158
+ },
+ {
+ "text": " {",
+ "lineNumber": 159
+ },
+ {
+ "text": " Host = SshHost,",
+ "lineNumber": 160
+ },
+ {
+ "text": " Port = SshPort,",
+ "lineNumber": 161
+ },
+ {
+ "text": " Args = SshArgs",
+ "lineNumber": 162
+ },
+ {
+ "text": " },",
+ "lineNumber": 163
+ },
+ {
+ "text": " WorkerTypes = new WorkerTypeConfig()",
+ "lineNumber": 164
+ },
+ {
+ "text": " };",
+ "lineNumber": 165
+ },
+ {
+ "lineNumber": 166
+ },
+ {
+ "text": " if (HasSheepIt)",
+ "lineNumber": 167
+ },
+ {
+ "text": " {",
+ "lineNumber": 168
+ },
+ {
+ "text": " worker.WorkerTypes.SheepIt = new SheepItConfig",
+ "lineNumber": 169
+ },
+ {
+ "text": " {",
+ "lineNumber": 170
+ },
+ {
+ "text": " Gpu = SheepItGpu,",
+ "lineNumber": 171
+ },
+ {
+ "text": " Username = SheepItUsername,",
+ "lineNumber": 172
+ },
+ {
+ "text": " RenderKey = SheepItRenderKey",
+ "lineNumber": 173
+ },
+ {
+ "text": " };",
+ "lineNumber": 174
+ },
+ {
+ "text": " }",
+ "lineNumber": 175
+ },
+ {
+ "lineNumber": 176
+ },
+ {
+ "text": " if (HasFlamenco)",
+ "lineNumber": 177
+ },
+ {
+ "text": " {",
+ "lineNumber": 178
+ },
+ {
+ "text": " worker.WorkerTypes.Flamenco = new FlamencoConfig",
+ "lineNumber": 179
+ },
+ {
+ "text": " {",
+ "lineNumber": 180
+ },
+ {
+ "text": " WorkerPath = FlamencoWorkerPath,",
+ "lineNumber": 181
+ },
+ {
+ "text": " NetworkDrives = NetworkDrives.ToList(),",
+ "lineNumber": 182
+ },
+ {
+ "text": " NetworkPaths = NetworkPaths.ToList()",
+ "lineNumber": 183
+ },
+ {
+ "text": " };",
+ "lineNumber": 184
+ },
+ {
+ "text": " }",
+ "lineNumber": 185
+ },
+ {
+ "lineNumber": 186
+ },
+ {
+ "text": " return worker;",
+ "lineNumber": 187
+ },
+ {
+ "text": " }",
+ "lineNumber": 188
+ },
+ {
+ "lineNumber": 189
+ },
+ {
+ "text": " public void Save()",
+ "lineNumber": 190
+ },
+ {
+ "text": " {",
+ "lineNumber": 191
+ },
+ {
+ "text": " ",
+ "lineNumber": 192
+ }
+ ]
+ },
+ "score": 0.3178243637084961
+ },
+ {
+ "codeBlock": {
+ "relativeWorkspacePath": ".\\README.md",
+ "range": {
+ "startPosition": {},
+ "endPosition": {
+ "line": 50
+ }
+ },
+ "contents": "# Unified Farm Launcher\n\nA desktop application for managing remote render farm workers across multiple systems. Supports both **SheepIt** and **Flamenco** worker types with a unified interface for starting, stopping, and monitoring workers via SSH.\n\n## Features\n\n- **Multi-Worker Support**: Manage multiple remote workers from a single interface\n- **Worker Types**: \n - **[SheepIt](https://www.sheepit-renderfarm.com/)**: Distributed rendering for Blender projects\n - **[Flamenco](https://flamenco.blender.org/)**: Blender Foundation's render farm manager\n- **Remote Management**: Control workers on remote machines via SSH\n- **Worker Lifecycle**: Start, stop, and attach to worker processes\n- **Filtering**: Filter workers by type (All, SheepIt, Flamenco)\n- **Configuration Management**: Add, edit, and delete worker configurations\n- **Real-time Logging**: Attach to worker logs for real-time monitoring\n\n## Requirements\n\n- **.NET 8.0** or later\n- **Windows** (PowerShell required on remote machines)\n- **SSH access** to remote worker machines\n- **Java** (Temurin 21 recommended) on remote machines for SheepIt workers\n- **Flamenco worker executable** on remote machines for Flamenco workers\n\n## Building\n\n```bash\ndotnet build\n```\n\n## Running\n\n```bash\ndotnet run\n```\n\nOr build and run the executable from the output directory.\n\n## Configuration\n\nConfiguration is stored in `%LocalAppData%\\UnifiedFarmLauncher\\workers.json`. The application will create this file automatically on first run.\n\n### Configuration Structure\n\n```json\n{\n \"workers\": [\n {\n \"id\": 1,\n \"name\": \"worker-name\",",
+ "signatures": {},
+ "detailedLines": [
+ {
+ "text": "# Unified Farm Launcher",
+ "lineNumber": 1
+ },
+ {
+ "lineNumber": 2
+ },
+ {
+ "text": "A desktop application for managing remote render farm workers across multiple systems. Supports both **SheepIt** and **Flamenco** worker types with a unified interface for starting, stopping, and monitoring workers via SSH.",
+ "lineNumber": 3
+ },
+ {
+ "lineNumber": 4
+ },
+ {
+ "text": "## Features",
+ "lineNumber": 5
+ },
+ {
+ "lineNumber": 6
+ },
+ {
+ "text": "- **Multi-Worker Support**: Manage multiple remote workers from a single interface",
+ "lineNumber": 7
+ },
+ {
+ "text": "- **Worker Types**: ",
+ "lineNumber": 8
+ },
+ {
+ "text": " - **[SheepIt](https://www.sheepit-renderfarm.com/)**: Distributed rendering for Blender projects",
+ "lineNumber": 9
+ },
+ {
+ "text": " - **[Flamenco](https://flamenco.blender.org/)**: Blender Foundation's render farm manager",
+ "lineNumber": 10
+ },
+ {
+ "text": "- **Remote Management**: Control workers on remote machines via SSH",
+ "lineNumber": 11
+ },
+ {
+ "text": "- **Worker Lifecycle**: Start, stop, and attach to worker processes",
+ "lineNumber": 12
+ },
+ {
+ "text": "- **Filtering**: Filter workers by type (All, SheepIt, Flamenco)",
+ "lineNumber": 13
+ },
+ {
+ "text": "- **Configuration Management**: Add, edit, and delete worker configurations",
+ "lineNumber": 14
+ },
+ {
+ "text": "- **Real-time Logging**: Attach to worker logs for real-time monitoring",
+ "lineNumber": 15
+ },
+ {
+ "lineNumber": 16
+ },
+ {
+ "text": "## Requirements",
+ "lineNumber": 17
+ },
+ {
+ "lineNumber": 18
+ },
+ {
+ "text": "- **.NET 8.0** or later",
+ "lineNumber": 19
+ },
+ {
+ "text": "- **Windows** (PowerShell required on remote machines)",
+ "lineNumber": 20
+ },
+ {
+ "text": "- **SSH access** to remote worker machines",
+ "lineNumber": 21
+ },
+ {
+ "text": "- **Java** (Temurin 21 recommended) on remote machines for SheepIt workers",
+ "lineNumber": 22
+ },
+ {
+ "text": "- **Flamenco worker executable** on remote machines for Flamenco workers",
+ "lineNumber": 23
+ },
+ {
+ "lineNumber": 24
+ },
+ {
+ "text": "## Building",
+ "lineNumber": 25
+ },
+ {
+ "lineNumber": 26
+ },
+ {
+ "text": "```bash",
+ "lineNumber": 27
+ },
+ {
+ "text": "dotnet build",
+ "lineNumber": 28
+ },
+ {
+ "text": "```",
+ "lineNumber": 29
+ },
+ {
+ "lineNumber": 30
+ },
+ {
+ "text": "## Running",
+ "lineNumber": 31
+ },
+ {
+ "lineNumber": 32
+ },
+ {
+ "text": "```bash",
+ "lineNumber": 33
+ },
+ {
+ "text": "dotnet run",
+ "lineNumber": 34
+ },
+ {
+ "text": "```",
+ "lineNumber": 35
+ },
+ {
+ "lineNumber": 36
+ },
+ {
+ "text": "Or build and run the executable from the output directory.",
+ "lineNumber": 37
+ },
+ {
+ "lineNumber": 38
+ },
+ {
+ "text": "## Configuration",
+ "lineNumber": 39
+ },
+ {
+ "lineNumber": 40
+ },
+ {
+ "text": "Configuration is stored in `%LocalAppData%\\UnifiedFarmLauncher\\workers.json`. The application will create this file automatically on first run.",
+ "lineNumber": 41
+ },
+ {
+ "lineNumber": 42
+ },
+ {
+ "text": "### Configuration Structure",
+ "lineNumber": 43
+ },
+ {
+ "lineNumber": 44
+ },
+ {
+ "text": "```json",
+ "lineNumber": 45
+ },
+ {
+ "text": "{",
+ "lineNumber": 46
+ },
+ {
+ "text": " \"workers\": [",
+ "lineNumber": 47
+ },
+ {
+ "text": " {",
+ "lineNumber": 48
+ },
+ {
+ "text": " \"id\": 1,",
+ "lineNumber": 49
+ },
+ {
+ "text": " \"name\": \"worker-name\",",
+ "lineNumber": 50
+ }
+ ]
+ },
+ "score": 0.3121049106121063
+ },
+ {
+ "codeBlock": {
+ "relativeWorkspacePath": ".\\Scripts\\remote_worker_controller.ps1",
+ "range": {
+ "startPosition": {
+ "line": 254
+ },
+ "endPosition": {
+ "line": 300
+ }
+ },
+ "contents": " while ($restartCount -le $MaxRestarts) {\n try {\n # Initialize worker process\n $psi = [System.Diagnostics.ProcessStartInfo]::new()\n $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue\n if ($pwsh) {\n $psi.FileName = $pwsh.Source\n }\n else {\n $psi.FileName = (Get-Command powershell -ErrorAction Stop).Source\n }\n\n $psi.Arguments = \"-NoLogo -NoProfile -ExecutionPolicy Bypass -File `\"$payloadPath`\"\"\n $psi.UseShellExecute = $false\n $psi.RedirectStandardInput = $true\n $psi.RedirectStandardOutput = $true\n $psi.RedirectStandardError = $true\n $psi.CreateNoWindow = $true\n\n $workerProcess = New-Object System.Diagnostics.Process\n $workerProcess.StartInfo = $psi\n\n if (-not $workerProcess.Start()) {\n throw \"Failed to start worker process.\"\n }\n\n Write-ControllerLog \"Worker process started with PID $($workerProcess.Id)\"\n Write-Metadata -Status 'running' -WorkerPid $workerProcess.Id -ControllerPid $controllerPid -Restarts $restartCount\n\n # Check if process exited immediately\n if ($workerProcess.HasExited) {\n $exitCode = -1\n try {\n $exitCode = $workerProcess.ExitCode\n }\n catch {\n Write-ControllerLog \"Unable to read immediate exit code: $($_.Exception.Message)\"\n }\n Write-ControllerLog \"Worker process exited immediately after start with code $exitCode\"\n Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode\n if ($exitCode -eq 0) { break }\n # Continue to restart logic below\n }\n else {\n $stdoutHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'OUT')",
+ "signatures": {},
+ "detailedLines": [
+ {
+ "text": " while ($restartCount -le $MaxRestarts) {",
+ "lineNumber": 256
+ },
+ {
+ "text": " try {",
+ "lineNumber": 257
+ },
+ {
+ "text": " # Initialize worker process",
+ "lineNumber": 258
+ },
+ {
+ "text": " $psi = [System.Diagnostics.ProcessStartInfo]::new()",
+ "lineNumber": 259
+ },
+ {
+ "text": " $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue",
+ "lineNumber": 260
+ },
+ {
+ "text": " if ($pwsh) {",
+ "lineNumber": 261
+ },
+ {
+ "text": " $psi.FileName = $pwsh.Source",
+ "lineNumber": 262
+ },
+ {
+ "text": " }",
+ "lineNumber": 263
+ },
+ {
+ "text": " else {",
+ "lineNumber": 264
+ },
+ {
+ "text": " $psi.FileName = (Get-Command powershell -ErrorAction Stop).Source",
+ "lineNumber": 265
+ },
+ {
+ "text": " }",
+ "lineNumber": 266
+ },
+ {
+ "lineNumber": 267
+ },
+ {
+ "text": " $psi.Arguments = \"-NoLogo -NoProfile -ExecutionPolicy Bypass -File `\"$payloadPath`\"\"",
+ "lineNumber": 268
+ },
+ {
+ "text": " $psi.UseShellExecute = $false",
+ "lineNumber": 269
+ },
+ {
+ "text": " $psi.RedirectStandardInput = $true",
+ "lineNumber": 270
+ },
+ {
+ "text": " $psi.RedirectStandardOutput = $true",
+ "lineNumber": 271
+ },
+ {
+ "text": " $psi.RedirectStandardError = $true",
+ "lineNumber": 272
+ },
+ {
+ "text": " $psi.CreateNoWindow = $true",
+ "lineNumber": 273
+ },
+ {
+ "lineNumber": 274
+ },
+ {
+ "text": " $workerProcess = New-Object System.Diagnostics.Process",
+ "lineNumber": 275
+ },
+ {
+ "text": " $workerProcess.StartInfo = $psi",
+ "lineNumber": 276
+ },
+ {
+ "lineNumber": 277
+ },
+ {
+ "text": " if (-not $workerProcess.Start()) {",
+ "lineNumber": 278
+ },
+ {
+ "text": " throw \"Failed to start worker process.\"",
+ "lineNumber": 279
+ },
+ {
+ "text": " }",
+ "lineNumber": 280
+ },
+ {
+ "lineNumber": 281
+ },
+ {
+ "text": " Write-ControllerLog \"Worker process started with PID $($workerProcess.Id)\"",
+ "lineNumber": 282
+ },
+ {
+ "text": " Write-Metadata -Status 'running' -WorkerPid $workerProcess.Id -ControllerPid $controllerPid -Restarts $restartCount",
+ "lineNumber": 283
+ },
+ {
+ "lineNumber": 284
+ },
+ {
+ "text": " # Check if process exited immediately",
+ "lineNumber": 285
+ },
+ {
+ "text": " if ($workerProcess.HasExited) {",
+ "lineNumber": 286
+ },
+ {
+ "text": " $exitCode = -1",
+ "lineNumber": 287
+ },
+ {
+ "text": " try {",
+ "lineNumber": 288
+ },
+ {
+ "text": " $exitCode = $workerProcess.ExitCode",
+ "lineNumber": 289
+ },
+ {
+ "text": " }",
+ "lineNumber": 290
+ },
+ {
+ "text": " catch {",
+ "lineNumber": 291
+ },
+ {
+ "text": " Write-ControllerLog \"Unable to read immediate exit code: $($_.Exception.Message)\"",
+ "lineNumber": 292
+ },
+ {
+ "text": " }",
+ "lineNumber": 293
+ },
+ {
+ "text": " Write-ControllerLog \"Worker process exited immediately after start with code $exitCode\"",
+ "lineNumber": 294
+ },
+ {
+ "text": " Write-Metadata -Status 'stopped' -WorkerPid $null -ControllerPid $controllerPid -Restarts $restartCount -LastExitCode $exitCode",
+ "lineNumber": 295
+ },
+ {
+ "text": " if ($exitCode -eq 0) { break }",
+ "lineNumber": 296
+ },
+ {
+ "text": " # Continue to restart logic below",
+ "lineNumber": 297
+ },
+ {
+ "text": " }",
+ "lineNumber": 298
+ },
+ {
+ "text": " else {",
+ "lineNumber": 299
+ },
+ {
+ "text": " $stdoutHandler = [UnifiedWorkers.ProcessLogHandler]::new($logWriter, 'OUT')",
+ "lineNumber": 300
+ }
+ ]
+ },
+ "score": 0.2923305928707123
+ },
+ {
+ "codeBlock": {
+ "relativeWorkspacePath": ".\\Services\\AttachService.cs",
+ "range": {
+ "startPosition": {},
+ "endPosition": {
+ "line": 41
+ }
+ },
+ "contents": "using System.Diagnostics;\nusing System.Threading.Tasks;\nusing UnifiedFarmLauncher.Models;\n\nnamespace UnifiedFarmLauncher.Services\n{\n public class AttachService\n {\n private readonly SshService _sshService;\n private readonly WorkerControllerService _controllerService;\n\n public AttachService(SshService sshService, WorkerControllerService controllerService)\n {\n _sshService = sshService;\n _controllerService = controllerService;\n }\n\n public async Task AttachToWorkerAsync(WorkerConfig worker, string workerType, bool commandOnly = false, string? command = null)\n {\n await _controllerService.DeployAttachHelperAsync(worker);\n\n var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);\n var remoteHelper = $\"{remoteBasePath.Replace(\"\\\\\", \"/\")}/attach-helper.ps1\";\n\n var paramsBlock = $\"-WorkerName \\\"{worker.Name}\\\" -WorkerType \\\"{workerType}\\\"\";\n if (commandOnly)\n {\n paramsBlock += \" -CommandOnly\";\n }\n if (!string.IsNullOrEmpty(command))\n {\n paramsBlock += $\" -Command \\\"{command}\\\"\";\n }\n\n var remoteCmd = $\"powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File \\\"{remoteHelper}\\\" {paramsBlock}\";\n\n _sshService.StartInteractiveSsh(worker, remoteCmd);\n }\n }\n}",
+ "signatures": {},
+ "detailedLines": [
+ {
+ "text": "using System.Diagnostics;",
+ "lineNumber": 1
+ },
+ {
+ "text": "using System.Threading.Tasks;",
+ "lineNumber": 2
+ },
+ {
+ "text": "using UnifiedFarmLauncher.Models;",
+ "lineNumber": 3
+ },
+ {
+ "lineNumber": 4
+ },
+ {
+ "text": "namespace UnifiedFarmLauncher.Services",
+ "lineNumber": 5
+ },
+ {
+ "text": "{",
+ "lineNumber": 6
+ },
+ {
+ "text": " public class AttachService",
+ "lineNumber": 7
+ },
+ {
+ "text": " {",
+ "lineNumber": 8
+ },
+ {
+ "text": " private readonly SshService _sshService;",
+ "lineNumber": 9
+ },
+ {
+ "text": " private readonly WorkerControllerService _controllerService;",
+ "lineNumber": 10
+ },
+ {
+ "lineNumber": 11
+ },
+ {
+ "text": " public AttachService(SshService sshService, WorkerControllerService controllerService)",
+ "lineNumber": 12
+ },
+ {
+ "text": " {",
+ "lineNumber": 13
+ },
+ {
+ "text": " _sshService = sshService;",
+ "lineNumber": 14
+ },
+ {
+ "text": " _controllerService = controllerService;",
+ "lineNumber": 15
+ },
+ {
+ "text": " }",
+ "lineNumber": 16
+ },
+ {
+ "lineNumber": 17
+ },
+ {
+ "text": " public async Task AttachToWorkerAsync(WorkerConfig worker, string workerType, bool commandOnly = false, string? command = null)",
+ "lineNumber": 18
+ },
+ {
+ "text": " {",
+ "lineNumber": 19
+ },
+ {
+ "text": " await _controllerService.DeployAttachHelperAsync(worker);",
+ "lineNumber": 20
+ },
+ {
+ "lineNumber": 21
+ },
+ {
+ "text": " var remoteBasePath = await _sshService.GetWorkerBasePathAsync(worker);",
+ "lineNumber": 22
+ },
+ {
+ "text": " var remoteHelper = $\"{remoteBasePath.Replace(\"\\\\\", \"/\")}/attach-helper.ps1\";",
+ "lineNumber": 23
+ },
+ {
+ "lineNumber": 24
+ },
+ {
+ "text": " var paramsBlock = $\"-WorkerName \\\"{worker.Name}\\\" -WorkerType \\\"{workerType}\\\"\";",
+ "lineNumber": 25
+ },
+ {
+ "text": " if (commandOnly)",
+ "lineNumber": 26
+ },
+ {
+ "text": " {",
+ "lineNumber": 27
+ },
+ {
+ "text": " paramsBlock += \" -CommandOnly\";",
+ "lineNumber": 28
+ },
+ {
+ "text": " }",
+ "lineNumber": 29
+ },
+ {
+ "text": " if (!string.IsNullOrEmpty(command))",
+ "lineNumber": 30
+ },
+ {
+ "text": " {",
+ "lineNumber": 31
+ },
+ {
+ "text": " paramsBlock += $\" -Command \\\"{command}\\\"\";",
+ "lineNumber": 32
+ },
+ {
+ "text": " }",
+ "lineNumber": 33
+ },
+ {
+ "lineNumber": 34
+ },
+ {
+ "text": " var remoteCmd = $\"powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File \\\"{remoteHelper}\\\" {paramsBlock}\";",
+ "lineNumber": 35
+ },
+ {
+ "lineNumber": 36
+ },
+ {
+ "text": " _sshService.StartInteractiveSsh(worker, remoteCmd);",
+ "lineNumber": 37
+ },
+ {
+ "text": " }",
+ "lineNumber": 38
+ },
+ {
+ "text": " }",
+ "lineNumber": 39
+ },
+ {
+ "text": "}",
+ "lineNumber": 40
+ }
+ ]
+ },
+ "score": 0.2903842031955719
+ }
+ ]
+}
+```
+Status: **completed**
+
+
+
+
+
+---
+
+
+Tool use: **read_file_v2** • Read file: Scripts/remote_worker_attach.ps1
+
+
+
+
+---
+
+
+
+Tool use: **ripgrep_raw_search**
+
+
+Parameters:
+
+```json
+{
+ "pattern": "username|renderKey|Username|RenderKey",
+ "path": "Scripts",
+ "caseInsensitive": true
+}
+```
+Result:
+
+```json
+{
+ "success": {
+ "pattern": "username|renderKey|Username|RenderKey",
+ "path": "Scripts",
+ "outputMode": "content",
+ "workspaceResults": {
+ "c:\\Users\\Nathan\\Desktop\\UFL": {
+ "content": {}
+ }
+ }
+ }
+}
+```
+Status: **completed**
+
+
+
+
+
+---
+
+
+Moving SheepIt username and renderKey to global settings. Creating a todo list and implementing the changes:
+
+
+---
+
+
+Tool use: **todo_write** • Todo List
+
+- [-] Update GlobalSettings model to include SheepIt username and renderKey
+- [ ] Update SheepItConfig to remove username and renderKey (keep only GPU)
+- [ ] Update WorkerControllerService to read credentials from global settings
+- [ ] Update WorkerEditViewModel to remove username/renderKey fields
+- [ ] Update WorkerEditWindow UI to remove username/renderKey fields
+- [ ] Add global settings UI to main window
+
+
+
+
+---
+
+
+Tool use: **read_file_v2** • Read file: Services/ConfigService.cs
+
+
+
+
+---
+
+
+Tool use: **read_file_v2** • Read file: Services/WorkerControllerService.cs
+
+
+
+
+---
+
+
+Updating the models first:
+
+
+---
+
+
+
+Tool use: **edit_file_v2**
+
+
+Parameters:
+
+```json
+{
+ "relativeWorkspacePath": "Models/GlobalSettings.cs"
+}
+```
+Additional data:
+
+```json
+{
+ "codeblockId": "831d3c4e-4760-40c0-884b-72a3fb603e55"
+}
+```
+Status: **completed**
+
+
+
+
+
+---
+
+
+
+Tool use: **edit_file_v2**
+
+
+Parameters:
+
+```json
+{
+ "relativeWorkspacePath": "Models/WorkerTypeConfig.cs"
+}
+```
+Additional data:
+
+```json
+{
+ "codeblockId": "8463f456-0d8a-4a88-9ed9-98e14caefd5a"
+}
+```
+Status: **completed**
+
+
+
+
+
+---
+
+
+Tool use: **todo_write** • Todo List
+
+- [x] Update GlobalSettings model to include SheepIt username and renderKey
+- [x] Update SheepItConfig to remove username and renderKey (keep only GPU)
+- [-] Update WorkerControllerService to read credentials from global settings
+- [ ] Update WorkerEditViewModel to remove username/renderKey fields
+- [ ] Update WorkerEditWindow UI to remove username/renderKey fields
+- [ ] Add global settings UI to main window
+
+
+
+
+---
+
+
+
+Tool use: **edit_file_v2**
+
+
+Parameters:
+
+```json
+{
+ "relativeWorkspacePath": "Services/WorkerControllerService.cs"
+}
+```
+Additional data:
+
+```json
+{
+ "codeblockId": "4a3c5db9-779e-4eae-8ae1-ce251440e62a"
+}
+```
+Status: **completed**
+
+
+
+
+
+---
+
+
+Tool use: **todo_write** • Todo List
+
+- [x] Update GlobalSettings model to include SheepIt username and renderKey
+- [x] Update SheepItConfig to remove username and renderKey (keep only GPU)
+- [x] Update WorkerControllerService to read credentials from global settings
+- [-] Update WorkerEditViewModel to remove username/renderKey fields
+- [ ] Update WorkerEditWindow UI to remove username/renderKey fields
+- [ ] Add global settings UI to main window
+
+
+
+
+---
+
+
+
+Tool use: **edit_file_v2**
+
+
+Parameters:
+
+```json
+{
+ "relativeWorkspacePath": "ViewModels/WorkerEditViewModel.cs"
+}
+```
+Additional data:
+
+```json
+{
+ "codeblockId": "d83aa3cf-d076-4a76-990c-77cce73f1fcb"
+}
+```
+Status: **completed**
+
+
+
+
+
+---
+
+
+
+Tool use: **edit_file_v2**
+
+
+Parameters:
+
+```json
+{
+ "relativeWorkspacePath": "ViewModels/WorkerEditViewModel.cs"
+}
+```
+Additional data:
+
+```json
+{
+ "codeblockId": "da60242b-27c0-4cfc-a034-959f876214e4"
+}
+```
+Status: **completed**
+
+
+
+
+
+---
+
+
+
+Tool use: **edit_file_v2**
+
+
+Parameters:
+
+```json
+{
+ "relativeWorkspacePath": "ViewModels/WorkerEditViewModel.cs"
+}
+```
+Additional data:
+
+```json
+{
+ "codeblockId": "9f1395a9-5d4e-4175-9f1a-2a6dce5c8808"
+}
+```
+Status: **completed**
+
+
+
+
+
+---
+
+
+
+Tool use: **edit_file_v2**
+
+
+Parameters:
+
+```json
+{
+ "relativeWorkspacePath": "ViewModels/WorkerEditViewModel.cs"
+}
+```
+Additional data:
+
+```json
+{
+ "codeblockId": "c21cdbd6-7759-44aa-a1e0-b875b72253a5"
+}
+```
+Status: **completed**
+
+
+
+
+
+---
+
+
+Tool use: **todo_write** • Todo List
+
+- [x] Update GlobalSettings model to include SheepIt username and renderKey
+- [x] Update SheepItConfig to remove username and renderKey (keep only GPU)
+- [x] Update WorkerControllerService to read credentials from global settings
+- [x] Update WorkerEditViewModel to remove username/renderKey fields
+- [-] Update WorkerEditWindow UI to remove username/renderKey fields
+- [ ] Add global settings UI to main window
+
+
+
+
+---
+
+
+Tool use: **read_file_v2** • Read file: Views/WorkerEditWindow.axaml
+
+
+
+
+---
+
+
+Removing username and render key fields from the UI:
+
+
+---
+
+
+
+Tool use: **edit_file_v2**
+
+
+Parameters:
+
+```json
+{
+ "relativeWorkspacePath": "Views/WorkerEditWindow.axaml"
+}
+```
+Additional data:
+
+```json
+{
+ "codeblockId": "a134506c-6e88-4003-90a7-4e26cf17ba97"
+}
+```
+Status: **completed**
+
+
+
+
+
+---
+
+
+Tool use: **todo_write** • Todo List
+
+- [x] Update GlobalSettings model to include SheepIt username and renderKey
+- [x] Update SheepItConfig to remove username and renderKey (keep only GPU)
+- [x] Update WorkerControllerService to read credentials from global settings
+- [x] Update WorkerEditViewModel to remove username/renderKey fields
+- [x] Update WorkerEditWindow UI to remove username/renderKey fields
+- [-] Add global settings UI to main window
+
+
+
+
+---
+
+
+Tool use: **read_file_v2** • Read file: ViewModels/MainWindowViewModel.cs
+
+
+
+
+---
+
+
+Tool use: **read_file_v2** • Read file: Views/MainWindow.axaml
+
+
+
+
+---
+
+
+Adding a global settings dialog. Creating the ViewModel and Window:
+
+
+---
+
+
+
+Tool use: **edit_file_v2**
+
+
+Parameters:
+
+```json
+{
+ "relativeWorkspacePath": "ViewModels/GlobalSettingsViewModel.cs"
+}
+```
+Additional data:
+
+```json
+{
+ "codeblockId": "42526de5-72a5-4a3b-a943-b30c95c067ef"
+}
+```
+Status: **completed**
+
+
+
+
+
+---
+
+
+
+Tool use: **edit_file_v2**
+
+
+Parameters:
+
+```json
+{
+ "relativeWorkspacePath": "Views/GlobalSettingsWindow.axaml"
+}
+```
+Additional data:
+
+```json
+{
+ "codeblockId": "d88c4447-0b74-4c28-89f0-e978c3424495"
+}
+```
+Status: **completed**
+
+
+
+
+
+---
+
+
+
+Tool use: **edit_file_v2**
+
+
+Parameters:
+
+```json
+{
+ "relativeWorkspacePath": "Views/GlobalSettingsWindow.axaml.cs"
+}
+```
+Additional data:
+
+```json
+{
+ "codeblockId": "d8c858c9-7957-4b7f-8f07-ee9e2cb54fac"
+}
+```
+Status: **completed**
+
+
+
+
+
+---
+
+
+
+Tool use: **edit_file_v2**
+
+
+Parameters:
+
+```json
+{
+ "relativeWorkspacePath": "Views/MainWindow.axaml"
+}
+```
+Additional data:
+
+```json
+{
+ "codeblockId": "ad6ad389-ff54-4390-ba30-3be148a05fdc"
+}
+```
+Status: **completed**
+
+
+
+
+
+---
+
+
+Tool use: **read_file_v2** • Read file: Views/MainWindow.axaml.cs
+
+
+
+
+---
+
+
+Adding the Settings button click handler:
+
+
+---
+
+
+
+Tool use: **edit_file_v2**
+
+
+Parameters:
+
+```json
+{
+ "relativeWorkspacePath": "Views/MainWindow.axaml.cs"
+}
+```
+Additional data:
+
+```json
+{
+ "codeblockId": "730e239c-79e6-4152-8324-f3382de6e374"
+}
+```
+Status: **completed**
+
+
+
+
+
+---
+
+
+Tool use: **read_file_v2** • Read file: Views/MainWindow.axaml.cs
+
+
+
+
+---
+
+
+Adding the Settings button click handler:
+
+
+---
+
+
+
+Tool use: **edit_file_v2**
+
+
+Parameters:
+
+```json
+{
+ "relativeWorkspacePath": "Views/MainWindow.axaml.cs"
+}
+```
+Additional data:
+
+```json
+{
+ "codeblockId": "82d9d1de-f44c-4f1b-b283-b86091858aac"
+}
+```
+Status: **completed**
+
+
+
+
+
+---
+
+
+Tool use: **todo_write** • Todo List
+
+- [x] Update GlobalSettings model to include SheepIt username and renderKey
+- [x] Update SheepItConfig to remove username and renderKey (keep only GPU)
+- [x] Update WorkerControllerService to read credentials from global settings
+- [x] Update WorkerEditViewModel to remove username/renderKey fields
+- [x] Update WorkerEditWindow UI to remove username/renderKey fields
+- [x] Add global settings UI to main window
+
+
+
+
+---
+
+
+Tool use: **run_terminal_command_v2** • Run command: dotnet build --no-restore 2>&1 | Select-Object -First 50
+
+```bash
+dotnet build --no-restore 2>&1 | Select-Object -First 50
+```
+
+```
+ UnifiedFarmLauncher -> C:\Users\Nathan\Desktop\UFL\bin\Debug\net8.0\UnifiedFarmLauncher.dll
+
+Build succeeded.
+ 0 Warning(s)
+ 0 Error(s)
+
+Time Elapsed 00:00:00.83
+
+```
+
+
+
+---
+
+
+Build succeeded. Changes:
+
+**Changes made:**
+
+1. **Models updated:**
+ - `GlobalSettings` now includes `SheepItUsername` and `SheepItRenderKey`
+ - `SheepItConfig` now only contains `Gpu` (removed username and renderKey)
+
+2. **Service updated:**
+ - `WorkerControllerService.GenerateSheepItPayload()` now reads credentials from global settings instead of per-worker config
+
+3. **Worker Edit UI:**
+ - Removed username and render key fields from the SheepIt tab
+ - Added a note that these are configured in Global Settings
+
+4. **Global Settings UI:**
+ - Added a "Settings" button to the main window toolbar
+ - Created `GlobalSettingsWindow` with a SheepIt tab for username and render key
+ - Created `GlobalSettingsViewModel` to manage the settings
+
+SheepIt credentials are now global. Configure them once in Settings, and all SheepIt workers will use them. Each worker still has its own GPU setting (OPTIX_0, CUDA_0, etc.).
+
+The build completed successfully. You can test it by clicking the "Settings" button to configure your SheepIt credentials globally.
+
+---
+
+_**User**_
+
+add one of those temp hide/unhide buttons next to the renderkey so it can toggle the asterisks on and off.
+
+---
+
+_**Agent (model default, mode Agent)**_
+
+
+Adding a show/hide toggle button next to the render key field. Updating the ViewModel and UI:
+
+
+---
+
+
+Tool use: **read_file_v2** • Read file: ViewModels/GlobalSettingsViewModel.cs
+
+
+
+
+---
+
+
+Tool use: **read_file_v2** • Read file: Views/GlobalSettingsWindow.axaml
+
+
+
+
+---
+
+
+Tool use: **read_file_v2** • Read file: Views/GlobalSettingsWindow.axaml.cs
+
+
+
+
+---
+
diff --git a/ViewModels/GlobalSettingsViewModel.cs b/ViewModels/GlobalSettingsViewModel.cs
index 6f0c0f7..bbff309 100644
--- a/ViewModels/GlobalSettingsViewModel.cs
+++ b/ViewModels/GlobalSettingsViewModel.cs
@@ -8,6 +8,7 @@ namespace UnifiedFarmLauncher.ViewModels
private readonly ConfigService _configService;
private string _sheepItUsername = string.Empty;
private string _sheepItRenderKey = string.Empty;
+ private bool _isRenderKeyVisible = false;
public GlobalSettingsViewModel(ConfigService configService)
{
@@ -27,6 +28,12 @@ namespace UnifiedFarmLauncher.ViewModels
set => SetAndRaise(ref _sheepItRenderKey, value);
}
+ public bool IsRenderKeyVisible
+ {
+ get => _isRenderKeyVisible;
+ set => SetAndRaise(ref _isRenderKeyVisible, value);
+ }
+
private void LoadSettings()
{
var config = _configService.Load();
diff --git a/Views/GlobalSettingsWindow.axaml b/Views/GlobalSettingsWindow.axaml
index 72af5b6..4786146 100644
--- a/Views/GlobalSettingsWindow.axaml
+++ b/Views/GlobalSettingsWindow.axaml
@@ -15,7 +15,18 @@
-
+
+
+
+
diff --git a/Views/GlobalSettingsWindow.axaml.cs b/Views/GlobalSettingsWindow.axaml.cs
index 9055d79..59075df 100644
--- a/Views/GlobalSettingsWindow.axaml.cs
+++ b/Views/GlobalSettingsWindow.axaml.cs
@@ -29,6 +29,7 @@ namespace UnifiedFarmLauncher.Views
{
this.FindControl