From obsidian
Set up an agentic OS — either inside an Obsidian vault (bundled command-center dashboard, 5 auto-installed plugins, button bar wired to Claude prompts) OR as a standalone Next.js web dashboard with live MCP integrations (Circle, Fireflies, YouTube/VidIQ, Unipile LinkedIn DMs, Apify Twitter, Reddit), Anthropic Agent SDK refreshes, and optional Railway deploy. Use when the user says "set up agentic OS", "install command center", "bootstrap a personal AI dashboard", "build a vault dashboard", "spin up an MCP-powered dashboard", "deploy an AI ops dashboard", "give me my own version of the dashboard", "set up my second brain dashboard", or asks to personalize a dashboard previously created with this skill. Skill asks one routing question first — Obsidian or standalone — then runs the matching full flow.
How this skill is triggered — by the user, by Claude, or both
Slash command
/obsidian:agentic-os-setupThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill installs an agentic OS for the user. There are two routes:
references/obsidian/claude-md-template.mdreferences/obsidian/command-center.cssreferences/obsidian/dashboard-template.jsreferences/obsidian/frappe-charts.min.jsreferences/obsidian/home-template.mdreferences/obsidian/plugins/customjs/main.jsreferences/obsidian/plugins/customjs/manifest.jsonreferences/obsidian/plugins/customjs/styles.cssreferences/obsidian/plugins/dataview/manifest.jsonreferences/obsidian/plugins/dataview/styles.cssreferences/obsidian/plugins/homepage/main.jsreferences/obsidian/plugins/homepage/manifest.jsonreferences/obsidian/plugins/homepage/styles.cssreferences/obsidian/plugins/obsidian-shellcommands/manifest.jsonreferences/obsidian/plugins/obsidian-shellcommands/styles.cssreferences/obsidian/plugins/terminal/manifest.jsonreferences/obsidian/plugins/terminal/styles.cssreferences/obsidian/profile-template.mdreferences/obsidian/setup-template.mdreferences/obsidian/vault-overview-template.mdThis skill installs an agentic OS for the user. There are two routes:
Both routes share the same conceptual model: a per-profile dashboard, a button-bar of actions, and snapshot-style data refreshes. They differ in surface (markdown vs Next.js), data shape (frontmatter vs JSON snapshots), and deployment (none vs Railway).
Before anything else, use AskUserQuestion to route:
"Where do you want this set up?"
- Inside an Obsidian vault — command-center dashboard rendered via Dataview + CustomJS. Best if you already use Obsidian, want markdown-native, no server.
- As a standalone web dashboard — Next.js app with live MCP integrations and optional Railway deploy. Best if you want a real web URL, live API integrations, multi-user access via HTTP basic auth.
Header: "Surface". multiSelect: false. Don't pre-select. Don't proceed until answered.
Then execute the matching route below.
Use this entire section when the user picks "Inside an Obsidian vault" in Step 0.
Installs a configurable command-center dashboard in any Obsidian vault. Home + per-profile + Vault Overview pages, KPI cards, sparklines, heatmap, task rollup, recent-files lists, and a button bar wired to user-defined Claude prompts via the Shell-commands plugin. All 5 required plugins are bundled and auto-enabled — the user only has to restart Obsidian.
{vault}/
├── Dashboard/
│ ├── Home.md # Team-wide dashboard
│ ├── Vault-Overview.md # Top-level vault health view
│ ├── {Profile}.md # One per configured profile
│ ├── Setup.md # In-vault setup reference
│ ├── CLAUDE.md # AI routing rules for this folder
│ ├── components/dashboard.js # The renderer (CONFIG substituted)
│ └── lib/frappe-charts.min.js
└── .obsidian/
├── snippets/command-center.css # Brand-styled CSS (enabled)
├── plugins/dataview/ # Side-loaded, enabled
├── plugins/customjs/ # Side-loaded, enabled
├── plugins/obsidian-shellcommands/ # Side-loaded, enabled, aliases pre-registered
├── plugins/terminal/ # Side-loaded, enabled, "Claude" profile configured
├── plugins/homepage/ # Side-loaded, enabled, points to Dashboard/Home
├── community-plugins.json # Union'd with the 5 plugin IDs
└── appearance.json # CSS snippet enabled
test -d .obsidian && echo "vault confirmed" || (echo "NOT A VAULT — cd into your Obsidian vault first" && exit 1)
test -d Dashboard && echo "DASHBOARD EXISTS — ask user before overwriting"
If Dashboard/ exists, ask: cancel / install into Dashboard-new/ / overwrite (require explicit "yes overwrite").
Use AskUserQuestion for each. Capture answers into variables for substitution into CONFIG.
"What's your organization or personal brand name? (Used in folder paths and dashboard header. Short — one word ideal.)"
Default if skipped: MyOrg. Variable: ORG_NAME.
"What should the dashboard show as its title?" Two parts:
ORG_NAME. Variable: BRAND_LABEL.Command Center. Variable: BRAND_SUB."Who's on the team? List each profile name on a separate line. Use lowercase or capitalized — must match the folder names you'll use in step 4. For a solo vault, just one name."
Parse into array. Variable: PROFILES (JSON array). The first is DEFAULT_PROFILE.
"Where do you keep per-profile data?" Show 4 options via AskUserQuestion:
Team/{ORG}/Profiles/{name} (recommended for orgs — matches Team/Acme/Profiles/Alex)Team/{name} (flat team layout)People/{name} (alternative naming){name}. {ORG} is optional.Variable: PROFILE_FOLDER_PATTERN. Verify each PROFILES[i] folder exists (warn if missing — installer will still write the dashboard; profile folders just won't have data yet).
Three quick AskUserQuestion calls (each with sensible defaults):
Daily. Empty disables. Variable: DAILY_SUBPATH.task-list). Default task-list. Empty disables the task widget. Variable: TASKS_SUBPATH.snapshots. Variable: SNAPSHOTS_SUBPATH.Also: Root daily path for team-wide rollups. Default Daily. Variable: ROOT_DAILY_PATH.
Which dashboard pages? MultiSelect:
Variable: PAGES (array). Installer creates one .md per page.
"Name 3-5 top-level folders to show on the Vault Overview page. For each: folder path, one-line description, optional CLAUDE.md path (or empty)."
Variable: OVERVIEW_FOLDERS (array of [path, description, claude_path]).
Defaults if skipped: detect Projects/, Resources/, Intelligence/, Departments/, Teams/, Daily/ if present.
"What action buttons should the dashboard expose? For each button: label (short), icon (emoji or empty), scope (team / profile / both), and a Claude prompt (the text passed to claude -p '...'). Use {profile} in the prompt to interpolate the current profile name. Add as many as you want; defaults are zero."
Suggest these as starter buttons (user can accept/reject each):
"Read today's daily for {profile} if it exists. Write a 5-line morning brief covering open loops, top priorities, and energy level. Append under a '## Morning brief' heading.")"Create today's daily note at <DAILY_PATH>/$(date +%Y-%m-%d).md with standard frontmatter and section headings. Don't overwrite if it exists.")cmd: terminal:open-terminal.claude.root) — wired via Terminal plugin "Claude" profile, NOT a shell-commandcmd: app:reload)cmd: app:open-settings)For every button with a prompt, the installer generates a shell-commands alias shell-command-<kebab-label> and a cmd: obsidian-shellcommands:shell-command-<alias>.
Variable: BUTTONS (object with team and profile arrays).
Two hex colors. Defaults: primary #020309, canvas #FAF3E3. Substitute into CONFIG.COLORS and the CSS snippet variables at the top of command-center.css.
"Path to a PNG/SVG logo to show in the dashboard header? Leave blank for no logo."
If provided, copy to Dashboard/lib/brand-mark.png and set BRAND_MARK_PATH to that. Default empty.
These two pitfalls broke real installs. Treat them as load-bearing.
CustomJS evaluates each .js file as eval(\(${file_contents})`)` — it wraps the entire file in parentheses to coerce it into a single expression and pull the class out. This means:
class dashboard { ... } declaration. Comments before and after are fine.const CONFIG = {...} or any other statement before/after the class. (const CONFIG = ...) is a syntax error because const is a statement, not an expression. CustomJS surfaces this as SyntaxError: ParseError: Unexpected token.In references/obsidian/dashboard-template.js, CONFIG lives as a static CONFIG = { ... } field at the top of the class body. All code references it as this.constructor.CONFIG.X (not bare CONFIG.X). Preserve that pattern when editing — if you re-introduce a top-level const, the dashboard renders as a blank page with no console error from the dataviewjs block.
Smoke test before shipping any renderer change:
node -e "$(cat dashboard-template.js | sed 's/__[A-Z_]*__/null/g' | python3 -c 'import sys; print(f\"({sys.stdin.read()})\");')"
If that fails with a SyntaxError, the file violates the constraint. Fix before substitution.
Always cross-reference the keys against a known-working vault — do not invent them from the plugin's UI labels. Confirmed-correct keys:
| Plugin | Key | Wrong-but-tempting alternative |
|---|---|---|
| customjs | jsFolder (and jsFiles: "") | ❌ folder (silently ignored — plugin scans nothing) |
| dataview | enableDataviewJs, enableInlineDataviewJs, enableInlineDataview | (these are correct) |
| homepage | homepages (plural, object keyed by display name) → .value | ❌ homepage (singular) |
| terminal | profiles.claude.executable (absolute path) | ❌ relying on claude in PATH (Obsidian doesn't inherit login shell PATH) |
| obsidian-shellcommands | shell_commands[shell-command-<alias>].shell_command | ❌ flat commands[] array |
When in doubt, look at a known-working install's data.json before writing the install flow.
After the interview, do these in order. Resolve $SKILL_DIR from the skill's own path. All references live under $SKILL_DIR/references/obsidian/.
mkdir -p Dashboard/components Dashboard/lib Dashboard/snapshots
mkdir -p .obsidian/snippets .obsidian/plugins
Read $SKILL_DIR/references/obsidian/dashboard-template.js. Substitute each placeholder with the captured value:
| Placeholder | Value |
|---|---|
__ORG_NAME__ | ORG_NAME |
__BRAND_LABEL__ | BRAND_LABEL |
__BRAND_SUB__ | BRAND_SUB |
__BRAND_MARK_PATH__ | BRAND_MARK_PATH (or "") |
__PROFILES_JSON__ | JSON.stringify(PROFILES) |
__DEFAULT_PROFILE__ | DEFAULT_PROFILE |
__PROFILE_FOLDER_PATTERN__ | PROFILE_FOLDER_PATTERN |
__DAILY_SUBPATH__ | DAILY_SUBPATH |
__TASKS_SUBPATH__ | TASKS_SUBPATH |
__SNAPSHOTS_SUBPATH__ | SNAPSHOTS_SUBPATH |
__ROOT_DAILY_PATH__ | ROOT_DAILY_PATH |
__OVERVIEW_FOLDERS_JSON__ | JSON.stringify(OVERVIEW_FOLDERS) |
__BUTTONS_JSON__ | JSON.stringify(BUTTONS) |
__CLAUDE_PROMPTS_JSON__ | "{}" (reserved for later use) |
__SKILLS_FOLDER__ | optional skills folder path (e.g. Plugins/skills); empty hides the Vault Overview Skills section |
__SKILL_GROUPS_JSON__ | "{}" (or JSON.stringify({"Group": ["skill-a", "skill-b"]})) |
__PROJECT_CATEGORIES_JSON__ | "[]" (or JSON.stringify(["Agency","Content"]) if user has Projects/<Cat>/ subfolders) |
__CONNECTORS_JSON__ | "[]" (or JSON.stringify([["Slack","Team comms"], ...])) |
__CHEATSHEET_JSON__ | "[]" (or JSON.stringify([["Meeting note","Intelligence/meetings/YYYY-MM-DD-{Title}.md"], ...])) |
Ask the user about these five optional Vault Overview fields only if they chose to ship the Vault Overview page. All accept empty defaults.
Write the result to Dashboard/components/dashboard.js. Verify with both checks before declaring success:
# 1. Plain JS parse
node --check Dashboard/components/dashboard.js || { echo "FATAL: dashboard.js has a syntax error"; exit 1; }
# 2. CustomJS-wrapped parse — catches the 'top-level const' pitfall (see Critical Constraints)
node -e "try { new Function('return (' + require('fs').readFileSync('Dashboard/components/dashboard.js','utf8') + ')'); console.log('CustomJS wrap OK'); } catch (e) { console.error('FATAL — CustomJS will reject this file:', e.message); process.exit(1); }"
Both must pass. If check #2 fails but #1 passes, you've reintroduced a top-level statement before the class.
cp "$SKILL_DIR/references/obsidian/frappe-charts.min.js" Dashboard/lib/
If a brand mark was provided in Q10, copy it into Dashboard/lib/.
For each page in PAGES, read the corresponding template and substitute placeholders:
references/obsidian/home-template.md → Dashboard/Home.mdreferences/obsidian/vault-overview-template.md → Dashboard/Vault-Overview.mdPROFILES: references/obsidian/profile-template.md → Dashboard/{Profile}.md, substitute {{PROFILE_NAME}}references/obsidian/setup-template.md → Dashboard/Setup.md, substitute {{BRAND_LABEL}}, {{PAGES_LINKS}}references/obsidian/claude-md-template.md → Dashboard/CLAUDE.md, substitute {{ORG_NAME}}, {{PROFILE_FOLDER_PATTERN}}For each of dataview, customjs, obsidian-shellcommands, terminal, homepage:
if [ ! -d ".obsidian/plugins/$ID" ]; then
mkdir -p ".obsidian/plugins/$ID"
cp "$SKILL_DIR/references/obsidian/plugins/$ID/"* ".obsidian/plugins/$ID/"
fi
The if guard preserves any newer version the user already has.
dataview:
{"enableDataviewJs": true, "enableInlineDataviewJs": true, "enableInlineDataview": true}
customjs: The key is jsFolder (NOT folder).
{"jsFiles": "", "jsFolder": "Dashboard/components", "startupScriptNames": [], "registeredInvocableScriptNames": []}
obsidian-shellcommands: For each button in BUTTONS.team and BUTTONS.profile that has a prompt, generate one shell-commands entry:
{
"shell_commands": {
"shell-command-<alias>": {
"shell_command": "cd \"{{vault_dir}}\" && claude -p \"<escaped-prompt>\" --dangerously-skip-permissions",
"alias": "<alias>",
"platform_specific_commands": {"default": "use this"},
"shells": {},
"events": {},
"debounce": null,
"execution_notification_mode": "disabled",
"output_channels": {"stdout": "notification", "stderr": "notification-bigger"},
"output_handlers": {},
"output_wrappers": {"stdout": null, "stderr": null},
"command_palette_availability": "enabled"
}
},
"preferences": {"debug": false},
"settings_version": "0.23.0"
}
Replace <alias> with kebab-cased label. Escape double quotes in the prompt.
terminal: Cross-platform "claude" integrated profile.
Resolving the claude binary path — #1 source of Launch Claude button failures. Obsidian's Terminal plugin spawns processes without the user's login shell, so PATH is not enough. Need absolute path.
Probe in this order, take the first hit:
CLAUDE_BIN="$(zsh -ic 'command -v claude' 2>/dev/null || bash -ic 'command -v claude' 2>/dev/null || command -v claude)"
if [ -z "$CLAUDE_BIN" ] || [ ! -x "$CLAUDE_BIN" ]; then
for candidate in \
"$HOME/.local/bin/claude" \
"$HOME/.claude/local/claude" \
"/opt/homebrew/bin/claude" \
"/usr/local/bin/claude" \
"$HOME/.npm-global/bin/claude" \
"$HOME/.volta/bin/claude" \
"/usr/bin/claude"; do
[ -x "$candidate" ] && CLAUDE_BIN="$candidate" && break
done
fi
if [ -n "$CLAUDE_BIN" ] && "$CLAUDE_BIN" --version >/dev/null 2>&1; then
echo "resolved: $CLAUDE_BIN"
else
CLAUDE_BIN=""
fi
If empty, fall through to AskUserQuestion asking for the full path or to skip. If skipped, omit the claude profile entirely rather than ship a broken one.
{
"profiles": {
"claude": {
"args": ["--dangerously-skip-permissions"],
"executable": "<resolved absolute path>",
"followTheme": true,
"name": "Claude",
"platforms": {"darwin": true, "linux": true, "win32": true},
"restoreHistory": false,
"rightClickAction": "copyPaste",
"successExitCodes": ["0", "SIGINT", "SIGTERM"],
"terminalOptions": {"documentOverride": null},
"type": "integrated",
"useWin32Conhost": true
}
},
"addToCommand": true,
"focusOnNewInstance": true,
"newInstanceBehavior": "newHorizontalSplit"
}
Print the resolved path to the user in the final summary (Launch Claude wired to: /opt/homebrew/bin/claude). Verify before declaring complete: test -x "$CLAUDE_BIN" and "$CLAUDE_BIN" --version. If a user later sees a Python FileNotFoundError stack from the terminal pane, the executable moved — recovery: re-run which claude, edit .obsidian/plugins/terminal/data.json → profiles.claude.executable, reload Obsidian. Skill should offer to rewire just this step if re-invoked on an existing install.
homepage:
{
"version": 4,
"homepages": {
"Main Homepage": {
"value": "Dashboard/Home",
"kind": "File",
"openOnStartup": true,
"openMode": "Replace all open notes",
"manualOpenMode": "Replace all open notes",
"view": "Reading view",
"revertView": true,
"openWhenEmpty": true,
"refreshDataview": true,
"autoCreate": false,
"autoScroll": false,
"pin": false,
"commands": [],
"alwaysApply": false,
"hideReleaseNotes": false
}
},
"separateMobile": false
}
Read .obsidian/community-plugins.json (create [] if missing). Backup to .bak. Union in ["dataview", "customjs", "obsidian-shellcommands", "terminal", "homepage"]. Preserve existing entries. Write back.
Read .obsidian/appearance.json (create {} if missing). Backup to .bak. Read references/obsidian/command-center.css, substitute color placeholders, write to .obsidian/snippets/command-center.css. Set enabledCssSnippets to union with ["command-center"].
Done. The dashboard, all 5 plugins, the Claude terminal profile, and the homepage are configured.
ONE final step Obsidian only picks up on launch:
1. Quit Obsidian fully (Cmd/Ctrl + Q — not just close the window).
2. Reopen the vault.
3. Obsidian will ask you to "Trust" each plugin. Click Trust for all 5.
4. Open Dashboard/Home — it should render immediately.
If a button doesn't fire, open Settings → Shell commands and confirm each alias matches the one shown in the button's tooltip. The dashboard's developer console (Cmd/Ctrl + Opt/Alt + I) logs every command attempt.
Dashboard/ and .obsidian/. The dashboard is read-only on Daily/, Team/, Tasks/, etc..obsidian/plugins/<id>/ installs — only side-load when missing. The user may have a newer version.| Template | Goes to | Substitutes |
|---|---|---|
obsidian/dashboard-template.js | Dashboard/components/dashboard.js | All __*__ placeholders in CONFIG |
obsidian/home-template.md | Dashboard/Home.md | {{BRAND_LABEL}} |
obsidian/vault-overview-template.md | Dashboard/Vault-Overview.md | {{BRAND_LABEL}} |
obsidian/profile-template.md | Dashboard/{Profile}.md | {{PROFILE_NAME}} |
obsidian/setup-template.md | Dashboard/Setup.md | {{BRAND_LABEL}} |
obsidian/claude-md-template.md | Dashboard/CLAUDE.md | {{ORG_NAME}}, {{PROFILE_FOLDER_PATTERN}} |
obsidian/command-center.css | .obsidian/snippets/command-center.css | --cc-primary, --cc-canvas color vars |
obsidian/frappe-charts.min.js | Dashboard/lib/frappe-charts.min.js | none |
Bundled plugin folders under references/obsidian/plugins/ are copied verbatim into .obsidian/plugins/<id>/.
Use this entire section when the user picks "As a standalone web dashboard" in Step 0.
This route packages a Next.js 14 dashboard wired to the Claude Agent SDK + up to 7 platform integrations, plus an optional Railway deployment path. The template lives in references/standalone/template/ and ships with no secrets. Substitutions and live credential probes are the skill's job.
The user is a founder, operator, or marketer — NOT a developer. Keep language plain. Never say "MCP", "snapshot prompt", "stdio transport", "TypeScript type" out loud. Say "platform", "data refresh", "your YouTube channel", "the dashboard's settings file".
Internally you still operate on those technical concepts. The user just shouldn't have to read jargon.
By the end the user has:
Two keys gate everything else. Get them upfront so the user isn't surprised mid-flow:
ANTHROPIC_API_KEY.railway login in their own terminal (browser flow), then railway whoami to confirm. Don't store the token — Railway's CLI keeps it locally.If they don't have the Anthropic key yet, pause here and wait. Don't keep going.
Ask these in plain English. Use AskUserQuestion when the options are clear.
"What should we call this thing?"
→ Display name (e.g. "Acme", "Sarah's Brain"). Show them what the kebab-case slug will be (e.g. acme-os) and let them override.
"Where on your computer should I put it?"
→ Default: ~/Projects/<slug>. Confirm before writing.
"Who's on your team?" → Free text: comma-separated first names, 1-8 of them. Example: "Sarah, Marcus, Priya". The first name becomes the "main" view (gets a synthesis dashboard); everyone else gets a standard view. If they're solo, one name is fine. Tell them they can drop a profile picture for each person later.
"Which platforms do you want me to pull from?" → Multi-select. Use these plain labels (map to MCPs internally — never show MCP names):
| Label to show user | Maps to (internal) | What it does |
|---|---|---|
| "Your Circle community" | circle-community | Pulls posts, comments, DMs, member stats |
| "Your meeting notes (Fireflies)" | fireflies | Pulls recent meeting transcripts + summaries |
| "Your YouTube channel" | vidiq + youtube | Channel stats, recent videos, comments |
| "Your LinkedIn DMs (Unipile)" | unipile | Unread DMs, draft replies, send button |
| "Twitter/X trend scanning" | apify | Tracks accounts you care about |
| "Reddit trend scanning" | reddit | Subreddit pulls |
Tell them they can skip any of these and add them later. Skipping a platform just leaves that tab empty in the dashboard.
For EACH platform they selected, collect the credentials (using plain language) THEN immediately probe to confirm it works. Don't batch — do one at a time so the user can fix issues as they come up.
The probe pattern: write a temp .mcp.json with just that one server, then run node scripts/probe-mcp.mjs <server-name> (the template ships this script). It calls one cheap read-only tool on the MCP and exits 0/1. If it fails, surface the actual error from the JSON output and re-ask for the credential.
MCP_CIRCLE_KEYCIRCLE_OWNER_EMAILprobe-mcp.mjs circle-community. If it fails on email, the dashboard still works for posts/comments; only DMs need the email.MCP_FIREFLIES_KEYprobe-mcp.mjs fireflies@yourchannel)" → YOUTUBE_CHANNEL_HANDLEUC)" → YOUTUBE_CHANNEL_IDMCP_YOUTUBE_API_KEY, MCP_VIDIQ_KEYprobe-mcp.mjs youtube vidiqMCP_UNIPILE_KEYhttps://apiNN.unipile.com:NNNNN in their dashboard)" → UNIPILE_BASE_URLUNIPILE_LINKEDIN_ACCOUNT_ID. Hint: you can list accounts via the probe and let the user pick.probe-mcp.mjs unipileMCP_APIFY_KEYprobe-mcp.mjs apifyMCP_REDDIT_URLprobe-mcp.mjs redditIf a probe fails 2x, offer to skip that platform and continue. Don't loop forever.
references/standalone/template/ to the target directory. Skip .DS_Store, node_modules, .next, .runs.{{ORG_NAME}} → display name{{PROJECT_SLUG}} → kebab-case slug (for package.json){{PROFILES_JSON}} → JSON literal like ["Sarah", "Marcus"] as const
Files known to contain placeholders: lib/config.ts, package.json, middleware.ts, app/page.tsx, app/layout.tsx, app/overview/page.tsx, components/views/team/index.tsx..env.local from .env.example, populated with everything collected so far. Leave blank lines for any deferred platforms — the dashboard skips them gracefully.openssl rand -hex 12. Set BASIC_AUTH_USER=<their first name lowercased> and BASIC_AUTH_PASS=<random>. If they're only running locally and don't want auth, leave both blank.<Name>.jpg in public/avatars/ for each teammate" — show the path. If they don't, the UI falls back to initials.cd in and npm install (Node 20+).Run npm run dev. Wait for "Ready". Open http://localhost:3015.
/profile/<Name>).Full deploy walkthrough lives in references/standalone/RAILWAY.md — read it before starting. Key gotchas:
IS_SANDBOX=1 is required on Railway. Without it, every refresh fails immediately with "Claude Code process exited with code 1". Set this BEFORE the first deploy.next must be a recent patch. Railway blocks builds for known CVEs. If it fails, bump next per the error and run npm install to refresh package-lock.json./app/data. The included startup script symlinks .runs/ and public/data/ into it so data survives redeploys.railway login needs a real TTY — if you try to run it, you'll get "non-interactive mode". Have the user run it in their own terminal, then continue.Commands the agent can drive directly (after the user has logged in):
railway init --name <project-slug> --workspace "<their workspace>"
railway add --service web
railway service web
# bulk-set every env var from .env.local + IS_SANDBOX=1
railway variables --set "KEY1=val1" --set "KEY2=val2" --skip-deploys
railway volume add --mount-path /app/data
railway up --ci --detach
railway domain
If railway init complains about multiple workspaces, list them with railway list and ask the user which one to use.
After deploy, sanity check: curl -sI -u user:pass <url>/profile/<Name> should return 200.
After they've seen the dashboard load with real data, ask: "What feels wrong or missing? I'll change it."
The user is non-technical. They'll say things like:
For every ask:
references/standalone/personalization.md (it has recipes for the common cases).railway up --ci --detach and wait for SUCCESS./profile/<their name> — does that look right?"Stop only when the user says they're done. End each session with:
Today's changes:
- <change 1>
- <change 2>
Anything you didn't get to that you want for next time?
Save that as a note so the next session can pick it up.
The dashboard is a real Next.js codebase. You can edit anything. Standard approach:
app/, components/, lib/).personalization.md after the fact so the next user gets it too.Print a summary:
✓ Project at <path>
✓ Connected platforms: <list> — all probed
~ Deferred platforms: <list> — empty until you add keys to .env.local
✓ Local URL: http://localhost:3015
✓ Live URL: <railway url> (basic auth: <user> / <pass>)
✓ Personalizations applied today: <list>
To customize later:
- Re-trigger this skill, or just describe what you want — I keep the conversation context
- npm run probe-mcps to re-test platform connectivity
- references/standalone/personalization.md has the recipes for common changes
references/standalone/RAILWAY.md — point them at it for deploy details rather than re-typing everything.test -d Dashboard/components/dashboard.js for Route A, test -f .env.local && test -d app/ for Route B) and offer to re-run a specific step rather than full reinstall.Builds a throwaway prototype to answer a design question about UI appearance or state/logic behavior. Guides you through two branches: interactive terminal app for logic validation, or multiple UI variations for visual exploration.
npx claudepluginhub benaios/benai-skills-main --plugin obsidian