NodeShell is a Wails v2 desktop SSH client: Go backend, React/TypeScript frontend.
main.go has two entries: the GUI (default) and a native MCP stdio server (--mcp). internal/app/app.go is the Wails binding facade; domain logic lives in the other internal/* packages.
Toolchain: Go 1.26, Node 22 (CI), Wails v2.13. The Wails CLI is not an npm dependency.
npm ci && go mod downloadnpm run build:wailsemitsfrontend/dist/, whichmain.goembeds with//go:embed all:frontend/dist. On a clean checkout,go test .andgo vet ./...fail until this exists;wails buildcreates it automatically.internal/*packages can be tested without it.npm run dev:wailsonly starts Vite on port 5173; usewails devfor the desktop app.- Build with
wails build -skipbindings. Install the CLI withgo install github.com/wailsapp/wails/v2/cmd/wails@v2.13.0. - Ubuntu 24.04/CI has only webkit2gtk 4.1: add
-tags webkit2_41(e.g.-platform linux/amd64 -tags webkit2_41). Windows release/MCP builds use-windowsconsoleso--mcpstdio works under redirected handles; do not drop it.
- Full check:
npm run typecheck,npm test,npm run lint,npm run build:wails,go vet ./..., then Go tests. - Single frontend test:
npx vitest run tests/ui/settings.test.tsx. - Single Go test:
go test ./internal/mcpcli -run TestName -count=1. Root-package tests usego test . -run TestName -count=1and requirefrontend/dist. - With
node_modulespresent, filter it from Go package lists like CI:go test $(go list ./... | grep -v /node_modules/). CI runs one package at a time with-timeout 90s; keep test waits bounded. - CI keeps
-raceoff on every platform (matrix entries setgo-test-race: ''). Local race runs have a history of hanging on Linux/macOS.
- Root-level
main.go,console_*.go, andwindowsize.goare the desktop entry package://go:embed frontend/distis root-relative and the WailsBindcall lives there. The App facade and its tests are ininternal/app; keep Go tests beside their package. - Keep domain packages (
internal/*exceptinternal/app) free of Wails imports. Services receive dependencies and anEventSink;internal/app/wails_sink.goadapts events to the runtime. This is what keeps them headless-testable. - Add a bound API in three places: an exported
<Group><Method>method on*Appininternal/app/app.go, a typed call insrc/renderer/src/api/adapter.ts, and shared DTOs/event constants insrc/shared/types.ts. The frontend callswindow.go.app.Appdirectly. - App lifecycle is driven by package functions
app.Start/app.Stop; do not turn them into methods, or Wails binds them as frontend-callable APIs. wailsjs/is generated and gitignored; do not edit or commit it.frontend/dist/is build output.- The frontend intentionally keeps the old
window.api/ElectronApinames even though the runtime is Wails; do not rename the contract. - Vite root is
src/rendererand output isfrontend/dist. Vitest only includestests/**/*.test.{ts,tsx}. - Go errors crossing IPC implement
ErrorCode() stringusing codes frominternal/apperror; the frontend whitelist isSSH_CODESinsrc/shared/ipc-error.ts. Keep both lists in sync, or codes collapse toUNKNOWN. - MCP exposes exactly 10 tools. The count is asserted in
internal/mcpcli/runmcp_test.goand bothscripts/mcp-smoke.*; update all of them (and the README) when changing tools.--mcpmust write protocol only to stdout and nothing to stderr during the smoke test. - The product is Wails-only:
tests/runtime-dependencies.test.tsrejects Electron/ssh2/MCP SDK dependencies and genericdev/buildscripts. Do not reintroduce them.
- SSH host keys are verified before authentication; do not bypass
internal/knownhosts. - Passwords and agent API keys live in the OS keyring, not JSON. Never write secrets into
hosts.jsonorsettings.json. - Local upload/download, private-key, and MCP file paths must pass
internal/localpathguardand stay under the user home. Remote SFTP paths use POSIX normalization. Do not add permissive path handling. - Data lives in
os.UserConfigDir()/nodeshell, intentionally compatible with the Electron 2.0.0 layout. The oldcredentials.jsonmust remain untouched.
- Vitest defaults to the
nodeenvironment; DOM tests need the// @vitest-environment jsdomdocblock. - Use
tests/ui/helpers.tsx(installFakeApi,renderWithI18n, event emitters) instead of hardcoding translations or mocking component behavior. - Go integration tests use the real in-process SSH server in
internal/sshtest; tests must not depend on the public network or an external SSH daemon. Root-package tests usetestAppfromapp_test.gowith a fake keyring. - UI tests subscribe through the fake
window.api; event names must match the Go constants.
.github/workflows/ci.ymlis the source of truth for packaging flags and platform dependencies; the README is a summary.- Windows, macOS, and Linux jobs build with Wails and run
scripts/mcp-smoke.*. Packaging must run on native runners; do not assume local cross-platform Wails builds work. tests/ci-workflow-structure.test.tsasserts CI structure and version handling; update it when intentionally changing the workflow.package.jsonis the version source for CI (APP_VERSION). Keepwails.jsonproductVersionand the README version in sync. Tags matchingv*produce a draft GitHub release.- Secrets are mapped to job-level env vars; never reference
secrets.*in a step-levelif:(GitHub rejects the workflow).
README.md is the user-facing document (Chinese). docs/design/wails-rebuild.md and docs/reports/ are historical design/acceptance notes, grouped by domain; they are not current authority, so verify claims against code and CI. Start at docs/README.md for the index. Put new design notes under docs/design/ and reports under docs/reports/<domain>/, not at the repository root.