frizynn/nenu/nenu-onboarding
Claude Code and agent skill by frizynn/nenu
- community
Checking that the SKILL.md file is still in the repository.
Check a Nenu install on this host item by item (plugin, bridge service, tailnet front door, access gates, Web Push, Claude hooks, status line, operator files, updates), report what is done and what is missing, and finish each missing item through Nenu's own Herdr actions with the operator's OK. Use when setting up Nenu, finishing a Nenu install, or asking what is left to configure.
Install
npx vskill@latest install frizynn/nenu/nenu-onboardingSKILL.md
Nenu onboarding
Run on the host where Herdr and Nenu run, not on the phone. Check every item read-only first, print one checklist, then offer the missing items one at a time. End with what the operator still does on the phone.
Rules
- Every check below is read-only. Run them all before offering anything.
- Ask before any step that restarts the bridge, changes a file or sends a notification. One item per
question. After a fix, re-run that item's check and show the result.
- Never print secrets:
.envvalues, the Claude hook token (NENU_HOOK_TOKENin Claude's
settings.json), VAPID keys, push subscription endpoints. Never cat .env, Claude's settings.json or push-subscriptions.json. List .env key names only.
- Never run
tailscale funnel. Never pass--forcetopush-keysunless the operator asks for new
keys knowing every phone must re-enable notifications.
- Complete items through Herdr actions:
herdr plugin action invoke <action> --plugin "$ID". Use
bash "$ROOT/scripts/collie-ctl.sh" <verb> only for a verb that has no action (qr, logs) or an action missing from herdr plugin action list --plugin "$ID".
- An item you cannot check read-only is reported as "can't check", never as done.
- The checks use
jq. If it is missing, parse the same fields withbun -e.
0. Context
IDis theplugin_idprinted.herdr.nenuis 1.0 or newer;herdr.collieis an install from
before 1.0, which still answers to the old id (.adr/0068-the-plugin-is-herdr-nenu-and-internal-names-stay.md). Use the printed id in every command.
ROOTisplugin_root, the checkout that is the plugin.kindgithubis a Herdr-managed
install, local a linked clone.
CFG="$(herdr plugin config-dir "$ID")"holds.envand the operator files.config-dirprints a
path for any id, installed or not, so it never proves the plugin is installed. ".env lists X" below means X appears in sed -n 's/^\([A-Z_][A-Z0-9_]*\)=.*/\1/p' "$CFG/.env", which prints key names and no values.
STATE="$HOME/.local/state/collie"(every version keeps that name), or theCOLLIE_STATE_DIRvalue
if .env lists that key. It is a path, not a secret.
LOGIN="$(tailscale status --json | jq -r '.User[(.Self.UserID|tostring)].LoginName')"and
NAME="$(tailscale status --json | jq -r '.Self.DNSName | rtrimstr(".")')".
PORTis the port on thelocalline of thestatusbanner (default 8787), and
B="http://127.0.0.1:$PORT".
S="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json", Claude's user settings. Repeat the Claude
checks for every profile the operator uses.
herdr plugin action invoke returns as soon as the action starts. Read its output from the log:
status, url and version need no OK. The first action run as herdr.nenu, these included, copies the old herdr.collie config dir over once; that carry-over is intended.
1. Checklist
For each item: the check, what done looks like, and what completes it.
Plugin installed
- Check: step 0.
- Done: one line for
herdr.nenuorherdr.collie. - Missing: offer
herdr plugin install frizynn/nenu, thenstart. A rawOs { code: 2, kind: NotFound }
from any herdr plugin command means the Herdr server is not running; start Herdr first.
- A
herdr.collieinstall is reported as "works, pre-1.0" and handled under Updates.
Bridge service
- Check:
act status. Then the supervisor: - macOS:
launchctl print "gui/$(id -u)/$ID" | grep -E '^\s*(state|pid) =' - Linux:
systemctl --user is-active nenu(pre-1.0 unit:collie) and
loginctl show-user "$USER" -p Linger.
- Done: the banner says
✓ Nenu is running, the service line saysactive, and on LinuxLinger=yes
if the host must serve Nenu without a login session.
- Missing: offer
start(builds if needed, starts the service, publishes the tailnet URL). A banner
saying ⚠ Nenu isn't answering needs the log, not another start: bash "$ROOT/scripts/collie-ctl.sh" logs. Lingering is loginctl enable-linger "$USER", which the operator runs.
Tailnet front door and URL
- Check:
act urlandtailscale serve status. If.envlistsCOLLIE_SKIP_SERVE, the operator runs
their own proxy or tunnel: report "own front door" and check it against DEPLOYMENT.md instead.
- Done:
urlprintshttps://$NAME(orhttps://$NAME:<COLLIE_SERVE_PORT>), andserve statusmaps
that URL to http://127.0.0.1:$PORT with (tailnet only). The status banner's tailnet line carries no (unreachable from other devices) note.
- Missing: offer
restart, which re-runstailscale serve. If it printsnote: tailscale serve failed,
the fix is on the operator's side: sudo tailscale set --operator=$USER, tailscale up, or COLLIE_SERVE_MODE=http for tailnets without HTTPS certificates (which disables PWA install and push). An unreachable note is the tailnet policy, fixed in the Tailscale admin console.
- Any Nenu mapping that is not
(tailnet only)is a stop: tell the operator and do nothing else
until it is removed.
Identity gate and host allowlist
- Check, against the local bridge:
``sh code() { curl -s -o /dev/null -w '%{http_code}\n' "$@"; } code "$B/api/config" # no identity code -H "Tailscale-User-Login: $LOGIN" -H "Host: $NAME" "$B/api/config" # tailnet name code -H "Tailscale-User-Login: $LOGIN" -H 'Host: allowlist-probe.invalid' "$B/api/config" ``
A 403 body names the reason; read it with curl -s only when the code is 403 (a 200 body carries the VAPID public key and operator rows).
- Done: no identity answers
403(identity required), soCOLLIE_TRUSTED_USERis set; the tailnet
name answers 200; the bogus host answers 403 (host not allowed).
- Missing:
- No identity answers
200and.envlists noCOLLIE_TRUSTED_USER_OPTIONAL: anyone on the
tailnet can drive every pane. Offer to add COLLIE_TRUSTED_USER=$LOGIN to $CFG/.env (confirm the login with the operator), then restart.
identity not trustedwith$LOGIN: the gate is set to another login. Ask which one is right.- The tailnet name answers
host not allowed: discovery missed it. Offerrestart; if it persists,
set COLLIE_TAILSCALE_HOSTS in .env.
- The bogus host answers
200:.envhasCOLLIE_ALLOW_ANY_HOST. Report it; removing it is the
operator's call.
- Under
COLLIE_SKIP_SERVEthe identity header plays no part; report it as "not used".
Device gate (optional)
- Check:
curl -s -H "Tailscale-User-Login: $LOGIN" "$B/api/snapshot" | jq 'has("device")'and whether
.env lists COLLIE_DEVICE_HEADER and COLLIE_DEVICE_ALLOWLIST.
- Done:
falsewith neither key on a plaintailscale servesetup ("not used", which is normal), or
true with both keys behind a proxy that injects the header.
- Missing: the header without an allowlist makes every device read-only. The gate needs a proxy in
front (DEPLOYMENT.md, variants B to D); nothing here sets it up. Report and point there.
Web Push
- Check:
``sh curl -s -H "Tailscale-User-Login: $LOGIN" "$B/api/config" | jq '{push, build}' sed -n 's/^\(COLLIE_VAPID_[A-Z_]*\)=.*/\1/p' "$CFG/.env" # key names only jq length "$STATE/push-subscriptions.json" 2>/dev/null || echo 0 # count only ``
- Done:
push: trueand at least one subscription.COLLIE_VAPID_SUBJECTis optional. - Missing, in order, each with its own OK:
- No
COLLIE_VAPID_PUBLIC/_PRIVATE:push-keys. To set a contact address too, the shell form
takes it: bash "$ROOT/scripts/collie-ctl.sh" push-keys mailto:you@example.com.
- Keys present but
push: false:restart(keys are read at start). - Zero subscriptions: a phone step, listed at the end.
- Once a phone is subscribed:
push-testsends one notification to every subscribed device.
- Push needs HTTPS. With
COLLIE_SERVE_MODE=httpreport push as unavailable instead of offering keys.
Claude live status hooks (optional)
- Check, without printing the file (it holds the hook token):
``sh jq '[.hooks // {} | .[] | .[]? | .hooks[]? | select((.url? // "") | endswith("/api/hooks/claude"))] | length' "$S" ls "$STATE/claude-hooks.token" ``
- Done: a count above 0 and the token file present.
- Missing: offer
claude-hooks. It backs upsettings.json, keeps every other hook, and only new
Claude sessions load it. claude-hooks-remove undoes it.
Claude subagent hooks (optional)
- Check: the hook command, and that the script it runs still exists (a hook installed from a removed
worktree breaks silently):
```sh jq -r '.hooks.SubagentStart[]?.hooks[]?.command, .hooks.SubagentStop[]?.hooks[]?.command' "$S" \
```
- Done: an
okline, ideally under$ROOT. - Missing or
missing: offersubagent-hooks. It backs upsettings.jsonand keeps other hooks.
Claude status line (optional)
- Check:
jq -r '.statusLine.command // empty' "$S" | paths_exist claude-statusline.ts. A
project-level statusLine overrides the user one.
- Done: an
okline. It feeds the composer's context and usage figures. - Missing: there is no action. Offer
bun "$ROOT/scripts/install-claude-statusline.ts", run from
$ROOT so the wrapper points at the plugin checkout. It backs up settings and keeps the operator's own status line command. A custom profile takes <settings.json> <state dir> as arguments.
Operator files (optional)
- Check:
ls "$CFG"forcommands.toml,keys.toml,quick-replies.toml, and
curl -s -H "Tailscale-User-Login: $LOGIN" "$B/api/config" | jq '{c: (.operatorCommands|length), k: (.operatorKeys|length), q: (.operatorQuickReplies|length)}'.
- Done: each file present has a nonzero count. Absent files mean the shipped defaults, which is fine.
- Missing: a file with count 0 has rejected rows; the log names them
(bash "$ROOT/scripts/collie-ctl.sh" logs). To start one, offer cp "$ROOT/commands.toml.example" "$CFG/commands.toml" (same for keys and quick-replies) and let the operator edit it. Edits are live, no restart. Grammar: docs/USER_GUIDE.md, "Your own slash commands" and the two sections after it.
Updates
- Check:
```sh act version git -C "$ROOT" symbolic-ref -q --short HEAD || echo detached # branch = linked clone git -C "$ROOT" ls-remote --tags --refs origin 'v*' | awk '{sub("refs/tags/v","",$2); print $2}' \
```
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 90 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/nenu-onboarding/SKILL.md
Popularity
- 7-day trend score
- 0
Refreshed 1 Nov 2026, 7 days ago.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/frizynn/nenu/nenu-onboarding)