Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 93 additions & 18 deletions install.ps1
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#Requires -Version 5.0
# SillyTavern Honcho Plugin Installer (Windows)
# Installs both the client extension and server plugin.
#
Expand All @@ -6,15 +7,14 @@
# irm https://raw.githubusercontent.com/plastic-labs/sillytavern-honcho/main/install.ps1 | iex
#
# Or if you've already cloned the repo:
# cd SillyTavern; .\path\to\sillytavern-honcho\install.ps1
# powershell -ExecutionPolicy Bypass -File .\path\to\sillytavern-honcho\install.ps1

$ST_DIR = if ($env:ST_DIR) { $env:ST_DIR } else { (Get-Location).Path }
$REPO_URL = "https://github.com/plastic-labs/sillytavern-honcho.git"
$EXT_DIR = Join-Path $ST_DIR "public\scripts\extensions\third-party\sillytavern-honcho"
$PLUGIN_DIR = Join-Path $ST_DIR "plugins\honcho-proxy"

# Verify we're in a SillyTavern directory
if (-not (Test-Path (Join-Path $ST_DIR "server.js")) -and -not (Test-Path (Join-Path $ST_DIR "package.json"))) {
if (-not (Test-Path (Join-Path $ST_DIR "server.js")) -or -not (Test-Path (Join-Path $ST_DIR "package.json"))) {
Write-Host "[!] Could not find SillyTavern at: $ST_DIR"
Write-Host " Run this script from your SillyTavern directory, or set ST_DIR:"
Write-Host ' $env:ST_DIR = "C:\path\to\SillyTavern"; .\install.ps1'
Expand All @@ -24,7 +24,7 @@ if (-not (Test-Path (Join-Path $ST_DIR "server.js")) -and -not (Test-Path (Join-
Write-Host "[*] Installing SillyTavern Honcho plugin..."
Write-Host " ST directory: $ST_DIR"

# 1. Install client extension
# 1. Client extension
if (Test-Path $EXT_DIR) {
Write-Host "[*] Client extension already exists, pulling latest..."
$null = git -C $EXT_DIR pull --ff-only 2>&1
Expand All @@ -40,44 +40,119 @@ if (Test-Path $EXT_DIR) {
}
}

# 2. Set up server plugin (directory junction from extension's plugin/ dir)
# 2. Server plugin junction
if (Test-Path $PLUGIN_DIR) {
Write-Host "[*] Server plugin already exists at $PLUGIN_DIR"
} else {
Write-Host "[*] Creating directory junction for server plugin..."
$pluginSource = Join-Path $EXT_DIR "plugin"
$null = cmd /c mklink /J "`"$PLUGIN_DIR`"" "`"$pluginSource`"" 2>&1
$null = New-Item -ItemType Junction -Path $PLUGIN_DIR -Target $pluginSource -ErrorAction SilentlyContinue
if (-not (Test-Path $PLUGIN_DIR)) {
Write-Host "[!] Failed to create junction. Try running PowerShell as Administrator."
$pluginParent = Split-Path $PLUGIN_DIR -Parent
Write-Host "[!] Failed to create junction at $PLUGIN_DIR."
Write-Host " Common causes:"
Write-Host " - Source ($pluginSource) and target ($PLUGIN_DIR) are on different drives (junctions can't cross volumes)"
Write-Host " - Source or target path is on a network share (junctions require local filesystem)"
Write-Host " - Current user lacks write permission on $pluginParent"
Write-Host " (run as Administrator only if SillyTavern is in a protected location like Program Files)"
exit 1
}
}

# 3. Install SDK dependencies
$npmCmdInfo = Get-Command npm.cmd -ErrorAction SilentlyContinue
if (-not $npmCmdInfo) {
Write-Host "[!] npm was not found on PATH. Install Node.js, reopen PowerShell, and retry."
exit 1
}
$npmCmd = $npmCmdInfo.Source
Write-Host "[*] Installing @honcho-ai/sdk..."
Push-Location $PLUGIN_DIR
$null = npm install --silent 2>&1
$null = & $npmCmd install --silent 2>&1
$npmExit = if ($?) { $LASTEXITCODE } else { 1 }
Pop-Location
if ($npmExit -ne 0) {
Write-Host "[!] npm install failed (exit $npmExit)."
Write-Host " Common causes: corporate proxy (set HTTPS_PROXY), SSL trust chain,"
Write-Host " AV locking node_modules, npm registry unreachable."
Write-Host " Re-run verbose to see the underlying error:"
Write-Host " cd `"$PLUGIN_DIR`"; npm install --verbose"
exit 1
}

# 4. Check config.yaml for server plugins
# 3.5 Bootstrap config.yaml — SillyTavern only creates it on first `npm start`.
$CONFIG = Join-Path $ST_DIR "config.yaml"
if (Test-Path $CONFIG) {
if (-not (Test-Path $CONFIG)) {
Write-Host "[*] Generating config.yaml by starting SillyTavern briefly..."
$bootLog = Join-Path $env:TEMP "silly-first-launch-$([System.IO.Path]::GetRandomFileName()).log"
$bootErr = "$bootLog.err"
$stProc = Start-Process -FilePath $npmCmd -ArgumentList "start" `
-WorkingDirectory $ST_DIR -WindowStyle Hidden -PassThru `
-RedirectStandardOutput $bootLog -RedirectStandardError $bootErr
Comment thread
coderabbitai[bot] marked this conversation as resolved.
for ($i = 0; $i -lt 60; $i++) {
if (Test-Path $CONFIG) { break }
if ($stProc.HasExited) {
Write-Host "[!] SillyTavern bootstrap exited early (code $($stProc.ExitCode))."
Write-Host " Inspect $bootLog (stdout) and $bootErr (stderr) for details."
exit 1
}
Start-Sleep -Seconds 1
}
if ($stProc -and -not $stProc.HasExited) {
$null = & taskkill.exe /F /T /PID $stProc.Id 2>&1
}
if (-not (Test-Path $CONFIG)) {
Write-Host "[!] config.yaml did not appear after 60s."
Write-Host " Inspect $bootLog (stdout) and $bootErr (stderr) for details."
exit 1
}
Write-Host "[*] config.yaml created at $CONFIG"
}

# 4. Enable server plugins in config.yaml
$content = Get-Content -Path $CONFIG -Raw
if ($content -match "(?m)^enableServerPlugins:[ \t]*true") {
Write-Host "[*] Server plugins already enabled in config.yaml"
} else {
# [ \t]* (not \s*) — .NET's \s matches \r\n; sed in install.sh is line-scoped.
$newContent = $content -replace "(?m)^enableServerPlugins:[ \t]*false", "enableServerPlugins: true"
# WriteAllText writes UTF-8 without BOM; Set-Content on PS 5.1 would inject a BOM that some YAML parsers reject.
[System.IO.File]::WriteAllText($CONFIG, $newContent)
$content = Get-Content -Path $CONFIG -Raw
if ($content -match "enableServerPlugins:\s*true") {
Write-Host "[*] Server plugins already enabled in config.yaml"
if ($content -match "(?m)^enableServerPlugins:[ \t]*true") {
Write-Host "[*] Enabled server plugins in config.yaml"
} else {
Write-Host ""
Write-Host "[!] Server plugins are NOT enabled in config.yaml."
Write-Host " Add or change this line in ${CONFIG}:"
Write-Host "[!] Could not set enableServerPlugins: true in $CONFIG"
Write-Host " Add this line manually and restart SillyTavern:"
Write-Host " enableServerPlugins: true"
}
}

# 5. Check for global Honcho config
# 5. Probe global Honcho config for a resolvable apiKey
$HONCHO_CONFIG = Join-Path $HOME ".honcho\config.json"
if (Test-Path $HONCHO_CONFIG) {
Write-Host "[*] Found global Honcho config at $HONCHO_CONFIG"
Write-Host " API key, workspace, and peer name will be auto-populated."
$resolvableKey = $false
$parseFailed = $false
try {
$cfg = Get-Content -Path $HONCHO_CONFIG -Raw | ConvertFrom-Json
$hostKey = $cfg.hosts.sillytavern.apiKey
$rootKey = $cfg.apiKey
if ($hostKey -or $rootKey) { $resolvableKey = $true }
} catch {
$parseFailed = $true
}
if ($parseFailed) {
Write-Host "[!] Found malformed Honcho config at $HONCHO_CONFIG"
Write-Host " Fix or remove it before re-running, or the plugin may recreate a minimal config and wipe existing settings."
exit 1
} elseif ($resolvableKey) {
Write-Host "[*] Found global Honcho config with resolvable apiKey at $HONCHO_CONFIG"
Write-Host " API key, workspace, and peer name will be auto-populated."
} else {
Write-Host "[*] Found $HONCHO_CONFIG but no resolvable apiKey."
Write-Host " (plugin checks hosts.sillytavern.apiKey, then root apiKey.)"
Write-Host " Enter your Honcho API key via the Extensions panel after restart."
Comment on lines +138 to +154

@coderabbitai coderabbitai Bot May 1, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align apiKey probe with actual plugin fallback chain.

At Line 138 and Line 153, the script treats hosts.sillytavern.apiKey as resolvable. But the provided plugin runtime paths (plugin/index.js:88-110, plugin/index.js:201-230) use SecretManager first, then root-level apiKey. This can report “auto-populated” when runtime auth still fails.

Suggested fix
-        $hostKey = $cfg.hosts.sillytavern.apiKey
         $rootKey = $cfg.apiKey
-        if ($hostKey -or $rootKey) { $resolvableKey = $true }
+        if ($rootKey) { $resolvableKey = $true }
...
-        Write-Host "    (plugin checks hosts.sillytavern.apiKey, then root apiKey.)"
+        Write-Host "    (plugin checks SillyTavern SecretManager first, then root apiKey.)"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$hostKey = $cfg.hosts.sillytavern.apiKey
$rootKey = $cfg.apiKey
if ($hostKey -or $rootKey) { $resolvableKey = $true }
} catch {
$parseFailed = $true
}
if ($parseFailed) {
Write-Host "[!] Found malformed Honcho config at $HONCHO_CONFIG"
Write-Host " Fix or remove it before re-running, or the plugin may recreate a minimal config and wipe existing settings."
exit 1
} elseif ($resolvableKey) {
Write-Host "[*] Found global Honcho config with resolvable apiKey at $HONCHO_CONFIG"
Write-Host " API key, workspace, and peer name will be auto-populated."
} else {
Write-Host "[*] Found $HONCHO_CONFIG but no resolvable apiKey."
Write-Host " (plugin checks hosts.sillytavern.apiKey, then root apiKey.)"
Write-Host " Enter your Honcho API key via the Extensions panel after restart."
$rootKey = $cfg.apiKey
if ($rootKey) { $resolvableKey = $true }
} catch {
$parseFailed = $true
}
if ($parseFailed) {
Write-Host "[!] Found malformed Honcho config at $HONCHO_CONFIG"
Write-Host " Fix or remove it before re-running, or the plugin may recreate a minimal config and wipe existing settings."
exit 1
} elseif ($resolvableKey) {
Write-Host "[*] Found global Honcho config with resolvable apiKey at $HONCHO_CONFIG"
Write-Host " API key, workspace, and peer name will be auto-populated."
} else {
Write-Host "[*] Found $HONCHO_CONFIG but no resolvable apiKey."
Write-Host " (plugin checks SillyTavern SecretManager first, then root apiKey.)"
Write-Host " Enter your Honcho API key via the Extensions panel after restart."
🧰 Tools
🪛 PSScriptAnalyzer (1.25.0)

[warning] Missing BOM encoding for non-ASCII encoded file 'install.ps1'

(PSUseBOMForUnicodeEncodedFile)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@install.ps1` around lines 138 - 154, The probe logic incorrectly treats
hosts.sillytavern.apiKey (hostKey) as sufficient for "resolvable" even though
the plugin runtime first consults the SecretManager and only falls back to the
root apiKey; update the check that sets resolvableKey so it mirrors the runtime
fallback: consider a SecretManager entry as the highest-priority resolvable
source and only mark resolvableKey true if a SecretManager config exists or the
root-level apiKey (rootKey) exists, rather than relying on
hosts.sillytavern.apiKey alone; adjust the conditional that examines
hostKey/rootKey (and any secret manager field) so the Write-Host message about
auto-population for HONCHO_CONFIG accurately reflects the runtime behavior.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not fixing since SecretManager path is dead — SECRET_KEYS.HONCHO isn't in ST's enum, so readSecret(undefined) always returns ''. Real runtime fallback is hosts.sillytavern.apiKey || globalConfig.apiKey. PR #14 removes the dead branch and codifies globalConfig as SSoT.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lowyelling, understood — thanks for the clarification. Since SECRET_KEYS.HONCHO is absent from ST's enum, readSecret(undefined) is a no-op and the effective fallback chain is hosts.sillytavern.apiKey → globalConfig.apiKey, which is exactly what the current probe logic reflects. My suggestion was based on an incorrect read of the runtime path. Makes sense to keep hostKey in the check until PR #14 lands.


✏️ Learnings added
Learnt from: lowyelling
Repo: plastic-labs/sillytavern-honcho PR: 13
File: install.ps1:138-154
Timestamp: 2026-05-01T19:59:36.570Z
Learning: In the sillytavern-honcho plugin (install.ps1 and plugin/index.js), the SecretManager path for the Honcho API key is dead code: `SECRET_KEYS.HONCHO` is not in SillyTavern's enum, so `readSecret(undefined)` always returns an empty string. The real runtime apiKey fallback chain is `hosts.sillytavern.apiKey` (from `~/.honcho/config.json`) first, then root-level `apiKey` (`globalConfig.apiKey`). PR `#14` removes the dead SecretManager branch and codifies globalConfig as the Single Source of Truth.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} else {
Write-Host ""
Write-Host "[i] No global Honcho config found at $HONCHO_CONFIG"
Expand Down
Loading