Install
Inside DeepSeek Harness, with dsh-market
dsh plugin --profile web add dshmarket
Or from the command line
dsh plugin --profile web add dsh-undo-savepoint
Installing runs third-party code with your own permissions — it can read your files, use your credentials and reach the network. Review the source first, and pin a commit (github:owner/repo#sha) when you can.
README
A security caretaker for DeepSeek Harness (DSH): every settings change is auto-snapshotted, with one-click undo / redo / restore to any version; proactive health checks, upgrade guarding, and crash rescue; and when DSH won't even boot, the offline WebUI / GUI / CLI still work.
The caretaker checks proactively and speaks only when something is wrong. The first health check completes 30 seconds after startup; after a crash it names the last-known-good snapshot to restore; and when the DSH version changes it takes an upgrade-insurance snapshot before letting the session continue.
Preview
| Conversation header: iconized Undo / Redo / Snapshot / Message-undo buttons + auto-snapshot badge (24 snapshots · 3h ago) |
|---|
| Offline WebUI: crash banner + Undo / Redo / Safe mode / Diagnose / Message undo / Settings — works even when DSH won't boot (snapshot diff & settings: see Offline tools) |
|---|
The caretaker
| Duty | Capability |
|---|---|
| Startup health check | 30 seconds after startup it inspects session directories, tolerance patches, snapshot coverage and disk usage, alerting only on findings |
| Crash rescue | When DSH cannot boot at all, SAFE MODE keeps only this plugin so boot is guaranteed; crash attribution names the last-known-good snapshot for one-click restore |
| Session doctor | undo_scan repairs single-frame layout violations and seq overlaps in session files; what it cannot repair is quarantined, originals untouched |
| Upgrade guard | On a detected DSH version change it takes an upgrade-insurance snapshot, then the first post-upgrade boot produces a compatibility and session-census report |
| Snapshots stay secret | .env, credentials and home-level settings are redacted in snapshots, real values live only in the local vault, export bundles leak nothing |
| Tolerance patches | Runtime fallbacks for known defect points of past DSH builds; they simply stop matching once upstream fixes land, with no functional impact |
Core capabilities
| Capability | What it does |
|---|---|
| Config + plugin-code rollback | Snapshots cover config files AND user-plugin code trees — any broken edit is undoable (incl. pure code incidents like the whale-kit yield* crash); undo / redo / restore-to-any-version from the WebUI, chat or offline CLI |
| Secret redaction + local vault | .env / credentials enter snapshots auto-redacted (structure preserved) — exported ZIPs are safe to share; real values live in a local vault, and local rollbacks restore them fully |
| Time Machine timeline | Snapshot timeline visualization + file-level diff (added/removed line highlighting, per-file navigation, prev/next) + one-click rollback |
| Message-level undo | Records workspace file changes per AI message; say "undo what the last message did" and exactly that is rolled back (before-content restored, new files deleted). Tracked dirs configurable in Settings |
| One-click SAFE MODE | When DSH cannot boot at all, temporarily disables every user plugin except the undo system so it always boots; auto-snapshots + config backup on entry, one-click exit (profile/home dual-level patches backed up & restored; bundle entries that would fail the loader's hard checks are neutralized with the original package.json backed up separately and fully restored on exit) |
| Crash attribution | After an abnormal exit, classifies the crash by log signature (session-corrupt / bundle-check / patch-tree), names the concrete last-known-good snapshot and offers a one-click rollback — no guessing |
| Session-file scan & repair | undo_scan scans <home>/sessions/**/session.jsonl.zstd: single-frame layout violations (the 8/18 crash root cause) and synthetic-closer seq overlap (interrupted-turn seq overlap after undo/snapshot restores) are repaired in place (original kept as .bak + quarantine copy) with triple verification; undecodable files are only isolated, never touched. Offline via dsh-undo.ps1 scan [--fix] (requires Node ≥22.15; degrades to a notice on Node 20) |
| One-click diagnostic & boot preflight | Store side: snapshot store writability, blob integrity (missing/orphan), settings health, snapshot scale. Boot side preflights the hard failures that kill DSH before any plugin is mounted: profile manifest BOM/JSON/shape, every declared dsh.profile.bundles entry resolvable, patchReload value, link: junction targets, duplicate loader ids in patch files, unfinished previous boot. Structured ok/warn/error report; repairable checks are marked [fixable] and can be repaired in one step (undo_doctor fix=true in chat, dsh-undo.ps1 doctor -Fix offline, or the "Repair fixable" button in the standalone WebUI diagnostic panel), with a manual snapshot taken before and an automatic re-check after |
| Safe cross-machine migration | Restore preflights missing plugins and warns clearly; snapshots export/import as one-click ZIP, optional AES-256-GCM encryption (see docs/migration.en.md) |
| Offline emergency kit | WebUI + GUI window + CLI + auto-created desktop shortcut: undo / restore / SAFE MODE / crash banner / rollback log — everything works when DSH is down |
| Auto slimming | Orphan-blob GC (undo_compact) frees disk; size gates keep the plugin tiny (~0.6 MB) with zero runtime dependencies |
Platform support
| Capability | Windows | macOS | Linux |
|---|---|---|---|
| Config/plugin snapshots, undo/redo | ✅ | ✅ | ✅ |
| Offline Web UI (undo-server) | ✅ | ✅ | ✅ |
| Offline CLI / GUI | ✅ (.bat/.ps1) | ✅ (.command) | ✅ (.sh/.desktop) |
| File/dir selection dialog | PowerShell native | osascript | zenity→kdialog (fallback: manual path) |
| CI regression | windows-latest | macos-latest | ubuntu-latest |
Crash rescue quick reference (pick by scenario)
| Scenario | Action |
|---|---|
| Config/plugin mount broken | Chat / WebUI / CLI: undo or restore -Id <id> |
| Plugin code broken | Same — snapshots include plugin code trees, one-click restore |
| Last run crashed, unsure what to roll back to | WebUI / GUI banner shows the last-known-good snapshot, one-click rollback |
| DSH will not boot at all | Desktop "dsh-undo-savepoint" → offline WebUI SAFE MODE button (or CLI safe-mode -Label on) → restart DSH, it always boots |
| Crash banner says session damage | Chat / CLI: undo_scan quarantine=true (or offline dsh-undo.ps1 scan --fix) |
| Missing plugins after restore (cross-machine) | Preflight warning in the restore report; install first or use safe mode |
| "My config suddenly changed" | CLI recent / chat undo_recent check the rollback log |
| Rollback touched plugins/mounts | Report says "restart DSH for it to take effect" |
| SAFE MODE confirmation: disables every user plugin except this one, so DSH always boots — then re-enable them one by one |
|---|
Installation
Prerequisites: DSH (@deepseek-ai/dsh) and Node.js (≥20). The host-mode plugin runs in profiles
that provide the tools / systemPrompt / webServer services (any Web profile qualifies); when DSH
cannot boot at all, use the offline CLI / GUI instead, which does not depend on the host.
Option A (GitHub direct) — install the latest master commit:
dsh plugin --profile web add github:lire1131/dsh-undo-savepoint#master
Restart DSH after installing. Snapshot directories and options are configurable in Settings.
Option C (npm registry) — install from npm:
dsh plugin --profile web add dsh-undo-savepoint
dsh plugin add forwards to pnpm in the profile directory; this package declares
dsh.bundle.patch in its package.json, so it is registered into the profile's bundle list
automatically. Restart DSH to activate.
Option B (local source / pre-release) — clone and mount manually:
- Clone the repository into a local plugins directory (an ASCII path is safer), e.g.
D:\dsh\plugins\dsh-undo-savepoint:
git clone https://github.com/lire1131/dsh-undo-savepoint.git D:\dsh\plugins\dsh-undo-savepoint
- Create a junction so DSH's module resolver can find the local package by name
dsh-undo-savepoint(used by both the host plugin and the WebUI client plugin):
mklink /J "<your-dsh-install>\node_modules\dsh-undo-savepoint" "D:\dsh\plugins\dsh-undo-savepoint"
DSH resolves package names by walking up from its own
node_modules. The default location isC:\Users\<username>\node_moduleswhen npm installed into the user directory; if you run DSH from an npx cache, junction into that cache'snode_modulesinstead. Check the path in DSH's startup error output or runnpm root -g.
- Mount it in the profile patch layer: edit
<DSH_HOME>\profiles\web\cordis.patch.ymland append:
- insert:
- id: dsh-undo-savepoint
name: dsh-undo-savepoint
- Activate: saving hot-reloads the host part; refresh the page to see the header buttons and settings rows; restart DSH for full steady state (legacy flat snapshots migrate automatically).
Dependency note: the host plugin loads
@deepseek-ai/dsh-toolsviacreateRequire('<dsh-install-root>/package.json'). If DSH lives elsewhere, set the environment variableDSH_ROOT=<dsh-install-root>, no extra package installation needed.
Support range (DSH versions)
At install time and at profile startup the plugin compares the runtime version reported by dsh --version against the range this package declares, and refuses to load on a mismatch. engines.dsh in package.json is a chain of parallel ranges covering the successive 0.1.x declarations, ending with >=0.2.0-rc.2.
Verified: DSH 0.2.0-rc.2 desktop build (full peer tree with cordis 4.0.4), covering the web profile together with the command line, the offline WebUI and the offline CLI. All four in-repo gates pass with zero failures, which is what the CLI and web coverage rests on.
Pending: on-device acceptance on the desktop edition. Until that is ticked off the desktop row keeps the pending label.
If your DSH version falls outside the declared range, startup reports it as incompatible. Do not edit package.json; use the official exact-version exemption instead, which lives in the profile's own compatibility.json and touches neither the dependency manifest nor the patch layer:
dsh plugin --profile web version-exemptions
dsh plugin --profile web allow-version dsh-undo-savepoint@0.4.9 --dsh-version <runtime> --accept-risk
dsh plugin --profile web revoke-version dsh-undo-savepoint@0.4.9 --dsh-version <runtime>
The first lists existing grants, the second submits one, the third revokes it. An exemption only authorises loading, never dependency scripts, and neither a plugin upgrade nor a DSH upgrade inherits a grant, so each of them needs a fresh confirmation. The declared range is not pinned to the newest release because DSH can still ship breaking changes; the range expands once a new version has been tested here.
Desktop
The DSH desktop build starts from its own installation directory and runs the desktop profile; swap web for desktop and add / remove / the three version-exemption commands all work the same way.
dsh plugin --profile desktop add github:lire1131/dsh-undo-savepoint#master
Prerequisite: launch the desktop client once so the profile initialises, then quit it completely before running the command above. While the desktop build is running its profile directory is held, so a write either fails or lands half applied.
The official recovery dialog and this plugin. When DSH 0.2.0 on the desktop hits a fatal error it shows a native dialog with three buttons: quit, restart, and disable third-party plugins and restart. The third button drops third-party entries from the bundle mount list and backs the profile patch up to cordis.patch.yml.bak-<timestamp>, keeping the package itself. Two things to remember:
- Being disabled by that button does not require a reinstall. The package stays in the profile directory with its dependencies untouched, so restoring the mount declaration brings it back.
- When the profile patch is moved aside, the tolerance patches (tolerate / isolate / selfheal) stop applying with it. If you restore the bundles by hand and leave the patch out, older sessions can hit known pitfalls. Two recovery paths, either one works: rename
cordis.patch.yml.bak-<timestamp>back tocordis.patch.ymland restart, or re-run the plugin's post-install step and let it rebuild the bundles and the patch.
The safe-mode limitation. On the desktop build safe mode is refused, and the escape hatch is the third button of the official recovery dialog.
Uninstall
Standard uninstall
Two commands; the second cleans up the leftovers:
dsh plugin remove dsh-undo-savepoint
node tools\uninstall.mjs
uninstall.mjs is gentle by default: it removes mount declarations, junctions and the desktop shortcut, and keeps the snapshot library and settings (reinstalling restores your history). When the history is no longer needed, add --purge:
node tools\uninstall.mjs --purge
Common options:
| Option | Effect |
|---|---|
--purge |
Also removes undo\ (settings/state) and undo-snapshots\ (snapshot library) |
--yes |
Skip confirmation (required in non-interactive contexts, prevents scripted deletion) |
--home <dir> |
Override the DSH home directory |
--profile <n> |
Process only the named profile (default: all) |
--json |
Print the cleanup plan as machine-readable JSON |
Without --yes the command first lists every path it will delete and then asks. In a non-interactive context (CI, pipes) it refuses to run unless --yes is given explicitly.
Manual uninstall map
If you really want to clean up by hand, these are the six locations and how to delete each correctly:
| # | Location | Correct removal |
|---|---|---|
| 1 | <dsh-install-root>\node_modules\dsh-undo-savepoint (or under a profile's node_modules) |
Delete the link itself, do not follow it. This is a junction; deleting it is safe. Browsing into it in Explorer and deleting the contents deletes your plugin source |
| 2 | The id: dsh-undo-savepoint entry in <DSH_HOME>\profiles\<profile>\cordis.patch.yml |
Remove that insert entry (or the whole item); an emptied file can be deleted |
| 3 | dsh-undo-savepoint inside dsh.profile.bundles of <DSH_HOME>\profiles\<profile>\package.json |
Remove the entry from the array |
| 4 | <DSH_HOME>\undo\ |
Settings and state (including settings.json). Delete only if you want settings reset |
| 5 | <DSH_HOME>\undo-snapshots\ |
The snapshot library. Confirm you no longer need rollback before deleting |
| 6 | Desktop dsh-undo-savepoint.lnk (macOS .command / Linux .desktop) |
Delete the file |
Windows long-path note
With a deep or non-ASCII Windows user directory, snapshot paths can exceed Explorer's MAX_PATH limit of 260 characters, and a manual delete fails with "file name too long" or leaves a half-deleted mess. Prefer uninstall.mjs: it deletes the fixed set of paths directly without walking into snapshot internals, so the limit does not apply.
Usage (inside DSH)
- Undo: header Undo button /
Ctrl+Alt+Z/ tell the AI "undo the last step" - Redo: Redo button /
Ctrl+Alt+Y(only when nothing changed since the undo) - Manual save: "Save" in the panel / tell the AI "save a snapshot" / CLI
snapshot - Restore to a fixed version: "Restore to this" on a panel row; or tell the AI "restore to "; or CLI
restore -Id <id> - Message-level undo: the header "conversation undo" entry opens the message list; "undo this message" rolls back exactly what that message changed
- Delete a snapshot: "Delete" in the panel; or CLI
remove -Id <id> - Custom shortcuts: Settings → General → Undo/Redo shortcut (click the box then press a combo; Backspace clears)
- Save options: Settings → General → Snapshot Settings (auto-save toggle, debounce, keep count, snapshot dirs, tracked workspace dirs; the 📁 button opens the native folder picker). "Tracked workspace dirs" accepts comma/semicolon-separated paths — non-empty replaces the default working-dir scope
| Message-level undo: pick a message batch, roll back exactly the files it changed |
|---|
Offline tools (work even when DSH won't boot)
WebUI (recommended) — run node tools\undo-server.mjs (or double-click tools\launch-undo.bat / .command / .sh / .desktop); it serves a local 127.0.0.1 page with the timeline / rollback / diff / diagnostics / SAFE MODE. A desktop shortcut is auto-created on plugin load. The diagnostic panel does not only show problems, it repairs them: repairable checks are badged "fixable" and one click on "Repair fixable" fixes them in place, after taking a manual snapshot and with an immediate re-check.
| Desktop shortcut: auto-created on plugin load, double-click to open the offline WebUI |
|---|
| Snapshot diff: file list + change stats (+8 / -2) + line-level highlighting — see every change before rolling back | Offline settings: debounce / retention / redaction mode / dirs / desktop shortcut |
|---|---|
UI language follows your system locale — English UI shown here; Chinese-UI screenshots in README.md.
# GUI window (WinForms): double-click tools\dsh-undo-savepoint-gui.bat, or:
powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo-savepoint-gui.ps1"
# CLI
powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo.ps1" list
powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo.ps1" snapshot -Label "reason"
powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo.ps1" undo
powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo.ps1" undo -SyncDeps
powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo.ps1" redo
powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo.ps1" restore -Id <id> -Force
powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo.ps1" restore -Id <id> -Force -SyncDeps
powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo.ps1" remove -Id <id>
powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo.ps1" prune -KeepAuto 20
# Boot preflight (works even when DSH cannot start); -Fix repairs what it can
powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo.ps1" doctor
powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-undo.ps1" doctor -Fix
# Safe plugin install (auto snapshots before/after; auto-rollback on failure)
powershell -NoProfile -ExecutionPolicy Bypass -File "tools\dsh-plugin.ps1" add <package>
# 30-line version: open a PowerShell window, paste the whole block, press Enter —
# a "DSH Undo Manager" shortcut appears on the desktop without locating any file
$dshHome = if ($env:DSH_HOME) { $env:DSH_HOME } else { "$env:USERPROFILE\.dsh" }
$d = @("$dshHome\profiles\web\node_modules\dsh-undo-savepoint", "$dshHome\profiles\node_modules\dsh-undo-savepoint", "$env:USERPROFILE\node_modules\dsh-undo-savepoint") | Where-Object { Test-Path (Join-Path $_ 'tools\dsh-undo-savepoint-gui.bat') } | Select-Object -First 1
if ($d) {
$w = New-Object -ComObject WScript.Shell
$s = $w.CreateShortcut((Join-Path ([Environment]::GetFolderPath('Desktop')) 'DSH Undo Manager.lnk'))
$s.TargetPath = Join-Path $d 'tools\dsh-undo-savepoint-gui.bat'
$s.WorkingDirectory = Join-Path $d 'tools'
$s.Save()
Write-Host "Desktop shortcut created: $($s.FullName)"
} else { Write-Host 'Plugin directory not found — install it first: dsh plugin --profile web add github:lire1131/dsh-undo-savepoint#master' }
Typical rescue: DSH fails to boot with something like duplicate loader entry id → open "DSH Undo Manager", pick the snapshot from before the change → Restore → restart DSH. No reinstall, no lost sessions.
UI language: force it with
DSH_UNDO_LANG=zh|en; otherwise Chinese on Chinese hosts, English elsewhere. Applies to the host command output, the offline CLI/GUI, and the WebUI. The single dictionary source islib/i18n/{zh,en}.json(shared by host and WebUI so they cannot drift).
Snapshots & storage
The snapshot captures DSH's boot-critical config: cordis.patch.yml, package.json, cordis.yml, pnpm-workspace.yaml, pnpm-lock.yaml (under the profile) + cordis.patch.yml, settings.yaml, .env, .credentials.yaml (under $DSH_HOME, default ~/.dsh).
When a restore touches package.json / pnpm-lock.yaml, the default behavior only reports that node_modules may be out of sync. To rebuild dependencies, pass -SyncDeps (offline CLI), sync_deps: true (chat tool), or syncDeps: true (REST); the plugin runs pnpm install --frozen-lockfile (plain pnpm install when there is no lockfile). A failed install leaves the restored config files in place.
| Store | Default path (configurable in settings) | Contents |
|---|---|---|
| Manual store | <snapshot root>\manual\ |
Manual snapshots (never auto-pruned) |
| Auto store | <snapshot root>\auto\ |
Auto snapshots, boot baselines, undo pre-restore snapshots (auto keeps latest 20) |
| Legacy store | <snapshot root>\ root |
Old flat layout — still read, auto-migrated on startup |
⚠️ Snapshots contain copies of
.envetc. which may include secrets — do not share or push them.
Multi-profile & custom home
- Profile detection: the plugin reads the active profile from the launch arguments (
dsh --profile mine/--profile=mine;dsh webfalls back toweb); config dir defaults to$DSH_HOME/profiles/<current profile>, and snapshot stores default to<snapshot root>/<current profile>/{auto,manual}(per-profile isolation; the old flat store is still honored so legacy snapshots are never hidden). Offline CLI/GUI cannot see the launch arguments — setDSH_UNDO_PROFILEorprofileNamein settings (defaultweb). - Custom DSH home: the home resolution matches the official launcher (
@deepseek-ai/dsh-home-paths) exactly —$DSH_HOMEwins (blank = unset;~prefixes supported), otherwise<user home>\.dsh; settings file, snapshot root, profile dir and plugin-discovery paths are all derived from it, so third-party clients with a customDSH_HOMEno longer suffer the "two homes" split. - Explicit overrides always win: env vars
DSH_UNDO_SETTINGS/DSH_UNDO_ROOT/DSH_UNDO_EXPORT/DSH_UNDO_PROFILE, config keyshomeDir/profileDir/manualDir/autoDir/profileName.
REST API (backend of the WebUI)
| Endpoint | Description |
|---|---|
GET /api/undo/status |
{canUndo, canRedo, total, bootAlert, safeModeActive, ...} |
GET /api/undo/list |
Snapshot list (with location: manual/auto/legacy) |
GET /api/undo/diff |
?id=<id> file-level structured diff of a snapshot vs current |
GET /api/undo/tree |
Directory-tree grouped diff of a snapshot vs current |
GET /api/undo/doctor |
One-click diagnostic (store writability / blob integrity / settings / scale) plus boot preflight (manifest BOM / bundles / junctions / duplicate loader ids / unfinished previous boot) |
POST /api/undo/doctor/fix |
Repair the fixable boot-preflight findings (strip manifest and patch BOMs, dedupe loader entries by id, recreate missing or dangling plugin junctions); takes a manual snapshot first and returns the repair list plus a re-check report |
GET/POST /api/undo/settings |
Read/write save options; POST applies immediately |
GET /api/undo/messages |
Message-level undo: per-message change list |
POST /api/undo/undo |
Undo the last change; optional body {syncDeps: true} rebuilds node_modules from the restored lockfile |
POST /api/undo/redo |
Redo the last undo; optional body {syncDeps: true} |
POST /api/undo/restore |
body {id, syncDeps?} — restore to a fixed version |
POST /api/undo/message |
body {id} — roll back one message's changes |
POST /api/undo/remove |
body {id} — delete a snapshot |
POST /api/undo/snapshot |
body {reason} — manual save (note/tags supported) |
POST /api/undo/note |
Edit a snapshot's note/tags |
POST /api/undo/prune |
Run expired-snapshot cleanup immediately |
POST /api/undo/compact |
Orphan-blob GC (supports dry_run) |
POST /api/undo/export / POST /api/undo/import |
Export/import all snapshots as ZIP (pure Node, PowerShell-compatible, optional AES-256-GCM password) |
POST /api/undo/safe-mode |
body {on} — enter/exit safe mode |
POST /api/undo/pick-dir / pick-file |
Open the native folder/file picker (per-platform), return the chosen path |
GET /api/undo/locale |
Return the current language (DSH_UNDO_LANG or auto) |
Design notes
- Undo semantics: auto snapshots are taken after a change, so "restoring the newest snapshot" would be a no-op; real undo restores the newest snapshot whose state differs from the current one. When everything matches, a clear "nothing to undo" message is shown instead of pretending.
- Undo can never undo itself: after restoring
cordis.patch.yml, the plugin re-ensures its own mount entry automatically. - Auto-archiving never sabotages undo: the watcher records content hashes of what a restore wrote, so the restore's own file changes are not auto-snapshotted (which would block redo); real changes are snapshotted as usual.
- Format parity: the Node plugin and the PowerShell tools share the same snapshot stores and manifest format; compatible with both Windows PowerShell 5.1 and PowerShell 7.
Development
- Dependency resolution: the host plugin loads
@deepseek-ai/dsh-toolsviacreateRequire(<dsh-install-root>/package.json)(override withDSH_ROOT), no in-repo dependencies required. - Tests (no DSH needed; run in the repository directory):
node tools\smoke-test.mjs :: 189 logic tests (snapshot/undo/redo/store split/no-change hint/message-undo/orphan-GC/zip-interop)
node tools\e2e-watch.mjs :: 10 real-timing regressions (auto-save/undo-no-harm/redo)
node tools\check-size.mjs :: size gate (<5MB)
node tools\check-version.mjs :: semver validation
License
MIT
Comments
Comments live in GitHub Discussions. Sign in with GitHub to post or react.