Character pack format
The .deskmate file for sharing a character — what is inside, what is deliberately left out, and how import decides what to keep.
A character pack is a single file for sharing a character: a persona, the parts of its configuration that make it itself, a description of its voice, and — when the avatar may be shared — the avatar. Packs are exported and imported in the console’s Characters page. You can also drag a pack onto the console or the character window.
A pack is a plain zip with the extension .deskmate. The reference implementation is src/core/characters/pack.ts.
my-character.deskmate
├── manifest.json
├── character.json
├── persona.md
└── avatar/ (only when the avatar is included)
├── avatar.json
└── mio.vrm The zip reader accepts stored and deflate entries and no zip64; paths pointing outside the archive are refused.
manifest.json
{
"format": "deskmate.character",
"version": 1,
"avatar": "included"
} | Field | Values |
|---|---|
format | Always "deskmate.character". Anything else isn’t a pack. |
version | Integer. A pack newer than the app understands is refused. |
avatar | "none", "builtin" (a reference to the built-in avatar, no files), or "included" (an avatar/ folder). |
character.json
Only these fields are read; anything else is ignored.
{
"name": "Mio",
"language": "en",
"affect": {
"baseline": { "valence": 0.1, "arousal": -0.2 },
"halfLifeMinutes": 10
},
"urge": {
"minGapMinutes": 20,
"quietAfterTalkSeconds": 90,
"chatAfterSilenceMinutes": 25
},
"voice": {
"vendor": "elevenlabs",
"model": "<model id>",
"voice": "<voice id>",
"cloned": false,
"rate": 1,
"pitch": 0,
"volume": 1
}
} name(string) andlanguage(string, such as"en","zh","ja") are required.affect.baselineis where the mood settles at rest, both numbers in[-1, 1].halfLifeMinutesis how fast mood returns to it.urgesets how often the character speaks up unasked. Numbers only.voiceis optional and is described below.rate,pitchandvolumeare optional numbers.
Voice
A voice in a pack is told of by vendor, not by the exporter’s own model service. The pack never contains a service id or an API key.
| Field | Meaning |
|---|---|
vendor | One of cosyvoice, elevenlabs, minimax, volcengine, fish. |
model | The vendor’s model id. |
voice | The vendor’s voice id. |
cloned | true when the voice was made by the exporter in their own account, so it speaks only there. |
cloned is decided at export time by asking the vendor’s voice list: system voices are not cloned; for vendors where that can’t be told, or when the lookup fails, the voice is treated as cloned.
On import, a voice is used only if it isn’t cloned and the importer has a model service from the same vendor. Otherwise the new character has no voice and shows subtitles only, and the importer is told why. rate, pitch and volume are kept only when the voice itself is.
persona.md
The character’s persona, as Markdown. It becomes the character’s instructions to the model. Treat packs from people you don’t know accordingly: before anything is created, the app shows the persona and warns that it will become the character’s instructions, and creates the character only after the user confirms. The same applies to packs dragged onto the character window.
avatar/
Present only when manifest.avatar is "included". It is the character’s avatar/ folder: the model and its avatar.json (format).
Whether an avatar goes in is decided at export:
| Avatar | Result |
|---|---|
| Built-in | Not included; manifest.avatar is "builtin". |
| VRM that allows redistribution | Included. |
| VRM whose terms say no | Never included, whatever the user says; the exporter is told why. |
VRM 0.x with license Other | The terms are behind the author’s link, which a program can’t read. The exporter is asked to confirm they have the right to share it; included only if they do. |
| Live2D | The exporter is asked to confirm they have the right to share it; included only if they do. |
For a VRM, the terms are read from the .vrm file itself on every export. The meta in avatar.json is only a cache and is never trusted.
Cubism Core never goes into a pack. If a pack contains it anyway, import drops it.
What a pack never contains
No API keys, service ids, memories, conversation history, or emotion state. There are tests that search the output for keys and for memory content.
Importing
- Import always creates a new character and never modifies an existing one. If the name is taken,
(2),(3)… is appended. - No avatar in the pack → the built-in avatar. A Live2D avatar is installed even if the importer has no Cubism Core yet, and they’re told to import Core; once they do, the character shows it with no second import.
- A pack whose
manifest.versionis higher than the app supports is refused.
Writing one by hand
Zip the files above, with manifest.json at the root, and rename the result .deskmate. For a character with no avatar, only manifest.json ("avatar": "none"), character.json and persona.md are needed.
zip my-character.deskmate manifest.json character.json persona.md Last updated on
Avatar mapping format
The avatar.json that tells DeskMate how to drive a VRM or Live2D model, with a walkthrough for writing one from scratch.
Helper protocol
The line-based JSON protocol between the core and the two native macOS helpers, frontmost and computer — for anyone porting DeskMate to another platform.