Agency-Watch Custom Apps
Jetzt mit AgencyOS27: kostenloses Update für alle, die die Uhr schon haben.

⌚ Eine eigene App für die Agency-Watch bauen
Die Uhr ist offen. Ein Serverbetreiber kann eine App ergänzen, die aussieht und sich verhält wie eine eingebaute, mit denselben Bausteinen, denselben Farben und denselben Übersetzungen.
Voraussetzung: die Pro-Edition. Eigene Apps gibt es nur, wenn das Agency-Phone auf dem Server läuft und der Spieler es besitzt. Ohne Pro werden sie angemeldet, aber nie ausgeliefert: die Oberfläche erfährt nie von ihnen, sie erscheinen nicht im App Store, und der Server lehnt jede Aktion ab, die sie schicken. Das wird an drei getrennten Stellen geprüft und lässt sich nicht abschalten.
| Datei | Pflicht | Was sie tut |
|---|---|---|
apps/<id>/app.lua | ja | Meldet die App an: Name, Icon, Farbe, Kategorie. |
apps/<id>/app.js | ja | Der Bildschirm: was er zeichnet und wie er reagiert. |
apps/<id>/app.css | nein | Eigenes Styling. Wird nur geladen, wenn style = true gesetzt ist. |
apps/<id>/server.lua | nein | Serverlogik, die deine App aufrufen kann. |
Der Ordner heißt genau wie die App-id. So findet die Uhr app.js und app.css, ohne dass der Pfad zweimal aufgeschrieben wird, und zweimal aufgeschriebene Pfade laufen früher oder später auseinander.
Alles unter apps/ steht in escrow_ignore: es bleibt auf deiner Platte lesbar und änderbar, und ein Update der Uhr überschreibt es nie.
1 Die App anmelden
apps/hello/app.lua
AgencyWatchApp({
id = 'hello', -- lowercase, digits and _, must match the folder
name = 'Hello',
icon = 'raster', -- one of the built-in icons, see below
color = '#5ac8fa', -- #rrggbb
category = 'tools', -- tools | games | driving | style | connected
style = true, -- also load app.css
texts = { en = 'Hello', de = 'Hallo' }, -- optional, name per language
})
Eine App mit fehlerhafter id, ohne Namen, mit unbekannter Kategorie oder einer Farbe, die nicht #rrggbb ist, wird in der Serverkonsole abgelehnt, deutlich und mit Begründung. Halb angemeldet gibt es nicht: so ein Fehler würde sich sonst erst zeigen, wenn ein Spieler die App antippt.
Nach dem Hinzufügen von Dateien refresh und restart Agency-Watch ausführen. Beim Start meldet die Uhr, was angemeldet wurde.
2 Den Bildschirm zeichnen
apps/hello/app.js
AgencyWatch.app('hello', {
render: function (w) { // required, returns HTML
var d = w.data();
return w.card('Hello, ' + w.esc(d.name)) +
w.section('Steps') +
w.row({ icon: 'schritte', static: true,
top: String(d.steps || 0) }) +
w.button('Roll a die', ' data-roll');
},
bind: function (w) { // optional, runs right after
w.click('[data-roll]', function () {
w.toServer('roll', { sides: 6 }).then(function (a) {
if (a.ok) w.notify('Hello', 'You rolled ' + a.data.value);
});
});
},
});
render läuft jedes Mal, wenn der Bildschirm gebaut wird, und behält daher keinen Zustand zwischen den Aufrufen. Was ein Neuzeichnen überleben muss, gehört in eine Variable außerhalb des App-Objekts.
Beide Funktionen laufen in einem Sicherheitsnetz. Wirft dein Code einen Fehler, zeigt deine App eine Fehlerzeile und die Uhr läuft weiter; der eigentliche Fehler landet in der F8-Konsole.
3 Der Werkzeugkasten
w enthält alles, was eine App auf einer Uhr braucht, und nichts darüber hinaus. Eine Schnittstelle, die alles kann, lässt sich nie wieder ändern, deshalb ist diese bewusst klein.
| Aufruf | Was du bekommst |
|---|---|
w.data() | Eine flache Momentaufnahme: time, hour, minute, date, weekday, name, job, health, armour, pulse, steps, distance, calories, speed, heading, weather, pro. |
w.row({icon, top, bottom, end, attrs, static}) | Eine Listenzeile im eigenen Stil der Uhr. |
w.section(text) | Eine Abschnittsüberschrift. |
w.card(html) | Eine Fläche. |
w.button(text, attrs) | Ein Knopf. attrs ist ein roher Attribut-String, z. B. ' data-roll'. |
w.toggle(on) | Ein Ein-/Aus-Schalter. |
w.center(html) | Zentriert, für einen einzelnen großen Wert. |
w.icon(name) | Eines der eingebauten Icons. |
w.esc(text) | Text maskieren. Nutze das für alles, was nicht von dir stammt. |
w.text(key, fallback) | Eine Übersetzung aus der Uhr. |
w.click(selector, fn) | Einen Listener anhängen. Nur innerhalb von bind. |
w.notify(title, text, icon, colour) | Eine Benachrichtigung auf der Uhr. |
w.refresh() | Neu zeichnen, ohne die Einblend-Animation. |
w.close() | Zurück, wie der Pfeil in der Kopfzeile. |
w.toServer(action, data) | Den Server fragen. Gibt ein Promise zurück. |
w.id, w.version | Deine App-id und die AgencyOS-Version. |
Die Momentaufnahme ist eine Kopie, kein Zugriff auf das Original. Die Uhr trägt auch Kontakte, Nachrichten und Kartendaten; nichts davon ist darin enthalten.
4 Die Serverseite
apps/hello/server.lua
AgencyWatchAction('hello', 'roll', function(src, data)
local sides = tonumber(data and data.sides) or 6
if sides < 2 then sides = 2 end
if sides > 100 then sides = 100 end
return { value = math.random(1, math.floor(sides)) }
end)
w.toServer löst immer auf, mit { ok: true, data: … } oder { ok: false, reason: '…' }, auch wenn der Server nie geantwortet hat. Deine App kann nie wartend hängen bleiben, und der Grund steht immer auf der obersten Ebene, egal welche Seite abgelehnt hat.
reason | Bedeutung |
|---|---|
no_pro | Keine Pro-Edition. Ohne sie gibt es keine eigenen Apps. |
unknown | Keine solche App oder keine solche Aktion angemeldet. |
too_fast | Ratenbegrenzung: höchstens zehn Anfragen pro fünf Sekunden je Spieler. |
error | Dein Handler hat einen Fehler geworfen. Die Einzelheiten stehen in der Serverkonsole. |
timeout | Keine Antwort innerhalb von zwölf Sekunden. |
data | Die Anfrage war keine Tabelle. |
Was in data ankommt, kommt vom Client und ist ein Wunsch, keine Tatsache. Prüf jeden Wert, bevor du ihn verwendest. Alles, was zählt (Geld, Items, Ergebnisse), gehört auf den Server und nirgendwo sonst hin.
5 Styling
Die Farben der Uhr stehen als CSS-Variablen bereit und folgen dem, was der Träger im Studio gewählt hat. Fest eingetragene Farben ergeben eine App, die auf einer grünen Uhr blau bleibt.
var(--text) var(--text-2) var(--leise)
var(--akzent) var(--gruen) var(--rot) var(--gelb)
var(--karte) var(--karte-hoch) var(--linie)
var(--grund)
Versieh deine eigenen Klassen mit einem Präfix. Der Bildschirm wird mit jeder anderen App geteilt, und eine Klasse namens .large findet schnell Gesellschaft.
6 Eingebaute Icons
Jedes davon kannst du an icon in app.lua oder an w.icon() übergeben:
akku auto blitz brief einkauf farben flugzeug gewitter glocke haken helligkeit herz hoch hupe karte karte-pin kein-signal kino kompass kreuz krone lampe laufen lautlos links loeschen minus mobil mond nebel neuladen pause personen pin plus qr raster rechts regen regler runter sanduhr schild schloss schloss-auf schnee schritte signal sonne sp-fall sp-flug sp-ring sp-turm sprechen stoppuhr telefon telefon-aus tempo ton uhr verboten wiedergabe wlan wolke zahnrad
Die Icon-Namen bleiben mit Absicht, wie sie sind. Sie sind ids, keine Beschriftungen: ein Spieler liest sie nie, und eine umbenannte id macht jede App kaputt, die sie schon benutzt.
📦 Ein funktionierendes Beispiel liegt der Uhr bei
apps/example/ in der Resource ist eine vollständige, lauffähige App: Bausteine, ein Serveraufruf, eine Benachrichtigung und eigenes CSS. Ordner kopieren, umbenennen, und du hast einen Startpunkt, der schon funktioniert.
Eine Kurzfassung dieser Seite liegt daneben in apps/README.md.