@7ots/cli 0.1.0 → 0.1.3

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 (50) hide show
  1. package/.env.example +17 -1
  2. package/cli/7ots.mjs +136 -36
  3. package/cli/lib/assistant.mjs +337 -0
  4. package/cli/lib/brain.mjs +10 -7
  5. package/cli/lib/i18n.mjs +87 -17
  6. package/cli/lib/install.mjs +29 -1
  7. package/cli/lib/pet-core.mjs +3 -2
  8. package/cli/lib/pet-server.mjs +30 -0
  9. package/cli/lib/prompts.mjs +200 -8
  10. package/cli/lib/terminal.mjs +27 -6
  11. package/cli/pet/electron/main.cjs +79 -28
  12. package/cli/pet/pet.html +55 -6
  13. package/dist/7ots.esm.js +370 -142
  14. package/dist/7ots.esm.js.map +4 -4
  15. package/dist/7ots.iife.js +370 -142
  16. package/dist/7ots.iife.js.map +4 -4
  17. package/llms.txt +199 -7
  18. package/package.json +1 -2
  19. package/server/channels/apuchat.mjs +8 -5
  20. package/server/channels/apumail.mjs +13 -3
  21. package/server/channels/index.mjs +5 -3
  22. package/server/platform/apuchat.mjs +266 -0
  23. package/server/platform/auth.mjs +39 -10
  24. package/server/platform/connect.mjs +347 -0
  25. package/server/platform/db.mjs +55 -0
  26. package/server/platform/look.mjs +20 -0
  27. package/server/platform/orquesta.mjs +499 -0
  28. package/server/platform/routes.mjs +57 -8
  29. package/server/platform/runtime.mjs +75 -15
  30. package/server/platform/store.mjs +130 -9
  31. package/server/server.mjs +14 -3
  32. package/skills/7ots/SKILL.md +1 -1
  33. package/src/avatar/AvatarStage.js +54 -9
  34. package/src/avatar/three.js +62 -0
  35. package/src/character/AvatarEditor.js +174 -50
  36. package/src/character/Character.js +164 -26
  37. package/src/character/motion.js +95 -34
  38. package/src/character/parts.js +43 -12
  39. package/src/character/skin.js +412 -0
  40. package/src/character/styles.js +1429 -0
  41. package/src/character3d/Character3D.js +1693 -0
  42. package/src/character3d/body.js +154 -0
  43. package/src/character3d/fx.js +323 -0
  44. package/src/character3d/index.js +281 -0
  45. package/src/character3d/materials.js +429 -0
  46. package/src/character3d/stage.js +187 -0
  47. package/src/i18n/messages/character.js +18 -42
  48. package/src/i18n/messages/platform.js +135 -0
  49. package/src/identity/schema.js +16 -8
  50. package/src/index.js +1 -0
@@ -0,0 +1,281 @@
1
+ /**
2
+ * 7ots en 3D — API pública.
3
+ *
4
+ * import { createCharacter3D, createCrowd3D } from '/src/character3d/index.js';
5
+ * const ot = createCharacter3D(el, look.character); // misma spec que el 2D
6
+ * ot.gesture('wave'); ot.mood('happy'); ot.speak('Hello!'); ot.lookAtPoint(x, y);
7
+ *
8
+ * Todas las vistas comparten un único renderer/canvas (stage.js). Requiere el importmap de
9
+ * three en la página (ver stage.js).
10
+ */
11
+
12
+ import * as THREE from 'three';
13
+ import { getStage } from './stage.js';
14
+ import { Character3D, MOODS_3D, GESTURES_3D, MORPHS_3D, EFFECTS_3D } from './Character3D.js';
15
+ import { createCharacter } from '../character/Character.js';
16
+ import { backdropCSS, styleSeed } from '../character/styles.js';
17
+
18
+ export { getStage, Character3D, MOODS_3D, GESTURES_3D, MORPHS_3D, EFFECTS_3D };
19
+
20
+ /** ¿Hay WebGL? Sin él (GPU en lista negra, aceleración desactivada) se usa el ot 2D. */
21
+ let glOk = null;
22
+ export function webglAvailable() {
23
+ if (glOk !== null) return glOk;
24
+ try {
25
+ const c = document.createElement('canvas');
26
+ glOk = !!c.getContext('webgl2');
27
+ } catch {
28
+ glOk = false;
29
+ }
30
+ return glOk;
31
+ }
32
+
33
+ /** Plan B 2D con la misma API (createCharacter de Character.js). */
34
+ function fallback2D(el, spec) {
35
+ const c = createCharacter(el, spec);
36
+ c.fallback = true;
37
+ return c;
38
+ }
39
+
40
+ const v3 = new THREE.Vector3();
41
+
42
+ /** Posición en pantalla (cliente) de la cara de un personaje en una vista. */
43
+ function screenOf(view, ch) {
44
+ const r = view.rect || view.el.getBoundingClientRect();
45
+ ch.root.updateMatrixWorld();
46
+ v3.set(0, ch.H * 0.6, 0).applyMatrix4(ch.root.matrixWorld).project(view.camera);
47
+ return { x: r.left + ((v3.x + 1) / 2) * r.width, y: r.top + ((1 - v3.y) / 2) * r.height };
48
+ }
49
+
50
+ /** Altura en píxeles de un personaje (para el LOD del pelo). */
51
+ function pixelHeight(view, ch) {
52
+ const r = view.rect;
53
+ if (!r) return 300;
54
+ const cam = view.camera;
55
+ const d = Math.max(0.1, cam.position.distanceTo(ch.root.getWorldPosition(v3)));
56
+ return ((ch.H * ch.root.scale.y) / d) * (r.height / 2 / Math.tan((cam.fov * Math.PI) / 360));
57
+ }
58
+
59
+ function api(view, ch, extra = {}) {
60
+ return {
61
+ character: ch,
62
+ gesture: (name, o) => ch.gesture(name, o),
63
+ mood: (name, o) => ch.mood(name, o),
64
+ lookAt: (x, y) => ch.lookAt(x, y),
65
+ lookAtPoint: (x, y) => ch.lookAtPoint(x, y),
66
+ speak: (text, o) => ch.speak(text, o),
67
+ lipsync: (analyser, o) => ch.lipsync(analyser, o),
68
+ stopSpeaking: () => ch.stopSpeaking(),
69
+ morph: (name, o) => ch.morph(name, o),
70
+ unmorph: (name) => ch.unmorph(name),
71
+ effect: (name, o) => ch.effect(name, o),
72
+ clearEffects: (name) => ch.clearEffects(name),
73
+ mouth: (v) => ch.mouth(v),
74
+ viseme: (id, open) => ch.viseme(id, open),
75
+ blink: () => ch.blink(),
76
+ get spec() { return ch.spec; },
77
+ get state() { return ch.state; },
78
+ ...extra,
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Una vista con un personaje que se encuadra solo en `el`.
84
+ * @param {HTMLElement} el contenedor (define tamaño y posición en la página)
85
+ * @param {object} spec look.character
86
+ * @param {object} [opts] { material: 'plush'|'vinyl'|'auto', moodFx, quality: 'high'|'low', orbit: true, stage: {...} }
87
+ */
88
+ export function createCharacter3D(el, spec, opts = {}) {
89
+ if (!webglAvailable()) return fallback2D(el, spec);
90
+ const stage = getStage(opts.stage);
91
+ const scene = stage.makeScene();
92
+ const camera = new THREE.PerspectiveCamera(opts.fov || 28, 1, 0.05, 50);
93
+ const ch = new Character3D(spec, { quality: opts.quality || 'high', material: opts.material || 'plush', moodFx: opts.moodFx });
94
+ scene.add(ch.root);
95
+ let aspect = 0;
96
+ const frame = () => {
97
+ const b = ch.bounds;
98
+ const size = b.getSize(new THREE.Vector3());
99
+ const c = b.getCenter(new THREE.Vector3());
100
+ const half = (camera.fov * Math.PI) / 360;
101
+ const fitH = (size.y * 0.5 * 1.12) / Math.tan(half);
102
+ const fitW = (Math.max(size.x, size.z) * 0.5 * 1.2) / (Math.tan(half) * camera.aspect);
103
+ const dist = Math.max(fitH, fitW) + size.z * 0.5;
104
+ camera.position.set(c.x, c.y + dist * 0.12, c.z + dist);
105
+ camera.lookAt(c.x, c.y * 0.96, 0);
106
+ camera.updateProjectionMatrix();
107
+ };
108
+ const view = {
109
+ el,
110
+ scene,
111
+ camera,
112
+ characters: [ch],
113
+ rect: null,
114
+ tick(t, dt) {
115
+ const a = this.rect.width / this.rect.height;
116
+ if (Math.abs(a - aspect) > 1e-3) {
117
+ aspect = a;
118
+ camera.aspect = a;
119
+ frame();
120
+ }
121
+ if (!dragging) ch.yaw *= Math.exp(-dt / 900); // vuelve de frente al soltar
122
+ ch.tick(t, dt);
123
+ ch.lod(pixelHeight(this, ch));
124
+ },
125
+ screenOf: (c) => screenOf(view, c),
126
+ };
127
+ ch.view = view;
128
+ stage.addView(view);
129
+
130
+ // fondo del estilo artístico detrás del lienzo (el render es transparente), como en el 2D
131
+ const bg0 = el.style.background;
132
+ const backdrop = () => {
133
+ if (opts.backdrop === false) return;
134
+ const st = ch.spec.style;
135
+ const url = st && st !== 'none' ? backdropCSS(st, styleSeed(ch.spec)) : '';
136
+ el.style.background = url ? `${url} center bottom / cover no-repeat` : bg0;
137
+ };
138
+ backdrop();
139
+
140
+ // arrastrar para girar
141
+ let dragging = false;
142
+ let lastX = 0;
143
+ const down = (e) => { dragging = true; lastX = e.clientX; el.setPointerCapture?.(e.pointerId); };
144
+ const move = (e) => { if (!dragging) return; ch.yaw += (e.clientX - lastX) * 0.012; lastX = e.clientX; };
145
+ const up = () => { dragging = false; };
146
+ if (opts.orbit !== false) {
147
+ el.addEventListener('pointerdown', down);
148
+ el.addEventListener('pointermove', move);
149
+ el.addEventListener('pointerup', up);
150
+ el.addEventListener('pointercancel', up);
151
+ el.style.touchAction = el.style.touchAction || 'pan-y';
152
+ el.style.cursor = el.style.cursor || 'grab';
153
+ }
154
+
155
+ return api(view, ch, {
156
+ stage,
157
+ update(next, o = {}) {
158
+ if (o.material) ch.opts.material = o.material;
159
+ ch.update(next);
160
+ backdrop();
161
+ aspect = 0;
162
+ },
163
+ destroy() {
164
+ stage.removeView(view);
165
+ el.style.background = bg0;
166
+ el.removeEventListener('pointerdown', down);
167
+ el.removeEventListener('pointermove', move);
168
+ el.removeEventListener('pointerup', up);
169
+ el.removeEventListener('pointercancel', up);
170
+ ch.dispose();
171
+ },
172
+ });
173
+ }
174
+
175
+ /**
176
+ * Muchos personajes en UNA escena y UNA cámara (un render para todo el grupo).
177
+ * @param {HTMLElement} el
178
+ * @param {object} [opts] { material, quality: 'low', fov, stage }
179
+ */
180
+ export function createCrowd3D(el, opts = {}) {
181
+ if (!webglAvailable()) {
182
+ // plan B: rejilla de ots 2D
183
+ el.style.display = 'flex';
184
+ el.style.flexWrap = 'wrap';
185
+ el.style.alignContent = 'center';
186
+ el.style.justifyContent = 'center';
187
+ const list = [];
188
+ return {
189
+ fallback: true,
190
+ characters: list,
191
+ add(spec) {
192
+ const box = document.createElement('div');
193
+ box.style.cssText = 'width:84px;height:84px';
194
+ el.append(box);
195
+ const c = createCharacter(box, spec);
196
+ list.push(c);
197
+ return c;
198
+ },
199
+ clear() { list.forEach((c) => c.destroy()); list.length = 0; el.textContent = ''; },
200
+ lookAtPoint(x, y) { for (const c of list) c.lookAtPoint(x, y); },
201
+ gesture(name, o) { list.forEach((c, i) => setTimeout(() => c.gesture(name, o), i * 25)); },
202
+ mood(name) { for (const c of list) c.mood(name); },
203
+ effect(name, o) { list.forEach((c, i) => setTimeout(() => c.effect(name, o), i * 25)); },
204
+ morph(name, o) { list.forEach((c, i) => setTimeout(() => c.morph(name, o), i * 25)); },
205
+ unmorph(name) { for (const c of list) c.unmorph(name); },
206
+ refit() {},
207
+ destroy() { this.clear(); },
208
+ };
209
+ }
210
+ const stage = getStage(opts.stage);
211
+ const scene = stage.makeScene();
212
+ const camera = new THREE.PerspectiveCamera(opts.fov || 32, 1, 0.1, 100);
213
+ const chars = [];
214
+ let aspect = 0;
215
+ const target = new THREE.Vector3();
216
+ const fit = () => {
217
+ const box = new THREE.Box3();
218
+ for (const c of chars) { c.root.updateMatrixWorld(); box.union(c.bounds.clone().applyMatrix4(c.root.matrixWorld)); }
219
+ if (box.isEmpty()) return;
220
+ const size = box.getSize(new THREE.Vector3());
221
+ box.getCenter(target);
222
+ const half = (camera.fov * Math.PI) / 360;
223
+ const dist = Math.max((size.y * 0.6) / Math.tan(half), (size.x * 0.58) / (Math.tan(half) * camera.aspect)) + size.z * 0.2;
224
+ camera.position.set(target.x, target.y + dist * 0.42, target.z + dist * 0.92);
225
+ camera.lookAt(target.x, target.y * 0.5, target.z + size.z * 0.08);
226
+ camera.updateProjectionMatrix();
227
+ };
228
+ const view = {
229
+ el,
230
+ scene,
231
+ camera,
232
+ characters: chars,
233
+ rect: null,
234
+ tick(t, dt) {
235
+ const a = this.rect.width / this.rect.height;
236
+ if (Math.abs(a - aspect) > 1e-3) {
237
+ aspect = a;
238
+ camera.aspect = a;
239
+ fit();
240
+ }
241
+ for (const c of chars) {
242
+ c.tick(t, dt);
243
+ c.lod(pixelHeight(this, c));
244
+ }
245
+ },
246
+ screenOf: (c) => screenOf(view, c),
247
+ };
248
+ stage.addView(view);
249
+ return {
250
+ stage,
251
+ camera,
252
+ characters: chars,
253
+ /** Añade un personaje en (x, z) del suelo. */
254
+ add(spec, p = {}) {
255
+ const c = new Character3D(spec, { quality: opts.quality || 'low', material: opts.material || 'plush', moodFx: opts.moodFx });
256
+ c.view = view;
257
+ c.root.position.set(p.x || 0, 0, p.z || 0);
258
+ c.root.rotation.y = p.rotY || 0;
259
+ if (p.scale) c.root.scale.setScalar(p.scale);
260
+ scene.add(c.root);
261
+ chars.push(c);
262
+ aspect = 0;
263
+ return api(view, c);
264
+ },
265
+ clear() {
266
+ for (const c of chars) c.dispose();
267
+ chars.length = 0;
268
+ },
269
+ lookAtPoint(x, y) { for (const c of chars) c.lookAtPoint(x, y); },
270
+ gesture(name, o) { chars.forEach((c, i) => setTimeout(() => c.gesture(name, o), i * 25)); },
271
+ mood(name) { for (const c of chars) c.mood(name); },
272
+ effect(name, o) { chars.forEach((c, i) => setTimeout(() => c.effect(name, o), i * 25)); },
273
+ morph(name, o) { chars.forEach((c, i) => setTimeout(() => c.morph(name, o), i * 25)); },
274
+ unmorph(name) { for (const c of chars) c.unmorph(name); },
275
+ refit() { aspect = 0; },
276
+ destroy() {
277
+ stage.removeView(view);
278
+ this.clear();
279
+ },
280
+ };
281
+ }