/run-wenlan
Build, launch, and stop the wenlan daemon (wenlan-server) for local dev and verification. Use when asked to run or restart the daemon, or before driving any surface (HTTP, CLI, MCP) against a live instance.
Claude Code
$ npx -y skills add wenlan-app/wenlan --skill run-wenlan --agent claude-code
Codex
$ npx skills add wenlan-app/wenlan --skill run-wenlan -a codex -y
Cursor
$ npx skills add wenlan-app/wenlan --skill run-wenlan -a cursor -y
Gemini CLI
$ npx skills add wenlan-app/wenlan --skill run-wenlan -a gemini-cli -y
GitHub Copilot
$ npx skills add wenlan-app/wenlan --skill run-wenlan -a github-copilot -y
OpenCode
$ npx skills add wenlan-app/wenlan --skill run-wenlan -a opencode -y
Windsurf
$ npx skills add wenlan-app/wenlan --skill run-wenlan -a windsurf -y
SKILL.md
run-wenlan.SKILL.mdname: run-wenlan
description: Build, launch, and stop the wenlan daemon (wenlan-server) for local dev and verification. Use when asked to run or restart the daemon, or before driving any surface (HTTP, CLI, MCP) against a live instance.
Run wenlan-server (dev)
Build: `cargo build -p wenlan-server -p wenlan` (add `-p wenlan-mcp` for the MCP bridge).
Ports — one allocation, no guessing
Three ports exist and they are not interchangeable. Stopping "the isolated daemon" on the wrong one leaves a live daemon behind and reports success.
| Port | Owner | Notes | |---|---|---| | 7878 | the user's real daemon | never kill casually, never verify against | | 17878 | manual isolated instance (this skill) | the recipes below | | 17881 | `scripts/smoke-cli.sh` | `PORT=` overrides | | 17882 | `scripts/smoke-mcp.sh` | `PORT=` overrides; runs alongside the CLI smoke |
The smokes own their own ports so both can run at once. Do not repoint them at 17878 — a manual daemon and a smoke would then fight over one listener, and the smoke's ownership assertion would fail on a daemon it did not start.
Isolated instance (default for verification)
Never verify against the shared prod daemon on :7878 — dev and prod share the platform data dir by default. When `WENLAN_DATA_DIR` names any root other than the installed app's own, the default pages folder is `$WENLAN_DATA_DIR/pages`, and the daemon refuses to start if a `knowledge_path` points into the real `~/.wenlan/pages` (or a custom pages folder in the real config). Builds older than that fix used the real `~/.wenlan/pages` by default, so the recipe below still writes `knowledge_path` explicitly BEFORE the daemon starts. Then read the live value back from `/api/knowledge/path` and the `Wenlan pages folder:` startup log line. Use one absolute data dir for every command: on macOS the sandboxed and unsandboxed shells see different `TMPDIR`s, so a path derived from `TMPDIR` in one shell is a different folder in the other.
`WENLAN_NO_AUTOSTART=1` belongs on every harness command: without it a failed connect starts the user's registered background service.
DATA_DIR="$(mktemp -d "${TMPDIR:-/tmp}/run.XXXXXX")"
mkdir -p "$DATA_DIR/pages"
printf '{"knowledge_path":"%s"}' "$DATA_DIR/pages" >"$DATA_DIR/config.json.tmp"
mv "$DATA_DIR/config.json.tmp" "$DATA_DIR/config.json" # atomic, before spawn
WENLAN_NO_AUTOSTART=1 WENLAN_PORT=17878 WENLAN_DATA_DIR="$DATA_DIR" \
./target/debug/wenlan-server &
# ready: curl -sf --max-time 2 http://127.0.0.1:17878/api/health (poll up to ~120s)
# check: curl -sf http://127.0.0.1:17878/api/knowledge/path → must be $DATA_DIR/pages
# stop: lsof -ti :17878 | xargs kill -9Sandbox gotcha: always give mktemp a template (`"${TMPDIR:-/tmp}/x.XXXXXX"`); bare `mktemp -d` lands in a denied dir on macOS.
Windows (Git Bash) — the same instance, four differences
`TMPDIR` is unset, `lsof` is absent, `$!` is not the pid the OS knows the daemon by, and an MSYS path is not a path the native daemon can open. Each one fails quietly in a different way, so none of them is optional.
# 1. mktemp has no TMPDIR to fall back on: give it an explicit template.
DATA_DIR="$(mktemp -d "${TMPDIR:-/tmp}/run.XXXXXX")"
mkdir -p "$DATA_DIR/pages"
# 2. TWO variables, not one rewritten one. The shell keeps the MSYS spelling for
# its own `rm -rf`; the daemon is handed the native spelling. `cygpath -m`
# converts at the daemon boundary ONLY: /tmp/run.EiVYlp →
# C:/Users/<you>/AppData/Local/Temp/run.EiVYlp
NATIVE_DATA_DIR="$(cygpath -m "$DATA_DIR")"
NATIVE_PAGES_DIR="$(cygpath -m "$DATA_DIR/pages")"
printf '{"knowledge_path":"%s"}' "$NATIVE_PAGES_DIR" >"$DATA_DIR/config.json.tmp"
mv "$DATA_DIR/config.json.tmp" "$DATA_DIR/config.json"
WENLAN_NO_AUTOSTART=1 WENLAN_PORT=17878 WENLAN_DATA_DIR="$NATIVE_DATA_DIR" \
/c/wl-target/debug/wenlan-server.exe &
JOB_PID=$! # an MSYS job pid — NOT the pid netstat and tasklist report**Listener check — source the library; do not hand-roll `netstat`.** There is no `lsof`, and the obvious `netstat -ano | awk ... $4=="LISTENING"` one-liner is NOT a substitute. It has two states where the question has three, and the state it cannot express is the dangerous one:
- `netstat` missing, killed, or exiting non-zero → no matching row → reads as
**free**.
- a non-English Windows → the State column says `ABHOEREN`, not `LISTENING` →
no matching row → reads as **free**, with the port genuinely busy.
- an unexpected or truncated table → no matching row → reads as **free**.
Starting a second daemon on a port nobody measured is how two daemons end up sharing one data directory. `scripts/lib/host-process.sh` already answers this in three states, keys on the structural shape of a listening row rather than on the localised word, and validates that what it parsed really is the table. Use it, on Windows and POSIX alike:
. scripts/lib/host-process.sh
probe_listener_port 17878
case "$LISTENER_PROBE_STATE" in
found) echo "busy: pid $LISTENER_PROBE_PID"; exit 1 ;;
none) echo "measured free" ;;
unmeasured) echo "COULD NOT MEASURE port 17878 — refusing to start"; exit 2 ;;
esac
Underneath is `listener_pid_for_port` (0 = found, 1 = measured free, 2 = could not measure); `probe_listener_port` is the wrapper that turns those into `LISTENER_PROBE_STATE`, because `out="$(f)"; rc=$?` aborts under `set -e` and `if out="$(f)"; then …; fi; rc=$?` reads the compound's status, which is 0. Branch on all three states every time. There is no fourth branch to add and no default that is safe to leave off.
**Resolve the real pid before killing anything.** `$!` is the MSYS job pid; the Windows pid is the `WINPID` column of `ps -W`, and the image is the `COMMAND` column read from the header's own offset — `STIME` is one token (`10:23:45`) or two (`Aug 27`), so a field-index parse shifts on every row for a process that outlived midnight. That parse also has to reject a table it c
Read more
name: run-wenlan description: Build, launch, and stop the wenlan daemon (wenlan-server) for local dev and verification. Use when asked to run or restart the daemon, or before driving any surface (HTTP, CLI, MCP) against a live instance.
Run wenlan-server (dev)
Build: `cargo build -p wenlan-server -p wenlan` (add `-p wenlan-mcp` for the MCP bridge).
Ports — one allocation, no guessing
Three ports exist and they are not interchangeable. Stopping "the isolated daemon" on the wrong one leaves a live daemon behind and reports success.
| Port | Owner | Notes | |---|---|---| | 7878 | the user's real daemon | never kill casually, never verify against | | 17878 | manual isolated instance (this skill) | the recipes below | | 17881 | `scripts/smoke-cli.sh` | `PORT=` overrides | | 17882 | `scripts/smoke-mcp.sh` | `PORT=` overrides; runs alongside the CLI smoke |
The smokes own their own ports so both can run at once. Do not repoint them at 17878 — a manual daemon and a smoke would then fight over one listener, and the smoke's ownership assertion would fail on a daemon it did not start.
Isolated instance (default for verification)
Never verify against the shared prod daemon on :7878 — dev and prod share the platform data dir by default. When `WENLAN_DATA_DIR` names any root other than the installed app's own, the default pages folder is `$WENLAN_DATA_DIR/pages`, and the daemon refuses to start if a `knowledge_path` points into the real `~/.wenlan/pages` (or a custom pages folder in the real config). Builds older than that fix used the real `~/.wenlan/pages` by default, so the recipe below still writes `knowledge_path` explicitly BEFORE the daemon starts. Then read the live value back from `/api/knowledge/path` and the `Wenlan pages folder:` startup log line. Use one absolute data dir for every command: on macOS the sandboxed and unsandboxed shells see different `TMPDIR`s, so a path derived from `TMPDIR` in one shell is a different folder in the other.
`WENLAN_NO_AUTOSTART=1` belongs on every harness command: without it a failed connect starts the user's registered background service.
DATA_DIR="$(mktemp -d "${TMPDIR:-/tmp}/run.XXXXXX")"
mkdir -p "$DATA_DIR/pages"
printf '{"knowledge_path":"%s"}' "$DATA_DIR/pages" >"$DATA_DIR/config.json.tmp"
mv "$DATA_DIR/config.json.tmp" "$DATA_DIR/config.json" # atomic, before spawn
WENLAN_NO_AUTOSTART=1 WENLAN_PORT=17878 WENLAN_DATA_DIR="$DATA_DIR" \
./target/debug/wenlan-server &
# ready: curl -sf --max-time 2 http://127.0.0.1:17878/api/health (poll up to ~120s)
# check: curl -sf http://127.0.0.1:17878/api/knowledge/path → must be $DATA_DIR/pages
# stop: lsof -ti :17878 | xargs kill -9Sandbox gotcha: always give mktemp a template (`"${TMPDIR:-/tmp}/x.XXXXXX"`); bare `mktemp -d` lands in a denied dir on macOS.
Windows (Git Bash) — the same instance, four differences
`TMPDIR` is unset, `lsof` is absent, `$!` is not the pid the OS knows the daemon by, and an MSYS path is not a path the native daemon can open. Each one fails quietly in a different way, so none of them is optional.
# 1. mktemp has no TMPDIR to fall back on: give it an explicit template.
DATA_DIR="$(mktemp -d "${TMPDIR:-/tmp}/run.XXXXXX")"
mkdir -p "$DATA_DIR/pages"
# 2. TWO variables, not one rewritten one. The shell keeps the MSYS spelling for
# its own `rm -rf`; the daemon is handed the native spelling. `cygpath -m`
# converts at the daemon boundary ONLY: /tmp/run.EiVYlp →
# C:/Users/<you>/AppData/Local/Temp/run.EiVYlp
NATIVE_DATA_DIR="$(cygpath -m "$DATA_DIR")"
NATIVE_PAGES_DIR="$(cygpath -m "$DATA_DIR/pages")"
printf '{"knowledge_path":"%s"}' "$NATIVE_PAGES_DIR" >"$DATA_DIR/config.json.tmp"
mv "$DATA_DIR/config.json.tmp" "$DATA_DIR/config.json"
WENLAN_NO_AUTOSTART=1 WENLAN_PORT=17878 WENLAN_DATA_DIR="$NATIVE_DATA_DIR" \
/c/wl-target/debug/wenlan-server.exe &
JOB_PID=$! # an MSYS job pid — NOT the pid netstat and tasklist report**Listener check — source the library; do not hand-roll `netstat`.** There is no `lsof`, and the obvious `netstat -ano | awk ... $4=="LISTENING"` one-liner is NOT a substitute. It has two states where the question has three, and the state it cannot express is the dangerous one:
- `netstat` missing, killed, or exiting non-zero → no matching row → reads as
**free**.
- a non-English Windows → the State column says `ABHOEREN`, not `LISTENING` →
no matching row → reads as **free**, with the port genuinely busy.
- an unexpected or truncated table → no matching row → reads as **free**.
Starting a second daemon on a port nobody measured is how two daemons end up sharing one data directory. `scripts/lib/host-process.sh` already answers this in three states, keys on the structural shape of a listening row rather than on the localised word, and validates that what it parsed really is the table. Use it, on Windows and POSIX alike:
. scripts/lib/host-process.sh probe_listener_port 17878 case "$LISTENER_PROBE_STATE" in found) echo "busy: pid $LISTENER_PROBE_PID"; exit 1 ;; none) echo "measured free" ;; unmeasured) echo "COULD NOT MEASURE port 17878 — refusing to start"; exit 2 ;; esac
Underneath is `listener_pid_for_port` (0 = found, 1 = measured free, 2 = could not measure); `probe_listener_port` is the wrapper that turns those into `LISTENER_PROBE_STATE`, because `out="$(f)"; rc=$?` aborts under `set -e` and `if out="$(f)"; then …; fi; rc=$?` reads the compound's status, which is 0. Branch on all three states every time. There is no fourth branch to add and no default that is safe to leave off.
**Resolve the real pid before killing anything.** `$!` is the MSYS job pid; the Windows pid is the `WINPID` column of `ps -W`, and the image is the `COMMAND` column read from the header's own offset — `STIME` is one token (`10:23:45`) or two (`Aug 27`), so a field-index parse shifts on every row for a process that outlived midnight. That parse also has to reject a table it c
A living wiki your AI writes from your notes, code, AI chats (memories), and docs, checked against sources. It flags what it can’t back up and updates pages when sources change. Supports Claude, Codex, Obsidian...
Other skills on wenlan.
More skills from other plugins.
codebase-wiki
How to work in a Codebase Wiki project (the `codebase-wiki` starter pack): an agent-authored, source-grounded wiki of the…
academic-paper-reviewe…
Simulates academic peer review, evaluating papers across Originality, Methodology, Results, and Writing to provide Major/Minor…
Questions about run-wenlan.
- Which coding agents can use run-wenlan?
- run-wenlan installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

