# Charming profile — handle, app URLs, and the visitor prompt

Three tools live on the "what visitors and the user see when they open an app URL" surface:

- **`set_handle`** — change the `<handle>` segment in front of every one of the user's friendly app URLs (`https://charm.ing/<handle>/<app-name>`).
- **`rename_app`** — change one app's `<app-name>` slug (the URL after the handle), independent of its title.
- **`set_starter_prompt`** — pre-author the prompt body that visitors send to Claude or ChatGPT when they click "Open in Claude" / "Open in ChatGPT" on the rendered app.

These tools do not delete stored app data. Renames keep the old URL working via an alias, while `set_starter_prompt` carries `destructiveHint: true` because it replaces or clears the current visitor-facing prompt. Confirm with the user before changing any of them — anyone who bookmarked an old URL will keep landing on the right page, but they should still know when its prefix or visitor prompt changes.

## How an app's URL is built

Every claimed app has two independent names plus the user's handle:

- **`displayName`** (the title) — free text, shown in listings and headers. Editing it with `update_app` is purely cosmetic and never changes the URL.
- **`app_name`** (the URL slug) — lowercase, URL-safe, unique per user. This is the second segment of the friendly URL. Auto-assigned from the title at create time; once assigned it stays put unless you call `rename_app`.
- **`handle`** — the first segment of every friendly URL. One per user, set at signup; the user can change it with `set_handle`.

The friendly URL is what to share — `https://charm.ing/<handle>/<app-name>` is typeable and memorable; the `/app/<uuid>` form keeps working forever and 301-redirects to the friendly URL when the user has a handle, so any link already shared stays valid.

These three names drift independently on purpose. Renaming the title does NOT renumber the URL slug, and changing the slug does NOT touch the handle.

## `rename_app` — change one app's URL slug

```
rename_app({ app_id: "<uuid>", app_name: "<new-slug>" })
```

Use this when the user has clearly outgrown the auto-assigned slug — e.g. they renamed the title from "Counter" to "Protein Tracker" and the slug is still `counter`. Confirm first; the old URL keeps working (it redirects to the new one), but anyone who bookmarked the prior slug should be told.

Slug rules: lowercase letters, digits, and hyphens; 2–48 chars; may start with a letter or digit; no leading, trailing, or consecutive hyphens. A few reserved words (`settings`, `new`, `api`, …) are refused.

Re-creating an existing app (calling `create_app` with a `manifestId` that already exists for this user) keeps the current slug — only an explicit `rename_app` changes the URL.

## `set_handle` — change the user's URL prefix

```
set_handle({ handle: "<new-handle>" })
```

Acts on the signed-in user and takes no `app_id`. The old handle keeps redirecting to the new one, so any link already shared stays valid. The user must be signed in — anonymous / render-token / test connections get `unauthenticated`.

Handle rules: lowercase letters, digits, and hyphens; 3–32 chars; must start with a letter; no leading/trailing or consecutive hyphens; some reserved words are refused. Rate-limited to 3 changes per rolling 30 days so that shared links don't churn under their owners.

The HTTP equivalent is the signed-in browser form at `https://charm.ing/account/handle` — there's no public HTTP route for handle changes today; sign-in plus the form (or this MCP tool) is the path.

## `set_starter_prompt` — author the "open this app in a chat" prompt

Every rendered Charming app shows three sibling control-panel actions — **Open in Claude**, **Open in ChatGPT**, and **Copy prompt** — that hand the current app's context back to an assistant. By default the prompt body is generic ("I'm using a Charming app at <url>, help me use it"). For shared or remixable apps you can author a custom prompt that steers the visiting assistant — give it a persona, ground it in the app's purpose, or call out key data the user might want to discuss.

```
set_starter_prompt({ app_id: "<uuid>", starter_prompt: "<1–3 sentences>" })
set_starter_prompt({ app_id: "<uuid>", starter_prompt: null })   // clears
```

- Owner-only; anonymous apps must be claimed first.
- Idempotent (re-setting the same value is a no-op).
- Length cap: 2000 characters.
- When a remixable template has a starter prompt, every fresh remix **snapshots** it at fork time. The remixer owns the snapshot and can re-author it with their own `set_starter_prompt` call later — editing the original template afterwards does not change what visitors of existing remixes see.

### HTTP

```
curl -sS -X PUT https://charm.ing/app/<id>/starter-prompt \
    -H "Authorization: Bearer $USER_TOKEN" \
    -H 'Content-Type: application/json' \
    -d '{"starter_prompt": "You are helping the user track water…"}'
```

Pass `{"starter_prompt": null}` (or `""`) to clear.

## When to use which

| User says | Do this |
| --- | --- |
| "Change my Charming username to X" | `set_handle({ handle })` |
| "Give this app a nicer URL" / "the slug is ugly" | `rename_app({ app_id, app_name })` |
| "Rename this app" (title only) | `update_app({ app_id, module })` with a new `manifest.displayName` |
| "Pre-author what visitors see in chat" | `set_starter_prompt({ app_id, ... })` |
| "Stop pre-authoring the chat prompt" | `set_starter_prompt({ starter_prompt: null })` |

For the full machine-readable contract — every route, error `kind`, and limit — fetch https://charm.ing/.well-known/openapi.json. For the full build manual, see https://usecharming.com/llms-full.txt.
