relaydeck 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plugins/__init__.py +19 -0
- plugins/bundle.toml +30 -0
- plugins/dashboard/SKILL.md +66 -0
- plugins/dashboard/__init__.py +1 -0
- plugins/dashboard/plugin.py +69 -0
- plugins/dashboard/plugin.toml +21 -0
- plugins/external_agents/__init__.py +2 -0
- plugins/external_agents/detector.py +223 -0
- plugins/external_agents/harness.py +83 -0
- plugins/external_agents/models.py +183 -0
- plugins/external_agents/plugin.py +640 -0
- plugins/external_agents/plugin.toml +30 -0
- plugins/external_agents/probes.py +165 -0
- plugins/external_agents/static/panel.js +365 -0
- plugins/external_agents/store.py +97 -0
- plugins/file_watcher/__init__.py +0 -0
- plugins/file_watcher/plugin.py +341 -0
- plugins/file_watcher/plugin.toml +9 -0
- plugins/gateway/__init__.py +0 -0
- plugins/gateway/plugin.py +218 -0
- plugins/gateway/plugin.toml +15 -0
- plugins/github/__init__.py +0 -0
- plugins/github/enrich.py +135 -0
- plugins/github/plugin.py +912 -0
- plugins/github/plugin.toml +50 -0
- plugins/github/poller.py +447 -0
- plugins/github/rules.py +302 -0
- plugins/github/spawn.py +151 -0
- plugins/github/static/panel.js +1440 -0
- plugins/harnesses/__init__.py +1 -0
- plugins/harnesses/antigravity/__init__.py +1 -0
- plugins/harnesses/antigravity/agent.py +302 -0
- plugins/harnesses/antigravity/plugin.py +17 -0
- plugins/harnesses/antigravity/plugin.toml +9 -0
- plugins/harnesses/claude_code/__init__.py +0 -0
- plugins/harnesses/claude_code/agent.py +471 -0
- plugins/harnesses/claude_code/plugin.py +23 -0
- plugins/harnesses/claude_code/plugin.toml +9 -0
- plugins/harnesses/codex/__init__.py +0 -0
- plugins/harnesses/codex/agent.py +613 -0
- plugins/harnesses/codex/plugin.py +17 -0
- plugins/harnesses/codex/plugin.toml +9 -0
- plugins/harnesses/cursor/__init__.py +1 -0
- plugins/harnesses/cursor/agent.py +390 -0
- plugins/harnesses/cursor/plugin.py +18 -0
- plugins/harnesses/cursor/plugin.toml +9 -0
- plugins/harnesses/opencode/__init__.py +0 -0
- plugins/harnesses/opencode/agent.py +394 -0
- plugins/harnesses/opencode/plugin.py +19 -0
- plugins/harnesses/opencode/plugin.toml +9 -0
- plugins/harnesses/pi/__init__.py +0 -0
- plugins/harnesses/pi/agent.py +390 -0
- plugins/harnesses/pi/plugin.py +56 -0
- plugins/harnesses/pi/plugin.toml +17 -0
- plugins/harnesses/relaydeck_native/__init__.py +0 -0
- plugins/harnesses/relaydeck_native/agent.py +365 -0
- plugins/harnesses/relaydeck_native/pi_engine.py +930 -0
- plugins/harnesses/relaydeck_native/pi_extension.ts +186 -0
- plugins/harnesses/relaydeck_native/pi_startup.ts +210 -0
- plugins/harnesses/relaydeck_native/plugin.py +135 -0
- plugins/harnesses/relaydeck_native/plugin.toml +24 -0
- plugins/harnesses/relaydeck_native/prompt.py +255 -0
- plugins/harnesses/relaydeck_native/static/calls.js +111 -0
- plugins/harnesses/relaydeck_native/static/context.js +109 -0
- plugins/harnesses/relaydeck_native/static/pi_install.js +183 -0
- plugins/harnesses/relaydeck_native/static/widget_chat.js +235 -0
- plugins/harnesses/relaydeck_native/tools.py +405 -0
- plugins/hitl/__init__.py +1 -0
- plugins/hitl/plugin.py +389 -0
- plugins/hitl/plugin.toml +27 -0
- plugins/loop/__init__.py +0 -0
- plugins/loop/agent.py +415 -0
- plugins/loop/plugin.py +51 -0
- plugins/loop/plugin.toml +13 -0
- plugins/messaging/SKILL.md +133 -0
- plugins/messaging/__init__.py +0 -0
- plugins/messaging/plugin.py +1403 -0
- plugins/messaging/plugin.toml +45 -0
- plugins/messaging/static/tile_compose.js +108 -0
- plugins/messaging/static/tile_inbox.js +253 -0
- plugins/metering/__init__.py +0 -0
- plugins/metering/plugin.py +354 -0
- plugins/metering/plugin.toml +25 -0
- plugins/metering/static/fleet.js +191 -0
- plugins/metering/static/tile_cost.js +86 -0
- plugins/metering/static/tile_tokens.js +95 -0
- plugins/prompts/SKILL.md +74 -0
- plugins/prompts/__init__.py +1 -0
- plugins/prompts/plugin.py +465 -0
- plugins/prompts/plugin.toml +64 -0
- plugins/prompts/static/tile_prompts.js +113 -0
- plugins/providers/__init__.py +0 -0
- plugins/providers/anthropic/__init__.py +0 -0
- plugins/providers/anthropic/plugin.py +109 -0
- plugins/providers/anthropic/plugin.toml +10 -0
- plugins/providers/ollama/__init__.py +0 -0
- plugins/providers/ollama/plugin.py +12 -0
- plugins/providers/ollama/plugin.toml +9 -0
- plugins/providers/openai/__init__.py +0 -0
- plugins/providers/openai/plugin.py +100 -0
- plugins/providers/openai/plugin.toml +10 -0
- plugins/providers/openrouter/__init__.py +0 -0
- plugins/providers/openrouter/plugin.py +224 -0
- plugins/providers/openrouter/plugin.toml +10 -0
- plugins/py.typed +1 -0
- plugins/registry.yaml +43 -0
- plugins/skills/FLEET_SKILL.md +62 -0
- plugins/skills/PLUGIN_AUTHORING_SKILL.md +215 -0
- plugins/skills/__init__.py +0 -0
- plugins/skills/commands.py +203 -0
- plugins/skills/manager.py +295 -0
- plugins/skills/plugin.py +204 -0
- plugins/skills/plugin.toml +44 -0
- plugins/skills/routes.py +121 -0
- plugins/skills/static/panel.js +374 -0
- plugins/telegram/SKILL.md +169 -0
- plugins/telegram/__init__.py +0 -0
- plugins/telegram/conversations.py +188 -0
- plugins/telegram/inbound_map.py +123 -0
- plugins/telegram/plugin.py +2421 -0
- plugins/telegram/plugin.toml +112 -0
- plugins/telegram/routes.py +378 -0
- plugins/telegram/static/conversations.js +71 -0
- plugins/telegram/static/panel.js +1487 -0
- plugins/telegram/static/tile_chats.js +107 -0
- plugins/telegram/typing_sessions.py +132 -0
- plugins/telegram/worker.py +600 -0
- plugins/theme/SKILL.md +70 -0
- plugins/theme/__init__.py +1 -0
- plugins/theme/plugin.py +75 -0
- plugins/theme/plugin.toml +21 -0
- plugins/usage_limits/__init__.py +0 -0
- plugins/usage_limits/core.py +153 -0
- plugins/usage_limits/plugin.py +469 -0
- plugins/usage_limits/plugin.toml +43 -0
- plugins/usage_limits/static/panel.js +135 -0
- plugins/usage_limits/static/tile.js +105 -0
- plugins/vault/__init__.py +0 -0
- plugins/vault/plugin.py +518 -0
- plugins/vault/plugin.toml +24 -0
- plugins/vault/static/vault.js +171 -0
- plugins/vault/store.py +338 -0
- relaydeck/__init__.py +55 -0
- relaydeck/__main__.py +8 -0
- relaydeck/agents_base.py +108 -0
- relaydeck/atomicio.py +37 -0
- relaydeck/audit.py +222 -0
- relaydeck/auth.py +155 -0
- relaydeck/auth_tokens.py +245 -0
- relaydeck/automation/__init__.py +42 -0
- relaydeck/automation/actions.py +481 -0
- relaydeck/automation/schedule.py +63 -0
- relaydeck/automation_runs.py +296 -0
- relaydeck/bundles.py +85 -0
- relaydeck/channels.py +337 -0
- relaydeck/config.py +449 -0
- relaydeck/daemon.py +258 -0
- relaydeck/dashboard_commands.py +170 -0
- relaydeck/db.py +1790 -0
- relaydeck/harness/__init__.py +27 -0
- relaydeck/harness/base.py +1246 -0
- relaydeck/harness_model.py +297 -0
- relaydeck/harness_options.py +686 -0
- relaydeck/integrations/__init__.py +239 -0
- relaydeck/integrations/classifier.py +79 -0
- relaydeck/integrations/claude.py +324 -0
- relaydeck/layouts.py +172 -0
- relaydeck/local_providers.py +143 -0
- relaydeck/maintenance.py +109 -0
- relaydeck/messages.py +616 -0
- relaydeck/metrics.py +382 -0
- relaydeck/model_invocations.py +296 -0
- relaydeck/model_roles.py +282 -0
- relaydeck/models/__init__.py +0 -0
- relaydeck/models_dev.py +372 -0
- relaydeck/orchestrator.py +1354 -0
- relaydeck/plugin.py +2088 -0
- relaydeck/plugin_disabled.py +88 -0
- relaydeck/plugin_install.py +478 -0
- relaydeck/plugin_lock.py +195 -0
- relaydeck/plugin_manifest.py +340 -0
- relaydeck/plugin_registry.py +126 -0
- relaydeck/plugin_settings.py +216 -0
- relaydeck/preferences.py +194 -0
- relaydeck/prompt_composition.py +185 -0
- relaydeck/prompts.py +711 -0
- relaydeck/provider.py +13 -0
- relaydeck/provider_config.py +102 -0
- relaydeck/providers_extra.py +284 -0
- relaydeck/providers_ollama.py +145 -0
- relaydeck/py.typed +1 -0
- relaydeck/screen.py +93 -0
- relaydeck/sdk.py +1689 -0
- relaydeck/semantic_engine.py +263 -0
- relaydeck/skills.py +639 -0
- relaydeck/skills_cache.py +247 -0
- relaydeck/state.py +299 -0
- relaydeck/tasks.py +322 -0
- relaydeck/testing.py +391 -0
- relaydeck/themes.py +451 -0
- relaydeck/tls.py +144 -0
- relaydeck/transports/__init__.py +0 -0
- relaydeck/transports/api.py +3668 -0
- relaydeck/transports/attach.py +429 -0
- relaydeck/transports/cli.py +7000 -0
- relaydeck/transports/view.py +1246 -0
- relaydeck/transports/viewers/__init__.py +212 -0
- relaydeck/transports/viewers/ghostty.py +362 -0
- relaydeck/transports/viewers/print_viewer.py +43 -0
- relaydeck/transports/viewers/tmux.py +175 -0
- relaydeck/utils/__init__.py +1 -0
- relaydeck/vault.py +75 -0
- relaydeck/version_check.py +134 -0
- relaydeck/web/__init__.py +0 -0
- relaydeck/web/static/app.js +1132 -0
- relaydeck/web/static/data.js +373 -0
- relaydeck/web/static/favicon.svg +10 -0
- relaydeck/web/static/fonts/LICENSE +13 -0
- relaydeck/web/static/fonts/fonts.css +253 -0
- relaydeck/web/static/fonts/ibm-plex-mono-300-latin-ext.woff2 +0 -0
- relaydeck/web/static/fonts/ibm-plex-mono-300-latin.woff2 +0 -0
- relaydeck/web/static/fonts/ibm-plex-mono-400-latin-ext.woff2 +0 -0
- relaydeck/web/static/fonts/ibm-plex-mono-400-latin.woff2 +0 -0
- relaydeck/web/static/fonts/ibm-plex-mono-500-latin-ext.woff2 +0 -0
- relaydeck/web/static/fonts/ibm-plex-mono-500-latin.woff2 +0 -0
- relaydeck/web/static/fonts/ibm-plex-mono-600-latin-ext.woff2 +0 -0
- relaydeck/web/static/fonts/ibm-plex-mono-600-latin.woff2 +0 -0
- relaydeck/web/static/fonts/ibm-plex-sans-300-latin-ext.woff2 +0 -0
- relaydeck/web/static/fonts/ibm-plex-sans-300-latin.woff2 +0 -0
- relaydeck/web/static/fonts/ibm-plex-sans-400-latin-ext.woff2 +0 -0
- relaydeck/web/static/fonts/ibm-plex-sans-400-latin.woff2 +0 -0
- relaydeck/web/static/fonts/ibm-plex-sans-500-latin-ext.woff2 +0 -0
- relaydeck/web/static/fonts/ibm-plex-sans-500-latin.woff2 +0 -0
- relaydeck/web/static/fonts/ibm-plex-sans-600-latin-ext.woff2 +0 -0
- relaydeck/web/static/fonts/ibm-plex-sans-600-latin.woff2 +0 -0
- relaydeck/web/static/fonts/ibm-plex-sans-700-latin-ext.woff2 +0 -0
- relaydeck/web/static/fonts/ibm-plex-sans-700-latin.woff2 +0 -0
- relaydeck/web/static/fonts/jetbrains-mono-400-latin-ext.woff2 +0 -0
- relaydeck/web/static/fonts/jetbrains-mono-400-latin.woff2 +0 -0
- relaydeck/web/static/fonts/jetbrains-mono-500-latin-ext.woff2 +0 -0
- relaydeck/web/static/fonts/jetbrains-mono-500-latin.woff2 +0 -0
- relaydeck/web/static/fonts/jetbrains-mono-600-latin-ext.woff2 +0 -0
- relaydeck/web/static/fonts/jetbrains-mono-600-latin.woff2 +0 -0
- relaydeck/web/static/harness_brand.js +124 -0
- relaydeck/web/static/home.js +1564 -0
- relaydeck/web/static/icon_data.js +69 -0
- relaydeck/web/static/index.html +35 -0
- relaydeck/web/static/lenses/agents.js +582 -0
- relaydeck/web/static/lenses/appearance.js +446 -0
- relaydeck/web/static/lenses/messages.js +378 -0
- relaydeck/web/static/lenses/models.js +1250 -0
- relaydeck/web/static/lenses/plugins.js +414 -0
- relaydeck/web/static/lenses/workers.js +752 -0
- relaydeck/web/static/lenses/workspaces.js +403 -0
- relaydeck/web/static/model_select.js +213 -0
- relaydeck/web/static/models.css +495 -0
- relaydeck/web/static/new_agent.js +1014 -0
- relaydeck/web/static/onboarding.js +517 -0
- relaydeck/web/static/overlays.js +1540 -0
- relaydeck/web/static/panels.css +1394 -0
- relaydeck/web/static/primitives.js +288 -0
- relaydeck/web/static/shell.js +292 -0
- relaydeck/web/static/styles.css +1406 -0
- relaydeck/web/static/theme.css +102 -0
- relaydeck/web/static/theme.js +91 -0
- relaydeck/web/static/tile_system.js +293 -0
- relaydeck/web/static/tiles/config.js +70 -0
- relaydeck/web/static/tiles/context.js +135 -0
- relaydeck/web/static/tiles/events.js +193 -0
- relaydeck/web/static/tiles/identity.js +468 -0
- relaydeck/web/static/tiles/raw.js +82 -0
- relaydeck/web/static/tiles/terminal.js +403 -0
- relaydeck/web/static/uikit/README.md +143 -0
- relaydeck/web/static/uikit/base.js +225 -0
- relaydeck/web/static/uikit/format.js +27 -0
- relaydeck/web/static/uikit/index.js +56 -0
- relaydeck/web/static/uikit/modal.js +84 -0
- relaydeck/web/static/uikit/reactive.js +84 -0
- relaydeck/web/static/uikit/settings-form.js +159 -0
- relaydeck/web/static/uikit/toggle.js +53 -0
- relaydeck/web/static/uikit/uikit.css +78 -0
- relaydeck/web/static/uikit/widgets.js +162 -0
- relaydeck/web/static/update_banner.js +163 -0
- relaydeck/web/static/vendor/lit-all.min.js +116 -0
- relaydeck/web/static/vendor/xterm/LICENSE +29 -0
- relaydeck/web/static/vendor/xterm/addon-fit.min.js +8 -0
- relaydeck/web/static/vendor/xterm/addon-webgl.min.js +8 -0
- relaydeck/web/static/vendor/xterm/xterm.min.css +8 -0
- relaydeck/web/static/vendor/xterm/xterm.min.js +8 -0
- relaydeck/web/static/worker_form.js +414 -0
- relaydeck/web/static/workers.css +717 -0
- relaydeck/web_runtime.py +47 -0
- relaydeck/workers.py +445 -0
- relaydeck/workspace_health.py +113 -0
- relaydeck/workspace_plugins.py +111 -0
- relaydeck/worktrees.py +685 -0
- relaydeck-0.1.0.dist-info/METADATA +335 -0
- relaydeck-0.1.0.dist-info/RECORD +301 -0
- relaydeck-0.1.0.dist-info/WHEEL +4 -0
- relaydeck-0.1.0.dist-info/entry_points.txt +3 -0
- relaydeck-0.1.0.dist-info/licenses/LICENSE +21 -0
plugins/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Official relaydeck plugins — relaydeck-maintained, all in one tree.
|
|
2
|
+
|
|
3
|
+
Every relaydeck-managed plugin lives here: the infrastructure plugins the
|
|
4
|
+
daemon can't boot without (``vault``, ``github``, ``loop``, ``external_agents``,
|
|
5
|
+
``harnesses``) alongside the separable extensions (``messaging``, ``skills``,
|
|
6
|
+
``theme``, ``metering``, ``telegram``, ``gateway``, ``file_watcher``,
|
|
7
|
+
``usage_limits``, ``hitl``, ``dashboard``, ``providers``).
|
|
8
|
+
|
|
9
|
+
These load through real package import via the daemon plugin loader
|
|
10
|
+
(``PluginRegistry._scan_package("plugins")``). Core never imports this package
|
|
11
|
+
statically — the boundary is one-directional: plugins import the public core
|
|
12
|
+
facades (``relaydeck.sdk``, ``relaydeck.harness``, ``relaydeck.provider``,
|
|
13
|
+
``relaydeck.vault``, ``relaydeck.automation``, ``relaydeck.testing``), never the
|
|
14
|
+
other way around.
|
|
15
|
+
|
|
16
|
+
The tree ships in the single ``relaydeck`` wheel today; it is laid out so a
|
|
17
|
+
future split into a separately-versioned ``relaydeck-plugins`` distribution (or
|
|
18
|
+
per-plugin wheels) is mechanical. See ``AGENTS.md`` (Package separation).
|
|
19
|
+
"""
|
plugins/bundle.toml
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Recommended bundles of OFFICIAL relaydeck plugins.
|
|
2
|
+
#
|
|
3
|
+
# Every official plugin ships in the single `relaydeck` wheel today, so a
|
|
4
|
+
# bundle is a *recommended set* — the plugins `relaydeck doctor` expects to be
|
|
5
|
+
# present and the dashboard surfaces as the default experience. This file is
|
|
6
|
+
# also the pinned manifest of what an eventual split `relaydeck-plugins` wheel
|
|
7
|
+
# (or per-plugin wheels) must provide, so the recommended set has one source of
|
|
8
|
+
# truth that travels with the plugins.
|
|
9
|
+
#
|
|
10
|
+
# Names are plugin manifest names (the `name = ...` in each plugin.toml), which
|
|
11
|
+
# is what discovery and the lockfile compare against.
|
|
12
|
+
|
|
13
|
+
[bundle.default]
|
|
14
|
+
description = "The full relaydeck experience — every official plugin."
|
|
15
|
+
plugins = [
|
|
16
|
+
# Infrastructure (engine depends on these)
|
|
17
|
+
"vault", "github", "loop", "external",
|
|
18
|
+
# Harnesses
|
|
19
|
+
"pi-harness", "claude-code-harness", "codex-harness", "cursor-harness",
|
|
20
|
+
"opencode-harness", "antigravity-harness", "relaydeck-native",
|
|
21
|
+
# Extensions
|
|
22
|
+
"messaging", "skills", "theme", "metering", "telegram",
|
|
23
|
+
"gateway", "file-watcher", "usage-limits", "hitl", "prompts", "dashboard",
|
|
24
|
+
# Provider catalogs
|
|
25
|
+
"openai", "anthropic", "ollama", "openrouter",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[bundle.minimal]
|
|
29
|
+
description = "Engine-essential only — one harness, secrets, and the operator."
|
|
30
|
+
plugins = ["vault", "pi-harness", "relaydeck-native"]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: relaydeck-dashboard
|
|
3
|
+
description: Reshape the live relaydeck web dashboard. Read this when asked to rearrange, add, remove, move, or resize Home widgets, switch the theme/density/glow, or "make the dashboard look like X". Changes apply instantly to every open dashboard. Drive it with the `relaydeck dashboard` CLI.
|
|
4
|
+
metadata:
|
|
5
|
+
short-description: Restyle and rearrange the relaydeck dashboard
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Controlling the relaydeck dashboard
|
|
9
|
+
|
|
10
|
+
The dashboard is a 12-column Home widget grid plus an appearance layer
|
|
11
|
+
(theme / density / glow). You change it through the **`relaydeck dashboard`**
|
|
12
|
+
CLI — every command POSTs to the daemon, which broadcasts the change so it
|
|
13
|
+
applies **instantly** to every open dashboard (no reload). A daemon must be
|
|
14
|
+
running.
|
|
15
|
+
|
|
16
|
+
## See what's there first
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
relaydeck dashboard get # theme / density / glow + saved widget grid
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For the full theme list use `relaydeck theme list`. To author or recolor a
|
|
23
|
+
theme (vs. just switching to one), use the **relaydeck-theme** skill.
|
|
24
|
+
|
|
25
|
+
## Appearance
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
relaydeck dashboard theme ink # any theme from `relaydeck theme list`
|
|
29
|
+
relaydeck dashboard density compact # compact | comfy | regular
|
|
30
|
+
relaydeck dashboard glow off # on | off
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Widgets (the Home grid)
|
|
34
|
+
|
|
35
|
+
The grid is **12 columns wide**; `x`/`y` are 0-based cell coordinates and
|
|
36
|
+
`w`/`h` are cell spans. Widget keys:
|
|
37
|
+
|
|
38
|
+
`fleet` · `usage` · `agents` · `feed` · `workspaces` · `spawn` · `workers`
|
|
39
|
+
· `worktrees` · `clock` · `notes` · `focus`
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
relaydeck dashboard add workers # add a widget
|
|
43
|
+
relaydeck dashboard remove clock # remove it
|
|
44
|
+
relaydeck dashboard move usage 8 0 # move 'usage' to column 8, row 0
|
|
45
|
+
relaydeck dashboard resize agents 6 4 # resize 'agents' to 6 wide x 4 tall
|
|
46
|
+
relaydeck dashboard tidy # auto-arrange, remove gaps
|
|
47
|
+
relaydeck dashboard reset # back to the default layout
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Notes
|
|
51
|
+
|
|
52
|
+
- Invalid values are rejected with the allowed set (e.g. an unknown widget or
|
|
53
|
+
theme) — read the error and retry; nothing partial is applied.
|
|
54
|
+
- **`theme` / `density` / `glow` persist server-side** and `dashboard get`
|
|
55
|
+
reflects them immediately — no browser needs to be open. They default to the
|
|
56
|
+
global (dashboard-wide) scope; pass `--workspace <name>` to scope one
|
|
57
|
+
workspace, and `dashboard get --workspace <name>` to read that scope.
|
|
58
|
+
- **The saved Home widget grid** is also in `dashboard get` (from
|
|
59
|
+
`preferences.yaml`). When nothing is saved yet, `get` shows the package
|
|
60
|
+
default layout. Live `add`/`move`/`resize`/`tidy`/`reset` ops apply instantly
|
|
61
|
+
in open browsers; the browser persists the grid back to the server.
|
|
62
|
+
- **Widget ops (`add`/`remove`/`move`/`resize`/`tidy`/`reset`) apply to open
|
|
63
|
+
dashboards live** — the `✓` from the command is your confirmation once the
|
|
64
|
+
browser applies it.
|
|
65
|
+
- This is the same capability the relaydeck-native agent's `dashboard` tool
|
|
66
|
+
has, exposed as a CLI so any harness can use it.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Dashboard plugin — ships the `relaydeck-dashboard` skill."""
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dashboard plugin — the skill-shipping surface for live dashboard control.
|
|
3
|
+
|
|
4
|
+
The control surface itself is core: the validator
|
|
5
|
+
(`relaydeck/dashboard_commands.py`), the endpoint
|
|
6
|
+
(`POST /api/dashboard/command`), and the CLI (`relaydeck dashboard …`). The
|
|
7
|
+
browser applies the emitted `dashboard.command` events live. This plugin
|
|
8
|
+
exists for one reason: to materialize the `relaydeck-dashboard` SKILL.md into
|
|
9
|
+
workspaces via the generic `[plugin.skills]` mechanism, so an agent on ANY
|
|
10
|
+
harness can reshape the dashboard through the `relaydeck dashboard` CLI — the
|
|
11
|
+
same capability the native `dashboard` tool gives the relaydeck-native agent.
|
|
12
|
+
|
|
13
|
+
Daemon-wide (not workspace-scoped) and opt-out via the `inject_skill`
|
|
14
|
+
setting; mirrors the theme plugin.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from relaydeck.sdk import Plugin, PluginHost
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
PLUGIN_NAME = "dashboard"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _setting(key: str, default: Any) -> Any:
|
|
30
|
+
from relaydeck.plugin_settings import get_setting
|
|
31
|
+
return get_setting(PLUGIN_NAME, key, default=default)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _inject_skill_enabled() -> bool:
|
|
35
|
+
v = _setting("inject_skill", True)
|
|
36
|
+
if isinstance(v, bool):
|
|
37
|
+
return v
|
|
38
|
+
if isinstance(v, str):
|
|
39
|
+
return v.strip().lower() not in ("false", "0", "no", "off", "")
|
|
40
|
+
return bool(v)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class DashboardPlugin(Plugin):
|
|
44
|
+
"""Daemon-wide plugin that ships the `relaydeck-dashboard` skill."""
|
|
45
|
+
|
|
46
|
+
def on_load(self, host: PluginHost) -> None:
|
|
47
|
+
self.host = host
|
|
48
|
+
self._config_home = host.config_home
|
|
49
|
+
|
|
50
|
+
def on_unload(self) -> None:
|
|
51
|
+
# The skills plugin strips materialized skills on plugin unload.
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
def on_settings_changed(self, new_values: dict[str, Any]) -> None:
|
|
55
|
+
del new_values
|
|
56
|
+
try:
|
|
57
|
+
self.host.events.emit("plugin.skills.changed", {"plugin": PLUGIN_NAME})
|
|
58
|
+
except Exception:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
def skill_target_workspaces(self, all_workspaces: list[str]) -> list[str]:
|
|
62
|
+
"""Dashboard control is universal — ship to every workspace unless the
|
|
63
|
+
operator turned `inject_skill` off."""
|
|
64
|
+
if not _inject_skill_enabled():
|
|
65
|
+
return []
|
|
66
|
+
return list(all_workspaces)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
PLUGIN = DashboardPlugin()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[plugin]
|
|
2
|
+
name = "dashboard"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Live dashboard control — ships the `relaydeck-dashboard` skill so any harness agent can reshape the dashboard"
|
|
5
|
+
author = "relaydeck"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
category = "tool"
|
|
8
|
+
host_api_version = 1
|
|
9
|
+
# Daemon-wide: the control surface (validator, POST /api/dashboard/command,
|
|
10
|
+
# `relaydeck dashboard` CLI) lives in core. This plugin's only job is to ship
|
|
11
|
+
# the `relaydeck-dashboard` SKILL.md to workspaces so agents learn the CLI.
|
|
12
|
+
workspace_scoped = false
|
|
13
|
+
declared_capabilities = [
|
|
14
|
+
"events.emit",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[plugin.settings]
|
|
18
|
+
inject_skill = { type = "bool", default = true, description = "Materialize the `relaydeck-dashboard` skill into every workspace so agents can reshape the dashboard." }
|
|
19
|
+
|
|
20
|
+
[plugin.skills]
|
|
21
|
+
relaydeck-dashboard = "SKILL.md"
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""Detect whether a path is a Hermes or OpenClaw runtime.
|
|
2
|
+
|
|
3
|
+
Detection is **filesystem-first and side-effect-free**: it reads a few marker
|
|
4
|
+
files and directory names and scores the evidence. The only host probe is an
|
|
5
|
+
injectable `which(<cli>)` check ("is the runtime's CLI installed?") which is a
|
|
6
|
+
soft signal — it never executes the CLI. We deliberately do NOT run
|
|
7
|
+
`hermes`/`openclaw` here (that's `probes.py`, and only on demand) so `detect`
|
|
8
|
+
stays fast and safe to call while scanning many directories.
|
|
9
|
+
|
|
10
|
+
Returns a `Detection` (scored evidence). The caller decides whether the
|
|
11
|
+
confidence is high enough to register or surface as a candidate.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import shutil
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from .models import (
|
|
20
|
+
HERMES,
|
|
21
|
+
OPENCLAW,
|
|
22
|
+
T_GATEWAY_WS,
|
|
23
|
+
T_MCP,
|
|
24
|
+
UNKNOWN,
|
|
25
|
+
Detection,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Confidence weights per signal. Tuned so a single strong marker (a
|
|
29
|
+
# pyproject naming hermes-agent, an openclaw.mjs, or a `.hermes`/`.openclaw`
|
|
30
|
+
# home) clears the 0.5 "candidate" bar, and two independent markers clear
|
|
31
|
+
# the 0.9 "confident" bar.
|
|
32
|
+
_W_HOME_NAME = 0.6 # dir literally named .hermes / .openclaw
|
|
33
|
+
_W_STRONG_REPO = 0.6 # pyproject/package.json/openclaw.mjs naming the project
|
|
34
|
+
_W_CONFIG_FILE = 0.35 # config.yaml in a plausible home
|
|
35
|
+
_W_CONTEXT_FILE = 0.15 # .hermes.md / SOUL.md etc.
|
|
36
|
+
_W_DIR_HINT = 0.12 # a hermes/openclaw-looking subdir
|
|
37
|
+
_W_CLI_ON_PATH = 0.25 # the runtime's CLI is installed on this host
|
|
38
|
+
|
|
39
|
+
_CONFIDENCE_CAP = 0.99
|
|
40
|
+
CANDIDATE_THRESHOLD = 0.5
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _which(name: str) -> str | None:
|
|
44
|
+
"""Indirection point so tests can monkeypatch CLI presence without
|
|
45
|
+
depending on whether hermes/openclaw is actually installed."""
|
|
46
|
+
return shutil.which(name)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _read_head(path: Path, limit: int = 8192) -> str:
|
|
50
|
+
try:
|
|
51
|
+
with path.open("r", encoding="utf-8", errors="replace") as f:
|
|
52
|
+
return f.read(limit)
|
|
53
|
+
except OSError:
|
|
54
|
+
return ""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _score_hermes(path: Path) -> tuple[float, list[str], str | None]:
|
|
58
|
+
signals: list[str] = []
|
|
59
|
+
score = 0.0
|
|
60
|
+
config_home: str | None = None
|
|
61
|
+
|
|
62
|
+
if path.name == ".hermes":
|
|
63
|
+
score += _W_HOME_NAME
|
|
64
|
+
signals.append("dir named .hermes")
|
|
65
|
+
config_home = str(path)
|
|
66
|
+
|
|
67
|
+
cfg = path / "config.yaml"
|
|
68
|
+
if cfg.exists():
|
|
69
|
+
score += _W_CONFIG_FILE
|
|
70
|
+
signals.append("config.yaml present")
|
|
71
|
+
config_home = config_home or str(path)
|
|
72
|
+
|
|
73
|
+
pyproject = path / "pyproject.toml"
|
|
74
|
+
if pyproject.exists():
|
|
75
|
+
head = _read_head(pyproject).lower()
|
|
76
|
+
if "hermes-agent" in head or "hermes_agent" in head or "hermes agent" in head:
|
|
77
|
+
score += _W_STRONG_REPO
|
|
78
|
+
signals.append("pyproject names hermes-agent")
|
|
79
|
+
elif "hermes" in head:
|
|
80
|
+
score += _W_DIR_HINT
|
|
81
|
+
signals.append("pyproject mentions hermes")
|
|
82
|
+
|
|
83
|
+
for ctx in (".hermes.md", "SOUL.md"):
|
|
84
|
+
if (path / ctx).exists():
|
|
85
|
+
score += _W_CONTEXT_FILE
|
|
86
|
+
signals.append(f"{ctx} present")
|
|
87
|
+
|
|
88
|
+
for d in ("hermes_cli", "hermes_agent"):
|
|
89
|
+
if (path / d).is_dir():
|
|
90
|
+
score += _W_DIR_HINT
|
|
91
|
+
signals.append(f"{d}/ dir")
|
|
92
|
+
|
|
93
|
+
if _which("hermes"):
|
|
94
|
+
score += _W_CLI_ON_PATH
|
|
95
|
+
signals.append("hermes CLI on PATH")
|
|
96
|
+
|
|
97
|
+
# A conventional home alongside a repo checkout.
|
|
98
|
+
if config_home is None:
|
|
99
|
+
home = Path.home() / ".hermes"
|
|
100
|
+
if home.exists():
|
|
101
|
+
config_home = str(home)
|
|
102
|
+
|
|
103
|
+
return score, signals, config_home
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _score_openclaw(path: Path) -> tuple[float, list[str], str | None]:
|
|
107
|
+
signals: list[str] = []
|
|
108
|
+
score = 0.0
|
|
109
|
+
config_home: str | None = None
|
|
110
|
+
|
|
111
|
+
if path.name == ".openclaw":
|
|
112
|
+
score += _W_HOME_NAME
|
|
113
|
+
signals.append("dir named .openclaw")
|
|
114
|
+
config_home = str(path)
|
|
115
|
+
|
|
116
|
+
if (path / "config.yaml").exists() or (path / "config.json").exists():
|
|
117
|
+
score += _W_CONFIG_FILE
|
|
118
|
+
signals.append("config file present")
|
|
119
|
+
config_home = config_home or str(path)
|
|
120
|
+
|
|
121
|
+
if (path / "openclaw.mjs").exists():
|
|
122
|
+
score += _W_STRONG_REPO
|
|
123
|
+
signals.append("openclaw.mjs present")
|
|
124
|
+
|
|
125
|
+
pkg = path / "package.json"
|
|
126
|
+
if pkg.exists():
|
|
127
|
+
head = _read_head(pkg).lower()
|
|
128
|
+
if "openclaw" in head:
|
|
129
|
+
score += _W_STRONG_REPO
|
|
130
|
+
signals.append("package.json names openclaw")
|
|
131
|
+
|
|
132
|
+
if (path / "pnpm-workspace.yaml").exists():
|
|
133
|
+
score += _W_DIR_HINT
|
|
134
|
+
signals.append("pnpm-workspace.yaml")
|
|
135
|
+
|
|
136
|
+
for d in ("extensions", "apps"):
|
|
137
|
+
if (path / d).is_dir():
|
|
138
|
+
score += _W_DIR_HINT * 0.5
|
|
139
|
+
signals.append(f"{d}/ dir")
|
|
140
|
+
|
|
141
|
+
if _which("openclaw"):
|
|
142
|
+
score += _W_CLI_ON_PATH
|
|
143
|
+
signals.append("openclaw CLI on PATH")
|
|
144
|
+
|
|
145
|
+
if config_home is None:
|
|
146
|
+
home = Path.home() / ".openclaw"
|
|
147
|
+
if home.exists():
|
|
148
|
+
config_home = str(home)
|
|
149
|
+
|
|
150
|
+
return score, signals, config_home
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def detect(path: str | Path) -> Detection:
|
|
154
|
+
"""Score `path` as a hermes/openclaw runtime. Filesystem-only."""
|
|
155
|
+
p = Path(path).expanduser()
|
|
156
|
+
root = str(p)
|
|
157
|
+
if not p.exists():
|
|
158
|
+
return Detection(
|
|
159
|
+
kind=UNKNOWN, confidence=0.0, root=root,
|
|
160
|
+
warnings=[f"path does not exist: {root}"],
|
|
161
|
+
)
|
|
162
|
+
if not p.is_dir():
|
|
163
|
+
return Detection(
|
|
164
|
+
kind=UNKNOWN, confidence=0.0, root=root,
|
|
165
|
+
warnings=[f"not a directory: {root}"],
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
h_score, h_signals, h_home = _score_hermes(p)
|
|
169
|
+
o_score, o_signals, o_home = _score_openclaw(p)
|
|
170
|
+
|
|
171
|
+
if h_score == 0.0 and o_score == 0.0:
|
|
172
|
+
return Detection(
|
|
173
|
+
kind=UNKNOWN, confidence=0.0, root=root,
|
|
174
|
+
warnings=["no hermes/openclaw markers found"],
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if h_score >= o_score:
|
|
178
|
+
kind, score, signals, home, transport = (
|
|
179
|
+
HERMES, h_score, h_signals, h_home, T_MCP,
|
|
180
|
+
)
|
|
181
|
+
else:
|
|
182
|
+
kind, score, signals, home, transport = (
|
|
183
|
+
OPENCLAW, o_score, o_signals, o_home, T_GATEWAY_WS,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return Detection(
|
|
187
|
+
kind=kind,
|
|
188
|
+
confidence=min(score, _CONFIDENCE_CAP),
|
|
189
|
+
signals=signals,
|
|
190
|
+
recommended_transport=transport,
|
|
191
|
+
root=root,
|
|
192
|
+
config_home=home,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def scan_candidates(extra_roots: list[str] | None = None) -> list[Detection]:
|
|
197
|
+
"""Auto-detect external runtimes on this machine.
|
|
198
|
+
|
|
199
|
+
Scans the conventional config homes (`~/.hermes`, `~/.openclaw`) plus any
|
|
200
|
+
`extra_roots` (e.g. registered relaydeck workspace paths) and returns
|
|
201
|
+
detections at or above the candidate threshold. The caller filters out
|
|
202
|
+
anything already registered before surfacing "detected — add?" affordances.
|
|
203
|
+
"""
|
|
204
|
+
seen: set[str] = set()
|
|
205
|
+
out: list[Detection] = []
|
|
206
|
+
roots: list[Path] = [Path.home() / ".hermes", Path.home() / ".openclaw"]
|
|
207
|
+
for r in extra_roots or []:
|
|
208
|
+
try:
|
|
209
|
+
roots.append(Path(r).expanduser())
|
|
210
|
+
except (OSError, ValueError):
|
|
211
|
+
continue
|
|
212
|
+
for root in roots:
|
|
213
|
+
key = str(root)
|
|
214
|
+
if key in seen:
|
|
215
|
+
continue
|
|
216
|
+
seen.add(key)
|
|
217
|
+
if not root.exists() or not root.is_dir():
|
|
218
|
+
continue
|
|
219
|
+
det = detect(root)
|
|
220
|
+
if det.matched and det.confidence >= CANDIDATE_THRESHOLD:
|
|
221
|
+
out.append(det)
|
|
222
|
+
out.sort(key=lambda d: d.confidence, reverse=True)
|
|
223
|
+
return out
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Harnesses that run a linked external runtime's interactive chat TUI as a
|
|
3
|
+
relaydeck agent.
|
|
4
|
+
|
|
5
|
+
The `external` plugin observes Hermes/OpenClaw read-only — but once a
|
|
6
|
+
runtime is linked, the most useful next step is to actually *use* it: drop
|
|
7
|
+
an instance of its chat TUI into any workspace, live in the dashboard
|
|
8
|
+
terminal tile alongside your pi/claude agents (attach, watch, message it).
|
|
9
|
+
|
|
10
|
+
relaydeck already wraps CLIs (pi, claude-code, codex) as PTY-backed
|
|
11
|
+
`HarnessAgent`s, so the external runtime's CLI is just another harness. We
|
|
12
|
+
spawn `<cli> <subcommand>` in the *workspace's* directory (so the runtime
|
|
13
|
+
operates on that workspace) using the runtime's OWN config/auth home
|
|
14
|
+
(`~/.hermes`, `~/.openclaw`). We never copy secrets or mutate the external
|
|
15
|
+
config — relaydeck only runs the binary.
|
|
16
|
+
|
|
17
|
+
Unlike the native harnesses this deliberately does NOT inject relaydeck's
|
|
18
|
+
model preset or `--append-system-prompt`: Hermes/OpenClaw own their model
|
|
19
|
+
selection + prompting, and those flags differ per runtime. The command is
|
|
20
|
+
just `<CLI> <DEFAULT_ARGS>` plus optional per-agent `config.command` (full
|
|
21
|
+
override) / `config.args` (additive) — so an operator can tune it
|
|
22
|
+
(`-m`, `--continue`, `--worktree`, …) without code changes.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import shlex
|
|
28
|
+
|
|
29
|
+
from relaydeck.harness import HarnessAgent
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ExternalChatAgent(HarnessAgent):
|
|
33
|
+
"""Base harness for an external runtime's interactive chat CLI."""
|
|
34
|
+
|
|
35
|
+
CLI = "" # set by subclass
|
|
36
|
+
DEFAULT_ARGS: list[str] = []
|
|
37
|
+
|
|
38
|
+
def _build_command(self) -> list[str]:
|
|
39
|
+
cmd = [self.CLI] + list(self.DEFAULT_ARGS)
|
|
40
|
+
user_cmd = self.config.get("command")
|
|
41
|
+
if isinstance(user_cmd, list):
|
|
42
|
+
cmd = [str(a) for a in user_cmd]
|
|
43
|
+
elif isinstance(user_cmd, str) and user_cmd.strip():
|
|
44
|
+
cmd = shlex.split(user_cmd)
|
|
45
|
+
extra = self.config.get("args")
|
|
46
|
+
if isinstance(extra, list):
|
|
47
|
+
cmd.extend(str(a) for a in extra)
|
|
48
|
+
elif isinstance(extra, str) and extra.strip():
|
|
49
|
+
cmd.extend(shlex.split(extra))
|
|
50
|
+
return cmd
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class HermesChatAgent(ExternalChatAgent):
|
|
54
|
+
"""Hermes Agent — `hermes chat` (verified: "Interactive chat with the agent")."""
|
|
55
|
+
|
|
56
|
+
CLI = "hermes"
|
|
57
|
+
DEFAULT_ARGS = ["chat"]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class OpenClawChatAgent(ExternalChatAgent):
|
|
61
|
+
"""OpenClaw — bare `openclaw` launches its TUI.
|
|
62
|
+
|
|
63
|
+
The exact chat subcommand is best-effort (OpenClaw needs Node >= 22.19;
|
|
64
|
+
if the local node is older the CLI exits with an upgrade hint, which the
|
|
65
|
+
operator will see in the terminal tile). Override per-agent with
|
|
66
|
+
`config.args` / `config.command` if the invocation differs.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
CLI = "openclaw"
|
|
70
|
+
DEFAULT_ARGS: list[str] = []
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# External-agent kind -> registered relaydeck harness type name.
|
|
74
|
+
KIND_TO_TYPE: dict[str, str] = {
|
|
75
|
+
"hermes": "hermes",
|
|
76
|
+
"openclaw": "openclaw",
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Harness type name -> class, registered by the plugin's on_load.
|
|
80
|
+
HARNESS_TYPES: dict[str, type[ExternalChatAgent]] = {
|
|
81
|
+
"hermes": HermesChatAgent,
|
|
82
|
+
"openclaw": OpenClawChatAgent,
|
|
83
|
+
}
|