agentsclimarketplace

Gnome shell extension local dev

Skill papanito/ai-skills/skills/gnome-shell-extension-local-dev

[MIRROR] My personal AI skills

Install
npx -y skills add papanito/ai-skills --skill gnome-shell-extension-local-dev

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Build, symlink-install, enable, debug, and package a GNOME Shell extension locally on GNOME 46–50 (Wayland-safe). Use when creating or iterating on any gnome-shell extension.

SKILL.md

7.4 KB, ~1.9k tokens by cl100k_base, as published. Nobody here has run it

GNOME Shell Extension Local Dev (GNOME 46–50)

When to use

Creating, iterating on, or packaging a GNOME Shell extension. Covers project structure, import paths, gettext, threading, live install/enable, debugging, and CI packaging.

Project structure (GNOME 50 verified)

<uuid>@<domain>/
  metadata.json          # uuid, name, shell-version[], version (int), version-name (str), settings-schema, gettext-domain
  extension.js           # import { Extension, gettext as _ } from 'resource:///org/gnome/shell/extensions/extension.js'
  prefs.js               # import { ExtensionPreferences } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'
  codeburn.js (etc)      # helper modules — receive gettext as a parameter, never import it
  stylesheet.css
  schemas/<schema>.gschema.xml
  install.sh             # symlink/enable/reload script (pure bash)
  Makefile               # install/link/uninstall/schemas targets
  .gitlab-ci.yml         # pack/validate/upload/release pipeline

UUID format: [email protected]. The extension dir name MUST equal the uuid.

Import paths (critical — getting either wrong silently breaks loading)

  • extension.js: import { Extension, gettext as _ } from 'resource:///org/gnome/shell/extensions/extension.js' (lowercase shell)
  • prefs.js: import { ExtensionPreferences } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js' (capital Shell/Extensions, different path js/extensions/prefs.js)
  • helper modules: do NOT import gettext. Receive it as a constructor/method parameter from extension.js.

gettext (GNOME 45+ ESM)

  • gettext from extension.js can ONLY be called while an extension's code is executing
  • Calling _() at module-import time (top-level const, helper module import) throws Error: gettext can only be called from extensions → extension fails to load (state INITIALIZED, stuck in error, indicator never appears)
  • Correct pattern: in extension.js, pass _ (the module-level import) into helper constructors: new MyIndicator(settings, _). Helpers store as this._ and call this._('...') only inside method bodies (render/init time)
  • In prefs.js, use this.gettext.bind(this) as a local _ inside fillPreferencesWindow
  • Add gettext-domain to metadata.json
  • Debug: gdbus call --session --dest org.gnome.Shell.Extensions --object-path /org/gnome/Shell/Extensions --method org.gnome.Shell.Extensions.GetExtensionErrors <uuid>

Threading (GJS)

  • GLib.Thread.try_new() is NOT introspectable in GJS — throws immediately
  • Use GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { ... return GLib.SOURCE_REMOVE; }) for synchronous spawns that take ~1s
  • The shell tolerates a brief block at idle priority

GObject signals

  • Declare in registerClass: GObject.registerClass({ Signals: { 'my-signal': {} } }, class ...)
  • Emit with matching arg count: this.emit('my-signal') for 0-arg signals
  • emit('my-signal', null) on a 0-arg signal throws Signal requires 0 args got 1

Install for live editing (symlink)

[email protected]
EXT=~/.local/share/gnome-shell/extensions/$UUID
ln -sfn "$PWD" "$EXT"
# compile schema into BOTH the extension's schemas/ dir AND the user schemas dir
glib-compile-schemas --strict --targetdir=schemas schemas
cp schemas/gschemas.compiled ~/.local/share/glib-2.0/schemas/gschemas.compiled

glib-compile-schemas not on PATH (NixOS)

GLIB_CS=$(command -v glib-compile-schemas || ls /nix/store/*-glib-*-dev/bin/glib-compile-schemas | head -1)

Enable + reload via DBus

# DBus interface: org.gnome.Shell.Extensions at /org/gnome/Shell/Extensions
gdbus call --session --dest org.gnome.Shell.Extensions \
  --object-path /org/gnome/Shell/Extensions \
  --method org.gnome.Shell.Extensions.EnableExtension <uuid>
gdbus call --session --dest org.gnome.Shell.Extensions \
  --object-path /org/gnome/Shell/Extensions \
  --method org.gnome.Shell.Extensions.GetExtensionInfo <uuid>
  • EnableExtension returns (true,) for known extensions, (false,) for unknown
  • GetExtensionInfo returns (@a{sv} {},) (empty dict) for unknown extensions
  • ReloadExtension is NOT implemented on GNOME 50.2

Shell scan limitation (Wayland)

  • GNOME Shell scans ~/.local/share/gnome-shell/extensions only at startup
  • No DBus method triggers a fresh scan
  • A freshly symlinked extension needs a one-time shell restart (log out/in)
  • After first scan, DisableExtension + EnableExtension can toggle live
  • ReloadExtension not implemented → code edits also need restart on GNOME 50.2
  • Pre-add uuid to enabled-extensions gsettings so it auto-enables on next login

UninstallExtension DANGER

  • UninstallExtension DBus method (and gnome-extensions uninstall) FOLLOWS SYMLINKS
  • On a symlinked extension, it recursively deletes the REAL source directory including .git
  • NEVER call UninstallExtension on a symlinked extension
  • To remove: rm ~/.local/share/gnome-shell/extensions/<uuid> (removes symlink only)

gsettings enabled-extensions (pure bash)

# Parse GLib variant text: ['a','b'] or @as []
parse() { gsettings get org.gnome.shell enabled-extensions | sed "s/^@\w\+\s*\[//; s/^\[//; s/\]$//; s/'//g; s/, /\n/g" | sed '/^$/d'; }
# Rebuild: ['a','b']
join() { local items=() line; while IFS= read -r line || [[ -n "$line" ]]; do [[ -z "$line" ]] && continue; items+=("'$line'"); done; local j; j="$(IFS=,; printf '%s' "${items[*]}")"; printf '[%s]' "$j"; }
# Append idempotent
add() { local list; list="$(parse)"; printf '%s\n' "${list}" | grep -qxF "$UUID" && return 0; gsettings set org.gnome.shell enabled-extensions "$(printf '%s\n%s\n' "${list}" "$UUID" | join)"; }

Debugging

# Check extension state and errors
gdbus call --session --dest org.gnome.Shell.Extensions \
  --object-path /org/gnome/Shell/Extensions \
  --method org.gnome.Shell.Extensions.GetExtensionErrors <uuid>
gdbus call --session --dest org.gnome.Shell.Extensions \
  --object-path /org/gnome/Shell/Extensions \
  --method org.gnome.Shell.Extensions.GetExtensionInfo <uuid>
# Check journal for stack traces
journalctl --user -b | grep -i "codeburn\|<uuid>" | tail -20

Packaging (CI)

  • gnome-extensions pack segfaults in Fedora 41 CI. Use manual zip:
mkdir -p dist/staging/schemas
cp metadata.json extension.js prefs.js *.js stylesheet.css dist/staging/
cp schemas/*.gschema.xml schemas/gschemas.compiled dist/staging/schemas/
cd dist/staging && zip -r ../<uuid>.shell-extension.zip . && cd ../..
  • gnome-extensions pack --extra-source=<file> is needed for helper modules (codeburn.js, ui.js) — without it they're omitted from the zip
  • ZIP structure: files at archive root, schemas/ subdir — no top-level directory wrapper

CI gotchas

  • [skip ci] in commit messages permanently poisons the tag — GitLab skips ALL pipelines for that commit. Use workflow:rules with $CI_COMMIT_MESSAGE =~ /^release:.*/ + when: never instead
  • CI_REPOSITORY_URL embeds the CI token (gitlab-ci-token:[MASKED]@...). Use CI_PROJECT_URL for constructing push URLs
  • python3 not available in before_script on Fedora 41 (before dnf install). Use sed for file patching
  • YAML colons in script strings break parsing. Use | block scalars for lines containing : followed by text

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.