dsh-home-hosted 0.7.3 → 0.7.5
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.
- package/README.md +11 -0
- package/docs/AGENT-TOOLS.md +3 -2
- package/docs/BOOT.md +47 -0
- package/docs/DESIGN.md +1 -1
- package/docs/ENTRIES.md +32 -0
- package/docs/PANEL.md +100 -5
- package/docs/WORKSPACES.md +24 -2
- package/lib/client.js +8 -8
- package/lib/index.js +572 -150
- package/lib/index.js.map +4 -4
- package/lib/types/boot/escape.d.ts +43 -2
- package/lib/types/boot/launchd.d.ts +13 -0
- package/lib/types/home-hosted/config-file.d.ts +52 -1
- package/lib/types/home-hosted/dsh-entry.d.ts +3 -1
- package/lib/types/home-hosted/entries.d.ts +6 -3
- package/lib/types/home-hosted/layout.d.ts +9 -0
- package/lib/types/home-hosted/panel-console.d.ts +20 -0
- package/lib/types/home-hosted/resolve.d.ts +9 -2
- package/lib/types/service.d.ts +41 -3
- package/lib/types/shared/contracts.d.ts +85 -10
- package/lib/types/util/suggest.d.ts +23 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -103,6 +103,7 @@ the server management, not the thing this plugin is.
|
|
|
103
103
|
| `home_hosted_servers_edit` | `create` · `update` · `delete` |
|
|
104
104
|
| `home_hosted_autostart_manage` | `install` · `uninstall` |
|
|
105
105
|
| `home_hosted_ui_manage` | `status` · `update` · `revert` · `switch` (official asset or local zip) |
|
|
106
|
+
| `home_hosted_panel_logs` | The panel's own console output. Needs no API token, so it works when the panel is up but not answering. |
|
|
106
107
|
|
|
107
108
|
Every server tool names its `workspace`, because an id is only unique inside one.
|
|
108
109
|
|
|
@@ -163,6 +164,16 @@ the CLI, so a row for it could only crash-loop.) Server entries of every other k
|
|
|
163
164
|
|
|
164
165
|
</details>
|
|
165
166
|
|
|
167
|
+
<details>
|
|
168
|
+
<summary><b>Read more</b></summary>
|
|
169
|
+
|
|
170
|
+
Topic docs, one per area — written for whoever has to touch it next:
|
|
171
|
+
|
|
172
|
+
[Boot autostart](docs/BOOT.md) · [the panel](docs/PANEL.md) · [server entries](docs/ENTRIES.md) ·
|
|
173
|
+
[workspaces and several panels](docs/WORKSPACES.md) · [agent tools](docs/AGENT-TOOLS.md)
|
|
174
|
+
|
|
175
|
+
</details>
|
|
176
|
+
|
|
166
177
|
<details>
|
|
167
178
|
<summary><b>Managed entry and port policy</b></summary>
|
|
168
179
|
|
package/docs/AGENT-TOOLS.md
CHANGED
|
@@ -6,9 +6,10 @@ Related: [server entries](ENTRIES.md)
|
|
|
6
6
|
|
|
7
7
|
## Agent tools are merged and on by default
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Eight tools, not ten: `servers_lifecycle` carries start/stop/restart, `servers_edit` carries
|
|
10
10
|
create/update/delete, `autostart_manage` carries install/uninstall, `ui_manage` drives the panel's own
|
|
11
|
-
UI, and `workspaces_list` is the read-only way to see every
|
|
11
|
+
UI, `panel_logs` reads the panel's console, and `workspaces_list` is the read-only way to see every
|
|
12
|
+
workspace. They register on by default;
|
|
12
13
|
what gates a mutating call is the session's own permission mode, not a plugin-level default.
|
|
13
14
|
|
|
14
15
|
A description is read at the approval prompt, so each is one line a person can glance at: what the tool
|
package/docs/BOOT.md
CHANGED
|
@@ -191,6 +191,53 @@ fallback gets a command that works.
|
|
|
191
191
|
Enabling and switching differ only in the retirement: a switch already has an entry keeping the panel
|
|
192
192
|
alive, so its plan carries the old mechanism's removal.
|
|
193
193
|
|
|
194
|
+
## Where the entry's own output goes
|
|
195
|
+
|
|
196
|
+
A boot-run panel's output does **not** go to the panel's own console
|
|
197
|
+
(`<home>/.hh/.logs/home-hosted.log`, which `panel_logs` reads and `docs/PANEL.md` describes).
|
|
198
|
+
Each mechanism sends it somewhere different, and the status line now names where:
|
|
199
|
+
|
|
200
|
+
| mechanism | where the output goes |
|
|
201
|
+
| --- | --- |
|
|
202
|
+
| `systemd-user` / `systemd-system` | the journal — the unit sets no `StandardOutput=`, so the detail names `journalctl --user -u <unit>` or `journalctl -u <unit>` |
|
|
203
|
+
| `launchd-agent` / `launchd-daemon` | `<logDir>/<label>.out.log` and `<label>.err.log`, the two paths the generated plist carries |
|
|
204
|
+
| `xdg-autostart` | nowhere persistent: a `.desktop` entry with `Terminal=false` has its output discarded by the session |
|
|
205
|
+
| `windows-run` / `windows-task` | nowhere persistent either |
|
|
206
|
+
|
|
207
|
+
So the hint appears only where a file or a query actually exists — and only once the entry is
|
|
208
|
+
installed, because naming a log for an entry that was never written sends someone to a
|
|
209
|
+
command that finds nothing. The launchd paths come from one computation
|
|
210
|
+
(`launchdLogPaths`), shared with the plist builder, so the plist and the status line cannot
|
|
211
|
+
name different files.
|
|
212
|
+
|
|
213
|
+
## What counts as "failing"
|
|
214
|
+
|
|
215
|
+
A unit's state comes from `systemctl show -p Result`. The value is compared against systemd's own
|
|
216
|
+
failure enum — `failed`, `exit-code`, `signal`, `timeout`, `core-dump`, `watchdog`,
|
|
217
|
+
`start-limit-hit`, `oom-kill` — and anything else, `success` included, is not a failure. `oom-kill`
|
|
218
|
+
is the one worth reading correctly: it is what a container limit or the OOM killer produces, and a
|
|
219
|
+
unit that died that way reading as merely "disabled" is how someone stops looking for it.
|
|
220
|
+
|
|
221
|
+
## The page says what a mechanism name means
|
|
222
|
+
|
|
223
|
+
The picker's values are the identifiers the setting stores — `systemd-system`, `systemd-user`,
|
|
224
|
+
`xdg-autostart`, `launchd-agent` — and they are not self-explanatory, so the page prints the host's own
|
|
225
|
+
one-line reason for the chosen one ("systemd is not the init system here (no /run/systemd/system)", "a
|
|
226
|
+
LaunchAgent loads at login, not at boot", "this needs root here, so the plugin stages the plist and
|
|
227
|
+
shows the sudo commands"). Those sentences come from `BootCandidate.reason`, which the host already
|
|
228
|
+
computed for every mechanism, available or not.
|
|
229
|
+
|
|
230
|
+
Because `auto` is not a mechanism but "let the host decide", the page also names what that decision
|
|
231
|
+
would be: `BootStatus.recommended` is the host's own `recommend()` answer, and `install()` falls through
|
|
232
|
+
to it when nothing is installed (`pickOf(mechanism ?? before.mechanism ?? before.recommended)`). So
|
|
233
|
+
"Automatic" says which mechanism the next install would use, instead of leaving a person to install it
|
|
234
|
+
and read the result. The sentence is only shown while nothing is installed under that mechanism, and it
|
|
235
|
+
is absent — never "undefined" — when an older panel does not send the field.
|
|
236
|
+
|
|
237
|
+
The `Details` block describes the mechanism that is actually **installed**, which is a different one
|
|
238
|
+
while a switch is pending, so both it and the picker read one shared lookup rather than each formatting
|
|
239
|
+
the name their own way.
|
|
240
|
+
|
|
194
241
|
## Privilege
|
|
195
242
|
|
|
196
243
|
Boot scope needs privilege somewhere on every platform: a system unit, or `loginctl enable-linger` on
|
package/docs/DESIGN.md
CHANGED
|
@@ -38,7 +38,7 @@ its page.
|
|
|
38
38
|
`onPortConflict: kill` arrived in home-hosted 0.6.0 and `persistent` in 0.6.3, and an older panel
|
|
39
39
|
handed either key can refuse to *boot* from the config. The plugin used to consult the answering
|
|
40
40
|
panel's version and refuse or drop them (`KILL_UNSUPPORTED`). That is gone on purpose: it pins
|
|
41
|
-
`home-hosted@^0.7.
|
|
41
|
+
`home-hosted@^0.7.19` and autostarts its own copy, so a panel older than those keys is only reachable by
|
|
42
42
|
deliberately preferring an old global install — and this is pre-1.0. Supporting older panels again means
|
|
43
43
|
bringing that check back from history, not re-deriving it.
|
|
44
44
|
|
package/docs/ENTRIES.md
CHANGED
|
@@ -61,6 +61,31 @@ The generated entry always binds loopback (`bind: 'local'`, `--host 127.0.0.1`):
|
|
|
61
61
|
`--host 0.0.0.0` as a usage error, so repeating a running harness's network bind would produce an
|
|
62
62
|
entry that can never start.
|
|
63
63
|
|
|
64
|
+
## The page's editor covers the common fields on purpose
|
|
65
|
+
|
|
66
|
+
`src/client/entry-editor.tsx` renders eight entry fields — id, command, args, working directory,
|
|
67
|
+
label, port, autostart and port-conflict policy — and the panel's `serverSchema` has 23. The other
|
|
68
|
+
fifteen (`enabled`, `bind`, `persistent`, `env`, `dataEnvs`, `envFile`, `bootstrap`, `dependsOn`,
|
|
69
|
+
`resources`, `backupPaths`, `logBufferLines`, `backupIgnoreGenerated`, `health`, `restart`, `stop`)
|
|
70
|
+
are **deliberately out of scope**, not forgotten.
|
|
71
|
+
|
|
72
|
+
The page says so where it happens: *"Entries are managed in the home-hosted panel:"* followed by a
|
|
73
|
+
link to that panel, whose own editor sets every field. So the dsh page is a convenience for the
|
|
74
|
+
common ones, and the panel remains the place a full config is written.
|
|
75
|
+
|
|
76
|
+
Two properties make that safe, and both are deliberate:
|
|
77
|
+
|
|
78
|
+
- **A minimal diff cannot destroy what it does not render.** An edit sends only the fields that
|
|
79
|
+
differ from the stored entry, so `bootstrap`, `envFile` or a `restart` policy is left exactly as it
|
|
80
|
+
was — the editor cannot silently reset a key it does not show.
|
|
81
|
+
- **The scope is a recorded decision, not an accident.** `EDITOR_FIELDS` and `OUT_OF_SCOPE_FIELDS`
|
|
82
|
+
are exported, and `test/client/entry-editor` reads the panel's own schema and fails if any field is
|
|
83
|
+
neither rendered nor declared. A field the panel grows therefore cannot become reachable there and
|
|
84
|
+
silently absent here — it fails until somebody decides which it is.
|
|
85
|
+
|
|
86
|
+
The same file asserts the *rendered* labels, not just the list: a field can be declared and never
|
|
87
|
+
drawn, which is the exact shape this guards against.
|
|
88
|
+
|
|
64
89
|
## Persistence keeps dsh alive across a panel restart
|
|
65
90
|
|
|
66
91
|
A managed entry asks for `persistent: true`: home-hosted then runs it under its nanny, which owns the
|
|
@@ -82,6 +107,13 @@ triggers its file watcher. A write to a workspace's `servers.config.json` does
|
|
|
82
107
|
`autostart: false`, then flip `autostart`. A later write to an existing entry is a *changed
|
|
83
108
|
definition*, which only takes effect at that entry's next start.
|
|
84
109
|
|
|
110
|
+
The file path has one subtlety worth knowing: the panel's API **merges** the nested groups
|
|
111
|
+
(`restart`, `health`, `stop`) key by key, and so must this write, or a partial patch would replace the
|
|
112
|
+
group and silently drop every sibling key — `{ restart: { maxRetries: 1 } }` would wipe `baseDelayMs`
|
|
113
|
+
and the rest, and the entry would fall back to the panel's defaults. The plugin mirrors the panel's own
|
|
114
|
+
`SERVER_MERGE_KEYS`/`CONTROL_MERGE_KEYS`, and a test reads those from the installed panel so the copy
|
|
115
|
+
cannot fall behind it.
|
|
116
|
+
|
|
85
117
|
## What the plugin owns
|
|
86
118
|
|
|
87
119
|
Exactly `autostart`, `onPortConflict`, `persistent` and `stop.killPortHolders` (`OWNED_ENTRY_KEYS`).
|
package/docs/PANEL.md
CHANGED
|
@@ -13,11 +13,38 @@ globally. An operator's `homeHostedCommand` always wins — that is an instructi
|
|
|
13
13
|
|
|
14
14
|
The range is stated twice and both must agree: `dependencies['home-hosted']` in `package.json` and
|
|
15
15
|
`EXPECTED_RANGE` in `resolve.ts` (what the page offers to install, and warns about when a global copy
|
|
16
|
-
is older). Bumping one alone makes the page recommend a range the plugin is not built against.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
is older). Bumping one alone makes the page recommend a range the plugin is not built against.
|
|
17
|
+
`test/resolve` **enforces** that agreement — it was a rule the docs stated and nothing checked, so
|
|
18
|
+
setting the manifest to `^0.7.99` while the constant stayed put failed nothing until the guard
|
|
19
|
+
existed. The same file asserts `MIN_SUPPORTED_VERSION` lies inside the advertised range, since a floor
|
|
20
|
+
above it would advertise a copy the plugin then refuses.
|
|
21
|
+
|
|
22
|
+
A bump is therefore those two plus the version-coupled tests — *unless* the release moved something
|
|
23
|
+
this plugin names: a path under `.hh`, `CONFIG_SCHEMA`, `serverSchema`, `/api/settings`, or a CLI
|
|
24
|
+
subcommand. Check that by **use**, not by filename: a file appearing in a diff cannot show that a
|
|
25
|
+
change reaches us, and the two disagree as soon as a comment lands in a schema file.
|
|
26
|
+
|
|
27
|
+
**0.7.18 and 0.7.19 did move one, and no filename showed it.** Upstream `applyPatch` now deletes a
|
|
28
|
+
top-level key set to `null` (`1f58a5d`), where it used to write it through; `serverSchema.label` also
|
|
29
|
+
began to admit `null` (`45d689e`). This plugin keeps its own copy of that function for the file
|
|
30
|
+
fallback, and the copies had drifted — the nested branch deleted, the top-level branch wrote the
|
|
31
|
+
`null` — so a top-level `null` that the panel deletes would have been stored, producing a config it
|
|
32
|
+
refuses to boot (`servers[0] ("smoke"): cwd must be a string (was null)`). One `applyPatchFields` now
|
|
33
|
+
serves both paths, and the refusal a `null` group gets is one rule `src/service.ts` reads too.
|
|
34
|
+
|
|
35
|
+
No shipped caller could reach the harmful case, and saying so is part of the finding: the agent tool
|
|
36
|
+
layer validates `patch` against the panel's own field types, so only `port` and `bootstrap` can carry
|
|
37
|
+
a `null` at all — exactly the two whose schema is `… | null` — while the page emits `port` alone. The
|
|
38
|
+
drift was real but latent; it is fixed because a copy of the panel's function is a fork of its
|
|
39
|
+
semantics, and the next field to allow `null` would have made it reachable.
|
|
40
|
+
|
|
41
|
+
Verified against a real 0.7.19 panel and its own CLI: `PATCH {port:null}` deletes the key; only `port`
|
|
42
|
+
and `bootstrap` have a schema that admits `null`; `_hh` is still exactly four routes; and every route,
|
|
43
|
+
subcommand and flag this plugin drives is unchanged. The console **file** — `.hh/.logs/home-hosted.log`
|
|
44
|
+
plus its `.1`, and the 5 MB rotation — is unchanged too, and this plugin's reader still matches
|
|
45
|
+
`home-hosted logs` line for line. A capability that needs a *newer* 0.7.x degrades rather than raising
|
|
46
|
+
the floor: `logs` (0.7.12) is a file read either way, and `restart <id>` (0.7.13) falls back to the
|
|
47
|
+
stop/start pair it replaced.
|
|
21
48
|
|
|
22
49
|
That copy resolves to a pnpm path carrying a version and a peer hash, which moves on the next install
|
|
23
50
|
and disappears when the profile is rebuilt. A boot entry that baked it in would fail exactly when it
|
|
@@ -65,6 +92,74 @@ response says the page will disconnect; the helper logs to `bin/panel-takeover.l
|
|
|
65
92
|
can still carry `ok: false` — the page reads the payload's `detail`/`output` rather than trusting
|
|
66
93
|
`envelope.ok`, so a refused control action says why instead of looking like a no-op.
|
|
67
94
|
|
|
95
|
+
## The console is readable when nothing else is
|
|
96
|
+
|
|
97
|
+
A panel that is up but misbehaving is exactly when its API is hardest to reach: the token may be
|
|
98
|
+
stale, `writeVia` may be `file`, and the thing that explains the symptom is what the panel printed
|
|
99
|
+
while starting. So `panel.console` reads `<root>/.hh/.logs/home-hosted.log` off disk — no session, no
|
|
100
|
+
API token, no CLI, and no dependence on the panel answering — and the `panel_logs` agent tool exposes
|
|
101
|
+
it. It reads one rotation too (`<log>.1`), because the end of the file that just rotated away is
|
|
102
|
+
usually where a crash is, and it matches `home-hosted logs` line for line (verified against a real
|
|
103
|
+
panel at every count).
|
|
104
|
+
|
|
105
|
+
The read **grows its window** rather than sizing one from the count. A line's length is unbounded —
|
|
106
|
+
a stack trace, a JSON dump or a verbose error is one line and can be kilobytes — so a window sized
|
|
107
|
+
per *line* silently returns fewer lines than asked for: on 500 lines of ~2 KB, `lines: 100` returned
|
|
108
|
+
50. The window instead grows backwards until `limit` lines are present or the file start is reached,
|
|
109
|
+
so a bounded request never reads an unbounded file and a short line still stops after one block
|
|
110
|
+
(20 lines of a 37.9 MB log reads 64 KiB). Home-hosted's own `readTail` grows for the same reason.
|
|
111
|
+
|
|
112
|
+
The rotation is joined the way **concatenating the files** joins it, not by gluing two line arrays.
|
|
113
|
+
A panel killed mid-write leaves `.1`'s last line unterminated, and concatenation makes that line and
|
|
114
|
+
the live file's first line one line (`rot2-partial` + `live1` → `rot2-partiallive1`); array-joining
|
|
115
|
+
invents a line break the log never had, and then "the last 2 lines" is one short. Sufficiency is
|
|
116
|
+
therefore counted in **separators** (`limit + 1`), the same test `logs` makes — counting lines stops
|
|
117
|
+
one early for exactly that reason.
|
|
118
|
+
|
|
119
|
+
A missing log is an empty one, not an error: a panel that has just started has nothing to say, and
|
|
120
|
+
reporting that as a failure would make the one diagnostic that always works look broken. The new tool
|
|
121
|
+
is **not** added to an existing settings file's allow-list — a tool that did not exist when that file
|
|
122
|
+
was written could not have been deselected, but granting it silently would expand a deliberate
|
|
123
|
+
selection, so the page's checkbox is how it is chosen.
|
|
124
|
+
|
|
125
|
+
## What `/_hh` does not offer, deliberately
|
|
126
|
+
|
|
127
|
+
`/_hh` is the one unauthenticated surface a local caller can use, and it is deliberately tiny. Read
|
|
128
|
+
out of `src/api/control.ts` (mounted before the `/api/*` guard in `src/app.ts`), it registers exactly
|
|
129
|
+
**four** routes: `POST /shutdown`, and `POST /servers/:id/{start,stop,restart}`. Every one needs the
|
|
130
|
+
token from `run.json` plus a loopback peer, which is the whole point — a local `down` needs no session,
|
|
131
|
+
password or API token.
|
|
132
|
+
|
|
133
|
+
There is **no local-token path to the reverse proxy, backups, TLS, notifications or the config**. Those
|
|
134
|
+
live behind `/api/*` and a real credential. So a plugin capability over any of them would have to mint
|
|
135
|
+
a token and speak `/api`, and it must not add an unauthenticated `/api` route of its own to avoid that
|
|
136
|
+
— that is the rule the plugin is built on (the CLI drives a running panel through `/_hh`, never
|
|
137
|
+
`/api`). `_acme` is the only other route outside the guard (loopback-only, credentials the panel
|
|
138
|
+
generates, a single `/:action` for DNS-01); this plugin never touches it.
|
|
139
|
+
|
|
140
|
+
The practical consequence for a foreign panel is the three mechanisms `docs/WORKSPACES.md` lists: its
|
|
141
|
+
config file, its own CLI, or a token the user is asked about. A panel the user has not confirmed is
|
|
142
|
+
never reached by a fourth route invented here.
|
|
143
|
+
|
|
144
|
+
## Two RPC endpoints have no caller, and that is fine
|
|
145
|
+
|
|
146
|
+
`servers.get` and `entries.restore` are handled by the service, typed in `RpcEndpoint`, and tested —
|
|
147
|
+
but no client or agent tool invokes either, which is the shape worth checking rather than assuming.
|
|
148
|
+
|
|
149
|
+
Both are **redundant surface, not unreachable capability**:
|
|
150
|
+
|
|
151
|
+
- `entries.restore`'s job is reached through `entries.remove`, which restores an entry this plugin
|
|
152
|
+
merely adopted instead of deleting it (`removeManagedEntry` → the `adopted` branch). That is proven
|
|
153
|
+
behaviourally, not by reading: `test/service` removes an adopted entry and asserts the panel got no
|
|
154
|
+
`DELETE` and the entry's own `onPortConflict` came back.
|
|
155
|
+
- `servers.get` returns one entry's view, and `servers.list` — which every consumer uses — returns
|
|
156
|
+
every entry's view for the workspace.
|
|
157
|
+
|
|
158
|
+
They are left in place rather than deleted: they are a coherent RPC surface for a caller that wants
|
|
159
|
+
one entry, deleting them is a wire change for no user-visible gain, and the capability each names is
|
|
160
|
+
already reachable. Recorded here so the next reader does not have to re-derive whether it was an
|
|
161
|
+
oversight.
|
|
162
|
+
|
|
68
163
|
## Credentials
|
|
69
164
|
|
|
70
165
|
home-hosted keeps only an API token's hash, so a token this plugin did not create can never be
|
package/docs/WORKSPACES.md
CHANGED
|
@@ -11,6 +11,23 @@ settings, secrets, logs and nanny state under `.hh/<workspace>/`, while the list
|
|
|
11
11
|
vitals, backups, UI and `run.json` stay at `.hh/`. A server id is therefore only unique *inside* a
|
|
12
12
|
workspace, and every call has to say which one it means.
|
|
13
13
|
|
|
14
|
+
The same rule reaches the page. The servers section is not remounted when the viewed workspace
|
|
15
|
+
changes, so anything it holds about a server has to carry the workspace it was made in — otherwise an
|
|
16
|
+
editor or a confirmation opened on workspace A's `web` stays live under workspace B and re-binds to
|
|
17
|
+
*B's* `web`. The displayed list is replaced on switch, so the ids simply stop matching; the plugin's
|
|
18
|
+
own in-flight keys (`busyKey`) already included the workspace, and the section's local selections now
|
|
19
|
+
do too.
|
|
20
|
+
|
|
21
|
+
The scoping is verified by driving the section under `happy-dom` and clicking it — open the editor,
|
|
22
|
+
switch workspace, read the screen — not only by calling the resolver, so the wiring for all three
|
|
23
|
+
selections is exercised. A control case re-renders in the *same* workspace and asserts the editor
|
|
24
|
+
stays open, so the failure cannot be mistaken for "it closed for some other reason".
|
|
25
|
+
|
|
26
|
+
The same lesson applies to the boot section's own stored state: `bootAttemptView` used to drop the
|
|
27
|
+
mechanism, so its note could only be gated on `state` and a persisted refusal survived a change of
|
|
28
|
+
preference — with the *old* mechanism's copy-pasteable commands. The view carries it now, and both
|
|
29
|
+
the fresh and persisted paths compare it.
|
|
30
|
+
|
|
14
31
|
The plugin manages exactly one workspace — **the panel's `default`**, fixed — because its intent (the
|
|
15
32
|
`dsh` entry, snapshots, reconcile) is a promise about one entry in one place. Making that a setting
|
|
16
33
|
bought nothing but a way to write into the wrong workspace, so it is a constant. The page and the agent
|
|
@@ -58,8 +75,13 @@ that endpoint has, least invasive first:
|
|
|
58
75
|
(`.hh/<workspace>/servers.config.json`, its default workspace unless the call names another) — no
|
|
59
76
|
credential, but no live status either, and a running panel applies a changed definition at that
|
|
60
77
|
entry's next start;
|
|
61
|
-
- `cli` runs the CLI against its state root — the only runtime path the panel itself offers
|
|
62
|
-
|
|
78
|
+
- `cli` runs the CLI against its state root — the only runtime path the panel itself offers.
|
|
79
|
+
`start`/`stop` act on one entry; a restart is now the CLI's own `restart <id>` (home-hosted
|
|
80
|
+
0.7.13), which is what the panel's UI does and leaves no window in which the entry is down. A
|
|
81
|
+
local panel older than that refused the command before touching anything, so the plugin falls
|
|
82
|
+
back to the stop-then-start pair and reports `downgraded` in the result — the refusal is
|
|
83
|
+
detected from the CLI's own text, including the `Unexpected argument` a 0.7.12 CLI prints for
|
|
84
|
+
the positional id;
|
|
63
85
|
- `api` mints a token for that panel, enrols it and uses its HTTP API — the only way to get live
|
|
64
86
|
status, and it replaces any token that panel had, which the question says outright.
|
|
65
87
|
|