@7ots/cli 0.1.7 → 0.1.9

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 (57) hide show
  1. package/README.md +1 -1
  2. package/cli/7ots.mjs +28 -4
  3. package/cli/lib/account.mjs +152 -2
  4. package/cli/lib/assistant.mjs +106 -41
  5. package/cli/lib/brain.mjs +13 -2
  6. package/cli/lib/config.mjs +3 -0
  7. package/cli/lib/inferences.mjs +30 -6
  8. package/cli/lib/integrations.mjs +69 -5
  9. package/cli/lib/orquesta.mjs +44 -1
  10. package/cli/lib/pet-server.mjs +344 -11
  11. package/cli/pet/electron/director.cjs +37 -5
  12. package/cli/pet/electron/main.cjs +92 -10
  13. package/cli/pet/pet.html +149 -10
  14. package/cli/pet/settings.html +545 -42
  15. package/dist/7ots.esm.js +216 -137
  16. package/dist/7ots.esm.js.map +4 -4
  17. package/dist/7ots.iife.js +216 -137
  18. package/dist/7ots.iife.js.map +4 -4
  19. package/package.json +1 -1
  20. package/server/channels/agent.mjs +7 -1
  21. package/server/channels/index.mjs +3 -2
  22. package/server/platform/connect.mjs +4 -1
  23. package/server/platform/db.mjs +9 -1
  24. package/server/platform/device.mjs +101 -4
  25. package/server/platform/mods.mjs +222 -0
  26. package/server/platform/orquesta.mjs +78 -14
  27. package/server/platform/profile.mjs +161 -0
  28. package/server/platform/routes.mjs +38 -33
  29. package/server/platform/runtime.mjs +3 -1
  30. package/server/platform/store.mjs +1 -1
  31. package/server/platform/vm.mjs +370 -91
  32. package/server/server.mjs +12 -2
  33. package/src/avatar/three.js +3 -2
  34. package/src/character/AvatarEditor.js +95 -7
  35. package/src/character/Character.js +116 -17
  36. package/src/character/TradingCard.js +226 -0
  37. package/src/character/mods/index.js +4 -0
  38. package/src/character/mods/orc.js +64 -0
  39. package/src/character/mods.js +251 -0
  40. package/src/character/more/acc-head.js +213 -0
  41. package/src/character/more/acc-rest.js +342 -0
  42. package/src/character/more/brows.js +39 -0
  43. package/src/character/more/cheeks.js +78 -0
  44. package/src/character/more/eyes.js +288 -0
  45. package/src/character/more/mouths.js +259 -0
  46. package/src/character/more/patterns.js +120 -0
  47. package/src/character/parts.js +90 -0
  48. package/src/character/skin.js +166 -1
  49. package/src/character/styles.js +1285 -9
  50. package/src/character3d/Character3D.js +148 -35
  51. package/src/character3d/index.js +7 -1
  52. package/src/character3d/model.js +367 -0
  53. package/src/i18n/messages/character.js +34 -22
  54. package/src/i18n/messages/platform.js +15 -0
  55. package/src/identity/random.js +17 -3
  56. package/src/identity/schema.js +10 -0
  57. package/src/index.js +1 -0
@@ -74,7 +74,7 @@ const es = {
74
74
  },
75
75
  },
76
76
  editor: {
77
- kinds: { label: 'Tipo de avatar', character: 'Personaje 2D', plush: 'Personaje 3D', image: 'Tarjeta' },
77
+ kinds: { label: 'Tipo de avatar', character: 'Personaje 2D', plush: 'Personaje 3D', image: 'Carta' },
78
78
  tabs: { styles: 'Estilos', shape: 'Forma', color: 'Color y acabado', eyes: 'Ojos', mouth: 'Boca', face: 'Cara', acc: 'Accesorios' },
79
79
  controls: 'Controles de edición',
80
80
  preview: 'Vista previa del avatar',
@@ -102,15 +102,19 @@ const es = {
102
102
  unavailable: 'Este navegador no puede mostrar el 3D; ves la vista 2D (el widget hará lo mismo).',
103
103
  },
104
104
  image: {
105
- title: 'Retrato',
106
- intro: 'Una imagen fija (PNG, JPG, WebP o SVG). La boca no se anima; al hablar se ilumina el contorno.',
107
- url: 'URL de la imagen',
108
- placeholder: 'https://…/retrato.png',
105
+ title: 'Carta coleccionable',
106
+ intro: 'La carta holográfica de tu ot, con su personaje, nombre, rasgos y rareza. Muévela con el puntero.',
107
+ foil: 'Lámina',
108
+ foils: { auto: 'Según rareza', holo: 'Holo', prism: 'Prisma', cosmos: 'Cosmos', gold: 'Oro', none: 'Sin lámina' },
109
+ url: 'Arte propio (opcional)',
110
+ hint: 'Si pones una imagen (PNG, JPG, WebP o SVG), reemplaza al personaje en la carta y como avatar.',
111
+ placeholder: 'https://…/arte.png',
109
112
  alt: 'Vista previa del retrato',
110
113
  },
111
114
  styles: {
112
- title: 'Estilos completos',
113
- hint: 'Un estilo reemplaza todo el personaje. Después puedes ajustar cada pieza.',
115
+ title: 'Ots al azar sin dueño',
116
+ hint: 'Caras que nadie tiene todavía. Elegir una reemplaza el personaje; después puedes ajustar cada pieza.',
117
+ more: 'Otros',
114
118
  keepBody: 'Conservar mi forma y color al cambiar de estilo',
115
119
  },
116
120
  shape: {
@@ -129,7 +133,7 @@ const es = {
129
133
  custom: 'Personalizado',
130
134
  finish: 'Acabado',
131
135
  outline: 'Contorno',
132
- outlines: { auto: 'Automático', none: 'Sin contorno', thin: 'Fino', bold: 'Grueso' },
136
+ outlines: { auto: 'Automático', none: 'Sin contorno', thin: 'Fino', bold: 'Grueso', tone: 'Tono', sticker: 'Pegatina' },
133
137
  pattern: 'Patrón',
134
138
  patternColor: 'Color del patrón',
135
139
  },
@@ -272,15 +276,19 @@ const en = {
272
276
  unavailable: 'This browser can’t show 3D; you see the 2D preview (the widget will do the same).',
273
277
  },
274
278
  image: {
275
- title: 'Portrait',
276
- intro: 'A still image (PNG, JPG, WebP or SVG). The mouth isn’t animated; the outline lights up while speaking.',
277
- url: 'Image URL',
278
- placeholder: 'https://…/portrait.png',
279
+ title: 'Trading card',
280
+ intro: 'Your ot’s holographic card, with its character, name, traits and rarity. Move your pointer over it.',
281
+ foil: 'Foil',
282
+ foils: { auto: 'By rarity', holo: 'Holo', prism: 'Prism', cosmos: 'Cosmos', gold: 'Gold', none: 'No foil' },
283
+ url: 'Custom art (optional)',
284
+ hint: 'An image (PNG, JPG, WebP or SVG) replaces the character on the card and as the avatar.',
285
+ placeholder: 'https://…/art.png',
279
286
  alt: 'Portrait preview',
280
287
  },
281
288
  styles: {
282
- title: 'Complete styles',
283
- hint: 'A style replaces the whole character. You can then adjust each part.',
289
+ title: 'Random unclaimed ots',
290
+ hint: 'Faces nobody has yet. Picking one replaces the character; you can then adjust each part.',
291
+ more: 'Shuffle',
284
292
  keepBody: 'Keep my shape and color when changing style',
285
293
  },
286
294
  shape: {
@@ -299,7 +307,7 @@ const en = {
299
307
  custom: 'Custom',
300
308
  finish: 'Finish',
301
309
  outline: 'Outline',
302
- outlines: { auto: 'Automatic', none: 'No outline', thin: 'Thin', bold: 'Bold' },
310
+ outlines: { auto: 'Automatic', none: 'No outline', thin: 'Thin', bold: 'Bold', tone: 'Tone', sticker: 'Sticker' },
303
311
  pattern: 'Pattern',
304
312
  patternColor: 'Pattern color',
305
313
  },
@@ -442,15 +450,19 @@ const pt = {
442
450
  unavailable: 'Este navegador não consegue mostrar o 3D; você vê a prévia 2D (o widget fará o mesmo).',
443
451
  },
444
452
  image: {
445
- title: 'Retrato',
446
- intro: 'Uma imagem fixa (PNG, JPG, WebP ou SVG). A boca não é animada; ao falar, o contorno se ilumina.',
447
- url: 'URL da imagem',
448
- placeholder: 'https://…/retrato.png',
453
+ title: 'Carta colecionável',
454
+ intro: 'A carta holográfica do seu ot, com personagem, nome, atributos e raridade. Passe o ponteiro sobre ela.',
455
+ foil: 'Lâmina',
456
+ foils: { auto: 'Pela raridade', holo: 'Holo', prism: 'Prisma', cosmos: 'Cosmos', gold: 'Ouro', none: 'Sem lâmina' },
457
+ url: 'Arte própria (opcional)',
458
+ hint: 'Uma imagem (PNG, JPG, WebP ou SVG) substitui o personagem na carta e como avatar.',
459
+ placeholder: 'https://…/arte.png',
449
460
  alt: 'Prévia do retrato',
450
461
  },
451
462
  styles: {
452
- title: 'Estilos completos',
453
- hint: 'Um estilo substitui o personagem inteiro. Depois você pode ajustar cada peça.',
463
+ title: 'Ots aleatórios sem dono',
464
+ hint: 'Rostos que ninguém tem ainda. Escolher um substitui o personagem; depois você pode ajustar cada peça.',
465
+ more: 'Outros',
454
466
  keepBody: 'Manter minha forma e cor ao trocar de estilo',
455
467
  },
456
468
  shape: {
@@ -469,7 +481,7 @@ const pt = {
469
481
  custom: 'Personalizada',
470
482
  finish: 'Acabamento',
471
483
  outline: 'Contorno',
472
- outlines: { auto: 'Automático', none: 'Sem contorno', thin: 'Fino', bold: 'Grosso' },
484
+ outlines: { auto: 'Automático', none: 'Sem contorno', thin: 'Fino', bold: 'Grosso', tone: 'Tom', sticker: 'Adesivo' },
473
485
  pattern: 'Padrão',
474
486
  patternColor: 'Cor do padrão',
475
487
  },
@@ -74,6 +74,7 @@ const es = {
74
74
  busy: 'Ya hay una tarea en marcha ({id}); espera a que acabe',
75
75
  noAgent: 'Ese proyecto de Orquesta no tiene agente: configura uno en Orquesta ({url})',
76
76
  noProject: 'Ese proyecto de Orquesta no existe o no está compartido con 7ots',
77
+ notLinked: 'Este ot no está conectado a ese proyecto en Orquesta, o no tiene permiso para ejecutar tareas ahí. Conéctalo en el proyecto → Identidad → Ots conectados: {url}',
77
78
  rateLimited: 'Orquesta está recibiendo muchas peticiones; espera un momento',
78
79
  submitFailed: 'Orquesta no aceptó la tarea (error {status})',
79
80
  agentOffline: 'El agente de este proyecto está desconectado: la tarea queda en cola y empezará cuando vuelva',
@@ -101,6 +102,10 @@ const es = {
101
102
  accountLimit: 'Tu cuenta ya tiene {max} servidor(es) de 7ots (su límite)',
102
103
  full: 'No quedan servidores de 7ots disponibles por ahora; inténtalo más tarde',
103
104
  badSize: 'Tamaño de servidor no disponible',
105
+ needsPayment: 'Falta el pago del servidor de 7ots: vuelve a pulsar «Pagar y crear servidor»',
106
+ userNeedsKey: 'Para crear el servidor en tu cuenta de Orquesta, conéctala con una clave oak_ que tenga los permisos projects:write y vms:write (Orquesta → Ajustes → API keys). La conexión por OAuth no puede crear servidores.',
107
+ userScopes: 'Tu cuenta de Orquesta no dejó crear el servidor: vuelve a conectarla con una clave oak_ que tenga los permisos projects:write y vms:write (Orquesta → Ajustes → API keys)',
108
+ subscriptionInUse: 'Elimina antes tus servidores de 7ots; luego puedes cancelar la suscripción',
104
109
  },
105
110
  },
106
111
  };
@@ -173,6 +178,7 @@ const en = {
173
178
  busy: 'A task is already running ({id}); wait for it to finish',
174
179
  noAgent: 'That Orquesta project has no agent: set up an agent in Orquesta ({url})',
175
180
  noProject: 'That Orquesta project does not exist or is not shared with 7ots',
181
+ notLinked: 'This ot is not connected to that Orquesta project, or may not run tasks there. Connect it in the project → Identity → Connected ots: {url}',
176
182
  rateLimited: 'Orquesta is getting too many requests; wait a moment',
177
183
  submitFailed: 'Orquesta did not accept the task (error {status})',
178
184
  agentOffline: "This project's agent is offline: the task is queued and will start when it is back",
@@ -200,6 +206,10 @@ const en = {
200
206
  accountLimit: 'Your account already has {max} 7ots server(s) (its limit)',
201
207
  full: 'No 7ots servers are available right now; try again later',
202
208
  badSize: 'That server size is not available',
209
+ needsPayment: 'The 7ots server has not been paid for yet: click "Pay and create server" again',
210
+ userNeedsKey: 'To create the server in your Orquesta account, connect it with an oak_ key that has the projects:write and vms:write scopes (Orquesta → Settings → API keys). An OAuth connection cannot create servers.',
211
+ userScopes: 'Your Orquesta account did not allow creating the server: reconnect it with an oak_ key that has the projects:write and vms:write scopes (Orquesta → Settings → API keys)',
212
+ subscriptionInUse: 'Delete your 7ots servers first; then you can cancel the subscription',
203
213
  },
204
214
  },
205
215
  };
@@ -272,6 +282,7 @@ const pt = {
272
282
  busy: 'Já há uma tarefa em andamento ({id}); espere ela terminar',
273
283
  noAgent: 'Esse projeto do Orquesta não tem agente: configure um agente no Orquesta ({url})',
274
284
  noProject: 'Esse projeto do Orquesta não existe ou não está compartilhado com o 7ots',
285
+ notLinked: 'Este ot não está conectado a esse projeto no Orquesta, ou não pode executar tarefas lá. Conecte-o no projeto → Identidade → Ots conectados: {url}',
275
286
  rateLimited: 'O Orquesta está recebendo muitas solicitações; espere um momento',
276
287
  submitFailed: 'O Orquesta não aceitou a tarefa (erro {status})',
277
288
  agentOffline: 'O agente deste projeto está desconectado: a tarefa fica na fila e começa quando ele voltar',
@@ -299,6 +310,10 @@ const pt = {
299
310
  accountLimit: 'Sua conta já tem {max} servidor(es) do 7ots (seu limite)',
300
311
  full: 'Não há servidores do 7ots disponíveis agora; tente mais tarde',
301
312
  badSize: 'Esse tamanho de servidor não está disponível',
313
+ needsPayment: 'O servidor da 7ots ainda não foi pago: clique de novo em «Pagar e criar servidor»',
314
+ userNeedsKey: 'Para criar o servidor na sua conta do Orquesta, conecte-a com uma chave oak_ que tenha as permissões projects:write e vms:write (Orquesta → Configurações → API keys). A conexão por OAuth não pode criar servidores.',
315
+ userScopes: 'Sua conta do Orquesta não permitiu criar o servidor: reconecte-a com uma chave oak_ que tenha as permissões projects:write e vms:write (Orquesta → Configurações → API keys)',
316
+ subscriptionInUse: 'Exclua antes seus servidores da 7ots; depois você pode cancelar a assinatura',
302
317
  },
303
318
  },
304
319
  };
@@ -33,13 +33,27 @@ const TEXT = {
33
33
  },
34
34
  };
35
35
 
36
- /** Semilla corta y legible (6 caracteres base36). */
36
+ /**
37
+ * Semilla corta y legible (7 caracteres base36). Las de 7 caracteres sortean entre el catálogo
38
+ * completo de piezas; cualquier otra longitud (las de 6 de antes de 2026-10, las escritas a mano)
39
+ * usa solo el catálogo 1, así que sigue dando exactamente el mismo ot que siempre.
40
+ */
37
41
  export function newSeed() {
38
42
  let s = '';
39
- for (let i = 0; i < 6; i++) s += Math.floor(Math.random() * 36).toString(36);
43
+ for (let i = 0; i < 7; i++) s += Math.floor(Math.random() * 36).toString(36);
40
44
  return s;
41
45
  }
42
46
 
47
+ /** ¿La semilla usa el catálogo 1 (todas salvo las de 7 caracteres, ver newSeed)? */
48
+ export function isLegacySeed(seed) {
49
+ return cleanSeed(seed).length !== 7;
50
+ }
51
+
52
+ /** Personaje de una semilla (el mismo en la web, el CLI, la mascota y el visor 3D). */
53
+ export function seedCharacter(seed) {
54
+ return randomCharacter(seedNumber(seed), { legacy: isLegacySeed(seed) });
55
+ }
56
+
43
57
  /** Normaliza una semilla escrita por una persona: minúsculas, [a-z0-9-], máx. 32. */
44
58
  export function cleanSeed(seed) {
45
59
  return String(seed ?? '').toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 32);
@@ -78,7 +92,7 @@ export function randomIdentity(seed, { lang = 'en', name } = {}) {
78
92
  const L = TEXT[lang] ? lang : 'en';
79
93
  const tx = TEXT[L];
80
94
  const { rnd, pick } = rng(seedNumber(sd));
81
- const character = randomCharacter(seedNumber(sd));
95
+ const character = seedCharacter(sd);
82
96
  const nm = String(name || '').trim().slice(0, 40) || randomName(sd);
83
97
  const traits = [];
84
98
  while (traits.length < 3) {
@@ -66,6 +66,9 @@ export const IDENTITY_DEFAULTS = Object.freeze({
66
66
  * @param {object} input
67
67
  * @param {object} [base] identidad sobre la que se aplica (por defecto, los valores de serie)
68
68
  */
69
+ /** Campos de la ficha de personaje y su largo máximo. */
70
+ const SHEET = { backstory: 3000, style: 1500, quirks: 1500, scenario: 1500, greeting: 600, examples: 4000 };
71
+
69
72
  export function defineIdentity(input = {}, base = IDENTITY_DEFAULTS) {
70
73
  const i = input && typeof input === 'object' ? input : {};
71
74
  const b = base;
@@ -87,6 +90,8 @@ export function defineIdentity(input = {}, base = IDENTITY_DEFAULTS) {
87
90
  tone: str(personality.tone, 120),
88
91
  traits: list(personality.traits, 12).map((t) => str(t, 40)).filter(Boolean),
89
92
  instructions: str(personality.instructions, 4000),
93
+ // ficha de personaje (la misma del pet de escritorio; privada como las instrucciones)
94
+ sheet: Object.fromEntries(Object.entries(SHEET).map(([k, n]) => [k, str(personality.sheet?.[k], n)]).filter(([, v]) => v)),
90
95
  },
91
96
  look: {
92
97
  kind: LOOK_KINDS.includes(look.kind) ? look.kind : look.image ? 'image' : 'character',
@@ -96,6 +101,7 @@ export function defineIdentity(input = {}, base = IDENTITY_DEFAULTS) {
96
101
  accent: color(look.accent) || IDENTITY_DEFAULTS.look.accent,
97
102
  emoji: str(look.emoji, 8),
98
103
  image: url(look.image),
104
+ card: { foil: ['holo', 'prism', 'cosmos', 'gold', 'none'].includes(look.card?.foil) ? look.card.foil : '' },
99
105
  avatar: {
100
106
  url: url(look.avatar?.url),
101
107
  body: look.avatar?.body === 'M' ? 'M' : 'F',
@@ -195,6 +201,10 @@ export function identityPrompt(id, locale = id.language) {
195
201
  id.bio ? t('identity.prompt.about', { bio: id.bio }) : '',
196
202
  p.tone ? t('identity.prompt.tone', { tone: p.tone }) : '',
197
203
  p.traits.length ? t('identity.prompt.traits', { traits: p.traits.join(', ') }) : '',
204
+ p.sheet?.backstory,
205
+ p.sheet?.style,
206
+ p.sheet?.quirks,
207
+ p.sheet?.examples,
198
208
  p.instructions,
199
209
  ]
200
210
  .filter(Boolean)
package/src/index.js CHANGED
@@ -79,6 +79,7 @@ export { createFace, IdentityCard };
79
79
  export {
80
80
  createCharacter, renderCharacter, normalizeCharacter, randomCharacter, applyPreset, DEFAULT_CHARACTER,
81
81
  CHARACTER_MOODS, CHARACTER_GESTURES, CHARACTER_MORPHS, CHARACTER_EFFECTS, CHARACTER_GESTURE_ALIAS, VISEMES, textToVisemes, SHAPES, EYES, BROWS, MOUTHS, CHEEKS, PATTERNS, FINISHES, ACCESSORIES, ACCESSORY_GROUPS, PRESETS, partLabel,
82
+ resolveCharacter, normalizeMods, normalizeMod, sanitizeSvg, modName, MOD_ANCHORS, MAX_MODS,
82
83
  } from './character/Character.js';
83
84
  import { createAvatarEditor, AvatarEditor } from './character/AvatarEditor.js';
84
85
  import { renderCharacter as _renderCharacter, createCharacter as _createCharacter } from './character/Character.js';