// pinger

Pluggable button-rack widget. Buttons + the functions behind them are entries in a registry; the rack itself is generic. Travels between mesh nodes (Rocky, aqua, quartz, Pebble) carrying its registry, proxies commands back to the host that owns the function.
source repoartificer3120/untitledSDK · shells/desktop/pinger.py · 330 lines target repodev/pinger/ (promoted out of SDK — own process, own registry) siblingpingerSLA.py · 224 lines · SLA milestone variant (will inherit same registry mechanics) v0.1 mockupradix-pinger (static button list, pre-registry) statusconcept — UI proposal phase
v0.1 radix-pinger mockup — six hardcoded buttons, the shape we generalize fromopen ↗

Concept — mobile detachable hub

v0.1 pinger is a fixed six-button control surface, hand-coded and Rocky-bound. v0.2 generalizes it: the rack is a frame, the buttons are data, the functions are a registry. The same widget becomes a media remote, a build runner, a localMesh dispatcher, a fast-launcher — by swapping its registry, not its code.

"Detachable" means the widget isn't bound to its launch node. Started on Rocky, it can be picked up and continued on aqua or quartz; the registry travels with it. Functions that are local to a host (e.g. start mini-mac on Rocky) get proxied back through localMesh; functions that are intrinsically portable (open URL, send mesh message) run wherever pinger is.

Architecture · topology

localMesh — command proxy + registry sync Rocky aqua quartz Pebble pinger reg istry
pinger floats; registry travels with it; localMesh carries commands back to whichever node owns the function

Architecture · registry shape

One button entry, one registry record. Stored as JSON, edited via GUI (see proposals below), persisted at ~/.pinger/registry.json (or wherever pinger currently is).

{
  "id": "ping",
  "label": "Ping!",
  "row": 1,
  "color": "primary",          // primary | secondary | danger | success | purple | custom
  "icon": "📡",                // optional emoji or path to png
  "hotkey": "F1",              // optional
  "scope": "remote@rocky",     // local | remote@<node> | mesh (any node)
  "command": {
    "type": "shell",           // shell | http | mesh-message | python | url
    "run":  "python ~/forge6/b33p/b33p.py identify vert"
  },
  "confirm": false,            // pop a "are you sure?" before firing
  "feedback": "chirp"          // chirp | toast | none
}

scope is the load-bearing field. local = run wherever pinger is. remote@rocky = proxy via localMesh to that node. mesh = broadcast or pick the right node from a hint. The frame doesn't care; only scope + command.type determine routing.

Architecture · click flow

user (aqua) pinger localMesh Rocky (host) tap "Ping!" scope=remote@rocky → POST /dispatch exec command.run { ok, stdout, exit_code } → toast/chirp
user is on aqua; pinger sees scope=remote@rocky; localMesh proxies; Rocky executes; result chirps back

Add / remove buttons via GUI — proposals

The frame is generic; the registry is the product. Three shapes for editing it without dropping to JSON.

A · inline edit mode

┌─ pinger ─────────── ⚙ ─┐ │ [Ping!] [Mac] [Stop ✎] │ │ [stack] [snap] [HALT] │ │ [ + add ] │ └────────────────────────┘ ↓ right-click on btn ┌──────────────┐ │ ✎ edit │ │ ⌘ rebind key │ │ ⎘ duplicate │ │ ✕ remove │ └──────────────┘

Press the gear once to enter edit mode; buttons get tiny ✎ corners + a "+ add" tile appears at the end. Right-click on any button for the full menu. Drag to reorder. Press gear again to commit.

  • no extra surface — the widget is the editor
  • muscle-memory: looks/feels like editing a phone home-screen
  • cramped on the smallest views (ribbon mode)
  • no room for advanced fields (scope routing, confirm, icon)

B · edit drawer

┌─ pinger ─────────── ⚙ ─┐ ┌── registry ─────┐ │ [Ping!] [Mac] [Stop ] │ │ ☰ Ping! ✎ ✕ │ │ [stack] [snap] [HALT ] │ │ ☰ Mini-Mac ✎ ✕ │ │ ready · localMesh:801 │ │ ☰ Stop M-M ✎ ✕ │ └────────────────────────┘ │ ☰ scrollst ✎ ✕ │ │ ☰ snap.reg ✎ ✕ │ │ ☰ HALT ✎ ✕ │ │ ─────────────── │ │ [ + add button] │ └─────────────────┘

Gear icon slides out a side drawer with the full button list. Drag handles for reorder, ✎ opens a per-button form (label, scope, command, hotkey, color, icon), ✕ removes. The pinger frame stays untouched; edits commit live.

  • clean separation — viewing vs configuring
  • room for every registry field including icon picker
  • drag-to-reorder is obvious
  • drawer wants screen real estate; breaks down on Pebble

C · companion editor

┌─ pinger ──────────────┐ ┌─ pinger.edit ─────────────┐ │ [Ping!] [Mac] [Stop ] │ │ ID LABEL SCOPE │ │ [stack] [snap] [HALT] │ │ ping Ping! remote@rky │ └───────────────────────┘ │ mac Mini-Mac local │ (live preview) │ stop Stop M-M local │ │ stack scrollst remote@rky │ │ snap snap.reg local │ │ halt HALT mesh │ │ [ + new ] [ import ] │ └───────────────────────────┘

Separate pinger.edit window — full table view with sortable columns, multi-select, import/export of JSON registries, live preview of pinger as you edit. Heaviest option but best for managing several profiles.

  • scales to dozens of buttons + multiple profiles
  • copy/paste between registries; import community packs
  • workable on a desktop without crowding pinger itself
  • two windows to keep track of
  • overkill for a five-button daily registry

Recommendation

B (edit drawer) on Rocky/desktop, A (inline) on aqua/Pebble. Same underlying registry, different surfacing per form factor. C (companion) earns its keep when registries grow past ~12 buttons or when sharing/importing button packs becomes a thing — defer until then.

Configure-a-button — the form behind "+ add"

All three proposals share the same form when you actually create or edit a button. The form is the only place a registry record gets shaped — proposals A/B/C just decide where it floats. Two modes: guided (default — dropdowns assemble the command for you) and raw (paste/edit the JSON directly).

guided mode (default)
┌─ new button ────────────────── ✕ ┐
│                                  │
│  LABEL                           │
│  ┌────────────────────────────┐  │
│  │ Ping!                      │  │
│  └────────────────────────────┘  │
│                                  │
│  COLOR     ROW                   │
│  [primary▾] [1 ▾]                │
│                                  │
│  ICON      HOTKEY                │
│  [📡]      [F1   ]               │
│                                  │
│  ─── what does it do ──────────  │
│                                  │
│  TYPE                            │
│  ( ) shell      ( ) http         │
│  (•) mesh-msg   ( ) python       │
│  ( ) url        ( ) chirp        │
│                                  │
│  RUNS ON                         │
│  ( ) local (wherever I am)       │
│  (•) remote@ [Rocky    ▾]        │
│  ( ) mesh   (any node)           │
│                                  │
│  TO       MESSAGE                │
│  [vert  ▾] [hey from pinger   ]  │
│                                  │
│  ─── on press ─────────────────  │
│                                  │
│  ☐ confirm before firing         │
│  FEEDBACK [chirp ▾]              │
│                                  │
│  [test it]   [cancel] [ save  ]  │
└──────────────────────────────────┘
raw mode (toggle)
┌─ new button ────── [guided|raw] ✕┐
│                                  │
│  ┌────────────────────────────┐  │
│  │ {                          │  │
│  │   "id": "ping",            │  │
│  │   "label": "Ping!",        │  │
│  │   "row": 1,                │  │
│  │   "color": "primary",      │  │
│  │   "icon": "📡",            │  │
│  │   "hotkey": "F1",          │  │
│  │   "scope": "remote@rocky", │  │
│  │   "command": {             │  │
│  │     "type": "mesh-msg",    │  │
│  │     "to": "vert",          │  │
│  │     "message": "hey from   │  │
│  │       pinger"              │  │
│  │   },                       │  │
│  │   "confirm": false,        │  │
│  │   "feedback": "chirp"      │  │
│  │ }                          │  │
│  └────────────────────────────┘  │
│  ✓ schema valid                  │
│                                  │
│  [test it]   [cancel] [ save  ]  │
└──────────────────────────────────┘
left: guided form assembles the command via type/runs-on/payload tiers. right: raw JSON editor with live schema validation. toggle in titlebar; both write the same registry record.

Field-by-field

Test-it flow

The [test it] button before save is load-bearing. It fires the command exactly as configured (respecting scope/proxy) and shows the result inline — exit code, stdout, error — without committing the button to the registry. Lets you iterate the command shape before paying the registry cost.

Function discovery shortcut (q3 preview)

Above the type radio, an optional "discover" chip:

┌────────────────────────────────────────────┐
│ ⌕ discover    "ping the agent vert"     ↵  │
└────────────────────────────────────────────┘
   suggests: type=mesh-msg, to=vert
             message="ping" (edit)
             [accept]  [skip]

Free-text intent → pinger queries known mesh capabilities (innate set + onMesh service registry) and pre-fills the form. Skip = fall through to manual. Tied to q3 — exists when discovery is wired, doesn't block the form's manual path.

Innate functions + onMesh discovery

Not every capability needs to be a registered button. Pinger ships with two layers of functions that exist below the registry, always available:

Layer 1 · innate (always on)

Pinger-level operations that don't belong in the user's button registry. Reachable from the gear menu in the titlebar regardless of what's been configured. Survive a wiped registry.

Layer 2 · onMesh discovery (when toggle is on)

When onMesh is on, pinger introspects the localMesh service registry and exposes whatever's currently registered as ad-hoc fireable functions — without making them user-buttons. Two surfaces:

a · services panel (live)
┌─ pinger ──────────── ⚙ ─┐
│ [Ping!] [Mac] [Stop  ] │   ← user registry
│ [stack] [snap] [HALT ] │
│ ─── services ─────────  │   ← onMesh layer
│ • pinger     /ping ⚡   │
│ • pingle     /ping ⚡   │
│ • mailbox    /ping ⚡   │
│ • registry   /ping ⚡   │
│ • messageRtr /ping ⚡   │
│  ⌕ filter…              │
│  ready · localMesh:8801 │
└─────────────────────────┘

Live list of registered services with one-tap probe. Auto-updates as services start/stop. Click ⚡ to fire /ping against that service; long-press to "promote to button" (opens the configure form pre-filled).

b · command palette overlay (⌘K)
┌─ pinger ──────────────────────┐
│ ┌───────────────────────────┐ │
│ │ ⌕ msg vert hey            │ │
│ └───────────────────────────┘ │
│  → mesh-msg · to=vert         │
│  → mesh-msg · to=vert.* (3)   │
│  → registry · ping vert       │
│                               │
│  recent:                      │
│   · ping pingle               │
│   · halt processFlow          │
│   · msg gourmand "ack"        │
│                               │
│  [↵ fire]   [⌘↵ promote]      │
└───────────────────────────────┘

⌘K opens a Spotlight-style command palette over pinger. Free-text intent matches against innate functions + mesh services + agents in registry. Enter fires it ad-hoc; ⌘+Enter opens the configure form to make it a permanent button.

two reachable surfaces for onMesh capabilities. (a) is for casual probing; (b) is for power-user dispatch + promotion to button

The fire-ad-hoc / promote-to-button distinction

Important separation: the registry is your buttons — curated, reordered, themed. The mesh service list is the world's services — discovered, ephemeral, changes as nodes come and go. Pinger surfaces both but doesn't conflate them. Promotion is an explicit user action: "this mesh function is something I want a permanent button for."

This means onMesh = on with zero user-buttons is still a useful pinger — it's a mesh probe. And onMesh = off falls back to a pure user-registry remote (offline / privacy mode / when traveling outside the tailnet).

Open questions

q1 · registry storage

One global ~/.pinger/registry.json, or per-profile (~/.pinger/profiles/{daily,build,media}.json) with a profile picker in the titlebar?

q2 · detachment mechanism

Does pinger physically run on aqua (separate process there, mesh-aware), or does it run on Rocky and project a UI to aqua via web/QR/screen-share? First is cleaner architecturally; second is easier to ship.

q3 · function discovery

Can buttons be auto-suggested from things already on the mesh? e.g. "I see localMesh.kill, b33p.identify, questboard.add — want any of these as buttons?" — or is the registry strictly hand-built?

q4 · sandboxing

Buttons can run arbitrary shell. Is that fine (operator-only widget) or should there be a confirmation prompt for any command.type=shell entry? Pre-approved registries vs ad-hoc edits?

Zero-hardcoded principle

no fixed buttons, no fixed services, no fixed integrations

Pinger ships empty. The frame, the gear, the statusbar — those are the chassis. Every visible action (chirp, halt, ping, mini-mac, scrollstack, questboard, mesh-msg) is either a registry entry the user added or an innate capability dispatched via the gear menu / command palette. There is no hardcoded button row, no hardcoded "Pingle integration," no hardcoded HALT semantics. v0.1's six buttons are example registry contents, not pinger features.

Availability is the gate

A button bound to a mesh service only appears, and is only configurable, when that service is online. Concretely:

This makes "is this button safe to fire right now?" a property of the live mesh, not of the registry. The registry is intent; availability is reality.

Out of scope (this phase)