mirror of
https://github.com/sotashimozono/obsidian-remote-ssh.git
synced 2026-07-22 17:10:32 +00:00
12 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4757bb18e4 |
feat(release): formalize next=beta / main=stable two-channel model
Version shape is the single source of truth for the channel: - X.Y.Z-beta.N → next (BRAT --beta consumers) - X.Y.Z → main (Obsidian Community Plugins store) bump-version.mjs is now branch-aware: a beta bump only updates plugin/manifest.json + manifest-beta.json (root manifest.json + versions.json stay pinned to the last stable). A stable bump advances every file together. version-check.yml enforces the branch ↔ channel invariant: - PR into next: head must be > base (semver-aware), root manifest.json + versions.json must NOT have moved - PR into main: head must be plain X.Y.Z (no beta suffix), all manifests must agree - Sync PRs (head=main → base=next) skip the workflow entirely release.yml triggers on push to main AND next, picks prerelease vs stable from the version shape, and gates: stable-on-next pushes (promotion-staging commits) are skipped so only the eventual main push publishes the stable release. sync-main-to-next.yml automates the post-promotion sync: after a push to main, it opens a PR main → next and enables auto-merge so the histories rejoin without manual `git merge main` on next. commitlint also skips on sync PRs (the autogenerated merge titles don't follow Conventional Commits). New npm scripts (run from plugin/): - bump:beta:start → X.Y.Z → X.Y.(Z+1)-beta.0 (start of cycle) - bump:beta → -beta.N → -beta.N+1 (within cycle) - bump:stable → -beta.N suffix dropped (promotion) CONTRIBUTING.md documents the lifecycle + promotion flow. Self-test: this PR bumps next from 1.0.43 to 1.0.44-beta.0 using the new bump:beta:start script — only the beta-channel files moved. |
||
|
|
9d231235c0
|
feat: BRAT manifest-beta channel + release asset (Phase G) (#231)
Issue #126 Phase G — enable BRAT --beta installs from this repo so external testers can subscribe to the auto-update channel before the 1.0 community-store submission. Changes: - manifest-beta.json (NEW, root) — what BRAT --beta polls. Pre-1.0 it tracks manifest.json byte-for-byte (every merge IS a beta); diverges from stable when a `next` branch is cut for post-1.0 work. - plugin/scripts/bump-version.mjs — npm version flow now writes manifest-beta.json alongside the existing four files. No new public API; doc-comment updated to spell out the BRAT contract. - plugin/package.json — `version` script `git add`s the new file. - .github/workflows/version-check.yml — new `manifest-beta == manifest` assertion guards against a hand-edit drifting the two channels apart. Drop this assert when `next` branch is introduced. - .github/workflows/release.yml — manifest-beta.json added to the GitHub Release `files:` block so BRAT can fetch it as a release asset. - README.md — Install section split into Option A (BRAT, recommended) and Option B (manual). The previous one-line BRAT mention was easy to miss; promoting it as the primary path matches the bug-bash flow for the rest of Phase G. - Version bump 0.4.125 → 0.4.126 (touches manifest.json, manifest-beta.json, plugin/manifest.json, plugin/versions.json, versions.json, plugin/package.json, plugin/package-lock.json). Verified locally: bump-version.mjs syncs all four manifests + the new beta to the same version; `diff manifest.json manifest-beta.json` returns clean (CI assert will pass). Closes the `manifest-beta.json` checkbox in Phase G of #126. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
ff93c72e67 |
chore: mirror versions.json to repo root and add .nvmrc
Obsidian's community plugin registry validator may look for versions.json at the repo root alongside manifest.json. The bump-version script and npm version lifecycle hook now sync both root mirrors (manifest.json + versions.json) automatically, and version-check CI asserts they stay in sync. Also adds .nvmrc (Node 20) to pin the development runtime, matching the engines field in plugin/package.json. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
1df4a05368 |
ci(replay): replay previous release tag's tests against current source
Catches PRs where source and tests are modified in lockstep so a previously-asserted invariant ships green even though it's broken. The replay script copies the most recent published tag's `plugin/tests/` over the PR's `plugin/src/`, runs vitest, and fails the (advisory) check on regression. How it works: - `git describe --tags --abbrev=0 origin/main` resolves the baseline tag (e.g. `0.4.88`). - `git worktree add` checks the tag out into a temp dir. - The full `plugin/tests/` tree is copied into the gitignored scratch dir `plugin/tests-replay/` (whole tree, including `integration/helpers/`, because some unit tests import shared helpers from there). - A dedicated `vitest.replay.config.ts` narrows discovery to `tests-replay/**/*.test.ts` and excludes `tests-replay/integration/**` so integration tests don't run. - Failures point at `.github/replay-skip.txt`, where a deliberately- retired test can be added with a "# reason / PR ref" comment. Files: - `plugin/scripts/replay-prev-tests.mjs` — orchestrator, runnable locally via `npm run test:replay` (also wired into `package.json`). - `plugin/vitest.replay.config.ts` — replay-only test config. - `.github/replay-skip.txt` — append-only retirement list, empty. - `.github/workflows/replay.yml` — PR-only trigger, advisory (`continue-on-error: true`); flip to required-check once the signal-to-noise has been validated over real PRs. - `.gitignore` — exclude `plugin/tests-replay/` scratch dir. Verified locally: replay against tag `0.4.88` runs vitest with 626 tests, all pass against current source. (One test-collection detail surfaced during dev: `AssertSyncReflect.test.ts` and `PerfAggregator.test.ts` import helpers from `integration/helpers/`, which is why the copy step now mirrors the entire tests tree rather than skipping `integration/` at the directory level — the exclude is enforced by the vitest config's `exclude` glob.) No plugin-version bump: this is CI-infrastructure only and ships nothing to end users. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
1c49941ae4 |
chore(release): mirror manifest.json at repo root for community store
Obsidian's community plugin browser fetches manifest.json from the repo root (raw.githubusercontent.com/<repo>/HEAD/manifest.json — the same convention our own PluginMarketplaceInstaller uses). Plugin source lives under plugin/ for repo-layout reasons (server/, proto/, deploy/ siblings), so we mirror to root rather than restructure. - bump-version.mjs writes both plugin/manifest.json and ./manifest.json - version-check.yml asserts they stay byte-identical (catches drift before submission to obsidianmd/obsidian-releases) Required to submit to the community plugin store. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
6476372550 |
ci(plugin): Phase C.M10 — perf-bench off the PR critical path; nightly + workflow_dispatch (0.4.48)
Closes the second half of the Phase C MVP, but with a structurally different cadence than the original M10 design (PR #106 v1). The bench (`plugin/tests/integration/perf.sync.bench.test.ts`) takes ~6 minutes per run — 96 % of the integration job's wall time, all of it in the SSH-tunneled RPC RTT (~41 ms × ~3000 calls). Running it on every PR was wrong on two counts: 1. **Feedback latency**: every PR adds 6 minutes to CI for a comment-only gate. Souta correctly called out that the bench is overkill for the per-PR feedback loop. 2. **Bench purpose**: the bench is a *trend tracker* and an *on-demand perf microscope*. Trends want a stable cadence (nightly), not noise from every PR; a microscope wants to be pulled out when there's a hypothesis to test, not run on every keystroke. So this PR splits the workflow: - **integration.yml** (PR + push to main): runs everything EXCEPT the bench. Drops back to ~30 s wall time. - **bench.yml** (new, nightly cron + workflow_dispatch + push-to-main): runs ONLY the bench. Updates `perf-baseline` branch on nightly + main-push. workflow_dispatch with an optional `pr_number` input posts the diff comment to the named PR — preserves the on-demand "hey what does this branch look like perf-wise" capability without paying the 6-minute tax on every push. What ships: - `plugin/scripts/perf/compare.mjs` (new, pure) Comparison core. Reads two NDJSON datasets, joins by `(span, transport, sizeBytes)`, computes p95 deltas, classifies each bucket as regressed / improved / unchanged / new / removed against per-span tolerance (default 25 %; S.fs widened to 40 % because shared-CI disk variance is high; S.note to 50 % for fsnotify debounce noise). Renders a Markdown table for the PR comment with arrows (🔴↑/🟢↓/−/🆕/➖), bolds regressed rows, appends a pass/fail summary + a footer with the baseline / head SHA pair + run URL. Status-ordered output (regressions first) so reviewers see the bad news without scrolling. - `plugin/scripts/perf/run-compare.mjs` (new, CLI) Picks the latest NDJSON in `plugin/perf-results/`, fetches the baseline at `<workspace>/perf-baseline/baseline.ndjson`, calls `compare()`, posts via `gh pr comment`. **Upserts** the comment by sentinel header so a long-running PR with multiple workflow_dispatch invocations gets one rolling comment instead of a per-trigger spam stack. Exits 0 unless `PERF_GATE=1` AND there's a regression past tolerance. - `plugin/scripts/perf/run-update-baseline.mjs` (new, CLI) Bootstraps the orphan `perf-baseline` branch on first run; on subsequent triggers, copies the latest NDJSON onto it as `baseline.ndjson` + writes a sidecar `baseline.sha`. Skips the commit when the baseline bytes haven't changed (no-op pushes on idle main). Uses `git worktree` to isolate the baseline checkout from the main checkout. - `.github/workflows/bench.yml` (new) Triggers: nightly cron (UTC 19:00 = JST 04:00) + manual `workflow_dispatch` + push to main. Manual triggers can pass an optional `pr_number` input to comment the diff to a PR (the on-demand spot-check path). Permissions: `contents: write` (baseline branch push) + `pull-requests: write` (PR comment). - `plugin/package.json` - `test:integration` — now excludes the bench file (`--exclude **/perf.sync.bench.test.ts`) so PR runs of the integration job stay fast. - `test:integration:bench` (new) — runs ONLY the bench file (used by bench.yml). - `plugin/tests/PerfCompare.test.ts` (new, 19 tests) Pure-module unit coverage for the comparison logic — runs in the fast unit suite via vitest's ESM loader (no .mjs build step needed; ~280 ms). Covered: parseNDJSON: empty, well-formed, malformed (line number in error), missing required fields. compare status semantics: new / removed / unchanged (within ±5 % band) / regressed (past tolerance) / improved (clear drop). Per-span tolerance override (S.fs 40 %). Drops to 'new' when baseline p95 is 0 (can't compute %). ordering: regressions surface first (status-grouped), then improvements, unchanged, new, removed, each lex-sorted. isolation: buckets across (span, transport, sizeBytes) tuples stay separate. formatMarkdown: header / table / pass-summary on no- regressions, ❌ + bolded rows on regressions, "no buckets" message on empty input, gate-enabled hint surface. round-trip: NDJSON in → table out, end-to-end smoke. - `.github/workflows/integration.yml` Unchanged from main — this PR deliberately does NOT add perf- related steps to the integration workflow. integration.yml stays the home of functional integration tests; bench.yml is the home of perf bench + baseline. The split keeps each workflow's purpose discoverable from its name. Architecture summary: PR push → integration.yml (no bench, ~30 s) push to main → integration.yml (no bench) → bench.yml (writes perf-baseline) nightly cron → bench.yml (refreshes perf-baseline) workflow_dispatch→ bench.yml (no baseline write; optional PR comment) Verification: - `npx vitest run` — 37 files / 501 tests green (was 482, +19 new) - `npx tsc --noEmit -p tsconfig.json` — clean - `npm run test:integration` (locally if Docker present) — bench excluded via `--exclude` flag; only the functional integration tests run. - workflow YAML: validated against the existing repo's structural patterns. manifest/package/versions bumped 0.4.46 → 0.4.48 (one ahead of the merged PR #105 / M9 at 0.4.47). Roadmap context: - M0 → M9 (Phase C MVP) ✓ - **M10 (this)** wires the gate but off the PR critical path. - Issue #110 (PERF_GATE flip) needs an update to track "flip on schedule + workflow_dispatch in bench.yml" rather than the old "flip in integration.yml" wording. - Issue #109 (E2E expansion) and #108 (modify case revival) are unaffected; they unlock more bench cells, not bench cadence. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
c9d465d1d0 |
fix(test): integration sshd healthcheck (install iproute2 + dump logs on fail)
CI on PR #56 first run came back "sshd entered unhealthy state: unhealthy". Two issues compounded: 1. The compose healthcheck shells `ss -lnt | grep ':22 '`, but `ss` isn't shipped in the minimal `ubuntu:22.04` base — the command exited non-zero every interval, so health stayed unhealthy past the retry budget. Adding `iproute2` to the apt-get install line gives us `ss` (~600 KB add). 2. When the start script gave up, it printed the timeout message and exited. We had no visibility into *why* sshd was unhealthy because docker logs weren't captured. Added `dumpContainerLogs` which calls `docker logs --tail 200` and `docker inspect --format '{{json .State.Health}}'` on every failure path so the next CI run (or local run) shows the actual sshd output and the healthcheck output history without manual digging. |
||
|
|
da3a2e9ffd |
feat(test): docker sshd integration test environment (Tier B-1+B-3, 0.4.0)
All existing tests mock ssh2, so the actual auth + SFTP channel code has been entirely untested up to now. Three real-world bugs in PRs #49 (ssh_config) and #50 (jump host) — neither caught by mocks — confirmed it was time to wire up real-network tests. This PR adds a reproducible docker sshd container, a vitest integration suite that talks to it via the actual SftpClient, and a CI job that brings the container up, runs the suite, and tears it down. ## Components - `docker/test-sshd/Dockerfile` — Ubuntu 22.04 + openssh-server. Pre-creates a `tester` user (UID 1000) with `vault/` in their home, pubkey-only auth, `StrictModes no` so a bind-mounted authorized_keys works regardless of host UID. - `docker-compose.yml` (repo root) — single `sshd` service on 127.0.0.1:2222, bind-mounts the public key + a host-side `docker/test-vault/` directory, healthcheck on port 22. - `docker/test-vault/` — bind-mounted vault root the test writes into. Contents are gitignored aside from a README. - `docker/keys/` — gitignored; populated on first run. - `scripts/start-test-sshd.mjs` — generates an ed25519 keypair on first run (otherwise reuses), `docker compose up -d --build`, polls `Health.Status` until healthy. Idempotent. - `scripts/stop-test-sshd.mjs` — `docker compose down -v`. - `vitest.integration.config.ts` — separate config that includes `tests/integration/**` and serialises files (no fileParallelism) so they don't fight over the single sshd. Default `vitest.config.ts` excludes the integration dir so `npm test` stays fast. - `tests/integration/ssh.integration.test.ts` — opens a real SSH session with the generated keypair and exercises list / read / write (small text + 64KB binary) / stat / exists / remove through the actual SftpClient. Each run uses a unique subdir under `/home/tester/vault` so leftovers don't collide. - `package.json` scripts: `sshd:start`, `sshd:stop`, `test:integration`. `npm test` is unchanged. - `.github/workflows/integration.yml` — separate from `ci.yml` so the integration run doesn't gate other PR signals. Brings the container up before the test step, tears it down in a finally-style step, uploads `docker logs` as an artefact on failure. ## Local usage ``` cd plugin npm run sshd:start npm run test:integration npm run sshd:stop ``` Requires Docker (Desktop or daemon) on PATH. Unit tests (`npm test`) work without Docker. ## Out of scope (B-2 / B-4) - ProxyJump / two-container bastion → target topology - Daemon (obsidian-remote-server) auto-deploy + RPC handshake end-to-end against the container Both queued for a follow-up PR once this base lands. ## Version 0.3.2 → 0.4.0 (new feature surface, minor bump). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
de3d0d6c32 |
chore(security): replace personal info with generic placeholders (0.3.2)
Security check turned up three places where the maintainer's
personal info had ended up in tracked source:
- plugin/tests/AdapterPatcher.test.ts:15
`target.greet('souta')` → `target.greet('alice')`
- plugin/tests/pathUtils.test.ts:15
`/home/souta/work/VaultDev/` → `/home/alice/vault/`
- plugin/scripts/dev-install.mjs
Default fallback for the dev vault path was hard-coded to
`../../SelfArchive-dev` (the maintainer's own dev vault folder
name). Renamed to the generic `../../dev-vault`. The
`REMOTE_SSH_DEV_VAULT` env var still wins, and the script accepts
a positional argv override too — the maintainer's actual
`SelfArchive-dev` keeps working with
`REMOTE_SSH_DEV_VAULT=/path/to/SelfArchive-dev npm run build:full`.
What stays:
- `github.com/sotashimozono/...` in every Go file's import path —
Go module canonical URL; this matches the repo URL on GitHub
and isn't replaceable.
- `work/VaultDev` mentions in README / docs / a couple of test
fixtures — generic enough not to identify the maintainer
(anyone could name a folder that), kept as-is.
No private keys / tokens / IPs / emails / hostnames were in the
tree to begin with — those live only in the user's local
`data.json` and ssh agent, not in the repo.
Bumps to 0.3.2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
e15a90dc30 |
chore(ci): version-check workflow + auto-release-on-merge + 0.3.0 bump
Three pieces of release infrastructure that travel together:
1. `.github/workflows/version-check.yml`
On every PR, asserts that
- plugin/manifest.json and plugin/package.json agree on version
- the PR's manifest.json version is strictly greater than
base's (rejects same-or-lower)
Pure-shell semver compare; no external action.
2. `.github/workflows/release-on-merge.yml`
On every push to main, reads plugin/manifest.json, pushes the
matching X.Y.Z tag if missing, then waits up to 30 s for the
existing release.yml (which triggers on the tag) to publish a
release. Falls back to a `gh release create --generate-notes`
if release.yml is unavailable. Marked as pre-release while
pre-1.0 (toggle the IS_PRERELEASE env to flip).
3. `plugin/scripts/bump-version.mjs`
The npm `version` lifecycle script the package.json already
referenced but didn't actually have. Reads the version npm just
wrote into package.json and mirrors it into manifest.json +
versions.json (with manifest.minAppVersion as the value). Now
`cd plugin && npm version patch` updates all three files at
once and the version-check passes.
Bumps to 0.3.0 in this PR (this is itself a non-trivial PR adding
release infra, so a minor bump fits).
Note on the original question: `Plugin remote-ssh v0.X.Y loaded`
in the runtime log uses `this.manifest.version` — i.e. it reads
from manifest.json, the Obsidian convention for plugin runtime
metadata. The version-check workflow asserts manifest.json and
package.json agree, so the runtime log effectively shows the same
number npm sees.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
874e4338ae |
feat(plugin): auto-deploy obsidian-remote-server over SSH
The "Debug: test RPC tunnel" command now ships, restarts, and verifies
the daemon end-to-end without any manual scp + nohup steps. Build
pipeline grew an `npm run build:server` that cross-compiles the Go
daemon for Linux/amd64 and stages it next to main.js so the plugin
can find it at runtime.
ServerDeployer (plugin/src/transport/ServerDeployer.ts)
- mkdir -p ~/.obsidian-remote && chmod 700 it
- pkill any prior daemon and remove stale socket / token
- SFTP-upload the local binary, chmod +x
- nohup the daemon with --vault-root / --socket / --token-file
/ --verbose, redirect stdout+stderr to ~/.obsidian-remote/server.log,
detach
- poll the token file for up to 5s; return the token + paths
- stop() helper for the inverse: pkill + cleanup
Built on a tiny `DeployerSshClient` interface so unit tests don't
have to bring up a real SSH session. shellQuote single-quotes every
argument so paths with spaces survive the shell.
SftpClient
- uploadFile(localPath, remotePath) — sftp.fastPut wrapper.
- exec(cmd) — runs a one-shot command, collects stdout/stderr/exit.
Used for mkdir / pkill / chmod / nohup. Long-running streams must
not go through here (they'd never close).
Build pipeline
- plugin/scripts/build-server.mjs cross-compiles
./cmd/obsidian-remote-server with GOOS=linux GOARCH=amd64
CGO_ENABLED=0 and writes the result to plugin/server-bin/
obsidian-remote-server-linux-amd64. Falls back to
~/tools/go-portable/go on the dev box when go isn't on PATH.
REMOTE_SSH_SKIP_SERVER_BUILD=1 short-circuits for hosts without a
Go toolchain.
- dev-install.mjs additionally copies plugin/server-bin/* into
<vault>/.obsidian/plugins/remote-ssh/server-bin/ so the plugin
finds the binary at runtime.
- New scripts: `npm run build:server`, `npm run build:full`
(server + plugin + install in one shot).
- Root .gitignore now excludes plugin/server-bin/.
main.ts
- locateDaemonBinary() resolves
<vault>/.obsidian/plugins/<id>/server-bin/obsidian-remote-server-
linux-amd64; returns null when missing.
- debugTestRpcTunnel():
1. locateDaemonBinary, error early when missing
2. ServerDeployer.deploy(...) ships and starts the daemon
3. openUnixStream + establishRpcConnection
4. RpcRemoteFsClient.list(activeRemoteBasePath) for the smoke
check, log each step, surface a Notice with daemon version.
Tests (vitest)
- ServerDeployer.test.ts (8 cases): order of mkdir → kill → upload
→ chmod → start → wait-for-token, killExisting=false skips the
kill, custom remote paths flow into the start command, paths
with spaces are single-quoted, non-zero exit on a non-tolerant
command surfaces, token-read retries until present, deadline is
enforced, stop() runs pkill + cleanup.
Verification
- cd plugin && npx tsc --noEmit clean
- cd plugin && npm test 123 / 123 pass (8 new)
- cd plugin && npm run build:full Go cross-compile (3.75
MiB) + plugin build +
dev vault install all
green; bundle 382 KB
(+3 KB over 5-D.3).
Dogfood (after merge)
1. cd plugin && npm run build:full
2. Reload the plugin in dev vault.
3. Run "Connect to remote vault" against the Panza profile.
4. Run "Debug: test RPC tunnel against obsidian-remote-server".
Expect: a Notice with daemon version + entry count, and a
per-step trace in console.log. No manual scp / start steps.
Follow-ups
- Phase 5-D.4: switch SftpDataAdapter's client over to
RpcRemoteFsClient automatically when a profile carries an
rpcSocketPath, with a fallback to the SFTP path.
- Cross-arch staging (linux/arm64, darwin/*) for non-amd64 hosts.
- ServerDeployer.stop wired into Plugin.disconnect so daemons don't
outlive the Obsidian session.
|
||
|
|
21bb70a5d0 |
chore: monorepo restructure (plugin/ + server/ + proto/)
Move the entire Obsidian plugin tree under plugin/ and add an empty
server/ tree (Go) and proto/ directory so the upcoming
obsidian-remote-server daemon has a home in the same repo. The plugin
remains the only thing that builds today; the server is a hello-world
skeleton that prints "not implemented yet" and exits non-zero, with a
Makefile that knows how to cross-compile for linux/darwin × amd64/arm64
once real code lands.
Background:
Approach A (vault-adapter monkey-patch) hit structural ceilings —
vault index frozen at startup, third-party plugins bypass DataAdapter,
empty-remote .obsidian read failures cascade through Templater/Kanban
etc. We are pivoting to the VS Code Remote-SSH model: a small Go
daemon on the remote host, JSON-RPC over a SSH-tunnelled WebSocket,
fsnotify-driven push events, attachment serving over HTTP on the same
socket. This restructure is the first mechanical step before the
protocol and transport land.
Mechanical changes:
- git mv all plugin sources, manifest, configs, scripts, and tests
into plugin/.
- plugin/scripts/dev-install.mjs walks one extra parent (`pluginRoot`
→ `repoRoot` → `..` for the dev vault) so REMOTE_SSH_DEV_VAULT
defaulting still resolves to ../SelfArchive-dev relative to the
repo, not to plugin/.
- CI: ci.yml runs lint/test/build with `working-directory: plugin`
and a server job that builds + tests the Go module. release.yml
builds out of plugin/ and uploads plugin/{main.js,manifest.json,
styles.css}.
- Dependabot: npm directory becomes /plugin; new gomod directory /server.
- Labeler: paths re-anchored to plugin/, with new plugin/server/proto
area labels.
Verification:
cd plugin && npm install && npx tsc --noEmit && npm test (76/76)
cd plugin && npm run build:install (367 KB; dev vault refreshed).
Go side waits on CI (no Go toolchain on the dev host).
|