diff --git a/scripts/vibe-kanban/vibe-dev-server.sh b/scripts/vibe-kanban/vibe-dev-server.sh
index ef483e65..266b4c8e 100755
--- a/scripts/vibe-kanban/vibe-dev-server.sh
+++ b/scripts/vibe-kanban/vibe-dev-server.sh
@@ -127,11 +127,59 @@ export TASK_ID="$TASK_ID"
export PORT_OFFSET="$PORT_OFFSET"
export TASK_SLOT="$TASK_SLOT"
-# Change to AppHost directory
+# Ensure HTTPS dev certificate is available (Aspire may need it even for HTTP mode)
+echo "🔐 Ensuring HTTPS developer certificate is available..."
+if ! dotnet dev-certs https --check > /dev/null 2>&1; then
+ echo " Generating HTTPS developer certificate..."
+ dotnet dev-certs https --trust > /dev/null 2>&1 || {
+ echo "⚠️ Could not generate/trust certificate"
+ echo " Will try to use HTTP-only profile"
+ }
+fi
+
+# Configure Aspire to use HTTP only (avoid certificate issues)
+# Use the "http" launch profile which is configured for HTTP-only
+export ASPNETCORE_URLS="http://localhost:15242"
+export DOTNET_DASHBOARD_OTLP_ENDPOINT_URL="http://localhost:19204"
+export DOTNET_RESOURCE_SERVICE_ENDPOINT_URL="http://localhost:20284"
+export DOTNET_DASHBOARD_OTLP_HTTP_ENDPOINT_URL="http://localhost:19204"
+export ASPNETCORE_ENVIRONMENT="Development"
+export DOTNET_ENVIRONMENT="Development"
+
+# Restore packages in the worktree first to ensure all dependencies are available
+# This is important because Aspire will build projects that may reference worktree paths
+echo ""
+echo "📦 Restoring NuGet packages..."
+echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+
+# Restore at solution level in worktree if it exists
+if [ -f "$WORKTREE_PROJECT_ROOT/src/Managing.sln" ]; then
+ echo " Restoring in worktree solution..."
+ cd "$WORKTREE_PROJECT_ROOT/src"
+ # Suppress all warnings and only show errors
+ dotnet restore Managing.sln --verbosity quiet --nologo 2>&1 | \
+ grep -vE "(warning|Warning|WARNING|NU[0-9]|\.csproj :)" || true
+fi
+
+# Restore at solution level in main repo (where we'll actually run from)
+echo " Restoring in main repo solution..."
+cd "$MAIN_REPO/src"
+# Suppress all warnings and only show errors
+RESTORE_OUTPUT=$(dotnet restore Managing.sln --verbosity quiet --nologo 2>&1 | \
+ grep -vE "(warning|Warning|WARNING|NU[0-9]|\.csproj :)" || true)
+if echo "$RESTORE_OUTPUT" | grep -qE "(error|Error|ERROR|failed|Failed)"; then
+ echo "❌ Package restore failed:"
+ echo "$RESTORE_OUTPUT"
+ exit 1
+else
+ echo "✅ Packages restored successfully"
+fi
+
+# Ensure we're in the AppHost directory for running Aspire
cd "$MAIN_REPO/src/Managing.AppHost"
+echo ""
# Run Aspire (this will start the API and Workers)
-echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "🚀 Starting Aspire..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@@ -141,8 +189,9 @@ echo ""
ASPIRE_LOG="$WORKTREE_PROJECT_ROOT/.task-pids/aspire-${TASK_ID}.log"
mkdir -p "$(dirname "$ASPIRE_LOG")"
-# Start Aspire
-dotnet run > "$ASPIRE_LOG" 2>&1 &
+# Start Aspire using the "http" launch profile (HTTP only, no HTTPS)
+# All output goes to log file (warnings will be filtered when displaying)
+dotnet run --launch-profile http > "$ASPIRE_LOG" 2>&1 &
ASPIRE_PID=$!
# Save PID
@@ -201,9 +250,9 @@ for i in {1..120}; do
# Show progress every 10 seconds
if [ $((i % 10)) -eq 0 ]; then
echo " Still starting... (${i}/120 seconds)"
- # Show last few lines of log for progress
+ # Show last few lines of log for progress (filter warnings)
if [ -f "$ASPIRE_LOG" ]; then
- LAST_LINE=$(tail -1 "$ASPIRE_LOG" 2>/dev/null | cut -c1-80)
+ LAST_LINE=$(tail -20 "$ASPIRE_LOG" 2>/dev/null | grep -vE "(warning|Warning|WARNING|NU[0-9]|\.csproj :)" | tail -1 | cut -c1-80)
if [ -n "$LAST_LINE" ]; then
echo " Latest: $LAST_LINE"
fi
@@ -213,8 +262,8 @@ for i in {1..120}; do
if [ $i -eq 120 ]; then
echo "⚠️ Aspire dashboard did not become ready after 120 seconds"
echo "💡 Check the log: $ASPIRE_LOG"
- echo "💡 Last 10 lines of log:"
- tail -10 "$ASPIRE_LOG" 2>/dev/null || echo " (log file not found)"
+ echo "💡 Last 10 lines of log (warnings filtered):"
+ tail -30 "$ASPIRE_LOG" 2>/dev/null | grep -vE "(warning|Warning|WARNING|NU[0-9]|\.csproj :)" | tail -10 || echo " (log file not found)"
# Try to use default port anyway
if [ -z "$ASPIRE_DASHBOARD_URL" ]; then
ASPIRE_DASHBOARD_PORT=15242
@@ -269,10 +318,11 @@ echo " Health check: http://localhost:${API_PORT}/alive"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
-# Tail the Aspire log
+# Tail the Aspire log (filter out warnings for cleaner output)
echo "📋 Showing Aspire logs (Press Ctrl+C to stop)"
+echo " (Warnings are hidden for cleaner output - full logs in: $ASPIRE_LOG)"
echo ""
-tail -f "$ASPIRE_LOG" 2>/dev/null || {
+tail -f "$ASPIRE_LOG" 2>/dev/null | grep -vE "(warning|Warning|WARNING|NU[0-9]|\.csproj :)" || {
echo "❌ Cannot read Aspire log: $ASPIRE_LOG"
echo "💡 Aspire may still be starting. Check the log manually."
exit 1
diff --git a/src/Managing.AppHost/Managing.AppHost.csproj b/src/Managing.AppHost/Managing.AppHost.csproj
index 981645df..c220ba97 100644
--- a/src/Managing.AppHost/Managing.AppHost.csproj
+++ b/src/Managing.AppHost/Managing.AppHost.csproj
@@ -12,6 +12,10 @@
+
+
+
+
diff --git a/src/Managing.AppHost/Program.cs b/src/Managing.AppHost/Program.cs
index 817a84ed..885c0bf1 100644
--- a/src/Managing.AppHost/Program.cs
+++ b/src/Managing.AppHost/Program.cs
@@ -1,31 +1,152 @@
-using Aspire.Hosting;
+using DotNetEnv;
+using Microsoft.Extensions.Configuration;
+
+// Detect running mode: IDE mode (appsettings + .env) or Vibe-kanban mode (env vars)
+var isVibeKanbanMode = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TASK_ID"))
+ && Environment.GetEnvironmentVariable("TASK_ID") != "DEFAULT";
+
+string taskId;
+int portOffset;
+string taskSlot;
+int apiPort;
+int postgresPort;
+int redisPort;
+int orleansSiloPort;
+int orleansGatewayPort;
+int orleansDashboardPort;
+string dbName;
+string orleansDbName;
+string postgresConnectionString;
+string postgresOrleansConnectionString;
+string redisConnectionString;
+string influxDbUrl;
+string influxDbToken;
+
+if (isVibeKanbanMode)
+{
+ // Vibe-kanban mode: Use environment variables directly
+ Console.WriteLine("🔧 Running in Vibe-kanban mode (using environment variables)");
+
+ taskId = Environment.GetEnvironmentVariable("TASK_ID") ?? "DEFAULT";
+ portOffset = int.Parse(Environment.GetEnvironmentVariable("PORT_OFFSET") ?? "0");
+ taskSlot = Environment.GetEnvironmentVariable("TASK_SLOT") ?? "1";
+
+ // Calculate ports based on task configuration
+ apiPort = 5000 + portOffset;
+ postgresPort = 5432 + portOffset;
+ redisPort = 6379 + portOffset;
+
+ // Calculate Orleans ports from TASK_SLOT
+ var taskSlotInt = int.Parse(taskSlot);
+ orleansSiloPort = 11111 + (taskSlotInt - 1) * 10;
+ orleansGatewayPort = 30000 + (taskSlotInt - 1) * 10;
+ orleansDashboardPort = 9999 + (taskSlotInt - 1);
+
+ // Database names
+ dbName = $"managing_{taskId.ToLower()}";
+ orleansDbName = $"orleans_{taskId.ToLower()}";
+
+ // Connection strings (using existing Docker containers managed by Docker Compose)
+ postgresConnectionString = $"Host=localhost;Port={postgresPort};Database={dbName};Username=postgres;Password=postgres";
+ postgresOrleansConnectionString = $"Host=localhost;Port={postgresPort};Database={orleansDbName};Username=postgres;Password=postgres";
+ redisConnectionString = $"localhost:{redisPort}";
+
+ // InfluxDB from environment or defaults
+ influxDbUrl = Environment.GetEnvironmentVariable("InfluxDb__Url") ?? "http://localhost:8086/";
+ influxDbToken = Environment.GetEnvironmentVariable("InfluxDb__Token") ?? "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA==";
+}
+else
+{
+ // IDE mode: Load .env file and optional appsettings.json
+ Console.WriteLine("🔧 Running in IDE mode (using environment variables, .env, and optional appsettings.json)");
+
+ // Load .env file if it exists (optional)
+ var enableEnvFile = Environment.GetEnvironmentVariable("ENABLE_ENV_FILE") != "false";
+ if (enableEnvFile)
+ {
+ var envFilePaths = new[]
+ {
+ Path.Combine(Directory.GetCurrentDirectory(), ".env"),
+ Path.Combine(AppContext.BaseDirectory, ".env"),
+ Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", ".env")),
+ Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", ".env")), // From AppHost to project root
+ };
+
+ string? loadedEnvPath = null;
+ foreach (var envPath in envFilePaths)
+ {
+ if (File.Exists(envPath))
+ {
+ try
+ {
+ Env.Load(envPath);
+ loadedEnvPath = envPath;
+ Console.WriteLine($"✅ Loaded .env file from: {envPath}");
+ break;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"⚠️ Failed to load .env file from {envPath}: {ex.Message}");
+ }
+ }
+ }
+ }
+
+ // Build configuration from appsettings.json (optional) and environment variables
+ // appsettings.json is optional because environment variables and .env files take precedence
+ var configBuilder = new ConfigurationBuilder()
+ .SetBasePath(AppContext.BaseDirectory)
+ .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
+ .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true)
+ .AddEnvironmentVariables();
+
+ var configuration = configBuilder.Build();
+
+ // Read configuration values
+ taskId = configuration["TASK_ID"] ?? Environment.GetEnvironmentVariable("TASK_ID") ?? "DEFAULT";
+ portOffset = int.Parse(configuration["PORT_OFFSET"] ?? Environment.GetEnvironmentVariable("PORT_OFFSET") ?? "0");
+ taskSlot = configuration["TASK_SLOT"] ?? Environment.GetEnvironmentVariable("TASK_SLOT") ?? "1";
+
+ // Calculate ports based on task configuration
+ apiPort = 5000 + portOffset;
+ postgresPort = 5432 + portOffset;
+ redisPort = 6379 + portOffset;
+
+ // Calculate Orleans ports from TASK_SLOT
+ var taskSlotInt = int.Parse(taskSlot);
+ orleansSiloPort = 11111 + (taskSlotInt - 1) * 10;
+ orleansGatewayPort = 30000 + (taskSlotInt - 1) * 10;
+ orleansDashboardPort = 9999 + (taskSlotInt - 1);
+
+ // Database names
+ dbName = $"managing_{taskId.ToLower()}";
+ orleansDbName = $"orleans_{taskId.ToLower()}";
+
+ // Connection strings from configuration or defaults
+ postgresConnectionString = configuration["PostgreSql:ConnectionString"]
+ ?? configuration["PostgreSql__ConnectionString"]
+ ?? $"Host=localhost;Port={postgresPort};Database={dbName};Username=postgres;Password=postgres";
+
+ postgresOrleansConnectionString = configuration["PostgreSql:Orleans"]
+ ?? configuration["PostgreSql__Orleans"]
+ ?? $"Host=localhost;Port={postgresPort};Database={orleansDbName};Username=postgres;Password=postgres";
+
+ redisConnectionString = configuration["Redis:ConnectionString"]
+ ?? configuration["Redis__ConnectionString"]
+ ?? $"localhost:{redisPort}";
+
+ // InfluxDB from configuration
+ influxDbUrl = configuration["InfluxDb:Url"]
+ ?? configuration["InfluxDb__Url"]
+ ?? "http://localhost:8086/";
+
+ influxDbToken = configuration["InfluxDb:Token"]
+ ?? configuration["InfluxDb__Token"]
+ ?? "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA==";
+}
var builder = DistributedApplication.CreateBuilder(args);
-// Get task-specific configuration from environment variables
-var taskId = Environment.GetEnvironmentVariable("TASK_ID") ?? "DEFAULT";
-var portOffset = int.Parse(Environment.GetEnvironmentVariable("PORT_OFFSET") ?? "0");
-var taskSlot = Environment.GetEnvironmentVariable("TASK_SLOT") ?? "1";
-
-// Calculate ports based on task configuration
-var apiPort = 5000 + portOffset;
-var postgresPort = 5432 + portOffset;
-var redisPort = 6379 + portOffset;
-
-// Calculate Orleans ports from TASK_SLOT
-var taskSlotInt = int.Parse(taskSlot);
-var orleansSiloPort = 11111 + (taskSlotInt - 1) * 10;
-var orleansGatewayPort = 30000 + (taskSlotInt - 1) * 10;
-var orleansDashboardPort = 9999 + (taskSlotInt - 1);
-
-// Database names
-var dbName = $"managing_{taskId.ToLower()}";
-var orleansDbName = $"orleans_{taskId.ToLower()}";
-
-// Connection strings (using existing Docker containers managed by Docker Compose)
-var postgresConnectionString = $"Host=localhost;Port={postgresPort};Database={dbName};Username=postgres;Password=postgres";
-var postgresOrleansConnectionString = $"Host=localhost;Port={postgresPort};Database={orleansDbName};Username=postgres;Password=postgres";
-var redisConnectionString = $"localhost:{redisPort}";
// Add API project
var api = builder.AddProject("api", "../Managing.Api/Managing.Api.csproj")
@@ -39,8 +160,8 @@ var api = builder.AddProject("api", "../Managing.Api/Managing.Api.csproj")
.WithEnvironment("SILO_ROLE", "Trading")
.WithEnvironment("PostgreSql__ConnectionString", postgresConnectionString)
.WithEnvironment("PostgreSql__Orleans", postgresOrleansConnectionString)
- .WithEnvironment("InfluxDb__Url", "http://localhost:8086/")
- .WithEnvironment("InfluxDb__Token", "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA==")
+ .WithEnvironment("InfluxDb__Url", influxDbUrl)
+ .WithEnvironment("InfluxDb__Token", influxDbToken)
.WithEnvironment("ORLEANS_SILO_PORT", orleansSiloPort.ToString())
.WithEnvironment("ORLEANS_GATEWAY_PORT", orleansGatewayPort.ToString())
.WithEnvironment("ORLEANS_DASHBOARD_PORT", orleansDashboardPort.ToString());
@@ -51,8 +172,8 @@ var workers = builder.AddProject("workers", "../Managing.Workers/Managing.Worker
.WithEnvironment("TASK_SLOT", taskSlot)
.WithEnvironment("ASPNETCORE_ENVIRONMENT", "Development")
.WithEnvironment("PostgreSql__ConnectionString", postgresConnectionString)
- .WithEnvironment("InfluxDb__Url", "http://localhost:8086/")
- .WithEnvironment("InfluxDb__Token", "Fw2FPL2OwTzDHzSbR2Sd5xs0EKQYy00Q-hYKYAhr9cC1_q5YySONpxuf_Ck0PTjyUiF13xXmi__bu_pXH-H9zA==");
+ .WithEnvironment("InfluxDb__Url", influxDbUrl)
+ .WithEnvironment("InfluxDb__Token", influxDbToken);
// Build and run
builder.Build().Run();