From vibe-portrait
Generates developer personality portraits from Claude Code and Codex conversation histories. Supports subcommands: generate/update portrait, install/list/remove/activate GitHub personas.
npx claudepluginhub dadwadw233/vibeportrait --plugin vibe-portraitThis skill uses the workspace's default tool permissions.
Developer personality portrait generator with subcommands.
Synthesizes persistent user identity profiles from behavioral observations across sessions, backing claims with evidence and git shipping metrics. Use for pattern tracking and self-insight.
Curates Agent Skills for persona distillation—extracting communication styles, decision frameworks, interaction patterns from conversations/works/digital traces—into AI agents like Claude Code. Browse categories, install via CLI/git/curl.
Creates or updates timestamped GitHub portfolio Markdown files by fetching and analyzing repository READMEs and structure via /github-portfolio <repo-url>. Use for career documentation snapshots.
Share bugs, ideas, or general feedback.
Developer personality portrait generator with subcommands.
Determine which subcommand the user wants based on their message:
| Trigger | Action |
|---|---|
| "generate my portrait" / "analyze my personality" / just "/vibe-portrait" | → Generate (full analysis, Steps 0-8) |
| "update my portrait" / "update portrait" / "更新我的画像" | → Update (incremental, see Update section) |
| "install persona from <url>" / "安装人格 <url>" | → Install (see Persona Management) |
| "list personas" / "我安装了哪些人格" | → List (see Persona Management) |
| "remove persona <id>" / "删除人格 <id>" | → Remove (see Persona Management) |
| "think like <name>" / "像<name>一样思考" | → Activate (load the persona skill, not handled here — Claude auto-activates from the installed skill) |
If ambiguous, ask the user which action they want.
Before reading any data, ask the user which analysis mode they prefer:
How thorough should the analysis be?
- ⚡ Quick (cost-efficient) — Sample ~200 messages. Fast, low token cost, good enough for most portraits.
- 🔍 Full (comprehensive) — Read ALL messages. Higher token cost, but captures every nuance and evolution of your personality. Best for users with long history who want maximum accuracy.
If the user doesn't respond or says "just do it" / "默认" / "whatever", default to Quick mode.
In Quick mode, follow the sampling strategy described in Step 1c.
In Full mode, read all lines from history.jsonl (and any imported files). Skip the sampling strategy entirely. If the file exceeds 3000 lines, still read in batches (e.g., 500 lines at a time) to avoid tool errors, but process every line.
Always read from ALL available sources regardless of which terminal the user is running. A developer's personality is shaped by all their AI interactions, not just one tool.
Check and read from every source that exists:
| Source | Path | Message field |
|---|---|---|
| Claude Code history | ~/.claude/history.jsonl | display |
| Claude Code projects | ~/.claude/projects/**/*.jsonl | display |
| Codex history | ~/.codex/history.jsonl | text (also has ts unix timestamp) |
| Codex sessions | ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl | filter type=response_item + payload.role=user → iterate payload.content[], take items where text does NOT start with < (skip system-injected <environment_context>, <permissions> etc.) |
Merge all messages from all sources into one pool before sampling. Use ts timestamps (Codex history) or timestamp (Codex sessions) for chronological sorting when available.
Vibe Portrait uses a private GitHub repo to sync persona data across machines. This is the recommended approach for users with multiple development machines.
First-time setup (if no portrait repo exists yet):
After analysis is complete (Step 7), the skill will offer to create a private repo. See Step 7 for details.
If the user already has a portrait repo:
Ask the user for their portrait repo URL, or check if ~/.vibe-portrait-repo exists (a file containing the repo URL, written during first-time setup).
# Check for existing repo config
cat ~/.vibe-portrait-repo 2>/dev/null
If found, clone or pull the repo into a temp directory:
# Clone if not already local
REPO_URL=$(cat ~/.vibe-portrait-repo)
REPO_DIR=~/.cache/vibe-portrait-sync
git clone "$REPO_URL" "$REPO_DIR" 2>/dev/null || (cd "$REPO_DIR" && git pull)
Then load existing analysis files from $REPO_DIR/analysis/*.json. When merging with the current machine's analysis:
analysis/{hostname}-{date}.jsonme/SKILL.md) is always regenerated from the merged resultIf the user provides a .jsonl file directly (fallback):
Read it as a supplementary source. Merge messages from all sources before sampling:
display contentUse a bash command to count total lines first:
wc -l ~/.claude/history.jsonl
Quick mode (default): Sample strategically:
Full mode: Read all lines. For large files (>500 lines), read in batches of 500 lines using offset/limit to avoid tool errors. Process every message.
Parse each line as JSON and extract the display field. Skip lines where display is empty, null, or a slash command (starts with /).
Minimum threshold: If fewer than 20 non-empty messages are found across all sources, inform the user that there isn't enough data for a meaningful portrait. Suggest they try again after more conversations.
Read the analysis framework for detailed signal definitions:
→ references/analysis-framework.md
Analyze across six dimensions: Communication Style, Technical Breadth, Technical Depth, Decision Patterns, Collaboration Style, Work Rhythm. See the reference file for detailed signal definitions.
Language detection (for i18n): if >50% of sampled messages are in Chinese, set meta.lang = "zh". Otherwise "en". This determines the portrait page language.
For each dimension, produce: a score (0-100), a one-line summary, and 2-3 evidence quotes (keep quotes short, anonymize sensitive content).
Read: → references/mbti-mapping.md
Map observed signals to four axes:
Each axis: 0-100 score. Letter determined by which side of 50.
Read: → references/rating-rubric.md
Rate on a six-tier scale (provide BOTH label for Chinese and labelEn for English):
| Tier | label (zh) | labelEn (en) | Emoji |
|---|---|---|---|
| S+ | 夯爆了 | Legendary | 👑 |
| S | 夯 | Elite | 💎 |
| A | 人上人 | Above Average | ⭐ |
| B | NPC | NPC | 🤖 |
| C | 拉 | Below Average | 😅 |
| D | 拉完了 | Inactive | 💀 |
Always include strength highlights AND growth areas, even for top-tier ratings.
Read: → references/famous-matching.md
Match the user across 3 independent dimensions (Technical Spirit / Strategic Mind / Communication Soul), each picking a different historical or contemporary figure. No predefined list — the AI uses its own knowledge. See reference file for rules and anti-bias constraints.
Read the HTML template:
→ templates/portrait.html
The template contains a PORTRAIT_DATA JavaScript object near the top of the <body>. Replace the ENTIRE PORTRAIT_DATA object with the computed data. The structure is:
const PORTRAIT_DATA = {
meta: {
username: "...", // infer from system username or ask
generatedAt: "...", // today's date
messageCount: 0, // actual count of sampled messages
dateRange: "...", // earliest to latest message date
confidence: "...", // "Early Sketch" / "Clear Portrait" / "Deep Portrait"
lang: "..." // "zh" if user's messages are predominantly Chinese, otherwise "en"
},
personality: {
mbtiType: "XXXX",
axes: {
EI: { score: 0, letter: "X", label: "..." },
SN: { score: 0, letter: "X", label: "..." },
TF: { score: 0, letter: "X", label: "..." },
JP: { score: 0, letter: "X", label: "..." }
},
summary: "..."
},
radar: {
technicalDepth: 0,
technicalBreadth: 0,
communication: 0,
decisionSpeed: 0,
collaboration: 0,
creativity: 0
},
rating: {
tier: "...",
label: "...", // Chinese label: 夯爆了/夯/人上人/NPC/拉/拉完了
labelEn: "...", // English label: Legendary/Elite/Above Average/NPC/Below Average/Inactive
emoji: "...",
color: "...",
reason: "...",
strengthHighlights: [],
growthAreas: []
},
famousMatch: {
technical: {
name: "...", emoji: "...",
dimension: "Technical Spirit", dimensionZh: "技术灵魂",
reason: "...", sharedTraits: []
},
strategic: {
name: "...", emoji: "...",
dimension: "Strategic Mind", dimensionZh: "思维内核",
reason: "...", sharedTraits: []
},
communication: {
name: "...", emoji: "...",
dimension: "Communication Soul", dimensionZh: "表达人格",
reason: "...", sharedTraits: []
}
},
communication: {
languages: {},
directnessScore: 0,
avgMessageLength: 0,
topKeywords: [],
questionRatio: 0
},
technical: {
domains: {},
topTools: []
},
workRhythm: {
hourlyActivity: [/* 24 integers, one per hour */],
sessionPattern: "...",
avgSessionLength: "...",
projectCount: 0
},
quotes: [
{ text: "...", context: "..." }
// 3-5 representative quotes
]
};
IMPORTANT:
PORTRAIT_DATA object. Do not modify any other part of the template.Read the persona skill template:
→ references/persona-skill-template.md
Generate a multi-file persona skill. Read the template for the full structure:
→ references/persona-skill-template.md
Create the following files under ~/.claude/skills/vibe-portrait-personas/me/:
me/
├── SKILL.md # Entry point (~80 lines)
├── portrait-meta.json # Timestamps for incremental updates
└── references/
├── thinking-patterns.md # From: technical depth + decision patterns
├── decision-framework.md # From: decision patterns + collaboration style
├── communication-style.md # From: communication style analysis
├── engineering-philosophy.md # From: technical depth + breadth + anti-patterns
└── mindset-markers.md # Abstracted attitudes (NO raw quotes)
portrait-meta.json is critical — it stores lastMessageIndex (line count of history.jsonl at analysis time) and updatedAt timestamp. These enable the update subcommand to do incremental analysis later.
Create directories if they don't exist. me/ is reserved for the user's own persona.
Write the following files to the current working directory:
1. HTML Portrait:
vibe-portrait-YYYY-MM-DD.html
2. Portrait Image (auto-export):
After writing the HTML, attempt to generate a PNG screenshot of the portrait. Try these methods in order:
If Playwright MCP is available (check for playwright_navigate and playwright_screenshot tools):
file:///absolute/path/to/vibe-portrait-YYYY-MM-DD.htmlvibe-portrait-YYYY-MM-DD.pngIf Playwright is not available, try bash:
# Try with npx playwright if installed
npx --yes playwright screenshot --full-page "file://$(pwd)/vibe-portrait-YYYY-MM-DD.html" "vibe-portrait-YYYY-MM-DD.png" 2>/dev/null
If neither works, skip silently. Tell the user they can use the "Export as Image" button on the HTML page instead.
3. Analysis JSON (for sync/merge):
vibe-portrait-analysis-YYYY-MM-DD.json
Contents:
{
"version": "1.0",
"machine": "<hostname from `hostname` command>",
"exportedAt": "YYYY-MM-DD",
"sourceFiles": ["~/.claude/history.jsonl"],
"messageCount": 200,
"portraitData": { /* the full PORTRAIT_DATA object */ }
}
3. Persona skill (also written in Step 5):
~/.claude/skills/vibe-portrait-personas/me/SKILL.md
After writing all output files, ask the user:
Sync to your portrait repo?
- Yes — create a new repo (first time)
- Yes — push to existing repo
- No — keep everything local
gh auth status. If not authenticated, tell user to run ! gh auth login.repo-template/ contents into a temp directory.analysis/, portraits/, and me/ respectively.{{PLACEHOLDER}} markers. Read repo-template/README.md (see the file for the full list of placeholders) and replace them all with the user's analysis data. Key formatting rules:
`item1` `item2`**ML/AI** ██████████████░░░░░░ 30% (20 chars wide, █ filled, ░ empty)> "text" — *context*"Chinese 65% · English 30% · Mixed 5%"git init && git add -A && git commit -m "Initial portrait"gh repo create my-vibe-portrait --private --source=. --pushgh repo view --json url -q '.url' > ~/.vibe-portrait-repo~/.vibe-portrait-repo.~/.cache/vibe-portrait-sync).README.md using the same placeholder-filling process as Option 1.git add -A && git commit -m "Update portrait from $(hostname)" && git pushDo nothing. Files are already written locally.
Tell the user:
~/.claude/skills/vibe-portrait-personas/me/)Based on total non-empty messages found:
confidence: "Early Sketch". Warn that results are approximate.confidence: "Clear Portrait".confidence: "Deep Portrait".This skill produces THREE outputs:
vibe-portrait-YYYY-MM-DD.html)PORTRAIT_DATA JavaScript object~/.claude/skills/vibe-portrait-personas/me/SKILL.md)vibe-portrait-analysis-YYYY-MM-DD.json)gh repo create my-vibe-portrait --private~/.vibe-portrait-repo for future syncsrepo-template/ within this skillAll outputs of VibePortrait (HTML, persona skill, analysis JSON, repo README) are potentially shareable. Apply these rules to every file you write:
[REDACTED]/home/username/, /Users/realname/) → replace with ~/ or <home>/Scan all files being committed for the redaction patterns above. If any slip through, fix before pushing.
templates/portrait.html as the base.references/analysis-framework.md · references/mbti-mapping.md · references/famous-matching.md · references/rating-rubric.md · references/persona-skill-template.md · templates/portrait.html · repo-template/
When the user says "update my portrait" / "更新我的画像":
~/.claude/skills/vibe-portrait-personas/me/portrait-meta.json.
lastMessageIndex from the meta file.~/.claude/history.jsonl.lastMessageIndex+1 to end).totalMessagesAnalyzed, new weight = new message count).updatedAt, lastMessageIndex, totalMessagesAnalyzed).This is much cheaper than a full re-analysis — typically only processes dozens of new messages.
Trigger: "install persona from <url>"
TEMP_DIR=$(mktemp -d)
git clone --depth 1 "<url>" "$TEMP_DIR"
PERSON_ID=$(grep -m1 'personaId' "$TEMP_DIR/me/portrait-meta.json" | sed 's/.*: *"\(.*\)".*/\1/' )
# Fallback: infer from GitHub username
TARGET=~/.claude/skills/vibe-portrait-personas/$PERSON_ID
cp -R "$TEMP_DIR/me" "$TARGET"
rm -rf "$TEMP_DIR"
Confirm: where installed + how to invoke (think like {person-id}).
Trigger: "list personas" / "我安装了哪些人格"
ls ~/.claude/skills/vibe-portrait-personas/
For each directory, read portrait-meta.json and show: name, MBTI type, rating, last updated.
Trigger: "remove persona <id>"
rm -rf ~/.claude/skills/vibe-portrait-personas/<id>
Confirm before deleting. Refuse to delete me/ — tell user to run generate to overwrite instead.
~/.claude/skills/vibe-portrait-personas/
├── me/ ← yours (multi-file, auto-generated)
│ ├── SKILL.md
│ ├── portrait-meta.json
│ └── references/
│ ├── thinking-patterns.md
│ ├── decision-framework.md
│ ├── communication-style.md
│ ├── engineering-philosophy.md
│ └── mindset-markers.md
├── jane-doe/ ← installed from GitHub
│ ├── SKILL.md
│ ├── portrait-meta.json
│ └── references/...
└── zhuge-liang/ ← installed from community
└── ...