Skip to content

Commit 001c1ec

Browse files
committed
Remove OpenCode --install (no plugin/hook system)
OpenCode (opencode-ai/opencode) has no plugin or hook system. The JS plugin approach was based on incorrect documentation. Verified against actual source: config schema only supports agents, providers, LSP, MCP servers, and TUI settings. Removed: install_opencode, uninstall_opencode, is_opencode_installed, detect_opencode, opencode preset, auto-detection, tests, README entry. Codex support remains (verified against config.schema.json). 26/26 tests pass.
1 parent 54f4609 commit 001c1ec

File tree

3 files changed

+7
-140
lines changed

3 files changed

+7
-140
lines changed

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,8 @@ Options:
3939
| GitHub Copilot |||| `sessionEnd` | `.github/hooks/toasty.json` |
4040
| Gemini CLI |||| `AfterAgent` | `~/.gemini/settings.json` |
4141
| OpenAI Codex |||| `notify` | `~/.codex/config.toml` |
42-
| OpenCode |||| JS plugin | `~/.config/opencode/plugins/` |
4342

44-
- **Icon**: Built-in icon for toast notifications (⬜ = uses default Toasty icon)
43+
- **Icon**: Built-in icon for toast notifications
4544
- **Auto-Detect**: Toasty recognizes the agent's process and applies the preset automatically
4645
- **`--install`**: `toasty --install` can automatically configure the agent's hook
4746

main.cpp

Lines changed: 3 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ const AppPreset APP_PRESETS[] = {
5757
{ L"copilot", L"GitHub Copilot", IDI_COPILOT },
5858
{ L"gemini", L"Gemini", IDI_GEMINI },
5959
{ L"codex", L"Codex", IDI_CODEX },
60-
{ L"cursor", L"Cursor", IDI_CURSOR },
61-
{ L"opencode", L"OpenCode", IDI_TOASTY }
60+
{ L"cursor", L"Cursor", IDI_CURSOR }
6261
};
6362

6463
// Extract embedded PNG resource to temp file and return path
@@ -200,11 +199,6 @@ const AppPreset* check_command_line_for_preset(const std::wstring& cmdLine) {
200199
return find_preset(L"cursor");
201200
}
202201

203-
// Check for OpenCode
204-
if (lowerCmd.find(L"opencode") != std::wstring::npos) {
205-
return find_preset(L"opencode");
206-
}
207-
208202
return nullptr;
209203
}
210204

@@ -304,7 +298,7 @@ void print_usage() {
304298
<< L" -i, --icon <path> Custom icon path (PNG recommended, 48x48px)\n"
305299
<< L" -v, --version Show version and exit\n"
306300
<< L" -h, --help Show this help\n"
307-
<< L" --install [agent] Install hooks for AI CLI agents (claude, gemini, copilot, codex, opencode, or all)\n"
301+
<< L" --install [agent] Install hooks for AI CLI agents (claude, gemini, copilot, codex, or all)\n"
308302
<< L" --uninstall Remove hooks from all AI CLI agents\n"
309303
<< L" --status Show installation status\n"
310304
<< L" --register Re-register app for notifications (troubleshooting)\n"
@@ -887,11 +881,6 @@ bool detect_codex() {
887881
return path_exists(codexPath);
888882
}
889883

890-
bool detect_opencode() {
891-
std::wstring opencodePath = expand_env(L"%USERPROFILE%\\.config\\opencode");
892-
return path_exists(opencodePath);
893-
}
894-
895884
// Read file content as string
896885
std::string read_file(const std::wstring& path) {
897886
std::ifstream file(path, std::ios::binary);
@@ -1228,58 +1217,6 @@ bool install_codex(const std::wstring& exePath) {
12281217
return write_file(configPath, content);
12291218
}
12301219

1231-
// Install plugin for OpenCode
1232-
// OpenCode uses JS plugins at ~/.config/opencode/plugins/
1233-
bool install_opencode(const std::wstring& exePath) {
1234-
std::wstring pluginsDir = expand_env(L"%USERPROFILE%\\.config\\opencode\\plugins");
1235-
std::wstring pluginPath = pluginsDir + L"\\toasty.js";
1236-
1237-
fs::create_directories(pluginsDir);
1238-
1239-
if (path_exists(pluginPath)) {
1240-
std::string content = read_file(pluginPath);
1241-
if (content.find("toasty") != std::string::npos) {
1242-
return true; // Already installed
1243-
}
1244-
}
1245-
1246-
// Convert exe path to JS-safe string (forward slashes)
1247-
std::string exePathUtf8;
1248-
{
1249-
int size = WideCharToMultiByte(CP_UTF8, 0, exePath.c_str(), -1, nullptr, 0, nullptr, nullptr);
1250-
if (size > 0) {
1251-
exePathUtf8.resize(size - 1);
1252-
WideCharToMultiByte(CP_UTF8, 0, exePath.c_str(), -1, &exePathUtf8[0], size, nullptr, nullptr);
1253-
}
1254-
}
1255-
// Escape backslashes for JS string
1256-
std::string jsPath;
1257-
for (char c : exePathUtf8) {
1258-
if (c == '\\') jsPath += "\\\\";
1259-
else jsPath += c;
1260-
}
1261-
1262-
std::string plugin =
1263-
"// Toasty notification plugin for OpenCode\n"
1264-
"const { execSync } = require('child_process');\n"
1265-
"\n"
1266-
"module.exports = {\n"
1267-
" name: 'toasty',\n"
1268-
" description: 'Toast notification when OpenCode finishes',\n"
1269-
" onComplete: () => {\n"
1270-
" try {\n"
1271-
" execSync('\"" + jsPath + "\" \"OpenCode finished\" -t \"OpenCode\"', { timeout: 5000 });\n"
1272-
" } catch (e) { /* ignore */ }\n"
1273-
" }\n"
1274-
"};\n";
1275-
1276-
if (path_exists(pluginPath)) {
1277-
backup_file(pluginPath);
1278-
}
1279-
1280-
return write_file(pluginPath, plugin);
1281-
}
1282-
12831220
// Check if Claude hook is installed
12841221
bool is_claude_installed() {
12851222
std::wstring configPath = expand_env(L"%USERPROFILE%\\.claude\\settings.json");
@@ -1522,38 +1459,13 @@ bool uninstall_codex() {
15221459
return write_file(configPath, result);
15231460
}
15241461

1525-
// Uninstall plugin for OpenCode
1526-
bool uninstall_opencode() {
1527-
std::wstring pluginPath = expand_env(L"%USERPROFILE%\\.config\\opencode\\plugins\\toasty.js");
1528-
1529-
try {
1530-
if (path_exists(pluginPath)) {
1531-
backup_file(pluginPath);
1532-
fs::remove(pluginPath);
1533-
}
1534-
} catch (const std::exception&) {
1535-
std::wcerr << L"Error uninstalling OpenCode plugin\n";
1536-
return false;
1537-
}
1538-
1539-
return true;
1540-
}
1541-
15421462
// Check if Codex hook is installed
15431463
bool is_codex_installed() {
15441464
std::wstring configPath = expand_env(L"%USERPROFILE%\\.codex\\config.toml");
15451465
std::string content = read_file(configPath);
15461466
return content.find("toasty") != std::string::npos;
15471467
}
15481468

1549-
// Check if OpenCode plugin is installed
1550-
bool is_opencode_installed() {
1551-
std::wstring pluginPath = expand_env(L"%USERPROFILE%\\.config\\opencode\\plugins\\toasty.js");
1552-
if (!path_exists(pluginPath)) return false;
1553-
std::string content = read_file(pluginPath);
1554-
return content.find("toasty") != std::string::npos;
1555-
}
1556-
15571469
// Show installation status
15581470
void show_status() {
15591471
std::wcout << L"Installation status:\n\n";
@@ -1562,22 +1474,19 @@ void show_status() {
15621474
bool geminiDetected = detect_gemini();
15631475
bool copilotDetected = detect_copilot();
15641476
bool codexDetected = detect_codex();
1565-
bool opencodeDetected = detect_opencode();
15661477

15671478
std::wcout << L"Detected agents:\n";
15681479
std::wcout << L" " << (claudeDetected ? L"[x]" : L"[ ]") << L" Claude Code\n";
15691480
std::wcout << L" " << (geminiDetected ? L"[x]" : L"[ ]") << L" Gemini CLI\n";
15701481
std::wcout << L" " << (copilotDetected ? L"[x]" : L"[ ]") << L" GitHub Copilot (in current repo)\n";
15711482
std::wcout << L" " << (codexDetected ? L"[x]" : L"[ ]") << L" OpenAI Codex\n";
1572-
std::wcout << L" " << (opencodeDetected ? L"[x]" : L"[ ]") << L" OpenCode\n";
15731483
std::wcout << L"\n";
15741484

15751485
std::wcout << L"Installed hooks:\n";
15761486
std::wcout << L" " << (is_claude_installed() ? L"[x]" : L"[ ]") << L" Claude Code\n";
15771487
std::wcout << L" " << (is_gemini_installed() ? L"[x]" : L"[ ]") << L" Gemini CLI\n";
15781488
std::wcout << L" " << (is_copilot_installed() ? L"[x]" : L"[ ]") << L" GitHub Copilot\n";
15791489
std::wcout << L" " << (is_codex_installed() ? L"[x]" : L"[ ]") << L" OpenAI Codex\n";
1580-
std::wcout << L" " << (is_opencode_installed() ? L"[x]" : L"[ ]") << L" OpenCode\n";
15811490
}
15821491

15831492
// Handle --install command
@@ -1595,15 +1504,13 @@ void handle_install(const std::wstring& agent) {
15951504
bool installGemini = installAll || agent == L"gemini";
15961505
bool installCopilot = installAll || agent == L"copilot";
15971506
bool installCodex = installAll || agent == L"codex";
1598-
bool installOpencode = installAll || agent == L"opencode";
15991507

16001508
if (g_dryRun) {
16011509
std::wcout << L"[dry-run] Install targets:";
16021510
if (installClaude) std::wcout << L" claude";
16031511
if (installGemini) std::wcout << L" gemini";
16041512
if (installCopilot) std::wcout << L" copilot";
16051513
if (installCodex) std::wcout << L" codex";
1606-
if (installOpencode) std::wcout << L" opencode";
16071514
std::wcout << L"\n";
16081515

16091516
if (installClaude) {
@@ -1630,11 +1537,6 @@ void handle_install(const std::wstring& agent) {
16301537
std::wcout << L"[dry-run] Would write: " << configPath << L"\n";
16311538
std::wcout << L"[dry-run] Hook type: notify\n";
16321539
}
1633-
if (installOpencode) {
1634-
std::wstring pluginPath = expand_env(L"%USERPROFILE%\\.config\\opencode\\plugins\\toasty.js");
1635-
std::wcout << L"[dry-run] Would write: " << pluginPath << L"\n";
1636-
std::wcout << L"[dry-run] Hook type: JS plugin\n";
1637-
}
16381540
return;
16391541
}
16401542

@@ -1644,13 +1546,11 @@ void handle_install(const std::wstring& agent) {
16441546
bool geminiDetected = detect_gemini();
16451547
bool copilotDetected = detect_copilot();
16461548
bool codexDetected = detect_codex();
1647-
bool opencodeDetected = detect_opencode();
16481549

16491550
std::wcout << L" " << (claudeDetected ? L"[x]" : L"[ ]") << L" Claude Code found\n";
16501551
std::wcout << L" " << (geminiDetected ? L"[x]" : L"[ ]") << L" Gemini CLI found\n";
16511552
std::wcout << L" " << (copilotDetected ? L"[x]" : L"[ ]") << L" GitHub Copilot (in current repo)\n";
16521553
std::wcout << L" " << (codexDetected ? L"[x]" : L"[ ]") << L" OpenAI Codex found\n";
1653-
std::wcout << L" " << (opencodeDetected ? L"[x]" : L"[ ]") << L" OpenCode found\n";
16541554
std::wcout << L"\n";
16551555

16561556
std::wcout << L"Installing toasty hooks...\n";
@@ -1695,15 +1595,6 @@ void handle_install(const std::wstring& agent) {
16951595
}
16961596
}
16971597

1698-
if (installOpencode && (opencodeDetected || explicitAgent)) {
1699-
if (install_opencode(exePath)) {
1700-
std::wcout << L" [x] OpenCode: Added toasty.js plugin\n";
1701-
anyInstalled = true;
1702-
} else {
1703-
std::wcout << L" [ ] OpenCode: Failed to install\n";
1704-
}
1705-
}
1706-
17071598
if (anyInstalled) {
17081599
std::wcout << L"\nDone! You'll get notifications when AI agents finish.\n";
17091600
} else {
@@ -1718,12 +1609,10 @@ void handle_uninstall() {
17181609
std::wstring claudePath = expand_env(L"%USERPROFILE%\\.claude\\settings.json");
17191610
std::wstring geminiPath = expand_env(L"%USERPROFILE%\\.gemini\\settings.json");
17201611
std::wstring codexPath = expand_env(L"%USERPROFILE%\\.codex\\config.toml");
1721-
std::wstring opencodePath = expand_env(L"%USERPROFILE%\\.config\\opencode\\plugins\\toasty.js");
17221612
std::wcout << L"[dry-run] Claude: " << claudePath << L"\n";
17231613
std::wcout << L"[dry-run] Gemini: " << geminiPath << L"\n";
17241614
std::wcout << L"[dry-run] Copilot: .github\\hooks\\toasty.json\n";
17251615
std::wcout << L"[dry-run] Codex: " << codexPath << L"\n";
1726-
std::wcout << L"[dry-run] OpenCode: " << opencodePath << L"\n";
17271616
return;
17281617
}
17291618

@@ -1767,15 +1656,6 @@ void handle_uninstall() {
17671656
}
17681657
}
17691658

1770-
if (is_opencode_installed()) {
1771-
if (uninstall_opencode()) {
1772-
std::wcout << L" [x] OpenCode: Removed toasty.js plugin\n";
1773-
anyUninstalled = true;
1774-
} else {
1775-
std::wcout << L" [ ] OpenCode: Failed to remove\n";
1776-
}
1777-
}
1778-
17791659
if (anyUninstalled) {
17801660
std::wcout << L"\nDone! Hooks have been removed.\n";
17811661
} else {
@@ -2004,7 +1884,7 @@ int wmain(int argc, wchar_t* argv[]) {
20041884
explicitApp = true;
20051885
} else {
20061886
std::wcerr << L"Error: Unknown app preset '" << appName << L"'\n";
2007-
std::wcerr << L"Available presets: claude, copilot, gemini, codex, cursor, opencode\n";
1887+
std::wcerr << L"Available presets: claude, copilot, gemini, codex, cursor\n";
20081888
return 1;
20091889
}
20101890
} else {

tests/test-toasty.ps1

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,7 @@ $presets = @(
162162
@{ Name = "copilot"; ExpectedTitle = "GitHub Copilot" },
163163
@{ Name = "gemini"; ExpectedTitle = "Gemini" },
164164
@{ Name = "codex"; ExpectedTitle = "Codex" },
165-
@{ Name = "cursor"; ExpectedTitle = "Cursor" },
166-
@{ Name = "opencode"; ExpectedTitle = "OpenCode" }
165+
@{ Name = "cursor"; ExpectedTitle = "Cursor" }
167166
)
168167

169168
foreach ($preset in $presets) {
@@ -255,23 +254,13 @@ if ((Assert-ExitCode "install codex exits 0" 0 $r.ExitCode) -and
255254
Pass "install codex --dry-run"
256255
}
257256

258-
# Install opencode
259-
$r = Run-Toasty @("--install", "opencode", "--dry-run")
260-
if ((Assert-ExitCode "install opencode exits 0" 0 $r.ExitCode) -and
261-
(Assert-OutputContains "install opencode target" $r.Stdout "Install targets: opencode") -and
262-
(Assert-OutputContains "install opencode path" $r.Stdout "toasty.js") -and
263-
(Assert-OutputContains "install opencode hook" $r.Stdout "Hook type: JS plugin")) {
264-
Pass "install opencode --dry-run"
265-
}
266-
267257
# Install all (no agent specified)
268258
$r = Run-Toasty @("--install", "--dry-run")
269259
if ((Assert-ExitCode "install all exits 0" 0 $r.ExitCode) -and
270260
(Assert-OutputContains "install all claude" $r.Stdout "claude") -and
271261
(Assert-OutputContains "install all gemini" $r.Stdout "gemini") -and
272262
(Assert-OutputContains "install all copilot" $r.Stdout "copilot") -and
273-
(Assert-OutputContains "install all codex" $r.Stdout "codex") -and
274-
(Assert-OutputContains "install all opencode" $r.Stdout "opencode")) {
263+
(Assert-OutputContains "install all codex" $r.Stdout "codex")) {
275264
Pass "install all --dry-run"
276265
}
277266

@@ -281,8 +270,7 @@ if ((Assert-ExitCode "uninstall exits 0" 0 $r.ExitCode) -and
281270
(Assert-OutputContains "uninstall claude" $r.Stdout "Claude:") -and
282271
(Assert-OutputContains "uninstall gemini" $r.Stdout "Gemini:") -and
283272
(Assert-OutputContains "uninstall copilot" $r.Stdout "Copilot:") -and
284-
(Assert-OutputContains "uninstall codex" $r.Stdout "Codex:") -and
285-
(Assert-OutputContains "uninstall opencode" $r.Stdout "OpenCode:")) {
273+
(Assert-OutputContains "uninstall codex" $r.Stdout "Codex:")) {
286274
Pass "uninstall --dry-run"
287275
}
288276

0 commit comments

Comments
 (0)