@7ots/cli 0.1.1 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.env.example +17 -1
  2. package/cli/7ots.mjs +144 -36
  3. package/cli/lib/assistant.mjs +347 -0
  4. package/cli/lib/brain.mjs +10 -7
  5. package/cli/lib/config.mjs +8 -1
  6. package/cli/lib/i18n.mjs +84 -11
  7. package/cli/lib/install.mjs +29 -1
  8. package/cli/lib/pet-core.mjs +3 -2
  9. package/cli/lib/pet-server.mjs +133 -13
  10. package/cli/lib/prompts.mjs +199 -7
  11. package/cli/lib/terminal.mjs +27 -6
  12. package/cli/pet/electron/main.cjs +96 -32
  13. package/cli/pet/pet.html +59 -6
  14. package/cli/pet/settings.html +430 -0
  15. package/dist/7ots.esm.js +370 -142
  16. package/dist/7ots.esm.js.map +4 -4
  17. package/dist/7ots.iife.js +370 -142
  18. package/dist/7ots.iife.js.map +4 -4
  19. package/llms.txt +198 -6
  20. package/package.json +1 -1
  21. package/server/channels/apuchat.mjs +8 -5
  22. package/server/channels/apumail.mjs +13 -3
  23. package/server/channels/index.mjs +5 -3
  24. package/server/platform/apuchat.mjs +266 -0
  25. package/server/platform/auth.mjs +39 -10
  26. package/server/platform/connect.mjs +347 -0
  27. package/server/platform/db.mjs +55 -0
  28. package/server/platform/look.mjs +20 -0
  29. package/server/platform/orquesta.mjs +499 -0
  30. package/server/platform/routes.mjs +57 -8
  31. package/server/platform/runtime.mjs +75 -15
  32. package/server/platform/store.mjs +130 -9
  33. package/server/server.mjs +14 -3
  34. package/src/avatar/AvatarStage.js +54 -9
  35. package/src/avatar/three.js +62 -0
  36. package/src/character/AvatarEditor.js +174 -50
  37. package/src/character/Character.js +164 -26
  38. package/src/character/motion.js +95 -34
  39. package/src/character/parts.js +43 -12
  40. package/src/character/skin.js +412 -0
  41. package/src/character/styles.js +1429 -0
  42. package/src/character3d/Character3D.js +1693 -0
  43. package/src/character3d/body.js +154 -0
  44. package/src/character3d/fx.js +323 -0
  45. package/src/character3d/index.js +281 -0
  46. package/src/character3d/materials.js +429 -0
  47. package/src/character3d/stage.js +187 -0
  48. package/src/i18n/messages/character.js +18 -42
  49. package/src/i18n/messages/platform.js +135 -0
  50. package/src/identity/schema.js +16 -8
  51. package/src/index.js +1 -0
@@ -73,7 +73,7 @@ const es = {
73
73
  },
74
74
  },
75
75
  editor: {
76
- kinds: { label: 'Tipo de avatar', character: 'Personaje 2D', realistic: 'Realista 3D', image: 'Imagen' },
76
+ kinds: { label: 'Tipo de avatar', character: 'Personaje 2D', plush: 'Personaje 3D', image: 'Imagen' },
77
77
  tabs: { styles: 'Estilos', shape: 'Forma', color: 'Color y acabado', eyes: 'Ojos', mouth: 'Boca', face: 'Cara', acc: 'Accesorios' },
78
78
  controls: 'Controles de edición',
79
79
  preview: 'Vista previa del avatar',
@@ -94,19 +94,11 @@ const es = {
94
94
  jsonCopied: 'JSON copiado',
95
95
  copyFailed: 'No se pudo copiar',
96
96
  pngFailed: 'No se pudo generar el PNG',
97
- realistic: {
98
- title: 'Avatar 3D realista',
99
- intro: 'Se anima con TalkingHead: lip-sync, expresiones y gestos. El modelo debe ser un .glb con esqueleto Mixamo, los 52 blendshapes ARKit y los 15 visemas Oculus.',
100
- url: 'URL del modelo (.glb)',
101
- body: 'Cuerpo',
102
- bodyF: 'Femenino',
103
- bodyM: 'Masculino',
104
- samples: 'Modelos de ejemplo',
105
- ownTitle: '¿De dónde saco un avatar propio?',
106
- ownMpfb: '{name} (Blender): los personajes exportados son CC0, aptos para uso comercial. Añade los blendshapes ARKit y los visemas y exporta a GLB.',
107
- ownVroid: '{name} para estilo anime (se exporta a VRM/GLB).',
108
- ownCommercial: 'Servicios comerciales (Avatar SDK / MetaPerson, Avaturn de pago) si necesitas avatares desde una foto. Revisa su licencia antes de usarlos.',
109
- fallback: 'Si el navegador no puede con WebGL, el widget cae al personaje 2D, así que conviene diseñar ambos.',
97
+ plush: {
98
+ material: 'Material',
99
+ materials: { plush: 'Peluche', vinyl: 'Vinilo' },
100
+ hint: 'Arrástralo para girarlo. Se edita con las mismas pestañas que el 2D.',
101
+ unavailable: 'Este navegador no puede mostrar el 3D; ves la vista 2D (el widget hará lo mismo).',
110
102
  },
111
103
  image: {
112
104
  title: 'Retrato',
@@ -250,7 +242,7 @@ const en = {
250
242
  },
251
243
  },
252
244
  editor: {
253
- kinds: { label: 'Avatar type', character: '2D character', realistic: 'Realistic 3D', image: 'Image' },
245
+ kinds: { label: 'Avatar type', character: '2D character', plush: '3D character', image: 'Image' },
254
246
  tabs: { styles: 'Styles', shape: 'Shape', color: 'Color & finish', eyes: 'Eyes', mouth: 'Mouth', face: 'Face', acc: 'Accessories' },
255
247
  controls: 'Editing controls',
256
248
  preview: 'Avatar preview',
@@ -271,19 +263,11 @@ const en = {
271
263
  jsonCopied: 'JSON copied',
272
264
  copyFailed: 'Could not copy',
273
265
  pngFailed: 'Could not create the PNG',
274
- realistic: {
275
- title: 'Realistic 3D avatar',
276
- intro: 'Animated with TalkingHead: lip-sync, expressions and gestures. The model must be a .glb with a Mixamo skeleton, the 52 ARKit blendshapes and the 15 Oculus visemes.',
277
- url: 'Model URL (.glb)',
278
- body: 'Body',
279
- bodyF: 'Female',
280
- bodyM: 'Male',
281
- samples: 'Sample models',
282
- ownTitle: 'Where do I get my own avatar?',
283
- ownMpfb: '{name} (Blender): exported characters are CC0 and fine for commercial use. Add the ARKit blendshapes and the visemes, then export to GLB.',
284
- ownVroid: '{name} for an anime style (exports to VRM/GLB).',
285
- ownCommercial: 'Commercial services (Avatar SDK / MetaPerson, paid Avaturn) if you need avatars from a photo. Check their license before using them.',
286
- fallback: 'If the browser can’t handle WebGL, the widget falls back to the 2D character, so it’s worth designing both.',
266
+ plush: {
267
+ material: 'Material',
268
+ materials: { plush: 'Plush', vinyl: 'Vinyl' },
269
+ hint: 'Drag to turn it. Edit it with the same tabs as the 2D one.',
270
+ unavailable: 'This browser can’t show 3D; you see the 2D preview (the widget will do the same).',
287
271
  },
288
272
  image: {
289
273
  title: 'Portrait',
@@ -427,7 +411,7 @@ const pt = {
427
411
  },
428
412
  },
429
413
  editor: {
430
- kinds: { label: 'Tipo de avatar', character: 'Personagem 2D', realistic: 'Realista 3D', image: 'Imagem' },
414
+ kinds: { label: 'Tipo de avatar', character: 'Personagem 2D', plush: 'Personagem 3D', image: 'Imagem' },
431
415
  tabs: { styles: 'Estilos', shape: 'Forma', color: 'Cor e acabamento', eyes: 'Olhos', mouth: 'Boca', face: 'Rosto', acc: 'Acessórios' },
432
416
  controls: 'Controles de edição',
433
417
  preview: 'Prévia do avatar',
@@ -448,19 +432,11 @@ const pt = {
448
432
  jsonCopied: 'JSON copiado',
449
433
  copyFailed: 'Não foi possível copiar',
450
434
  pngFailed: 'Não foi possível gerar o PNG',
451
- realistic: {
452
- title: 'Avatar 3D realista',
453
- intro: 'Animado com TalkingHead: lip-sync, expressões e gestos. O modelo deve ser um .glb com esqueleto Mixamo, os 52 blendshapes ARKit e os 15 visemas Oculus.',
454
- url: 'URL do modelo (.glb)',
455
- body: 'Corpo',
456
- bodyF: 'Feminino',
457
- bodyM: 'Masculino',
458
- samples: 'Modelos de exemplo',
459
- ownTitle: 'Onde consigo um avatar próprio?',
460
- ownMpfb: '{name} (Blender): os personagens exportados são CC0, liberados para uso comercial. Adicione os blendshapes ARKit e os visemas e exporte para GLB.',
461
- ownVroid: '{name} para estilo anime (exporta para VRM/GLB).',
462
- ownCommercial: 'Serviços comerciais (Avatar SDK / MetaPerson, Avaturn pago) se você precisa de avatares a partir de uma foto. Confira a licença antes de usá-los.',
463
- fallback: 'Se o navegador não suportar WebGL, o widget volta para o personagem 2D, então vale a pena criar os dois.',
435
+ plush: {
436
+ material: 'Material',
437
+ materials: { plush: 'Pelúcia', vinyl: 'Vinil' },
438
+ hint: 'Arraste para girá-lo. Edite com as mesmas abas do 2D.',
439
+ unavailable: 'Este navegador não consegue mostrar o 3D; você vê a prévia 2D (o widget fará o mesmo).',
464
440
  },
465
441
  image: {
466
442
  title: 'Retrato',
@@ -9,11 +9,15 @@ import { addCatalog } from '../index.js';
9
9
  const es = {
10
10
  platform: {
11
11
  api: {
12
+ tooManyAccounts: 'Se crearon demasiadas cuentas desde esta conexión; inténtalo mañana',
13
+ otsDailyLimit: 'Este asistente llegó a su límite de hoy; vuelve mañana',
14
+ accountLinked: 'Ese correo ya está vinculado a otra cuenta de notlogin',
12
15
  login: 'Inicia sesión',
13
16
  notFound: 'Ese ots no existe',
14
17
  paused: 'Este asistente está en pausa',
15
18
  originNotAllowed: 'Este sitio no está autorizado para usar este asistente. Añade su dominio en 7ots.com.',
16
19
  tooManyOts: 'Has llegado al máximo de {max} ots por cuenta',
20
+ lookTaken: 'Ya existe un ot con esa misma cara: cada ot es único. Cámbiale algo (color, ojos, accesorio…)',
17
21
  badEmail: 'Escribe un correo válido',
18
22
  tooManyLinks: 'Ya te enviamos varios enlaces; revisa tu correo o espera un rato',
19
23
  mailNotConfigured: 'El acceso por correo no está disponible ahora mismo',
@@ -38,17 +42,62 @@ const es = {
38
42
  ready: 'plataforma activa · base de datos {db}',
39
43
  magicLink: 'enlace de acceso para {email} (sin buzón configurado): {url}',
40
44
  },
45
+ apuchat: {
46
+ unreachable: 'No se pudo contactar con apuchat; inténtalo en un momento',
47
+ paymentsOff: 'El pago con tarjeta llega pronto. Mientras, puedes conseguir el nombre con USDC en {url}',
48
+ insufficientScope: 'apuchat no dio a 7ots ese permiso. Vuelve a conectar tu cuenta de apuchat y acéptalo',
49
+ notTransferable: 'Esa identidad no se puede pasar a tu cuenta (es de pago o está verificada en otra)',
50
+ hubBusy: 'apuchat está recibiendo muchas peticiones; espera un momento',
51
+ taken: 'Ese nombre ya está cogido en apuchat',
52
+ failed: 'apuchat no lo aceptó (error {status})',
53
+ notLinked: 'Conecta primero tu cuenta de apuchat',
54
+ linkExpired: 'La conexión con tu cuenta de apuchat caducó; vuelve a conectarla',
55
+ tooManyIdentities: 'Ya creaste {max} identidades hoy; inténtalo mañana',
56
+ alreadyFree: 'Este ot ya tiene una identidad gratis',
57
+ hasIdentity: 'Este ot ya tiene una identidad de apuchat; quítala antes de crear otra',
58
+ claimFirst: 'Esta identidad de pago está a nombre de 7ots: conecta tu cuenta de apuchat y reclámala antes, o se perdería',
59
+ badKey: 'Esa clave de identidad no es válida',
60
+ keyRefused: 'apuchat no reconoce esa clave de identidad',
61
+ badCallsign: 'Nombre no válido: minúsculas, números, - o _ (hasta 32)',
62
+ noIdentity: 'Este ot no tiene identidad de apuchat',
63
+ },
64
+ orquesta: {
65
+ unreachable: 'No se pudo contactar con Orquesta; inténtalo en un momento',
66
+ failed: 'Orquesta no lo aceptó (error {status})',
67
+ badKey: 'Esa clave no parece de Orquesta (empieza por oak_)',
68
+ keyRefused: 'Orquesta no acepta esa clave',
69
+ notConnected: 'Orquesta no está conectado. El dueño puede conectarlo en 7ots.com → el ot → Orquesta.',
70
+ reconnect: 'La conexión con Orquesta caducó; vuelve a conectarla en 7ots.com',
71
+ off: 'Las tareas de Orquesta no están activadas para este ot',
72
+ emptyTask: 'La tarea está vacía',
73
+ dailyLimit: 'Este ot ya mandó {max} tareas hoy (su límite diario)',
74
+ busy: 'Ya hay una tarea en marcha ({id}); espera a que acabe',
75
+ noAgent: 'Ese proyecto de Orquesta no tiene agente: configura uno en Orquesta ({url})',
76
+ noProject: 'Ese proyecto de Orquesta no existe o no tienes acceso',
77
+ rateLimited: 'Orquesta está recibiendo muchas peticiones; espera un momento',
78
+ submitFailed: 'Orquesta no aceptó la tarea (error {status})',
79
+ agentOffline: 'El agente de este proyecto está desconectado: la tarea queda en cola y empezará cuando vuelva',
80
+ stillRunning: 'La tarea sigue en marcha (id {id}); consulta luego su estado con task_status',
81
+ noOutput: '(terminó sin texto de salida)',
82
+ unknownTask: 'No hay ninguna tarea con ese id en este ot',
83
+ badAllow: 'No se entiende: {list}. Escribe @handles de apuchat o correos, sin comodines.',
84
+ pickProject: 'Elige un proyecto antes de activar las tareas',
85
+ },
41
86
  },
42
87
  };
43
88
 
44
89
  const en = {
45
90
  platform: {
46
91
  api: {
92
+ tooManyAccounts: 'Too many accounts were created from this connection; try again tomorrow',
93
+ otsDailyLimit: 'This assistant has reached its limit for today; come back tomorrow',
94
+ accountLinked: 'That email is already linked to another notlogin account',
47
95
  login: 'Please sign in',
48
96
  notFound: 'That ots does not exist',
49
97
  paused: 'This assistant is paused',
50
98
  originNotAllowed: 'This site is not allowed to use this assistant. Add its domain on 7ots.com.',
51
99
  tooManyOts: 'You have reached the maximum of {max} ots per account',
100
+ lookTaken: 'An ot with that exact face already exists: every ot is unique. Change something (color, eyes, accessory…)',
52
101
  badEmail: 'Enter a valid email',
53
102
  tooManyLinks: 'We already sent you several links; check your inbox or wait a while',
54
103
  mailNotConfigured: 'Email sign-in is not available right now',
@@ -73,17 +122,62 @@ const en = {
73
122
  ready: 'platform on · database {db}',
74
123
  magicLink: 'sign-in link for {email} (no inbox configured): {url}',
75
124
  },
125
+ apuchat: {
126
+ unreachable: 'Could not reach apuchat; try again in a moment',
127
+ paymentsOff: 'Card payment is coming soon. Meanwhile you can get the handle with USDC at {url}',
128
+ insufficientScope: 'apuchat did not grant 7ots that permission. Connect your apuchat account again and allow it',
129
+ notTransferable: 'That identity cannot be moved to your account (it is paid or verified elsewhere)',
130
+ hubBusy: 'apuchat is getting too many requests; wait a moment',
131
+ taken: 'That name is already taken on apuchat',
132
+ failed: 'apuchat did not accept it (error {status})',
133
+ notLinked: 'Connect your apuchat account first',
134
+ linkExpired: 'The connection to your apuchat account expired; connect it again',
135
+ tooManyIdentities: 'You already created {max} identities today; try again tomorrow',
136
+ alreadyFree: 'This ot already has a free identity',
137
+ hasIdentity: 'This ot already has an apuchat identity; remove it before creating another',
138
+ claimFirst: 'This paid identity is held by 7ots: connect your apuchat account and claim it first, or it would be lost',
139
+ badKey: 'That identity key is not valid',
140
+ keyRefused: 'apuchat does not recognise that identity key',
141
+ badCallsign: 'Invalid name: lowercase letters, digits, - or _ (up to 32)',
142
+ noIdentity: 'This ot has no apuchat identity',
143
+ },
144
+ orquesta: {
145
+ unreachable: 'Could not reach Orquesta; try again in a moment',
146
+ failed: 'Orquesta did not accept it (error {status})',
147
+ badKey: 'That does not look like an Orquesta key (it starts with oak_)',
148
+ keyRefused: 'Orquesta does not accept that key',
149
+ notConnected: 'Orquesta is not connected. The owner can connect it on 7ots.com → the ot → Orquesta.',
150
+ reconnect: 'The Orquesta connection expired; connect it again on 7ots.com',
151
+ off: 'Orquesta tasks are not enabled for this ot',
152
+ emptyTask: 'The task is empty',
153
+ dailyLimit: 'This ot already sent {max} tasks today (its daily limit)',
154
+ busy: 'A task is already running ({id}); wait for it to finish',
155
+ noAgent: 'That Orquesta project has no agent: set up an agent in Orquesta ({url})',
156
+ noProject: 'That Orquesta project does not exist or you have no access to it',
157
+ rateLimited: 'Orquesta is getting too many requests; wait a moment',
158
+ submitFailed: 'Orquesta did not accept the task (error {status})',
159
+ agentOffline: "This project's agent is offline: the task is queued and will start when it is back",
160
+ stillRunning: 'The task is still running (id {id}); check it later with task_status',
161
+ noOutput: '(finished without output text)',
162
+ unknownTask: 'There is no task with that id in this ot',
163
+ badAllow: 'Not understood: {list}. Use apuchat @handles or email addresses, no wildcards.',
164
+ pickProject: 'Pick a project before enabling tasks',
165
+ },
76
166
  },
77
167
  };
78
168
 
79
169
  const pt = {
80
170
  platform: {
81
171
  api: {
172
+ tooManyAccounts: 'Muitas contas foram criadas a partir desta conexão; tente amanhã',
173
+ otsDailyLimit: 'Este assistente atingiu o limite de hoje; volte amanhã',
174
+ accountLinked: 'Esse e-mail já está vinculado a outra conta notlogin',
82
175
  login: 'Faça login',
83
176
  notFound: 'Esse ots não existe',
84
177
  paused: 'Este assistente está pausado',
85
178
  originNotAllowed: 'Este site não está autorizado a usar este assistente. Adicione o domínio em 7ots.com.',
86
179
  tooManyOts: 'Você chegou ao máximo de {max} ots por conta',
180
+ lookTaken: 'Já existe um ot com esse mesmo rosto: cada ot é único. Mude algo (cor, olhos, acessório…)',
87
181
  badEmail: 'Digite um e-mail válido',
88
182
  tooManyLinks: 'Já enviamos vários links; confira seu e-mail ou espere um pouco',
89
183
  mailNotConfigured: 'O acesso por e-mail não está disponível agora',
@@ -108,6 +202,47 @@ const pt = {
108
202
  ready: 'plataforma ativa · banco de dados {db}',
109
203
  magicLink: 'link de acesso para {email} (sem caixa de e-mail configurada): {url}',
110
204
  },
205
+ apuchat: {
206
+ unreachable: 'Não foi possível contatar o apuchat; tente de novo em instantes',
207
+ paymentsOff: 'O pagamento com cartão chega em breve. Enquanto isso, você pode obter o nome com USDC em {url}',
208
+ insufficientScope: 'O apuchat não deu essa permissão ao 7ots. Conecte sua conta do apuchat de novo e aceite',
209
+ notTransferable: 'Essa identidade não pode ser passada para sua conta (é paga ou verificada em outra)',
210
+ hubBusy: 'O apuchat está recebendo muitas solicitações; espere um momento',
211
+ taken: 'Esse nome já está em uso no apuchat',
212
+ failed: 'O apuchat não aceitou (erro {status})',
213
+ notLinked: 'Conecte primeiro sua conta do apuchat',
214
+ linkExpired: 'A conexão com sua conta do apuchat expirou; conecte de novo',
215
+ tooManyIdentities: 'Você já criou {max} identidades hoje; tente amanhã',
216
+ alreadyFree: 'Este ot já tem uma identidade grátis',
217
+ hasIdentity: 'Este ot já tem uma identidade do apuchat; remova-a antes de criar outra',
218
+ claimFirst: 'Esta identidade paga está em nome do 7ots: conecte sua conta do apuchat e reivindique-a antes, ou ela se perderia',
219
+ badKey: 'Essa chave de identidade não é válida',
220
+ keyRefused: 'O apuchat não reconhece essa chave de identidade',
221
+ badCallsign: 'Nome inválido: minúsculas, números, - ou _ (até 32)',
222
+ noIdentity: 'Este ot não tem identidade do apuchat',
223
+ },
224
+ orquesta: {
225
+ unreachable: 'Não foi possível contatar o Orquesta; tente de novo em instantes',
226
+ failed: 'O Orquesta não aceitou (erro {status})',
227
+ badKey: 'Isso não parece uma chave do Orquesta (começa com oak_)',
228
+ keyRefused: 'O Orquesta não aceita essa chave',
229
+ notConnected: 'O Orquesta não está conectado. O dono pode conectá-lo em 7ots.com → o ot → Orquesta.',
230
+ reconnect: 'A conexão com o Orquesta expirou; conecte de novo em 7ots.com',
231
+ off: 'As tarefas do Orquesta não estão ativadas para este ot',
232
+ emptyTask: 'A tarefa está vazia',
233
+ dailyLimit: 'Este ot já enviou {max} tarefas hoje (seu limite diário)',
234
+ busy: 'Já há uma tarefa em andamento ({id}); espere ela terminar',
235
+ noAgent: 'Esse projeto do Orquesta não tem agente: configure um agente no Orquesta ({url})',
236
+ noProject: 'Esse projeto do Orquesta não existe ou você não tem acesso',
237
+ rateLimited: 'O Orquesta está recebendo muitas solicitações; espere um momento',
238
+ submitFailed: 'O Orquesta não aceitou a tarefa (erro {status})',
239
+ agentOffline: 'O agente deste projeto está desconectado: a tarefa fica na fila e começa quando ele voltar',
240
+ stillRunning: 'A tarefa ainda está em andamento (id {id}); consulte depois com task_status',
241
+ noOutput: '(terminou sem texto de saída)',
242
+ unknownTask: 'Não há nenhuma tarefa com esse id neste ot',
243
+ badAllow: 'Não entendi: {list}. Use @handles do apuchat ou e-mails, sem curingas.',
244
+ pickProject: 'Escolha um projeto antes de ativar as tarefas',
245
+ },
111
246
  },
112
247
  };
113
248
 
@@ -6,7 +6,8 @@
6
6
  * {
7
7
  * id, name, role, tagline, seed, bio, language, languages[],
8
8
  * personality: { tone, traits[], instructions }, // instructions: privadas, solo servidor
9
- * look: { kind: character|realistic|image, character: {…personaje 2D, ver src/character},
9
+ * look: { kind: character|plush|image (realistic: heredado, se trata como character),
10
+ * character: {…personaje, ver src/character}, material: plush|vinyl (kind plush),
10
11
  * color, accent, emoji, image, avatar: { url, body, mood, cameraView },
11
12
  * face: { skin, eyes }, meetAvatar, meetScene },
12
13
  * voice: { provider: auto|apuchat|elevenlabs|grok|fish|openai|browser, voiceId, model, style, lang, rate, pitch, browserVoice },
@@ -20,7 +21,11 @@ import { normalizeCharacter } from '../character/Character.js';
20
21
  import { translator } from '../i18n/index.js';
21
22
  import '../i18n/messages/identity.js';
22
23
 
23
- export const LOOK_KINDS = ['character', 'realistic', 'image'];
24
+ /** Tipos de aspecto válidos. 'realistic' (.glb externo) ya no se ofrece: se lee y se muestra como 'character'. */
25
+ export const LOOK_KINDS = ['character', 'plush', 'image', 'realistic'];
26
+ /** Tipos que ofrecen los editores. */
27
+ export const LOOK_KINDS_OFFERED = ['character', 'plush', 'image'];
28
+ export const LOOK_MATERIALS = ['plush', 'vinyl'];
24
29
  export const MEET_AVATARS = ['shibu', 'shino', 'fumiriya', 'victoria', 'vita', 'vivi', 'kuro', 'maya', 'ingrid', 'kenji', 'viktor', 'marcus', 'leo', 'mei', 'claire', 'doctor', 'amira'];
25
30
  export const MEET_SCENES = ['studio', 'beach', 'castle', 'space', 'sunset', 'forest', 'night', 'office', 'neon'];
26
31
  export const VOICE_PROVIDERS = ['auto', 'apuchat', 'elevenlabs', 'grok', 'fish', 'openai', 'browser'];
@@ -40,8 +45,9 @@ export const IDENTITY_DEFAULTS = Object.freeze({
40
45
  languages: [],
41
46
  personality: { tone: 'cercano y claro', traits: [], instructions: '' },
42
47
  look: {
43
- kind: '', // character (2D editable) | realistic (3D .glb) | image (retrato). Vacío = se deduce
44
- character: null, // personaje 2D (null = el de serie)
48
+ kind: '', // character (2D) | plush (el mismo personaje en 3D) | image (retrato). Vacío = se deduce
49
+ character: null, // personaje (null = el de serie); lo usan character y plush
50
+ material: 'plush', // plush (peluche con pelo) | vinyl (juguete liso), solo para kind plush
45
51
  color: '#6d5dfc',
46
52
  accent: '#22d3ee',
47
53
  emoji: '',
@@ -83,8 +89,9 @@ export function defineIdentity(input = {}, base = IDENTITY_DEFAULTS) {
83
89
  instructions: str(personality.instructions, 4000),
84
90
  },
85
91
  look: {
86
- kind: LOOK_KINDS.includes(look.kind) ? look.kind : look.avatar?.url ? 'realistic' : look.image ? 'image' : 'character',
92
+ kind: LOOK_KINDS.includes(look.kind) ? look.kind : look.image ? 'image' : 'character',
87
93
  character: normalizeCharacter(look.character || {}),
94
+ material: LOOK_MATERIALS.includes(look.material) ? look.material : 'plush',
88
95
  color: color(look.color) || IDENTITY_DEFAULTS.look.color,
89
96
  accent: color(look.accent) || IDENTITY_DEFAULTS.look.accent,
90
97
  emoji: str(look.emoji, 8),
@@ -201,10 +208,11 @@ export function identityPrompt(id, locale = id.language) {
201
208
  export function identityToWidgetConfig(id) {
202
209
  const a = id.look.avatar;
203
210
  const kind = id.look.kind || 'character';
204
- // El personaje 2D va siempre: es el avatar en modo 'character' y el respaldo si falla el 3D.
211
+ // El personaje va siempre: es el avatar en 'character' y 'plush' (y el respaldo 2D si falla el 3D).
212
+ // 'realistic' (heredado) se muestra como 'character'.
205
213
  const avatar =
206
- kind === 'realistic'
207
- ? { mode: '3d', ...(a.url ? { url: a.url } : {}), body: a.body, mood: a.mood, cameraView: a.cameraView, character: id.look.character }
214
+ kind === 'plush'
215
+ ? { mode: '3d-plush', character: id.look.character, material: id.look.material || 'plush', mood: a.mood }
208
216
  : kind === 'image' && id.look.image
209
217
  ? { mode: '2d', image: id.look.image, face: id.look.face, mood: a.mood }
210
218
  : { mode: '2d', character: id.look.character, mood: a.mood };
package/src/index.js CHANGED
@@ -65,6 +65,7 @@ export { AuthManager, decodeJwt } from './auth/AuthManager.js';
65
65
  export { McpClient } from './mcp/McpClient.js';
66
66
  export { UIManager } from './ui/UIManager.js';
67
67
  export { AvatarStage, MOODS, GESTURES, MOODS_2D, ALL_GESTURES, GESTURE_3D } from './avatar/AvatarStage.js';
68
+ export { loadCharacter3D } from './avatar/three.js';
68
69
  export { VoiceEngine } from './voice/VoiceEngine.js';
69
70
  export { ProxyLLM } from './llm/ProxyLLM.js';
70
71
  export { ApuchatBridge, createApuchatActions } from './integrations/apuchat.js';