Add Discord-native hybrid front-end for Jarvis (bot + bridge)
Some checks failed
Release / semantic-release (push) Successful in 59s
tests / Unit tests (Linux, Python 3.11) (push) Successful in 13m45s
Release / build-linux (push) Failing after 7m47s
Release / build-windows (push) Has been cancelled
Release / build-macos (arm64, macos-latest) (push) Has been cancelled
Release / build-macos (x64, macos-15-intel) (push) Has been cancelled
Release / release-main (push) Has been cancelled
Release / release-develop (push) Has been cancelled

Transform isair/jarvis into a Discord-controlled voice assistant running on
the Ubuntu VNC desktop, keeping the mature ~39k-line Python brain intact.

- bot/ (Node + bun, discord.js): /자비스 slash commands (ephemeral),
  voice channel join + voice receive/playback, pluggable VNC screen broadcast
  (selfbot live / noVNC / screenshot)
- bridge/ (Python, Flask): wraps jarvis STT + run_reply_engine + Piper TTS
  behind a thin localhost HTTP API
- .env.example, scripts/ (start_bridge/start_bot/dev), README rewrite,
  docs/language-comparison.md and docs/vnc-xfce-setup.md

Language decision: hybrid (Python brain + Node/bun Discord layer) because
Discord blocks bot video; native screen broadcast only works via a Node
selfbot library.
This commit is contained in:
javis-bot
2026-06-09 14:51:05 +09:00
parent a5bf8d1826
commit c4abf63f38
308 changed files with 94135 additions and 1 deletions

View File

@@ -0,0 +1,284 @@
<#
.SYNOPSIS
Download and install CUDA libraries for GPU-accelerated speech recognition.
.DESCRIPTION
Downloads NVIDIA cuBLAS and cuDNN libraries from PyPI wheel packages
and extracts the DLLs into the target directory. Wheels are just ZIP
files, so no Python is needed.
The script is intended to be safe to re-run: a stale marker file from
a previous half-successful install does not cause us to skip work.
Every run probes for the expected DLLs first, downloads what's
missing, verifies SHA256 against the digest PyPI returns, verifies
that every expected DLL ended up on disk, and only then writes the
marker. Output is also written to a transcript log so failures from
Inno Setup's hidden invocation are recoverable.
Invoked by the Inno Setup installer when the user opts into GPU
acceleration, by the tray-menu recovery action, or manually:
powershell -ExecutionPolicy Bypass -File install_cuda.ps1 `
-TargetDir "C:\Program Files\Jarvis\cuda"
.PARAMETER TargetDir
Directory to extract CUDA DLLs into (e.g. {app}\cuda).
.PARAMETER LogPath
Optional path for the transcript log. Defaults to {TargetDir}\install.log.
.PARAMETER PyPIIndexUrl
Base URL for the PyPI JSON API. Override for testing only.
.PARAMETER SkipGpuCheck
Skip the local nvcuda.dll check. Used by tests; never set in production.
#>
param(
[Parameter(Mandatory=$true)]
[string]$TargetDir,
[string]$LogPath,
[string]$PyPIIndexUrl = "https://pypi.org/pypi",
[switch]$SkipGpuCheck
)
$ErrorActionPreference = "Stop"
# Suppress the progress bar before any Invoke-WebRequest call. With the
# default 'Continue' preference, PowerShell repaints the progress UI on
# every byte, which slows large downloads by 510x; the 643 MB cuDNN
# wheel goes from ~3 minutes to half an hour on common connections.
$ProgressPreference = "SilentlyContinue"
# ---------------------------------------------------------------------------
# Package manifest
# ---------------------------------------------------------------------------
# Pinned versions known to work with CTranslate2 4.x (CUDA 12, cuDNN 9).
# `ExpectedDlls` is the list we verify on disk after extraction; if any are
# missing or suspiciously small the install fails loudly instead of leaving
# a stale marker behind.
$packages = @(
@{
Name = "nvidia-cublas-cu12"
Version = "12.9.1.4"
Wheel = "nvidia_cublas_cu12-12.9.1.4-py3-none-win_amd64.whl"
Prefix = "nvidia/cublas/bin/"
ExpectedDlls = @(
"cublas64_12.dll",
"cublasLt64_12.dll",
"nvblas64_12.dll"
)
},
@{
Name = "nvidia-cudnn-cu12"
Version = "9.20.0.48"
Wheel = "nvidia_cudnn_cu12-9.20.0.48-py3-none-win_amd64.whl"
Prefix = "nvidia/cudnn/bin/"
ExpectedDlls = @(
"cudnn64_9.dll",
"cudnn_adv64_9.dll",
"cudnn_cnn64_9.dll",
"cudnn_engines_precompiled64_9.dll",
"cudnn_engines_runtime_compiled64_9.dll",
"cudnn_graph64_9.dll",
"cudnn_heuristic64_9.dll",
"cudnn_ops64_9.dll"
)
}
)
# Minimum reasonable size for a CUDA DLL. The smallest real cuDNN file is
# ~260 KB (`cudnn64_9.dll`); anything below this is almost certainly a
# truncated download or an AV stub. Catch this case explicitly so we don't
# write a marker for a corrupt install.
$MIN_DLL_BYTES = 4096
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
function Get-AllExpectedDlls {
$names = New-Object System.Collections.Generic.List[string]
foreach ($pkg in $packages) {
foreach ($dll in $pkg.ExpectedDlls) {
$names.Add($dll) | Out-Null
}
}
return ,$names.ToArray()
}
function Test-InstalledDlls {
param([string]$Dir)
$missing = New-Object System.Collections.Generic.List[string]
foreach ($name in (Get-AllExpectedDlls)) {
$path = Join-Path $Dir $name
if (-not (Test-Path $path)) {
$missing.Add($name) | Out-Null
continue
}
$size = (Get-Item $path).Length
if ($size -lt $MIN_DLL_BYTES) {
$missing.Add("$name (truncated: $size bytes)") | Out-Null
}
}
return ,$missing.ToArray()
}
function Get-WheelInfo {
param([string]$PackageName, [string]$Version, [string]$WheelFilename)
$url = "$PyPIIndexUrl/$PackageName/$Version/json"
$resp = Invoke-RestMethod -Uri $url -UseBasicParsing -TimeoutSec 60
foreach ($file in $resp.urls) {
if ($file.filename -eq $WheelFilename) {
$sha256 = $null
if ($file.digests -and $file.digests.sha256) {
$sha256 = $file.digests.sha256
}
return @{ Url = $file.url; Sha256 = $sha256 }
}
}
throw "Wheel $WheelFilename not found on PyPI for $PackageName==$Version"
}
function Test-FileSha256 {
param([string]$Path, [string]$Expected)
if ([string]::IsNullOrEmpty($Expected)) {
# PyPI always returns digests for hosted wheels; if it didn't, fail
# loudly rather than silently skip the integrity check.
throw "PyPI did not return a SHA256 digest for $Path"
}
$actual = (Get-FileHash -Path $Path -Algorithm SHA256).Hash.ToLower()
if ($actual -ne $Expected.ToLower()) {
throw "SHA256 mismatch for $Path (expected $Expected, got $actual)"
}
}
# ---------------------------------------------------------------------------
# Begin install
# ---------------------------------------------------------------------------
New-Item -ItemType Directory -Force -Path $TargetDir | Out-Null
if (-not $LogPath) {
$LogPath = Join-Path $TargetDir "install.log"
}
# Ensure log directory exists, then start a transcript so every line — Write-Host,
# Write-Error, exceptions — lands in the file. The Inno Setup invocation runs
# hidden, so without this a failure is invisible to the user.
$logDir = Split-Path -Parent $LogPath
if ($logDir) { New-Item -ItemType Directory -Force -Path $logDir | Out-Null }
try {
Start-Transcript -Path $LogPath -Force | Out-Null
$transcriptStarted = $true
} catch {
$transcriptStarted = $false
}
$marker = Join-Path $TargetDir ".cuda_installed"
try {
# --- Pre-flight: NVIDIA GPU driver detection ---
if (-not $SkipGpuCheck) {
$nvcudaPaths = @(
(Join-Path $env:SystemRoot "System32\nvcuda.dll"),
(Join-Path $env:windir "System32\nvcuda.dll")
)
$gpuFound = $false
foreach ($p in $nvcudaPaths) {
if (Test-Path $p) { $gpuFound = $true; break }
}
if (-not $gpuFound) {
Write-Host "No NVIDIA GPU detected, skipping CUDA installation."
return # exit 0; no GPU is not a failure
}
}
# --- Idempotence: skip only if every expected DLL is actually on disk ---
$missing = Test-InstalledDlls -Dir $TargetDir
if ((Test-Path $marker) -and $missing.Length -eq 0) {
Write-Host "CUDA libraries already installed and verified."
return
}
if (Test-Path $marker) {
Write-Host "Stale marker found but DLLs missing/truncated; reinstalling..."
Write-Host " Missing: $($missing -join ', ')"
# Remove the marker up-front so a crash mid-install can't leave a
# falsely-green state.
Remove-Item -Force $marker -ErrorAction SilentlyContinue
}
Write-Host "Downloading CUDA libraries for GPU acceleration..."
Write-Host "Target: $TargetDir"
Write-Host "Log: $LogPath"
foreach ($pkg in $packages) {
Write-Host ""
Write-Host "Downloading $($pkg.Name) $($pkg.Version)..."
$info = Get-WheelInfo `
-PackageName $pkg.Name `
-Version $pkg.Version `
-WheelFilename $pkg.Wheel
$tmpFile = [System.IO.Path]::GetTempFileName() + ".whl"
try {
# Use Invoke-WebRequest: it's slower than WebClient on some
# systems but it raises on truncation rather than silently
# writing a partial file, which is the documented WebClient
# failure mode that motivated this rewrite.
Invoke-WebRequest -Uri $info.Url -OutFile $tmpFile -UseBasicParsing -TimeoutSec 600
Write-Host " Download complete."
Test-FileSha256 -Path $tmpFile -Expected $info.Sha256
Write-Host " SHA256 verified."
Write-Host " Extracting DLLs..."
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [System.IO.Compression.ZipFile]::OpenRead($tmpFile)
try {
foreach ($entry in $zip.Entries) {
if ($entry.FullName.StartsWith($pkg.Prefix) -and $entry.FullName.EndsWith(".dll")) {
$destPath = Join-Path $TargetDir $entry.Name
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $destPath, $true)
Write-Host " $($entry.Name)"
}
}
} finally {
$zip.Dispose()
}
} finally {
if (Test-Path $tmpFile) {
Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue
}
}
}
# --- Post-extract verification ---
$missingAfter = Test-InstalledDlls -Dir $TargetDir
if ($missingAfter.Length -gt 0) {
throw "Verification failed after extract; missing/truncated: $($missingAfter -join ', ')"
}
# --- Marker is the LAST thing written ---
$markerContent = $packages | ForEach-Object { "$($_.Name)==$($_.Version)" }
$markerContent | Out-File -FilePath $marker -Encoding utf8
Write-Host ""
Write-Host "CUDA libraries installed successfully!"
} catch {
Write-Host ""
Write-Host "CUDA installation FAILED: $_"
Write-Host "See transcript at $LogPath"
if ($transcriptStarted) { Stop-Transcript | Out-Null }
exit 1
} finally {
if ($transcriptStarted) {
try { Stop-Transcript | Out-Null } catch { }
}
}

View File

@@ -0,0 +1,150 @@
; Jarvis Inno Setup Script
; Builds a Windows installer from the PyInstaller onedir output.
;
; Usage:
; iscc installer\windows\jarvis_setup.iss
;
; Expects the PyInstaller onedir output at dist\Jarvis\
#define MyAppName "Jarvis"
#define MyAppExeName "Jarvis.exe"
#define MyAppPublisher ""
; Version can be overridden via ISCC command line: /DMyAppVersion=1.2.3
#ifndef MyAppVersion
#define MyAppVersion "0.0.0"
#endif
; VC++ Redistributable download URL (VS 2015-2022 x64)
#define VCRedistURL "https://aka.ms/vs/17/release/vc_redist.x64.exe"
[Setup]
AppId={{B8A3D6F1-7C42-4E5A-9D12-3F8E6A1B5C90}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
DefaultDirName={autopf}\{#MyAppName}
DefaultGroupName={#MyAppName}
DisableProgramGroupPage=yes
OutputDir=..\..\dist
OutputBaseFilename=Jarvis-Setup-x64
Compression=lzma2
SolidCompression=yes
WizardStyle=modern
ArchitecturesInstallIn64BitMode=x64compatible
ArchitecturesAllowed=x64compatible
UninstallDisplayIcon={app}\{#MyAppExeName}
PrivilegesRequired=admin
SetupIconFile=..\..\src\desktop_app\desktop_assets\icon_idle.ico
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "cudalibs"; Description: "Download NVIDIA CUDA libraries for GPU-accelerated speech recognition (~1.1 GB download)"; GroupDescription: "GPU Acceleration:"; Check: HasNvidiaGPU; Flags: unchecked
[Files]
; Bundle the entire PyInstaller onedir output
Source: "..\..\dist\Jarvis\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; Bundle the CUDA installer script (PowerShell — no Python needed)
Source: "install_cuda.ps1"; DestDir: "{app}"; Flags: ignoreversion
[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{group}\Uninstall {#MyAppName}"; Filename: "{uninstallexe}"
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run]
; Install VC++ Redistributable silently if missing
Filename: "{tmp}\vc_redist.x64.exe"; Parameters: "/quiet /norestart"; StatusMsg: "Installing Visual C++ Redistributable..."; Flags: waituntilterminated; Check: VCRedistNeeded
; Download CUDA libraries if task selected (uses PowerShell to download and extract wheels).
; -LogPath ensures every run leaves a transcript at {app}\cuda\install.log so a hidden
; failure here is recoverable from the bug-report flow and the tray "Reinstall GPU libraries" action.
Filename: "powershell.exe"; Parameters: "-NoProfile -ExecutionPolicy Bypass -File ""{app}\install_cuda.ps1"" -TargetDir ""{app}\cuda"" -LogPath ""{app}\cuda\install.log"""; StatusMsg: "Downloading CUDA libraries for GPU acceleration (this may take several minutes)..."; Flags: waituntilterminated runhidden; Tasks: cudalibs; AfterInstall: VerifyCudaInstall
; Launch the application after installation
Filename: "{app}\{#MyAppExeName}"; Description: "Launch {#MyAppName}"; Flags: nowait postinstall skipifsilent
[UninstallDelete]
Type: filesandordirs; Name: "{app}"
[Code]
// Check whether the VC++ 2015-2022 runtime is already installed
function VCRedistNeeded: Boolean;
var
Version: String;
begin
// Check for VC++ 2015-2022 x64 runtime via registry
Result := True;
if RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64', 'Version', Version) then
begin
// Runtime is installed
Result := False;
end;
end;
// Check whether an NVIDIA GPU is present by looking for the CUDA driver DLL
function HasNvidiaGPU: Boolean;
var
NvSmiPath: String;
begin
// nvcuda.dll is the CUDA driver — present on any system with NVIDIA drivers
NvSmiPath := ExpandConstant('{sys}\nvcuda.dll');
Result := FileExists(NvSmiPath);
end;
// Surface CUDA install failures to the user instead of silently letting the
// installer report success. install_cuda.ps1 only writes its marker after
// verifying every expected DLL is on disk, so a missing marker means the
// install really did fail and the user needs to know they can recover via
// the tray menu's "Reinstall GPU libraries" action.
procedure VerifyCudaInstall;
var
MarkerPath, LogPath: String;
begin
MarkerPath := ExpandConstant('{app}\cuda\.cuda_installed');
LogPath := ExpandConstant('{app}\cuda\install.log');
if not FileExists(MarkerPath) then
begin
Log('CUDA install marker not found at ' + MarkerPath + '; install failed.');
MsgBox(
'GPU library download did not complete. Jarvis will run on CPU.' #13#10 #13#10 +
'You can retry later from the tray menu via "Reinstall GPU libraries".' #13#10 #13#10 +
'Details: ' + LogPath,
mbInformation, MB_OK);
end;
end;
// Download VC++ Redistributable if needed
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssInstall then
begin
if VCRedistNeeded then
begin
// Download vc_redist.x64.exe from Microsoft
DownloadTemporaryFile('{#VCRedistURL}', 'vc_redist.x64.exe', '', nil);
end;
end;
end;
// After installation, clean up the old exe if the installer was launched
// from a legacy location (e.g. old updater placed it at a custom path).
// The installer can't delete itself while running, so we schedule a
// cmd /c del command that retries until the file is unlocked.
procedure DeinitializeSetup;
var
InstallerPath, InstalledDir: String;
ResultCode: Integer;
begin
InstallerPath := ExpandConstant('{srcexe}');
InstalledDir := ExpandConstant('{app}');
// Only clean up if the installer is NOT inside the installation directory
// (i.e. it was placed somewhere else by the old updater)
if Pos(Lowercase(InstalledDir), Lowercase(InstallerPath)) = 0 then
begin
Log('Scheduling cleanup of old installer at: ' + InstallerPath);
Exec('cmd.exe',
'/c ping -n 3 127.0.0.1 >nul & del /f "' + InstallerPath + '"',
'', SW_HIDE, ewNoWait, ResultCode);
end;
end;