feat: add shelly plugin (#2)

* feat: add shelly plugin

* docs: document click action

* add license

* feat: add setting to hide when there are no updates

* remove some tags

* address tags feedback

* Update tags in plugin.toml for clarity

---------

Co-authored-by: Lemmy <studio@quadbyte.net>
This commit is contained in:
Josh Slate
2026-07-14 22:55:48 -04:00
committed by GitHub
co-authored by Lemmy
parent aea4b526b3
commit 49761167d0
6 changed files with 343 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
# Shelly
Shelly is a plugin that uses the [Shelly Arch Package Manager](https://github.com/Seafoam-Labs/Shelly-ALPM) to display package update information in a bar widget.
## Plugin
| Field | Value |
| ------- | --------------------------------------- |
| ID | `joshuaslate/shelly` |
| Entries | Service: `poller`; bar widget: `shelly` |
## Requirements
Install the Shelly Arch Package Manager from the [AUR](https://aur.archlinux.org/packages/shelly).
Test that it works by running `shelly check-updates` in a terminal. If it returns a list of packages, then it is working correctly.
## Settings
| Setting | Type | Default | Description |
| ---------------------- | -------- | ------------ | -------------------------------------------------------------------------------------------------- |
| `interval` | `int` | `300` | The interval (in seconds) between update checks |
| `notify` | `bool` | `false` | Whether or not you want to receive notifications when there are new package updates available |
| `color` | `color` | `on_surface` | The text color for the bar widget |
| `glyph_color` | `color` | `on_surface` | The color of the glyph on the bar widget |
| `glyph` | `glyph` | `package` | Bar widget icon. |
| `click_action` | `select` | `open_gui` | The action to perform when the bar widget is clicked. Options: `open_gui`, `open_updater`, `none`. |
| `hide_when_up_to_date` | `bool` | `false` | Whether or not to hide the bar widget when there are no package updates available |
+74
View File
@@ -0,0 +1,74 @@
id = "joshuaslate/shelly"
name = "Shelly"
icon = "package"
version = "1.0.0"
min_noctalia = "5.0.0"
license = "MIT"
author = "joshuaslate"
dependencies = ["shelly"]
description = "A plugin that provides a widget for displaying system update information."
tags = ["system", "arch"]
deprecated = false
[[setting]]
key = "interval"
type = "int"
label_key = "settings.interval.label"
description_key = "settings.interval.description"
default = 300
min = 0
[[setting]]
key = "notify"
type = "bool"
label_key = "settings.notify.label"
description_key = "settings.notify.description"
default = false
[[service]]
id = "update_poller"
entry = "poller.luau"
[[widget]]
id = "shelly"
entry = "widget.luau"
[[widget.setting]]
key = "hide_when_up_to_date"
type = "bool"
label_key = "settings.hide_when_up_to_date.label"
description_key = "settings.hide_when_up_to_date.description"
default = false
[[widget.setting]]
key = "color"
type = "color"
label_key = "settings.color.label"
description_key = "settings.color.description"
default = "on_surface"
[[widget.setting]]
key = "glyph"
type = "glyph"
label_key = "settings.glyph.label"
description_key = "settings.glyph.description"
default = "package"
[[widget.setting]]
key = "glyph_color"
type = "color"
label_key = "settings.glyph_color.label"
description_key = "settings.glyph_color.description"
default = "on_surface"
[[widget.setting]]
key = "click_action"
type = "select"
label_key = "settings.click_action.label"
description_key = "settings.click_action.description"
default = "open_gui"
options = [
{ value = "none", label_key = "settings.click_action.options.none" },
{ value = "open_gui", label_key = "settings.click_action.options.open_gui" },
{ value = "open_updater", label_key = "settings.click_action.options.open_updater" }
]
+57
View File
@@ -0,0 +1,57 @@
--!nonstrict
type Update = { Name: string, Version: string, OldVersion: string?, DownloadSize: string? }
type UpdateResult = {
Packages: { [number]: Update },
Aur: { [number]: Update },
Flatpak: { [number]: Update },
}
local updates: UpdateResult = { Packages = {}, Aur = {}, Flatpak = {} }
local updateError: string | nil = nil
local updateInterval = (noctalia.getConfig("interval") or 300)*1000
local isPolling = false
noctalia.setUpdateInterval(updateInterval)
function refresh()
isPolling = true
noctalia.runAsync("shelly check-updates --json", function(result)
isPolling = false
if result.exitCode ~= 0 then
updateError = result.stderr
noctalia.state.set("updates", nil)
noctalia.state.set("update_error", updateError)
return
end
local decoded = noctalia.json.decode(result.stdout)
if type(decoded) ~= "table" then
updateError = noctalia.tr("bar.tooltip.error.decode_json")
noctalia.state.set("update_error", updateError)
return
end
updates = {
Packages = decoded.Packages or {},
Aur = decoded.Aur or {},
Flatpak = decoded.Flatpak or {},
}
noctalia.state.set("updates", updates)
noctalia.state.set("update_error", nil)
end)
end
function update()
if isPolling then
return
end
refresh()
end
refresh()
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

+68
View File
@@ -0,0 +1,68 @@
{
"title": "Shelly",
"notify": {
"new_updates": {
"title": "New Updates Available",
"description": "There are {count} new updates available for your system."
}
},
"bar": {
"text": {
"error": "?",
"updates": {
"one": "{count} Update",
"other": "{count} Updates"
}
},
"tooltip": {
"aur_title": "AUR",
"flatpak_title": "Flatpak",
"packages_title": "Packages",
"no_updates": "System is up to date",
"error": {
"decode_json": "Failed to decode output from `shelly check-updates --json`"
}
},
"click_handler": {
"error": {
"missing_gui": "Shelly GUI is not installed. Please install it to use this feature.",
"missing_cli": "Shelly CLI is not installed. Please install it to use this feature."
}
}
},
"settings": {
"interval": {
"label": "Refresh Seconds",
"description": "How often the service checks for updates in the background."
},
"notify": {
"label": "Notify",
"description": "Whether to show a notification when new updates are available."
},
"click_action": {
"label": "Click Action",
"description": "What happens when you click on the bar.",
"options": {
"none": "Nothing",
"open_gui": "Open Shelly GUI",
"open_updater": "Start Update in Terminal"
}
},
"hide_when_up_to_date": {
"label": "Hide When Up-to-Date",
"description": "Whether to hide the bar when there are no updates available."
},
"color": {
"label": "Color",
"description": "The color of the bar text."
},
"glyph": {
"label": "Glyph",
"description": "The glyph shown before the label."
},
"glyph_color": {
"label": "Glyph Color",
"description": "The color of the glyph."
}
}
}
+116
View File
@@ -0,0 +1,116 @@
--!nonstrict
local glyph = barWidget.getConfig("glyph")
local color = barWidget.getConfig("color") or "on_surface"
local glyphColor = barWidget.getConfig("glyph_color") or color
local updateError = noctalia.state.get("update_error")
local notifyOnNewUpdates = barWidget.getConfig("notify")
local hideWhenUpToDate = barWidget.getConfig("hide_when_up_to_date")
local click_action = barWidget.getConfig("click_action") or "open_gui"
local updates = noctalia.state.get("updates") or { Packages = {}, Aur = {}, Flatpak = {} }
local function countUpdates(scopedUpdates)
return #scopedUpdates.Packages + #scopedUpdates.Aur + #scopedUpdates.Flatpak
end
local function getUpdateLabel(update)
if update.OldVersion then
return string.format("%s → %s", update.OldVersion, update.Version)
else
return update.Version
end
end
local function appendUpdateSection(rows, title, list)
if #list == 0 then
return
end
table.insert(rows, {
key = title,
value = string.format("%d", #list),
})
for _, pkg in ipairs(list) do
table.insert(rows, {
key = " " .. pkg.Name,
value = getUpdateLabel(pkg),
})
if pkg.DownloadSize then
table.insert(rows, {
key = "",
value = pkg.DownloadSize,
})
end
end
end
local function updateTooltip(count)
if updateError then
barWidget.setTooltip(updateError)
return
end
if count == 0 then
barWidget.setTooltip(noctalia.tr("bar.tooltip.no_updates"))
return
end
local rows = {}
appendUpdateSection(rows, noctalia.tr("bar.tooltip.packages_title"), updates.Packages)
appendUpdateSection(rows, noctalia.tr("bar.tooltip.aur_title"), updates.Aur)
appendUpdateSection(rows, noctalia.tr("bar.tooltip.flatpak_title"), updates.Flatpak)
barWidget.setTooltip(rows)
end
local function render()
local container = barWidget.isVertical() and ui.column or ui.row
local count = countUpdates(updates)
barWidget.render(container({ gap = 6, align = "center", visible = not hideWhenUpToDate or count > 0 }, {
ui.glyph({ name = glyph, size = 14, color = glyphColor, visible = glyph ~= "" }),
ui.label({ text = noctalia.trp("bar.text.updates", count), color = color }),
}))
updateTooltip(count)
end
noctalia.state.watch("updates", function(value)
local prevCount = countUpdates(updates)
local newCount = countUpdates(value)
updates = value
if notifyOnNewUpdates and newCount > prevCount then
noctalia.notify(noctalia.tr("notify.new_updates.title"), noctalia.trp("notify.new_updates.description", newCount))
end
render()
end)
function onClick()
if click_action == "open_gui" then
local cmd = "shelly-ui"
if not noctalia.commandExists(cmd) then
noctalia.notifyError("Shelly", noctalia.tr("bar.click_handler.error.missing_gui"))
return
end
noctalia.runAsync(cmd)
elseif click_action == "open_updater" then
local cmd = "shelly"
if not noctalia.commandExists(cmd) then
noctalia.notifyError("Shelly", noctalia.tr("bar.click_handler.error.missing_cli"))
return
end
noctalia.runInTerminal(cmd .. " upgrade-all")
end
end
render()