Skip to content
Development
Skill

/welcome

First-touch experience for new Ouroboros users

From plugin
ouroboros
5.4k22 skills21 agents3 hooks1 MCP
Install
$ npx -y skills add Q00/ouroboros --skill welcome --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/welcome

Context preview

The summary Claude sees to decide when to auto-load this skill.

First-touch experience for new Ouroboros users

SKILL.md

welcome.SKILL.md
name: welcome
description: "First-touch experience for new Ouroboros users"

/ouroboros:welcome

Interactive onboarding for new Ouroboros users.

Usage

/ouroboros:welcome              # First-time or update onboarding
/ouroboros:welcome --skip       # Skip welcome, mark as shown
/ouroboros:welcome --force      # Force re-run welcome even if shown

Instructions

When this skill is invoked, follow this flow:

Before running any shell snippets below, choose a Python command without assuming a system `python3` binary. Marketplace installs require `uvx`, not a global Python executable:

if [ -z "${OUROBOROS_WELCOME_PYTHON:-}" ]; then
  if command -v python3 >/dev/null 2>&1; then
    OUROBOROS_WELCOME_PYTHON="python3"
  elif command -v python >/dev/null 2>&1; then
    OUROBOROS_WELCOME_PYTHON="python"
  elif command -v uv >/dev/null 2>&1; then
    OUROBOROS_WELCOME_PYTHON="uv run --no-project --quiet python"
  else
    echo "Ouroboros welcome requires python3, python, or uv to inspect local setup."
    exit 1
  fi
fi

---

Pre-Check: Already Completed?

First, check `~/.ouroboros/prefs.json` for `welcomeCompleted`. For upgrades from older releases, also treat legacy `welcomeShown: true` as completed so the welcome prompt does not reappear forever:

PREFFILE="$HOME/.ouroboros/prefs.json"

if [ -f "$PREFFILE" ]; then
  WELCOME_COMPLETED=$($OUROBOROS_WELCOME_PYTHON - <<'PY'
import json, os
path = os.path.expanduser('~/.ouroboros/prefs.json')
try:
    prefs = json.load(open(path, encoding='utf-8'))
except Exception:
    prefs = {}
if not isinstance(prefs, dict):
    prefs = {}
print(prefs.get('welcomeCompleted') or ('legacy-welcomeShown' if prefs.get('welcomeShown') else ''))
PY
)
  WELCOME_VERSION=$($OUROBOROS_WELCOME_PYTHON - <<'PY'
import json, os
path = os.path.expanduser('~/.ouroboros/prefs.json')
try:
    prefs = json.load(open(path, encoding='utf-8'))
except Exception:
    prefs = {}
if not isinstance(prefs, dict):
    prefs = {}
print(prefs.get('welcomeVersion') or '')
PY
)

  if [ -n "$WELCOME_COMPLETED" ] && [ "$WELCOME_COMPLETED" != "null" ]; then
    ALREADY_COMPLETED="true"
  fi
fi

Before honoring that completion marker, determine whether the Codex setup is ready. A previously completed welcome must never hide the setup gate from a user who chose **나중에** or whose setup was later removed:

CODEX_HOME_DIR="${CODEX_HOME:-$HOME/.codex}"
case "$CODEX_HOME_DIR" in
  "~") CODEX_HOME_DIR="$HOME" ;;
  "~/"*) CODEX_HOME_DIR="$HOME/${CODEX_HOME_DIR#"~/"}" ;;
esac
if $OUROBOROS_WELCOME_PYTHON - "$HOME/.ouroboros/config.yaml" "$CODEX_HOME_DIR/config.toml" <<'PY'
from __future__ import annotations

import re
import os
import shutil
import sys
from pathlib import Path

try:
    import tomllib
except ModuleNotFoundError:  # Python 3.10 and earlier hosts
    tomllib = None

try:
    import yaml
except ModuleNotFoundError:
    yaml = None

config_path, codex_config_path = map(Path, sys.argv[1:])

def yaml_mapping(source: str) -> dict[str, dict[str, str]]:
    """Read only the top-level mapping scalars owned by this readiness gate."""
    if yaml is not None:
        loaded = yaml.safe_load(source) or {}
        return loaded if isinstance(loaded, dict) else {}

    parsed: dict[str, dict[str, str]] = {}
    section: str | None = None

    def scalar_value(raw: str) -> str:
        return raw.strip().split(" #", 1)[0].strip().rstrip(",}").strip().strip("'\"")

    def flow_mapping(raw: str) -> dict[str, str]:
        value = raw.strip().split(" #", 1)[0].strip()
        if not (value.startswith("{") and value.endswith("}")):
            return {}
        fields: dict[str, str] = {}
        for part in value[1:-1].split(","):
            key, separator, field_value = part.partition(":")
            if separator:
                fields[key.strip().strip("'\"")] = scalar_value(field_value)
        return fields

    for raw_line in source.splitlines():
        if not raw_line.strip() or raw_line.lstrip().startswith("#"):
            continue
        indent = len(raw_line) - len(raw_line.lstrip())
        key, separator, raw_value = raw_line.strip().partition(":")
        if not separator:
            continue
        if indent == 0:
            section = key.strip("'\"")
            parsed[section] = flow_mapping(raw_value)
        elif section is not None:
            parsed[section][key.strip("'\"")] = scalar_value(raw_value)
    return parsed


def toml_mcp_servers(source: str) -> dict[str, dict[str, object]]:
    """Read MCP server table membership when the host lacks ``tomllib``."""
    servers: dict[str, dict[str, object]] = {}
    table: list[str] = []

    def scalar_value(raw: str) -> str:
        value = raw.strip().split(" #", 1)[0].strip().rstrip(",}").strip()
        return value.strip("'\"").strip()

    def inline_value(raw: str, key: str) -> str | None:
        match = re.search(rf"\b{re.escape(key)}\s*=\s*(\"[^\"]*\"|'[^']*'|[^,}}]+)", raw)
        if match is None:
            return None
        return scalar_value(match.group(1))

    for raw_line in source.splitlines():
        line = raw_line.strip()
        if not line or line.startswith("#"):
            continue
        if line.startswith("[") and line.endswith("]"):
            table = [part.strip().strip("'\"") for part in line[1:-1].split(".")]
            if len(table) >= 2 and table[0] == "mcp_servers":
                servers.setdefault(table[1], {})
            continue
        if table == ["mcp_servers"] and "=" in line:
            key, raw_value = line.split("=", 1)
            server = servers.setdefault(key.strip().strip("'\""), {})
            for field in ("command", "url"):
                value = inline_value(raw_value, field)
                if value is not None:
                    server[field] = value
            continue
        if len(table) >= 2 and table[0] == "mcp_servers" and "=" in line:
            key, raw_value = line.split("=", 1)
Read more
Ships withouroboros

Agent OS: Stop prompting. Start specifying.

Get the whole plugin