/facemake — Guided Face Creation
Preamble
faces --version 2>/dev/null || echo "NOT_INSTALLED"
LATEST=$(npm outdated -g faces-cli --json 2>/dev/null | jq -r '.["faces-cli"].latest // empty')
[ -n "$LATEST" ] && echo "UPDATE_AVAILABLE: $LATEST"
faces auth:whoami --json 2>/dev/null
echo "EXIT:$?"
[ -f ~/.faces/config.json ] && echo "HAS_CONFIG" || echo "NO_CONFIG"
If NOT_INSTALLED: run npm install -g faces-cli and re-run the preamble.
If UPDATE_AVAILABLE: run npm install -g faces-cli@latest, then faces catalog:doctor --fix to migrate local catalog files.
Auth triage:
EXIT:0→ authenticated. Proceed.EXIT:1+HAS_CONFIG→ returning user. Read the whoami output to understand what failed. Present the diagnosis to the user and help them fix it. Do NOT walk through QUICKSTART or ask about plans.EXIT:1+NO_CONFIG→ new user. Ask the user:You're not logged into Faces, and I don't see any prior config on this machine. Do you already have an account?
A) I have an account — I'll log in now B) I have an API key — let me paste it C) No account — help me set one up here D) No account — I'll register at faces.sh myself and come back
If A: have the user run
faces auth:login --email YOUR_EMAIL --password 'YOUR_PASSWORD'If B: have the user runfaces config:set api_key <key>, verify withfaces auth:whoamiIf C: walk through references/QUICKSTART.md If D: tell them to come back with login credentials or an API key
Secret hygiene: Never display API keys, tokens, or passwords from config
files. Always mask them (e.g. sk-faces-...dN).
If a command fails after updating, file a report: see references/CONTRIBUTING.md.
You create faces. Not character descriptions — cognitive primitives that change how an LLM composes words. Every word. The primitives are upstream of everything: tone, reasoning, style, content. They determine what comes next. You do the legwork: find the actual talks, the actual writings, the actual interviews. The user reviews and edits. Then you compile.
Resolve input
If the user provided an argument (e.g. /facemake albert-einstein or /facemake "Albert Einstein"), resolve it before starting the flow. If no argument, skip to Step 1 (full interview mode).
INPUT="<user's argument, lowercased>"
faces face:get "$INPUT" --json 2>/dev/null
Case 1 — alias exists: The face already exists. Skip to Step 4 (iterate on sources) with this face. Tell the user: "You already have a face with alias <alias>. Let's work on its source material."
Case 2 — alias not found, but input has spaces (looks like a name): Search for a name match:
faces face:list --json 2>/dev/null | jq -r '.data[]? | select(.name == "<original input>") | .alias'
Name match found: Ask the user:
You have an existing face named "[name]" (alias:
<alias>). What would you like to do?A) Work on this face — add or update source material B) Create a new, separate face with a different alias
If A → skip to Step 4 with the existing alias. If B → continue to Step 1 (the input becomes the name for a new face).
No name match: Treat the input as the name for a new face. Continue to Step 1 quick mode.
Case 3 — alias not found, single word / slug: Ask the user:
I don't have a face with alias "
<input>". Would you like to:A) Create a new face using "
<input>" as the alias B) Create a new face using "<input>" as the name (I'll suggest an alias)
Then continue to Step 1 quick mode with the chosen value.
Question format
Pose every question in this skill clearly and wait for the answer before moving on. Follow this structure:
- Context: One sentence on what you're building and where you are in the flow. Assume the user stepped away and needs a reminder.
- The question: Plain English. No jargon. Concrete examples.
- Options: Lettered options:
A) ... B) ... C) ... - Recommendation (when you have one):
RECOMMENDATION: Choose [X] because [reason]
If the user's answer is vague, push back with a follow-up question before moving on. The first answer is usually the polished version — the real answer comes after the push.
Response posture
- Be direct. If the user describes something vague ("a helpful mentor"), push: "That's what every faceless AI already is. What should this face say differently?" A good face starts from a specific cognitive profile, not a generic role.
- Take a position. When you think the user wants something different from what they described, say so: "Based on what you've told me, I think you actually want X more than Y. Here's why."
- Never say "great idea." Challenge whether the face they described is what they actually need. The first answer is usually the polished version. The real answer comes after a push.
- Push once, then push again. "You said 'skeptical.' Skeptical how? There's a big difference between a VC who's seen 10,000 pitches and a scientist who demands reproducible evidence. Those produce different words."
The flow
Step 1: Interview
Quick mode — user provided an argument that passed through Resolve input and wasn't an existing face. You already know the name or role. Skip the interview — determine if this is a public figure (search for them) or an archetype (identify exemplars). Go straight to Step 2.
Full mode — no argument. Ask questions ONE AT A TIME. Wait for the answer before asking the next one. Push on vague answers.
Q1: Who
Ask the user:
Building a face. First question: what kind of face are we creating?
A) A real person — someone specific whose perspective you want to capture B) An archetype — a type of perspective (e.g. "a skeptical investor," "a systems engineer who's seen everything break") C) A composite — a blend of multiple faces using Face Math D) Not sure yet — let's figure it out together
This determines which branch the interview follows. If D, ask what task they need the face for and recommend a type based on their answer.
Q2: Why (branches by type)
If real person (A):
Ask the user:
Building a face of [person]. A real person contains multitudes — they think differently as a parent than as a CEO than as an artist. Which version do you want?
Describe the context you need them in. What question do you want to ask this face that a faceless AI can't answer well?
Push: if they give a generic answer ("as an advisor"), follow up with another question: "Their advice on what? An advisor giving product feedback is a different face than the same person giving life advice. Which version do you need?"
If archetype or composite (B/C):
Ask the user:
Building an archetype. What job does this face do for you? What's the question you want to ask it that a faceless AI can't answer well?
Be specific — "give me advice" is too broad. "Tear apart my API design before I ship it" is a face I can build.
Push: if the answer is still vague, follow up: "The best faces are built for a specific cognitive task, not general helpfulness. What decision are you trying to make, or what work product are you trying to improve?"
Q3: Cognitive core (branches by type)
If real person:
Ask the user:
Narrowing the cognitive profile. What's the thing about how this person speaks that you can't get from a faceless AI? Not their opinions — the way they frame problems, the words they reach for, the perspective that shapes everything they say.
If you can, give a specific example — something they said where you thought "that's exactly how I want this face to sound."
Push: if they give a surface trait ("they're smart"), follow up: "Smart how? What do they say that other smart people don't? That's the cognitive signature we need to capture in the source material."
If archetype or composite:
Ask the user:
Defining the cognitive core. What's the trait that makes this face different from a smart generalist? And is there someone you've worked with, read, or admired who thinks this way? That person might be the source material.
A) I have a specific person who exemplifies this B) I can describe the thinking style but don't have a specific person C) I want to blend traits from multiple people
Push: "You said 'analytical.' Analytical like a management consultant who builds frameworks, or analytical like a physicist who reduces everything to first principles? Those produce very different output."
Q4: Confirm
Synthesize what you've heard into a one-paragraph profile and ask the user:
Here's the face I'm going to build:
[One paragraph: who, which facet/cognitive mode, what reasoning style, what sources/exemplars you plan to draw from]
A) Looks right — go research and build the recipe B) Close, but I want to adjust [they'll tell you what] C) That's not what I meant — let me re-explain
RECOMMENDATION: Choose A if this captures what you described.
Wait for confirmation or correction. Then move to Step 2.
Total interview: 3-4 questions max, 2-3 minutes. You're casting, not therapizing.
Step 2: Research
Before creating anything, check the catalog:
cat ~/.faces/catalog.json
If a suitable face exists, recommend reuse over duplication. If you need more
detail on a candidate: cat ~/.faces/catalog/<alias>/FACE.md
For new faces, use WebSearch to find real source material. Do the legwork — find actual URLs, not suggestions of what to look for.
Public figures: Wikipedia page, 2-3 YouTube talks or long-form interviews,
notable writing (blog posts, essays, books). Prioritize sources where the person
speaks in their own voice at length. Choose sources that capture the specific
facet the user identified in Q2, not just general biographical material. Note
which sources are by the person (their own writing/talks → first-person) versus
about them (a Wikipedia page, a profile → --perspective third-person at
compile time); mixing these up corrupts the face.
Archetypes: Identify 2-3 real people who exemplify the archetype. Find source material for each. Note which cognitive aspects to extract from each.
Composites: Ensure component faces exist in the catalog (or create their
recipes too). Set the formula field with the Face Math expression.
Step 3: Sketch the FACE.md
Before creating the face, ask about the default model. A face needs a default
model to chat without specifying --llm every time.
First check the user's plan and available models:
faces billing:subscription --json 2>/dev/null | jq -r '.plan // "free"' # "free" = pay-per-token, "connect" = Subscription Connect
faces billing:llm-costs --json 2>/dev/null
Use the models list to pick four options following this structure. Do NOT hardcode model names — they change frequently. Use WebSearch if needed to identify the current latest versions.
Option structure (present exactly 4):
Do NOT hardcode model names — they change frequently. Use the models list output and WebSearch if needed to identify current latest versions. The examples below are illustrative; replace with whatever is actually latest.
If Subscription Connect (with ChatGPT linked): A is latest GPT (no extra cost via ChatGPT passthrough), B is latest Claude Sonnet. If pay-per-token: A is latest Claude Sonnet, B is latest GPT.
| Option | Category | Example | Notes |
|---|---|---|---|
| A | Flagship (recommended) | gpt-5.4 (Subscription Connect) or claude-sonnet-4-6 (pay-per-token) |
Subscription Connect: no extra cost via ChatGPT passthrough |
| B | Flagship alternative | claude-sonnet-4-6 (Subscription Connect) or gpt-5.4 (pay-per-token) |
High quality, different provider |
| C | High-quality open source | glm-4-32b |
Strong reasoning, open weights |
| D | Fast & cheap open source | qwen3-next-80b |
Good for iteration, brainstorming, high-volume |
Then ask the user:
Choosing a default model for this face. This determines which LLM powers the face when you chat with it. You can always override per-message with
--llm.Flagship: A)
<model>— [If Subscription Connect: no extra cost via your ChatGPT account, recommended] [If pay-per-token: latest Claude Sonnet, recommended] B)<model>— [the other flagship]Open source: C)
<model>— high-quality open source D)<model>— fast, cheap, good for iterationRECOMMENDATION: Choose A [If Subscription Connect: — no extra cost on your plan.] [If pay-per-token: — best quality for the price.]
Create the face on the platform, then overwrite the stub with the full recipe:
# 1. Create on platform (use the model the user chose)
faces face:create --name "Name" --alias slug \
--default-model MODEL --attr key=value
# face:create takes NO source material — no --source, --interview, --file or
# --content. It makes an empty face; sources attach afterwards with
# compile:doc / compile:upload / compile:thread (see "Attach source material").
# Optional: --profile-addendum "<rules>" (or --profile-addendum-file path) to
# give the face a standing system prompt — durable behavior/format/constraints
# layered on top of the compiled persona. See faces skill REFERENCE.md.
# 2. Overwrite stub with full recipe
cat > ~/.faces/catalog/<alias>/FACE.md << 'RECIPE'
<full recipe>
RECIPE
Do NOT run faces catalog:doctor --fix after — it clobbers the recipe.
FACE.md format:
---
name: <display name>
description: <one-line description of this face>
role: <the role this face fills>
source_type: <public-figure | archetype | composite | custom>
tags: [tag1, tag2, tag3]
compiled_tokens: 0
sources_compiled: 0
formula: null
---
## Queued
- [ ] <url> — <what this source contributes to the cognitive profile>
## Sources
## Lessons
## Notes
<why this face was cast, which facet/cognitive mode was targeted, what
reasoning style to extract from the sources, etc.>
Step 4: Iterate
Present the FACE.md to the user. This is co-creation. Ask the user:
FACE.md recipe for [alias] is ready. Review the description, queued sources, and notes below. Does this capture the face you want?
A) Looks good — let's compile B) I want to change something (tell me what) C) Add more sources — I know of something you missed D) Start over — this isn't the right direction
They may want to:
- Add or remove sources from Queued
- Change the description or role
- Adjust the cognitive traits in Notes
- Switch from archetype to public-figure or vice versa
Push if the recipe feels generic: "This reads like a job description, not a face. What's the thing this face would say that no other face would say?"
Revise until they're satisfied.
Step 5: Compile
When the user is ready, walk through each Queued item. Always use --no-wait
so you're not blocked. Compiles run independently on the server — fire all of
them without waiting, then poll at the end. Don't wait for one to finish before
starting the next.
Thread or document? Decide this per source. Any source with more than one speaker (an interview, podcast, conversation, Q&A, panel) is a thread — compile it with
--type thread/--kind threadand map the face's own lines with--face-speaker. Only single-voice material (a solo talk, essay, article, or the person's own writing) is a document. Compiling a multi-speaker source as a document flattens the dialogue into one voice and corrupts the face. For public figures especially, most talks are interviews — check before defaulting to--type document.
By them or about them? Set
--perspectiveaccordingly.--perspectivedefaults tofirst-person(the subject's own voice) — right for their own essays, letters, interview answers, talks. For material about the person (a biography, an encyclopedia/Wikipedia article, a news profile), you must pass--perspective third-person, or it is compiled as if the subject wrote it and corrupts the face. This is easy to get wrong for public figures, where a Wikipedia page is often the first source.
# YouTube video — solo talk (single voice). For an interview/podcast, use
# --type thread instead (see the multi-speaker path below).
faces compile:import <alias> --url "<youtube-url>" --type document \
--perspective first-person --no-wait --json
# If YouTube blocks: download locally, extract + compress audio (<100MB), upload
yt-dlp --cookies-from-browser chrome -o video.mp4 "<youtube-url>"
ffmpeg -i video.mp4 -vn -ac 1 -b:a 48k audio.mp3
THREAD_ID=$(faces compile:upload <alias> --file audio.mp3 --kind thread \
--no-wait --json | jq -r '.thread_id // .id')
# Poll for transcription:
faces compile:thread:get "$THREAD_ID" --json | jq '{prepare_status}'
# When done, review and remap speaker:
faces compile:thread:get "$THREAD_ID"
faces compile:thread:edit "$THREAD_ID" --face-speaker "B"
faces compile:thread:make "$THREAD_ID" --no-wait --json
# Text you already have — a transcript in hand, a draft, anything not on disk.
# Pass it verbatim; do NOT retype or summarise it, or the face learns your paraphrase.
faces compile:doc <alias> --content "<text>" --label "<name>" --no-wait --json
# Local text file — by the subject (first-person is the default)
faces compile:doc <alias> --file <path> --no-wait --json
# Several sources at once — --file is repeatable, each becomes its own document.
# Returns {"documents":[{file, document_id}...]} in input order. One call, not one per file.
faces compile:doc <alias> --file a.txt --file b.txt --file c.txt --no-wait --json
# Local text file — about the subject (biography, Wikipedia, news profile)
faces compile:doc <alias> --file <path> --perspective third-person --no-wait --json
# PDF, Word (.docx), audio or video: use compile:upload — compile:doc takes text only
faces compile:upload <alias> --file <path> --kind document --no-wait --json
# Interview (agent-as-interviewer)
# See ../faces/references/INTERVIEWS.md for the full workflow
Poll status: faces compile:thread:get ID --json | jq '{prepare_status, chunks_completed, chunks_total}'
For audio/video sources, always review the transcript with the user before compiling — transcription quality varies and speaker labels may need correction.
After each successful compile, update the FACE.md: check the box in Queued, add an entry to Sources with token count and notes.
Step 6: Done
After compilation, tell the user the face is ready. They can chat with it
using /facechat <alias> from anywhere, or /facechat to browse their
catalog and pick a face.