Microeden Agry SDK (ENG)
1. What Agry Dev Mode is
Agry Dev Mode is the development and test environment for extensions running inside Agry. The host loads the plugin, while third-party code runs in an isolated runtime. Isolation is part of the security model: a plugin receives explicit bridge methods instead of direct access to the host page, browser storage, host DOM or authenticated session.
Dev Mode requires an authenticated developer account with a persistent Microeden session, a matching developer token and development tools enabled. A Pro subscription alone does not grant Dev Mode or Store publication access.
2. Runtime model and lifecycle
An isolated plugin normally runs as an asynchronous module or self-invoking async function:
(async function () {
Agry.log("Plugin started", "info");
// Register UI and event handlers here.
})();
Agry is the main facade. MyMicroeden exposes account and IoT device services. Both are proxies that communicate through a validated RPC bridge.
Recommended lifecycle:
- Register widgets, ribbon buttons, windows and listeners.
- Read only the data needed by the current view.
- Use isolated plugin storage for state that must survive a reload.
- Register cleanup with
Agry.onStop. - Remove timers, handlers, markers and windows when the plugin stops.
3. Minimal plugin example
(async function () {
Agry.log("Plugin started", "info");
await Agry.addWidget(
"field-summary",
"Field summary",
"π±",
'<p id="summary-text">Loadingβ¦</p>'
);
const fields = await Agry.fields.list();
await Agry.dom.setHTML("summary-text", `Fields: ${fields.length}`);
Agry.onStop(async function () {
await Agry.clearAllMarkers();
});
})();
Use isolated DOM methods for plugin UI. Returned handles are limited to the plugin and cannot select arbitrary host nodes.
4. Agry API reference
The following methods are available in the isolated third-party runtime. They return promises unless the description explicitly says that a callback is registered. Map, widget, window and ribbon APIs use positional arguments. DOM setters receive the ID of a plugin element, not a browser DOM node.
4.1 Runtime information and logging
Agry.versionβ runtime API version string. The sandbox currently reports3.3 (sandbox).Agry.log(message, type)β diagnostic log; common types areinfo,success,warninganderror.Agry.toast(message, type)β short notification in the host UI.Agry.onStop(callback)β cleanup callback invoked when the plugin is stopped.
4.2 Fields
Agry.fields.list()β fields/areas visible to the current account.Agry.fields.getDiary(fieldName)β diary entries for a field.Agry.fields.getTasks(fieldName)β scheduled and completed tasks for a field.Agry.fields.getFieldIcon(fieldName)β the field icon or emoji used by Agry.Agry.getFields()β compatibility helper returning fields.Agry.getNearestField(latitude, longitude)β nearest visible field when a position is available.
Returned records are snapshots. Treat names, IDs, dates, tasks, elements, weather and assignments as optional because older projects may not contain properties introduced later.
4.3 Diary and tasks
Agry.diary.addEntry(fieldName, noteText, photoBase64) creates a diary entry. The photo is optional and should be a base64 data URL only when a photo was actually attached.
await Agry.diary.addEntry(
"North field",
"Regular growth after irrigation.",
null
);
Agry.tasks.schedule(fieldName, activityType, dateISO, time)creates a task;timemay benullfor all-day work.Agry.tasks.reschedule(fieldName, activityType, originalDateISO, newDateISO, newTime)moves a task to another date and optionally changes its time.Agry.tasks.delete(fieldName, activityType, dateISO, activityId)deletes a scheduled task; the date may be omitted when an activity ID is supplied.- There is no public isolated list method for tasks; use
Agry.fields.getTasks(fieldName).
Use ISO dates (YYYY-MM-DD) and, where present, 24-hour times (HH:mm). Preserve the collaborator ID and display name returned by the host; do not use an email as a visual label when a name is available.
4.4 Warehouse
Agry.warehouse.getInventory()β inventory for the current project/account.Agry.warehouse.addItem(item)β adds or updates an item.Agry.warehouse.removeItem(itemId)β removes an item.Agry.warehouse.save()β persists pending changes.
Warehouse data is capability-protected. When access is denied, show an explicit unavailable state instead of assuming the warehouse is empty.
4.5 Network access
Agry.fetch(url, options)β validated request to an external HTTPS service.Agry.httpGet(url)β GET helper.Agry.httpPost(url, body)β JSON POST helper.Agry.openExternal(url)β opens an HTTPS site in a new tab after user consent and firewall validation.
Requests pass through the sandbox firewall. Native fetch, XHR, WebSocket, EventSource and browser imports are not available to the plugin.
const response = await Agry.fetch("https://api.example.com/status", {
method: "GET",
headers: { "Accept": "application/json" }
});
const status = await response.json();
Do not send credentials, cookies, CSRF tokens or other secrets. A host may require explicit consent when a request exports sensitive project data.
Agry.openExternal accepts only complete HTTPS URLs, without a username or password and with a maximum length of 2,048 characters. The first call requires the revocable external-navigation capability and firewall permission for the destination host. Treat denial as a normal application state.
4.6 Map
Agry.map.setView(latitude, longitude, zoom)β changes map view.Agry.map.drawShape(type, coordinates, options)β draws a plugin-owned shape.Agry.map.clearLayers()β removes plugin layers.Agry.map.getUserPosition()β requests the current position when authorized.Agry.addMarker(latitude, longitude, popupContent, iconSource, alertLevel)β adds a plugin marker.Agry.removeMarker(markerHandle)β removes a marker returned byAgry.addMarker.Agry.clearAllMarkers()β removes all plugin markers.Agry.onMapClick(callback)β registers a map-click callback.
const marker = await Agry.addMarker(
45.123,
11.456,
"<strong>Beehive observation</strong>",
"π",
"normal"
);
Markers and layers belong to the plugin. Remove them during cleanup when they should not survive plugin shutdown.
4.7 Ribbon buttons, widgets and windows
Agry.addRibbonButton(label, icon, onClickFunc)β adds a button to the Agry ribbon.Agry.addWidget(widgetId, title, icon, contentHTML)β creates a dashboard widget. Reusing an ID is idempotent and returns success, not a mutable widget handle.Agry.createWindow(windowId, title, contentHTML, width, height)β opens a plugin window/panel.Agry.onWindowClose(windowId, callback)β registers a close callback.Agry.drawRiskLink(startCoord, endCoord, level)β draws a supported risk/relationship link.
Use stable IDs, short titles, approved emoji/icons and content owned by the plugin. Update plugin content through the isolated DOM bridge.
await Agry.createWindow(
"my-plugin-panel",
"Field report",
'<p id="report-content">Report ready.</p>',
560,
"auto"
);
await Agry.dom.setHTML("report-content", "<p>Updated report.</p>");
4.8 Dialogs
Agry.ui.confirm(message, callback)Agry.ui.prompt(message, defaultValue, callback)
These use host dialogs and return their result through the callback. Browser window.confirm, window.prompt and alert are not available in the isolated runtime.
4.9 DOM and styles
Agry.dom.getElementById(id)Agry.dom.createElement(tagName)Agry.dom.querySelector(selector)Agry.dom.removeElement(id)Agry.dom.getValue(id)Agry.dom.getProperty(id, property)β reads the controlledchecked,value,textContent,innerTextordisabledproperties.Agry.dom.setValue(id, value)Agry.dom.setGaugeValue(id, value)β updates a gauge value, arc and ARIA attributes.Agry.dom.setChartData(id, payload)β updates chart data and rendering without recreating the component.payloadmay containlabels,values,min,maxand supported chart options.Agry.dom.setHTML(id, html)Agry.dom.setStyles(id, styles)Agry.dom.setAttributes(id, attributes)Agry.dom.setProperty(id, property, value)β sets only the controlledcheckedandvalueproperties.Agry.styles.set(styleId, cssText)
The first three methods return plugin-scoped handles. Handles expose compatibility helpers such as css, attr, html and val. Escape HTML containing user or project data and keep IDs inside the plugin namespace.
setValue, setHTML, setStyles, setAttributes, setProperty, setGaugeValue and setChartData can queue an update when a component (for example inside a window or marker popup) has not been mounted yet; the host applies it when the component becomes available.
4.10 Events
Agry.events.add(eventName, callback)Agry.events.remove(eventName, callback)Agry.events.getTargetId()
Event names are host-defined and may grow over time. Keep callbacks fast and use simple serializable payloads.
4.11 Storage and files
Plugin storage is isolated from the site's local storage:
Agry.storage.set(key, value)Agry.storage.get(key)Agry.storage.clear()β clears keys for the current plugin.Agry.storage.getAllKeys()
For user downloads:
Agry.file.download(filename, data, mimeType)β starts a download through the host UI.
Direct localStorage, sessionStorage, IndexedDB, Cache Storage and filesystem access are intentionally unavailable.
4.12 Dates, time and utilities
Date helpers:
Agry.date.now()Agry.date.getHours(date)Agry.date.getMinutes(date)Agry.date.getSeconds(date)Agry.date.format(date, withTime)Agry.date.addDays(date, days)Agry.date.diffDays(firstDate, secondDate)Agry.date.toISO(date)
Timers:
Agry.time.setTimeout(callback, delay)Agry.time.setInterval(callback, delay)Agry.time.clearTimeout(id)Agry.time.clearInterval(id)
Utilities:
Agry.utils.getDistance(a, b)β distance calculation.Agry.utils.formatArea(area)β area formatting.
The full host may contain additional helpers; the isolated facade intentionally exposes only the safe subset documented here.
4.13 Database snapshot
Agry.DB contains read-only snapshots used by supported UI functions:
Agry.DB.areaIconsAgry.DB.categoriesAgry.DB.wildlifeAgry.DB.cropAgry.DB.alertsAgry.DB.presetsAgry.DB.schemas
Snapshots may be absent or incomplete in older projects. Do not modify them or treat them as a write API.
4.14 External sites and the Flows block
Use Agry.openExternal(url) to open an external site:
try {
await Agry.openExternal("https://example.com/docs");
} catch (error) {
Agry.log(error?.message || error, "warning");
}
The method validates the URL, checks the firewall and requests the external-navigation capability. It opens a new tab with noopener,noreferrer only after the user grants consent. HTTPS, no credentials and the 2,048-character URL limit are enforced. Agry.openUrl(url) remains as a compatibility alias and follows the same rules.
The Flows editor provides an Apri sito / Open site block with one input and one output. Its properties can use a literal HTTPS URL or the value produced by the previous block (context.value). The boolean result is written back to context.value for the next node, and generated code calls the same Agry.openExternal API as handwritten code.
5. MyMicroeden SDK bridge
The isolated runtime exposes a limited MyMicroeden facade:
const profile = await MyMicroeden.account.getProfile();
const devices = await MyMicroeden.devices.list();
Account
MyMicroeden.account.getProfile()β account name and roles when consent allows; internal account ID and email are not exposed.MyMicroeden.account.getPreferences()β isolated plugin preferences.MyMicroeden.account.setPreference(key, value)β saves an isolated plugin preference.MyMicroeden.account.deletePreference(key)β removes an isolated plugin preference.
Devices
MyMicroeden.devices.list()β devices visible to the current account.MyMicroeden.devices.getHistory(deviceId, day, month, year)β limited telemetry/history for the requested day.MyMicroeden.devices.getCategories()β device categories.
Do not assume other site SDK namespaces. Never call internal endpoints directly to bypass the bridge or capability checks.
6. Capabilities and consent
Sensitive APIs are protected by the plugin capability manager. Current base scopes include:
| Capability | Purpose |
|---|---|
| account-profile-read | Read account name and roles |
| preferences-read | Read isolated plugin preferences |
| preferences-write | Write isolated plugin preferences |
| devices-read | Read devices and limited telemetry |
| project-data-read | Read fields, tasks and geometries |
| diary-write | Create diary entries |
| tasks-write | Create or modify tasks |
| warehouse-read | Read warehouse data |
| warehouse-write | Modify warehouse data |
| location-read | Read user location |
| plugin-storage-write | Use isolated plugin storage |
| file-download | Start a download |
| external-navigation | Open an external HTTPS site in a new tab after confirmation |
| sensitive-data-export:hostname | Export sensitive data to a specific external host |
Consent is stored per plugin and account until revoked. Dev Mode may use a temporary choice during tests; published plugins must still work when consent is denied or revoked.
Capability principles:
- Request the minimum scope required.
- Explain why a capability is needed in UI or metadata.
- Treat denial as a normal state.
- Never put secrets in source, metadata or storage.
- Do not use a network request to avoid a consent prompt.
7. Network policy
Every external request is validated before leaving the runtime:
- HTTPS is required.
- URLs are limited to 2,048 characters.
- Usernames and passwords in URLs are rejected.
- Same-origin URLs and
microeden.ioor its subdomains are rejected by the plugin network bridge. - Localhost, internal/LAN/home/corp names, private, link-local, reserved, multicast and literal IPv6 addresses are blocked.
- Firewall blacklist entries are blocked.
- Allowed methods are GET, HEAD, POST, PUT, PATCH and DELETE.
- GET and HEAD cannot include a body.
- Bodies must be strings and are limited to 32 KiB.
- At most 20 headers are accepted, each limited to 2,048 characters.
- Cookie, host, origin, referer, proxy-authorization, CSR-token and CSRF-token headers are blocked.
- The default budget is 20 requests per execution.
- Each request has an eight-second timeout.
- Responses are limited to 5 MiB.
- Credentials are omitted, redirects are rejected, cache is disabled and no referrer is sent.
Use a server-side integration for credentials and rate limiting. Never embed an API key in a plugin distributed through the Store.
8. Sandbox limits (informational)
The implementation currently uses an isolated iframe and a worker/RPC bridge. Native browser globals and direct host objects are intentionally unavailable. Current validation limits include:
- 30 RPC arguments maximum;
- 20 concurrent RPC calls maximum;
- 16,384 characters per argument;
- maximum nested object depth of 7;
- arrays up to 500 items;
- 120 keys per object maximum;
- keys must match
[A-Za-z0-9_.-]{1,96}; __proto__,prototypeandconstructorkeys are rejected;- cyclic and non-plain objects are rejected;
- 96 registered callbacks maximum;
- 32 KiB serialized request budget;
- 5 MiB ordinary isolated response budget;
- 256 KiB MyMicroeden SDK response budget.
The runtime also blocks references or calls to browser globals, browser storage, direct network constructors, dynamic code execution, dynamic imports and cross-window messaging. These checks are defense in depth; plugins must still validate input, escape displayed text and protect their own backends.
9. Source and package requirements
A Store plugin has a stable ID and metadata. A development source normally contains a JavaScript entry point and a manifest in the plugin directory. The current catalog validates the exact package schema; local metadata alone is not sufficient for publication.
Recommended metadata:
- stable ID;
- readable name;
- semantic version;
- category;
- short description;
- icon or emoji;
- up to three screenshots;
- entry source;
- requested capabilities;
- privacy information and external services.
Keep source within the Developer Console limit and move very large static data to host-managed resources where possible.
10. Developer Console API
Developer Console operations are reserved for the authenticated console owner and are not public runtime APIs. Requests require a persistent authenticated session, development tools enabled, a valid MICROEDEN-CSR-TOKEN header and the service's same-site/referer checks.
| Action | Description |
|---|---|
| list | List the developer's plugins and states |
| quota | Read source/storage quota |
| open_source | Read current source |
| autosave_source | Save an automatic draft |
| save_draft | Save a normal draft |
| submit_review | Submit a plugin for review |
| update_metadata | Update name, version, category, description, icon and screenshots |
| delete | Delete a developer plugin |
Current metadata limits are: name 120 characters, version 30, category 80, description 2,000, icon 512, up to three screenshot URLs of 1,500 characters each, and source 45,000 characters. Common review states are draft, pending, review, testing, approved, published and rejected.
The server performs a static security scan for browser globals, direct network constructors, dynamic code, dynamic imports, native fetch, prototype/constructor tricks and cross-window messaging. Source is encrypted server-side with AES-256-GCM (enc:v2) and may be compressed before storage; encryption does not make unsafe runtime logic safe.
11. Store loading and execution modes
The Store catalog is loaded from the authenticated cloud catalog. Catalog metadata and plugin source are separate operations. If a catalog entry has no migration source, the host reports a source/migration error; verify that cloud source is published and that the stable ID has not changed.
Third-party plugins run in isolated mode. Only a small, explicitly authorized legacy integration uses the previous host mode. A plugin must not depend on legacy access, internal globals or DOM selectors outside its own namespace.
12. Reliable plugin patterns
Reading and rendering
Read the minimum data, normalize optional fields, escape user text and distinguish loading, empty, error and success states.
function escapeText(value) {
return String(value == null ? "" : value)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/\"/g, """)
.replace(/'/g, "'");
}
Updates
Use Agry.time.setInterval only when polling is necessary, choose a conservative interval and clear it in Agry.onStop. Avoid updating a form while the user is editing it.
Errors
Catch bridge and network errors, log technical details with Agry.log and show a short useful toast with Agry.toast. Do not display raw server responses that may contain secrets.
Idempotency
Use stable IDs for widgets, windows, markers and saved records. Check current plugin state before creating duplicates; repeated widget/window calls with the same ID are handled by the host.
Dates
Store dates in ISO format and display them with host helpers. Respect the timezone represented by returned task data.
Collaborators and projects
Use the display name supplied by Agry for collaborator assignments and respect host visibility rules.
13. Troubleshooting
Sandbox argument too long
Reduce the size of each RPC argument. Do not pass a whole project, image or source file to one method; split data or use a host-managed reference and keep request bodies within the documented limits.
widget.setHTML is not a function
Agry.addWidget(...) returns success, not a mutable widget object. Pass initial HTML as the fourth argument and update a plugin element with Agry.dom.setHTML(elementId, html) or a DOM-handle html helper.
Invalid marker coordinates
Agry.addMarker uses positional arguments. Passing an options object produces NaN coordinates and is correctly rejected by the bridge. Use finite latitude/longitude values in valid ranges.
Invalid window ID
Agry.createWindow also uses positional arguments. The first argument must be a stable ID matching [A-Za-z][A-Za-z0-9_-]{0,63} and must not be reserved by the host.
Capability denied
The user did not grant the requested scope or revoked it. Show an explanatory empty state and continue with the capabilities still available.
External navigation blocked
Check that the URL is HTTPS, complete, credential-free and no longer than 2,048 characters. Confirm that external-navigation is requested and that the firewall permits the host. A denial or revocation is expected user input, not a reason to break the whole UI.
Plugin source unavailable
Verify the stable ID, cloud catalog entry, published source and review state. Legacy local metadata does not automatically provide Store source.
Unexpected UI behavior
Check direct browser globals, non-isolated selectors, duplicate event registration and uncleared timers. Use stable IDs and the isolated DOM bridge.
Network request blocked
Check HTTPS, host authorization, size, method, headers, body and request budget. Do not bypass the firewall with XHR, WebSocket, data URLs or a second proxy.
14. Compatibility checklist
Before submitting a plugin:
- Read
Agry.versionand feature-detect optional methods. - Test with no fields, tasks, warehouse items or devices.
- Test denied capabilities and expired sessions.
- Test long text, missing names, old records and invalid dates.
- Test a slow or unreachable external service.
- Stop and restart the plugin to verify cleanup.
- Confirm credentials and tokens are absent from source and logs.
- Run the Developer Console security scan and resolve warnings.
- Verify metadata, screenshots, privacy information and requested capabilities.
- Keep behavior within the isolated facade documented here.
15. Version management note
This reference describes the implementation available when it was generated. The host may add APIs or tighten limits without changing existing plugin data. Treat undocumented methods as private, avoid internal endpoints and always provide a fallback when a method or capability is unavailable.