module sources import os import time import x.json2 as json // transform_plugin runs an external executable, passes the raw event on stdin, // and expects a JSON Event on stdout. Exit code 0 = success, non-zero = drop. // timeout defaults to 10s if not set. pub fn transform_plugin(raw string, source PluginSource) ?Event { timeout := if source.timeout > 0 { source.timeout } else { 10 } // Write raw input to a temp file for the plugin to read (optional) // We use os.execute with timeout via a wrapper start := time.now() // Run the plugin command. The plugin receives nothing on stdin; // it's a polling model — the plugin is called periodically and // produces output when it has something to report. result := os.execute(source.command) elapsed := time.now().unix() - start.unix() if elapsed > timeout { return none } // Exit code 0 = event, non-zero = skip if result.exit_code != 0 { return none } output := result.output.trim_space() if output == '' { return none } // Parse plugin output as JSON doc := json.decode[json.Any](output, json.DecoderOptions{}) or { return none } obj := doc.as_map() priority := obj['priority'] or { json.Any(3) }.int() mut tags := []string{} if tags_any := obj['tags'] { for tag in tags_any.as_array() { tags << tag.str() } } message := obj['message'] or { json.Any(output) }.str() click_url := obj['click_url'] or { json.Any('') }.str() mut actions := []Action{} if actions_any := obj['actions'] { for a in actions_any.as_array() { am := a.as_map() actions << Action{ action: am['action'] or { json.Any('view') }.str() label: am['label'] or { json.Any('') }.str() url: am['url'] or { json.Any('') }.str() clear: am['clear'] or { json.Any(false) }.bool() } } } // Resolve topic: plugin output can override, or fall back to source config mut topic := source.topic if plugin_topic := obj['topic'] { t := plugin_topic.str() if t != '' { topic = t } } return Event{ source: 'plugin' name: source.name topic: topic priority: int(priority) tags: tags message: message raw: output click_url: click_url actions: actions } }