opencode-plugin-boops 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/browse +103 -13
- package/package.json +1 -1
package/cli/browse
CHANGED
|
@@ -56,6 +56,37 @@ function isPluginInstalled() {
|
|
|
56
56
|
|
|
57
57
|
const PLUGIN_INSTALLED = isPluginInstalled();
|
|
58
58
|
|
|
59
|
+
// Event descriptions for the picker
|
|
60
|
+
const eventDescriptions = {
|
|
61
|
+
"session.idle": "AI completes response",
|
|
62
|
+
"permission.asked": "AI needs permission",
|
|
63
|
+
"session.error": "Error occurs",
|
|
64
|
+
"command.executed": "Command executed",
|
|
65
|
+
"file.edited": "File edited",
|
|
66
|
+
"file.watcher.updated": "File watcher detected change",
|
|
67
|
+
"installation.updated": "Installation/package updated",
|
|
68
|
+
"lsp.client.diagnostics": "LSP diagnostics received",
|
|
69
|
+
"lsp.updated": "LSP server updated",
|
|
70
|
+
"message.part.removed": "Message part removed",
|
|
71
|
+
"message.part.updated": "Message part updated",
|
|
72
|
+
"message.removed": "Message removed",
|
|
73
|
+
"message.updated": "Message updated",
|
|
74
|
+
"permission.replied": "Permission response given",
|
|
75
|
+
"server.connected": "Connected to server",
|
|
76
|
+
"session.created": "New session created",
|
|
77
|
+
"session.compacted": "Session compacted",
|
|
78
|
+
"session.deleted": "Session deleted",
|
|
79
|
+
"session.diff": "Session diff generated",
|
|
80
|
+
"session.status": "Session status changed",
|
|
81
|
+
"session.updated": "Session updated",
|
|
82
|
+
"todo.updated": "Todo list updated",
|
|
83
|
+
"tool.execute.before": "Before tool execution",
|
|
84
|
+
"tool.execute.after": "After tool execution",
|
|
85
|
+
"tui.prompt.append": "Text appended to prompt",
|
|
86
|
+
"tui.command.execute": "TUI command executed",
|
|
87
|
+
"tui.toast.show": "Toast notification shown"
|
|
88
|
+
};
|
|
89
|
+
|
|
59
90
|
// Load current boops.toml config
|
|
60
91
|
function loadCurrentConfig() {
|
|
61
92
|
const configPath = join(homedir(), ".config", "opencode", "plugins", "boops", "boops.toml");
|
|
@@ -96,7 +127,7 @@ function loadCurrentConfig() {
|
|
|
96
127
|
}
|
|
97
128
|
}
|
|
98
129
|
|
|
99
|
-
// Save config to boops.toml
|
|
130
|
+
// Save config to boops.toml (preserves existing entries and comments)
|
|
100
131
|
function saveConfig(config) {
|
|
101
132
|
if (!PLUGIN_INSTALLED) {
|
|
102
133
|
console.error("\n⚠️ Cannot save: Plugin not installed");
|
|
@@ -106,14 +137,51 @@ function saveConfig(config) {
|
|
|
106
137
|
}
|
|
107
138
|
|
|
108
139
|
const configPath = join(homedir(), ".config", "opencode", "plugins", "boops", "boops.toml");
|
|
109
|
-
let content = "# OpenCode Boops Plugin Configuration\n\n[sounds]\n";
|
|
110
140
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
141
|
+
// Read existing config to preserve comments and other sections
|
|
142
|
+
let existingContent = "";
|
|
143
|
+
if (existsSync(configPath)) {
|
|
144
|
+
existingContent = readFileSync(configPath, "utf-8");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Parse existing [sounds] section to merge with new values
|
|
148
|
+
const existingSounds = {};
|
|
149
|
+
if (existingContent) {
|
|
150
|
+
const lines = existingContent.split("\n");
|
|
151
|
+
let inSoundsSection = false;
|
|
152
|
+
|
|
153
|
+
for (const line of lines) {
|
|
154
|
+
const trimmed = line.trim();
|
|
155
|
+
|
|
156
|
+
if (trimmed === "[sounds]") {
|
|
157
|
+
inSoundsSection = true;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (trimmed.startsWith("[") && trimmed !== "[sounds]") {
|
|
162
|
+
inSoundsSection = false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (inSoundsSection && !trimmed.startsWith("#") && trimmed) {
|
|
166
|
+
const match = trimmed.match(/^"?([^"=]+)"?\s*=\s*"?([^"#]+)"?/);
|
|
167
|
+
if (match) {
|
|
168
|
+
const [, key, value] = match;
|
|
169
|
+
existingSounds[key.trim()] = value.trim().replace(/"/g, "");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
114
172
|
}
|
|
115
173
|
}
|
|
116
174
|
|
|
175
|
+
// Merge new sounds with existing
|
|
176
|
+
const mergedSounds = { ...existingSounds, ...(config.sounds || {}) };
|
|
177
|
+
|
|
178
|
+
// Write back, preserving the header
|
|
179
|
+
let content = "# OpenCode Boops Plugin Configuration\n# Sounds can be local file paths OR URLs (automatically downloaded and cached)\n\n[sounds]\n";
|
|
180
|
+
|
|
181
|
+
for (const [event, sound] of Object.entries(mergedSounds).sort()) {
|
|
182
|
+
content += `"${event}" = "${sound}"\n`;
|
|
183
|
+
}
|
|
184
|
+
|
|
117
185
|
writeFileSync(configPath, content);
|
|
118
186
|
return true;
|
|
119
187
|
}
|
|
@@ -1078,10 +1146,14 @@ async function browse() {
|
|
|
1078
1146
|
const boxHeight = Math.min(availableEvents.length + 4, termHeight - boxStartY - fixedFooterLines);
|
|
1079
1147
|
|
|
1080
1148
|
// Calculate scroll offset for event list
|
|
1081
|
-
const maxVisibleEvents = boxHeight -
|
|
1149
|
+
const maxVisibleEvents = boxHeight - 6; // Reserve space for header(2) + description(2) + footer(2)
|
|
1082
1150
|
const eventScrollOffset = Math.max(0, Math.min(state.pickerSelectedEvent - Math.floor(maxVisibleEvents / 2), availableEvents.length - maxVisibleEvents));
|
|
1083
1151
|
const eventEndIndex = Math.min(eventScrollOffset + maxVisibleEvents, availableEvents.length);
|
|
1084
1152
|
|
|
1153
|
+
// Get selected event info
|
|
1154
|
+
const selectedEvent = availableEvents[state.pickerSelectedEvent];
|
|
1155
|
+
const eventDesc = eventDescriptions[selectedEvent] || "No description available";
|
|
1156
|
+
|
|
1085
1157
|
// Draw each line of the picker box
|
|
1086
1158
|
for (let row = 0; row < boxHeight; row++) {
|
|
1087
1159
|
const screenY = boxStartY + row;
|
|
@@ -1104,6 +1176,14 @@ async function browse() {
|
|
|
1104
1176
|
} else if (row === 2) {
|
|
1105
1177
|
// Separator
|
|
1106
1178
|
process.stdout.write(`\x1b[38;5;240m├${'─'.repeat(pickerWidth - 2)}┤\x1b[0m`);
|
|
1179
|
+
} else if (row === boxHeight - 3) {
|
|
1180
|
+
// Separator before description
|
|
1181
|
+
process.stdout.write(`\x1b[38;5;240m├${'─'.repeat(pickerWidth - 2)}┤\x1b[0m`);
|
|
1182
|
+
} else if (row === boxHeight - 2) {
|
|
1183
|
+
// Event description
|
|
1184
|
+
const desc = eventDesc.length > pickerWidth - 4 ? eventDesc.slice(0, pickerWidth - 7) + '...' : eventDesc;
|
|
1185
|
+
const padding = ' '.repeat(Math.max(0, pickerWidth - desc.length - 4));
|
|
1186
|
+
process.stdout.write(`\x1b[38;5;240m│\x1b[0m \x1b[38;5;246m${desc}\x1b[0m${padding} \x1b[38;5;240m│\x1b[0m`);
|
|
1107
1187
|
} else if (row === boxHeight - 1) {
|
|
1108
1188
|
// Bottom border
|
|
1109
1189
|
process.stdout.write(`\x1b[38;5;240m└${'─'.repeat(pickerWidth - 2)}┘\x1b[0m`);
|
|
@@ -1194,7 +1274,7 @@ async function browse() {
|
|
|
1194
1274
|
let controls;
|
|
1195
1275
|
if (state.pickerMode) {
|
|
1196
1276
|
controls = PLUGIN_INSTALLED
|
|
1197
|
-
? ' \x1b[38;5;240m↑/↓ select
|
|
1277
|
+
? ' \x1b[38;5;240m↑/↓ select enter play ctrl+s save d unassign ←/esc close\x1b[0m'
|
|
1198
1278
|
: ' \x1b[38;5;240m↑/↓ select event enter play ←/esc close \x1b[38;5;208m(save disabled)\x1b[0m';
|
|
1199
1279
|
} else {
|
|
1200
1280
|
controls = ' \x1b[38;5;240m↑/↓ navigate ←/→ tags / search s assign enter play q quit\x1b[0m';
|
|
@@ -1846,20 +1926,30 @@ async function browse() {
|
|
|
1846
1926
|
}
|
|
1847
1927
|
return;
|
|
1848
1928
|
} else if (key === '\u0013') {
|
|
1849
|
-
// Ctrl+S - save assignment
|
|
1929
|
+
// Ctrl+S - save assignment (stay in picker mode to assign more)
|
|
1850
1930
|
const config = loadCurrentConfig();
|
|
1851
1931
|
const event = availableEvents[state.pickerSelectedEvent];
|
|
1852
1932
|
|
|
1853
1933
|
if (!config.sounds) config.sounds = {};
|
|
1854
|
-
config.sounds[event] = state.pickerSound.
|
|
1934
|
+
config.sounds[event] = state.pickerSound.name; // Save friendly name, not ID
|
|
1855
1935
|
|
|
1856
1936
|
// Save to config file
|
|
1857
|
-
|
|
1937
|
+
saveConfig(config);
|
|
1858
1938
|
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1939
|
+
// Stay in picker mode, just show it was saved
|
|
1940
|
+
render();
|
|
1941
|
+
return;
|
|
1942
|
+
} else if (key === 'd') {
|
|
1943
|
+
// 'd' - unassign/delete sound from selected event
|
|
1944
|
+
const config = loadCurrentConfig();
|
|
1945
|
+
const event = availableEvents[state.pickerSelectedEvent];
|
|
1946
|
+
|
|
1947
|
+
if (config.sounds && config.sounds[event]) {
|
|
1948
|
+
delete config.sounds[event];
|
|
1949
|
+
saveConfig(config);
|
|
1862
1950
|
}
|
|
1951
|
+
|
|
1952
|
+
// Stay in picker mode
|
|
1863
1953
|
render();
|
|
1864
1954
|
return;
|
|
1865
1955
|
}
|