small optimizations + allow for users to force software H264 since it's not slow as balls

This commit is contained in:
KingKunta
2026-07-06 14:00:34 -05:00
parent e0394ea1fa
commit f7e9da26f8
11 changed files with 171 additions and 27 deletions
+18 -4
View File
@@ -494,10 +494,11 @@ var (
statBlockRegions atomic.Int64 statBlockRegions atomic.Int64
statBlockFallbacks atomic.Int64 statBlockFallbacks atomic.Int64
overrideQuality atomic.Int64 overrideQuality atomic.Int64
overrideCodec atomic.Value overrideCodec atomic.Value
h264WarnOnce sync.Once desktopSoftwareH264 atomic.Bool
codecLogOnce sync.Once h264WarnOnce sync.Once
codecLogOnce sync.Once
prevMu sync.Mutex prevMu sync.Mutex
prevFrame *prevImage prevFrame *prevImage
@@ -627,6 +628,19 @@ func RequestDesktopFullFrame() {
RequestDesktopH264Keyframe() RequestDesktopH264Keyframe()
} }
func SetDesktopSoftwareH264(enabled bool) {
if desktopSoftwareH264.Swap(enabled) == enabled {
return
}
resetH264Encoder()
requestFullFrames(2)
log.Printf("capture: desktop software h264 %v", enabled)
}
func useDesktopSoftwareH264() bool {
return desktopSoftwareH264.Load()
}
func RequestHVNCFullFrame() { func RequestHVNCFullFrame() {
hvncPrevMu.Lock() hvncPrevMu.Lock()
hvncPrevFrame = nil hvncPrevFrame = nil
@@ -10,7 +10,7 @@ import (
) )
func tryBuildDirectH264Frame(display int) (wire.Frame, time.Duration, time.Duration, bool, error) { func tryBuildDirectH264Frame(display int) (wire.Frame, time.Duration, time.Duration, bool, error) {
if blockCodec() != "h264" || !useDesktopDuplication() { if blockCodec() != "h264" || useDesktopSoftwareH264() || !useDesktopDuplication() {
return wire.Frame{}, 0, 0, false, nil return wire.Frame{}, 0, 0, false, nil
} }
forceKeyframe := webrtcpub.ConsumeKeyframeRequest() forceKeyframe := webrtcpub.ConsumeKeyframeRequest()
@@ -547,6 +547,10 @@ func closeH264Encoder(slot *h264FrameEncoder) {
} }
func newWindowsH264Encoder(stream string, width, height, fps int) (h264FrameEncoder, error) { func newWindowsH264Encoder(stream string, width, height, fps int) (h264FrameEncoder, error) {
if stream == "desktop" && useDesktopSoftwareH264() {
log.Printf("capture: desktop software h264 requested; skipping native NVENC and Media Foundation hardware encoders")
return newMFSoftwareH264Encoder(width, height, fps)
}
if enc, err := newNativeH264Encoder(stream, width, height, fps); err == nil { if enc, err := newNativeH264Encoder(stream, width, height, fps); err == nil {
return enc, nil return enc, nil
} else { } else {
@@ -592,6 +596,21 @@ func newMFH264Encoder(width, height, fps int) (*mfH264Encoder, error) {
if fallbackErr != nil { if fallbackErr != nil {
return nil, fallbackErr return nil, fallbackErr
} }
return newMFH264EncoderFromSoftwareTransform(transform, fallbackHardware, fallbackProvider, width, height, fps)
}
func newMFSoftwareH264Encoder(width, height, fps int) (*mfH264Encoder, error) {
if err := ensureMFStartup(); err != nil {
return nil, err
}
transform, fallbackHardware, fallbackProvider, fallbackErr := createSoftwareH264Transform()
if fallbackErr != nil {
return nil, fallbackErr
}
return newMFH264EncoderFromSoftwareTransform(transform, fallbackHardware, fallbackProvider, width, height, fps)
}
func newMFH264EncoderFromSoftwareTransform(transform *mfTransform, fallbackHardware bool, fallbackProvider string, width, height, fps int) (*mfH264Encoder, error) {
enc := newMFH264EncoderFromTransform(transform, width, height, fps, fps, fallbackHardware, fallbackProvider, MFVideoFormat_NV12, "NV12") enc := newMFH264EncoderFromTransform(transform, width, height, fps, fps, fallbackHardware, fallbackProvider, MFVideoFormat_NV12, "NV12")
if err := enc.configure(); err != nil { if err := enc.configure(); err != nil {
enc.Close() enc.Close()
+53 -9
View File
@@ -2,23 +2,38 @@ package capture
import ( import (
"image" "image"
"sync" "sync/atomic"
) )
var rgbaPool sync.Pool const (
maxPooledRGBABuffers = 8
maxPooledRGBABytes = 64 << 20
maxTotalRGBABytes = 192 << 20
)
var (
rgbaPool = make(chan []byte, maxPooledRGBABuffers)
pooledRGBABytes atomic.Int64
)
func GetRGBA(w, h int) *image.RGBA { func GetRGBA(w, h int) *image.RGBA {
need := w * h * 4 need := w * h * 4
if need <= 0 { if need <= 0 {
return image.NewRGBA(image.Rect(0, 0, w, h)) return image.NewRGBA(image.Rect(0, 0, w, h))
} }
if v := rgbaPool.Get(); v != nil { for attempts := len(rgbaPool); attempts > 0; attempts-- {
if buf, ok := v.([]byte); ok && cap(buf) >= need { select {
return &image.RGBA{ case buf := <-rgbaPool:
Pix: buf[:need], pooledRGBABytes.Add(-int64(cap(buf)))
Stride: w * 4, if cap(buf) >= need && cap(buf) <= maxReusableCapacity(need) {
Rect: image.Rect(0, 0, w, h), return &image.RGBA{
Pix: buf[:need],
Stride: w * 4,
Rect: image.Rect(0, 0, w, h),
}
} }
default:
attempts = 0
} }
} }
return image.NewRGBA(image.Rect(0, 0, w, h)) return image.NewRGBA(image.Rect(0, 0, w, h))
@@ -28,5 +43,34 @@ func PutRGBA(img *image.RGBA) {
if img == nil || len(img.Pix) == 0 { if img == nil || len(img.Pix) == 0 {
return return
} }
rgbaPool.Put(img.Pix) buf := img.Pix
capacity := cap(buf)
if capacity <= 0 || capacity > maxPooledRGBABytes {
return
}
for {
cur := pooledRGBABytes.Load()
if cur+int64(capacity) > maxTotalRGBABytes {
return
}
if pooledRGBABytes.CompareAndSwap(cur, cur+int64(capacity)) {
break
}
}
select {
case rgbaPool <- buf:
default:
pooledRGBABytes.Add(-int64(capacity))
}
}
func maxReusableCapacity(need int) int {
limit := need * 2
if limit < need+(1<<20) {
limit = need + (1 << 20)
}
if limit > maxPooledRGBABytes {
limit = maxPooledRGBABytes
}
return limit
} }
@@ -687,7 +687,9 @@ func (s *duplicationState) closeLocked() {
s.bounds = image.Rectangle{} s.bounds = image.Rectangle{}
s.cursorBounds = image.Rectangle{} s.cursorBounds = image.Rectangle{}
s.display = -1 s.display = -1
PutRGBA(s.lastBase)
s.lastBase = nil s.lastBase = nil
PutRGBA(s.lastFrame)
s.lastFrame = nil s.lastFrame = nil
s.lastFrameAt = time.Time{} s.lastFrameAt = time.Time{}
s.cursorScratch = nil s.cursorScratch = nil
@@ -807,11 +809,15 @@ func (s *duplicationState) capture(display int) (*image.RGBA, error) {
} }
} }
// Cache an independent clone so the timeout path (which serves the cached // Cache an independent copy so the timeout path (which serves the cached
// frame back to callers) doesn't alias a buffer the caller will return to // frame back to callers) doesn't alias a buffer the caller will return to
// the RGBA pool via PutRGBA. // the RGBA pool via PutRGBA.
s.lastBase = cloneRGBA(img) captured := img
img = s.composeFrame(img, width, height) s.copyLastBaseLocked(captured)
img = s.composeFrame(captured, width, height)
if img != captured {
PutRGBA(captured)
}
if img == s.cursorScratch { if img == s.cursorScratch {
// cursorScratch is reused across captures; clone before returning so // cursorScratch is reused across captures; clone before returning so
// the next compose doesn't write into a buffer the caller has pooled. // the next compose doesn't write into a buffer the caller has pooled.
@@ -823,6 +829,26 @@ func (s *duplicationState) capture(display int) (*image.RGBA, error) {
return img, nil return img, nil
} }
func (s *duplicationState) copyLastBaseLocked(src *image.RGBA) {
if src == nil {
PutRGBA(s.lastBase)
s.lastBase = nil
return
}
width := src.Rect.Dx()
height := src.Rect.Dy()
need := len(src.Pix)
if s.lastBase != nil && cap(s.lastBase.Pix) >= need && cap(s.lastBase.Pix) <= need*2 {
s.lastBase.Pix = s.lastBase.Pix[:need]
s.lastBase.Stride = width * 4
s.lastBase.Rect = image.Rect(0, 0, width, height)
copy(s.lastBase.Pix, src.Pix)
return
}
PutRGBA(s.lastBase)
s.lastBase = cloneRGBA(src)
}
func (s *duplicationState) captureH264(display int, forceKeyframe bool) ([]byte, int, int, time.Duration, time.Duration, bool, error) { func (s *duplicationState) captureH264(display int, forceKeyframe bool) ([]byte, int, int, time.Duration, time.Duration, bool, error) {
capStart := time.Now() capStart := time.Now()
s.mu.Lock() s.mu.Lock()
@@ -774,6 +774,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
payload, _ := envelope["payload"].(map[string]interface{}) payload, _ := envelope["payload"].(map[string]interface{})
quality := 90 quality := 90
codec := "" codec := ""
softwareH264 := false
reason := "" reason := ""
source := "" source := ""
if payload != nil { if payload != nil {
@@ -783,6 +784,9 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
if v, ok := payload["codec"].(string); ok { if v, ok := payload["codec"].(string); ok {
codec = v codec = v
} }
if v, ok := payload["softwareH264"].(bool); ok {
softwareH264 = v
}
if v, ok := payload["reason"].(string); ok { if v, ok := payload["reason"].(string); ok {
reason = strings.TrimSpace(v) reason = strings.TrimSpace(v)
} }
@@ -790,11 +794,13 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
source = strings.TrimSpace(v) source = strings.TrimSpace(v)
} }
} }
softwareH264 = softwareH264 && strings.EqualFold(strings.TrimSpace(codec), "h264")
if source != "" || reason != "" { if source != "" || reason != "" {
log.Printf("desktop: set quality=%d codec=%s source=%s reason=%s", quality, codec, source, reason) log.Printf("desktop: set quality=%d codec=%s software_h264=%v source=%s reason=%s", quality, codec, softwareH264, source, reason)
} else { } else {
log.Printf("desktop: set quality=%d codec=%s", quality, codec) log.Printf("desktop: set quality=%d codec=%s software_h264=%v", quality, codec, softwareH264)
} }
capture.SetDesktopSoftwareH264(softwareH264)
capture.SetQualityAndCodec(quality, codec) capture.SetQualityAndCodec(quality, codec)
sendCommandResultSafe(env, cmdID, true, "") sendCommandResultSafe(env, cmdID, true, "")
return nil return nil
+30 -5
View File
@@ -70,6 +70,7 @@ import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./shared-ui-s
const qualitySlider = document.getElementById("qualitySlider"); const qualitySlider = document.getElementById("qualitySlider");
const qualityValue = document.getElementById("qualityValue"); const qualityValue = document.getElementById("qualityValue");
const codecH264 = document.getElementById("codecH264"); const codecH264 = document.getElementById("codecH264");
const softwareH264Ctrl = document.getElementById("softwareH264Ctrl");
const codecMode = document.getElementById("codecMode"); const codecMode = document.getElementById("codecMode");
const canvas = document.getElementById("frameCanvas"); const canvas = document.getElementById("frameCanvas");
const canvasContainer = document.getElementById("canvasContainer"); const canvasContainer = document.getElementById("canvasContainer");
@@ -403,6 +404,7 @@ import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./shared-ui-s
if (kbdCtrl && typeof settings.keyboard === "boolean") kbdCtrl.checked = settings.keyboard; if (kbdCtrl && typeof settings.keyboard === "boolean") kbdCtrl.checked = settings.keyboard;
if (cursorCtrl && typeof settings.cursor === "boolean") cursorCtrl.checked = settings.cursor; if (cursorCtrl && typeof settings.cursor === "boolean") cursorCtrl.checked = settings.cursor;
if (duplicationCtrl && typeof settings.duplication === "boolean") duplicationCtrl.checked = settings.duplication; if (duplicationCtrl && typeof settings.duplication === "boolean") duplicationCtrl.checked = settings.duplication;
if (softwareH264Ctrl && typeof settings.softwareH264 === "boolean") softwareH264Ctrl.checked = settings.softwareH264;
if (clipboardSyncCtrl && typeof settings.clipboardSync === "boolean") clipboardSyncCtrl.checked = settings.clipboardSync; if (clipboardSyncCtrl && typeof settings.clipboardSync === "boolean") clipboardSyncCtrl.checked = settings.clipboardSync;
if (audioCtrl && typeof settings.audio === "boolean") audioCtrl.checked = settings.audio; if (audioCtrl && typeof settings.audio === "boolean") audioCtrl.checked = settings.audio;
if (typeof settings.preferH264 === "boolean") { if (typeof settings.preferH264 === "boolean") {
@@ -423,6 +425,7 @@ import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./shared-ui-s
keyboard: !!kbdCtrl?.checked, keyboard: !!kbdCtrl?.checked,
cursor: !!cursorCtrl?.checked, cursor: !!cursorCtrl?.checked,
duplication: !!duplicationCtrl?.checked, duplication: !!duplicationCtrl?.checked,
softwareH264: !!softwareH264Ctrl?.checked,
clipboardSync: !!clipboardSyncCtrl?.checked, clipboardSync: !!clipboardSyncCtrl?.checked,
audio: !!audioCtrl?.checked, audio: !!audioCtrl?.checked,
audioTransport: getAudioTransport(), audioTransport: getAudioTransport(),
@@ -439,6 +442,9 @@ import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./shared-ui-s
codecH264.checked = prefersH264; codecH264.checked = prefersH264;
codecH264.disabled = typeof VideoDecoder !== "function"; codecH264.disabled = typeof VideoDecoder !== "function";
} }
if (softwareH264Ctrl) {
softwareH264Ctrl.disabled = !prefersH264;
}
function setCodecModeLabel(mode, detail) { function setCodecModeLabel(mode, detail) {
if (!codecMode) return; if (!codecMode) return;
@@ -1216,12 +1222,17 @@ import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./shared-ui-s
function pushQuality(val) { function pushQuality(val) {
const q = Number(val) || 90; const q = Number(val) || 90;
const codec = q >= 100 ? "raw" : (prefersH264 ? "h264" : "jpeg"); const codec = q >= 100 ? "raw" : (prefersH264 ? "h264" : "jpeg");
console.debug("rd: pushQuality val=", val, "q=", q, "codec=", codec); const softwareH264 = codec === "h264" && useSoftwareH264();
console.debug("rd: pushQuality val=", val, "q=", q, "codec=", codec, "softwareH264=", softwareH264);
setCodecModeLabel(codec, "requested"); setCodecModeLabel(codec, "requested");
if (codec === "h264") { if (codec === "h264" && !softwareH264) {
ensureDuplicationForH264(); ensureDuplicationForH264();
} }
sendCmd("desktop_set_quality", { quality: q, codec }); sendCmd("desktop_set_quality", { quality: q, codec, softwareH264 });
}
function useSoftwareH264() {
return !!softwareH264Ctrl?.checked && prefersH264;
} }
function ensureDuplicationForH264() { function ensureDuplicationForH264() {
@@ -1236,7 +1247,10 @@ import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./shared-ui-s
codecH264.addEventListener("change", function () { codecH264.addEventListener("change", function () {
resetH264SessionState(); resetH264SessionState();
prefersH264 = !!codecH264.checked && typeof VideoDecoder === "function"; prefersH264 = !!codecH264.checked && typeof VideoDecoder === "function";
if (prefersH264) { if (softwareH264Ctrl) {
softwareH264Ctrl.disabled = !prefersH264;
}
if (prefersH264 && !useSoftwareH264()) {
ensureDuplicationForH264(); ensureDuplicationForH264();
} }
if (!prefersH264) { if (!prefersH264) {
@@ -1249,6 +1263,15 @@ import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./shared-ui-s
sharedSettingsSaver.scheduleSave(); sharedSettingsSaver.scheduleSave();
}); });
} }
if (softwareH264Ctrl) {
softwareH264Ctrl.addEventListener("change", function () {
resetH264SessionState();
if (qualitySlider) {
pushQuality(qualitySlider.value);
}
sharedSettingsSaver.scheduleSave();
});
}
function pushResolution() { function pushResolution() {
if (resolutionSelect) { if (resolutionSelect) {
@@ -1335,7 +1358,7 @@ import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./shared-ui-s
if (qualitySlider) { if (qualitySlider) {
pushQuality(qualitySlider.value); pushQuality(qualitySlider.value);
} }
if (prefersH264) { if (prefersH264 && !useSoftwareH264()) {
ensureDuplicationForH264(); ensureDuplicationForH264();
} }
pushTargetFps(); pushTargetFps();
@@ -1588,6 +1611,7 @@ import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./shared-ui-s
prefersH264 = false; prefersH264 = false;
destroyVideoDecoder(); destroyVideoDecoder();
if (codecH264) codecH264.checked = false; if (codecH264) codecH264.checked = false;
if (softwareH264Ctrl) softwareH264Ctrl.disabled = true;
sharedSettingsSaver.scheduleSave(); sharedSettingsSaver.scheduleSave();
console.warn("rd: falling back to jpeg codec", reasonText); console.warn("rd: falling back to jpeg codec", reasonText);
const q = Number(qualitySlider?.value) || 90; const q = Number(qualitySlider?.value) || 90;
@@ -1636,6 +1660,7 @@ import { createSharedUiSettingsSaver, loadSharedUiSettings } from "./shared-ui-s
sendCmd("desktop_set_quality", { sendCmd("desktop_set_quality", {
quality: q, quality: q,
codec: "h264", codec: "h264",
softwareH264: useSoftwareH264(),
source: "rd_viewer", source: "rd_viewer",
reason: "h264_recovery_quality_push", reason: "h264_recovery_quality_push",
}); });
@@ -198,6 +198,9 @@
</label> </label>
<span id="codecMode" class="pill pill-ghost text-slate-100 text-xs">Codec: auto</span> <span id="codecMode" class="pill pill-ghost text-slate-100 text-xs">Codec: auto</span>
</div> </div>
<label class="mt-2 flex items-center gap-2 text-sm text-slate-200" title="Skip NVENC/direct GPU H.264 and use the software H.264 fallback path">
<input type="checkbox" id="softwareH264Ctrl" /> Software H.264
</label>
<label class="mt-2 flex items-center gap-2 text-sm text-slate-200" title="Use GPU desktop duplication (Windows only)"> <label class="mt-2 flex items-center gap-2 text-sm text-slate-200" title="Use GPU desktop duplication (Windows only)">
<input type="checkbox" id="duplicationCtrl" /> GPU Capture <input type="checkbox" id="duplicationCtrl" /> GPU Capture
</label> </label>
@@ -132,6 +132,7 @@ describe("remote desktop viewer control", () => {
display: 0, display: 0,
quality: 90, quality: 90,
codec: "h264", codec: "h264",
softwareH264: false,
duplication: true, duplication: true,
maxHeight: 1080, maxHeight: 1080,
maxFps: 120, maxFps: 120,
@@ -177,6 +177,7 @@ export const rdStreamingState = new Map<string, {
display: number; display: number;
quality: number; quality: number;
codec: string; codec: string;
softwareH264: boolean;
duplication: boolean; duplication: boolean;
maxHeight: number; maxHeight: number;
maxFps: number; maxFps: number;
@@ -201,6 +202,7 @@ function defaultRdStreamingState() {
display: 0, display: 0,
quality: 90, quality: 90,
codec: "", codec: "",
softwareH264: false,
duplication: false, duplication: false,
maxHeight: 0, maxHeight: 0,
maxFps: 120, maxFps: 120,
@@ -507,23 +509,26 @@ export function handleRemoteDesktopViewerMessage(ws: ServerWebSocket<SocketData>
case "desktop_set_quality": { case "desktop_set_quality": {
const newQuality = Number(payload.quality) || 90; const newQuality = Number(payload.quality) || 90;
const newCodec = String(payload.codec || "").toLowerCase(); const newCodec = String(payload.codec || "").toLowerCase();
const newSoftwareH264 = newCodec === "h264" && !!(payload as any).softwareH264;
const reason = typeof payload.reason === "string" const reason = typeof payload.reason === "string"
? payload.reason.slice(0, 512) ? payload.reason.slice(0, 512)
: ""; : "";
const source = typeof payload.source === "string" const source = typeof payload.source === "string"
? payload.source.slice(0, 128) ? payload.source.slice(0, 128)
: ""; : "";
if (state.quality !== newQuality || state.codec !== newCodec) { if (state.quality !== newQuality || state.codec !== newCodec || state.softwareH264 !== newSoftwareH264) {
sendDesktopCommand(target, "desktop_set_quality", { sendDesktopCommand(target, "desktop_set_quality", {
quality: newQuality, quality: newQuality,
codec: newCodec, codec: newCodec,
softwareH264: newSoftwareH264,
...(reason ? { reason } : {}), ...(reason ? { reason } : {}),
...(source ? { source } : {}), ...(source ? { source } : {}),
}); });
state.quality = newQuality; state.quality = newQuality;
state.codec = newCodec; state.codec = newCodec;
state.softwareH264 = newSoftwareH264;
rdStreamingState.set(clientId, state); rdStreamingState.set(clientId, state);
logger.debug(`[rd] set quality=${newQuality} codec=${newCodec || "(default)"}${source ? ` source=${source}` : ""}${reason ? ` reason=${reason}` : ""}`); logger.debug(`[rd] set quality=${newQuality} codec=${newCodec || "(default)"} software_h264=${newSoftwareH264}${source ? ` source=${source}` : ""}${reason ? ` reason=${reason}` : ""}`);
} }
break; break;
} }
+2 -1
View File
@@ -27,10 +27,11 @@ set PORT=5173
set OVERLORD_AGENT_TOKEN=dev-token-insecure-local-only set OVERLORD_AGENT_TOKEN=dev-token-insecure-local-only
set OVERLORD_DISABLE_AGENT_AUTH=true set OVERLORD_DISABLE_AGENT_AUTH=true
set LOG_LEVEL=debug set LOG_LEVEL=debug
set CGO_ENABLED=1
start "Overlord-Server" cmd /k "cd /d ""%ROOT%Overlord-Server"" && set OVERLORD_DISABLE_AGENT_AUTH=true && set OVERLORD_AGENT_TOKEN=dev-token-insecure-local-only && set LOG_LEVEL=debug && set NODE_ENV=development && bun install && bun run start" start "Overlord-Server" cmd /k "cd /d ""%ROOT%Overlord-Server"" && set OVERLORD_DISABLE_AGENT_AUTH=true && set OVERLORD_AGENT_TOKEN=dev-token-insecure-local-only && set LOG_LEVEL=debug && set NODE_ENV=development && bun install && bun run start"
timeout /t 3 /nobreak >nul timeout /t 3 /nobreak >nul
start "Overlord-Client" cmd /k "cd /d ""%ROOT%Overlord-Client"" && set OVERLORD_SERVER=wss://localhost:5173 && set OVERLORD_AGENT_TOKEN=dev-token-insecure-local-only && set OVERLORD_TLS_INSECURE_SKIP_VERIFY=true && set OVERLORD_MODE=dev && set OVERLORD_CAPTURE_METRICS=true && set GOINSECURE=* && set GOSUMDB=off && set GOPROXY=https://proxy.golang.org,direct && go mod tidy && go run ./cmd/agent" start "Overlord-Client" cmd /k "cd /d ""%ROOT%Overlord-Client"" && set CGO_ENABLED=1 && set OVERLORD_SERVER=wss://localhost:5173 && set OVERLORD_AGENT_TOKEN=dev-token-insecure-local-only && set OVERLORD_TLS_INSECURE_SKIP_VERIFY=true && set OVERLORD_MODE=dev && set OVERLORD_CAPTURE_METRICS=true && set GOINSECURE=* && set GOSUMDB=off && set GOPROXY=https://proxy.golang.org,direct && go mod tidy && go run ./cmd/agent"
echo Done. Terminals stay open (/k) for logs. echo Done. Terminals stay open (/k) for logs.
endlocal endlocal