@7ots/cli 0.1.0

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 (97) hide show
  1. package/.env.example +140 -0
  2. package/LICENSE +21 -0
  3. package/README.md +655 -0
  4. package/brand/apple-touch-icon.png +0 -0
  5. package/brand/favicon.svg +4 -0
  6. package/brand/icon-512.png +0 -0
  7. package/brand/mark-mono.svg +3 -0
  8. package/brand/mark.svg +4 -0
  9. package/brand/og-es.png +0 -0
  10. package/brand/og-pt.png +0 -0
  11. package/brand/og.png +0 -0
  12. package/brand/ots.json +11 -0
  13. package/brand/tokens.css +59 -0
  14. package/brand/wordmark.svg +5 -0
  15. package/cli/7ots.mjs +400 -0
  16. package/cli/lib/brain.mjs +143 -0
  17. package/cli/lib/config.mjs +70 -0
  18. package/cli/lib/hooks.mjs +151 -0
  19. package/cli/lib/i18n.mjs +480 -0
  20. package/cli/lib/identity.mjs +51 -0
  21. package/cli/lib/install.mjs +79 -0
  22. package/cli/lib/lines.mjs +73 -0
  23. package/cli/lib/meet.mjs +166 -0
  24. package/cli/lib/open.mjs +19 -0
  25. package/cli/lib/orquesta.mjs +84 -0
  26. package/cli/lib/paths.mjs +49 -0
  27. package/cli/lib/pet-core.mjs +184 -0
  28. package/cli/lib/pet-server.mjs +296 -0
  29. package/cli/lib/prompts.mjs +75 -0
  30. package/cli/lib/terminal.mjs +145 -0
  31. package/cli/lib/ui.mjs +234 -0
  32. package/cli/lib/wizard.mjs +279 -0
  33. package/cli/pet/electron/main.cjs +86 -0
  34. package/cli/pet/pet.html +404 -0
  35. package/dist/7ots.esm.js +627 -0
  36. package/dist/7ots.esm.js.map +7 -0
  37. package/dist/7ots.iife.js +627 -0
  38. package/dist/7ots.iife.js.map +7 -0
  39. package/llms.txt +57 -0
  40. package/package.json +73 -0
  41. package/server/admin.mjs +213 -0
  42. package/server/apuchat-relay.mjs +170 -0
  43. package/server/channels/agent.mjs +136 -0
  44. package/server/channels/apuchat.mjs +364 -0
  45. package/server/channels/apumail.mjs +211 -0
  46. package/server/channels/index.mjs +69 -0
  47. package/server/contact.mjs +151 -0
  48. package/server/demo.mjs +154 -0
  49. package/server/i18n.mjs +54 -0
  50. package/server/identity.mjs +108 -0
  51. package/server/llm.mjs +413 -0
  52. package/server/platform/auth.mjs +290 -0
  53. package/server/platform/crypto.mjs +80 -0
  54. package/server/platform/db.mjs +130 -0
  55. package/server/platform/routes.mjs +388 -0
  56. package/server/platform/runtime.mjs +180 -0
  57. package/server/platform/store.mjs +331 -0
  58. package/server/sdk.mjs +125 -0
  59. package/server/server.mjs +456 -0
  60. package/server/settings.mjs +177 -0
  61. package/server/tts.mjs +186 -0
  62. package/skills/7ots/SKILL.md +59 -0
  63. package/src/actions/ActionRegistry.js +254 -0
  64. package/src/actions/PageTools.js +270 -0
  65. package/src/actions/builtins.js +565 -0
  66. package/src/auth/AuthManager.js +128 -0
  67. package/src/avatar/AvatarStage.js +473 -0
  68. package/src/character/AvatarEditor.js +829 -0
  69. package/src/character/Character.js +1191 -0
  70. package/src/character/motion.js +794 -0
  71. package/src/character/parts.js +559 -0
  72. package/src/context/ContextManager.js +472 -0
  73. package/src/core/AgentBrain.js +341 -0
  74. package/src/core/AgentWidget.js +1059 -0
  75. package/src/core/EventBus.js +61 -0
  76. package/src/core/Proactivity.js +363 -0
  77. package/src/core/storage.js +35 -0
  78. package/src/i18n/index.js +193 -0
  79. package/src/i18n/messages/character.js +543 -0
  80. package/src/i18n/messages/identity.js +141 -0
  81. package/src/i18n/messages/platform.js +114 -0
  82. package/src/i18n/messages/server.js +630 -0
  83. package/src/i18n/messages/widget.js +362 -0
  84. package/src/identity/ContactCard.js +351 -0
  85. package/src/identity/Face.js +148 -0
  86. package/src/identity/random.js +112 -0
  87. package/src/identity/schema.js +262 -0
  88. package/src/index.js +117 -0
  89. package/src/integrations/apuchat.js +212 -0
  90. package/src/integrations/apumail.js +88 -0
  91. package/src/llm/ProxyLLM.js +87 -0
  92. package/src/mcp/McpClient.js +153 -0
  93. package/src/ui/Companion.js +498 -0
  94. package/src/ui/UIManager.js +425 -0
  95. package/src/ui/VirtualPointer.js +746 -0
  96. package/src/ui/markdown.js +94 -0
  97. package/src/voice/VoiceEngine.js +227 -0
@@ -0,0 +1,480 @@
1
+ /**
2
+ * CLI texts. English is the default; es/pt come from SEVENOTS_LANG, the saved config or LANG.
3
+ */
4
+
5
+ import { readJson, homeFile } from './paths.mjs';
6
+
7
+ const M = {
8
+ en: {
9
+ 'help': `7ots — give your agent a face, a voice and a pet.
10
+
11
+ 7ots new [--seed s] [--name n] [--lang en|es|pt] [--global] [--force] create a random identity
12
+ 7ots show [--json|--prompt] show the current identity (project, else global)
13
+ 7ots install [--target html|self|print] [file] put the identity in this project
14
+ 7ots setup wizard: where it lives, brain, voice, pet, hooks
15
+ 7ots pet [--mode desktop|terminal|browser|auto] [--detach] wake up your virtual pet
16
+ 7ots feed | play | sleep | wake | status | say <text> | stop
17
+ 7ots hooks install|remove [--claude] [--shell] [--global]
18
+ 7ots meet --new | "<invite or call URL>" join apuchat meet with this identity
19
+ 7ots prompt [identity|install|pet|meet|all] prompts for coding agents
20
+ 7ots login orquesta connect a getorquesta.com account
21
+
22
+ Identities are reproducible: the same seed gives the same ot everywhere (web, CLI, pet, meet).
23
+ Docs: https://7ots.com/llms.txt`,
24
+ 'new.created': 'New ot created → {file}',
25
+ 'new.kept': 'There is already an identity in {file} (use --force to replace it):',
26
+ 'new.next': 'Next: "7ots install" (website) · "7ots pet" (desktop pet) · "7ots meet --new" (video call) · "7ots setup" (wizard)',
27
+ 'show.none': 'No identity yet. Create one with: 7ots new',
28
+ 'install.html': 'Added the 7ots widget to {file}.',
29
+ 'install.htmlUpdated': 'Updated the 7ots widget in {file}.',
30
+ 'install.noHtml': 'No HTML file found. Paste this before </body>:',
31
+ 'install.endpointLocal': 'The widget talks to a 7ots proxy at /api/agent. Run one next to your site:\n IDENTITY_FILE={file} npx -p 7ots 7ots-server\n(or host it on 7ots.com: {app})',
32
+ 'install.self': 'Identity ready for your own server:\n IDENTITY_FILE={file} npx -p 7ots 7ots-server',
33
+ 'install.platform': 'Host it on 7ots.com with this exact identity: {app}',
34
+ 'pet.food': 'Food',
35
+ 'pet.energy': 'Energy',
36
+ 'pet.fun': 'Fun',
37
+ 'pet.level': 'Level {level} · {xp}/{next} xp',
38
+ 'pet.keys': 'f feed · p play · s sleep/wake · q quit',
39
+ 'pet.running': '{name} is awake at {url}',
40
+ 'pet.already': '{name} is already awake (pid {pid}).',
41
+ 'pet.detached': '{name} now lives in the background. Stop it with: 7ots stop',
42
+ 'pet.notRunning': 'The pet is not running (start it with: 7ots pet). Saved for later.',
43
+ 'pet.stopped': 'Good night, {name}.',
44
+ 'pet.electron': 'Opening the desktop pet (the first time npx downloads Electron, ~100 MB)…',
45
+ 'pet.electronFail': 'Could not open the desktop window ({error}); opening the browser instead.',
46
+ 'pet.chooseMode': 'How should {name} live on your computer?',
47
+ 'pet.status': '{name} · level {level} · food {food} · energy {energy} · fun {fun}{asleep}',
48
+ 'pet.asleep': ' · asleep',
49
+ 'mode.desktop': 'desktop — a little window that walks along the bottom of your screen',
50
+ 'mode.terminal': 'terminal — drawn right here',
51
+ 'mode.browser': 'browser — a tab',
52
+ 'mode.ask': 'ask me every time',
53
+ 'hooks.claude': 'Claude Code hooks → {file}',
54
+ 'hooks.shell': 'Shell hook → {files}',
55
+ 'hooks.shellNone': 'No ~/.bashrc or ~/.zshrc found; source ~/.7ots/shell.sh from your shell.',
56
+ 'hooks.removed': 'Hooks removed.',
57
+ 'hooks.reload': 'Open a new terminal (or "source ~/.bashrc") for the shell hook to start.',
58
+ 'meet.creating': 'Creating a call…',
59
+ 'meet.open': 'Open this link to talk with {name} (it has the keys, keep it to yourself):\n {url}',
60
+ 'meet.badInvite': 'That is not a meet invite. Use the "Channel id / Token / PIN" text or a …/call#c=…&t=… link, or --new.',
61
+ 'meet.joining': '{name} is joining the call as {avatar}…',
62
+ 'meet.noBrain': 'Note: without a brain the ot can only say stock phrases. Run "7ots setup" to give it one.',
63
+ 'login.open': 'Authorize 7ots in your browser:\n {url}',
64
+ 'login.ok': 'Connected to Orquesta ({org}).',
65
+ 'err.unknown': 'Unknown command "{cmd}". Try: 7ots help',
66
+ 'err.noIdentity': 'No identity yet. Create one with "7ots new" or run "7ots setup".',
67
+ 'w.title': '7ots setup — {name}',
68
+ 'w.intro': 'Your ot in 7 short steps. Keys stay on this computer.',
69
+ 'w.lang': 'Language',
70
+ 'w.identity': 'Identity',
71
+ 'w.idKeep': 'keep {name}',
72
+ 'w.idNew': 'new random one',
73
+ 'w.reroll': 'Enter = keep · r = another one · or type a name',
74
+ 'w.home': 'Where does the ot live?',
75
+ 'w.home.local': 'only on this computer',
76
+ 'w.home.7ots': 'hosted on 7ots.com (your account)',
77
+ 'w.home.server': 'my own 7ots server',
78
+ 'w.home.orquesta': 'a getorquesta.com project',
79
+ 'w.home7otsUrl': 'Create or open it at {app}\nthen paste its endpoint (https://7ots.com/api/o/…)',
80
+ 'w.homeServerUrl': 'Your server endpoint (https://your.site/api/agent)',
81
+ 'w.originHint': 'Add http://127.0.0.1:{port} to the ot\'s allowed domains so the pet can use it.',
82
+ 'w.orqLogin': 'Log in to Orquesta now?',
83
+ 'w.orqProject': 'Which project?',
84
+ 'w.orqNone': 'No projects found in Orquesta; staying local.',
85
+ 'w.brain': 'Who thinks for it?',
86
+ 'w.brain.cli': 'your {cli} CLI (a separate instance)',
87
+ 'w.brain.api': 'my own API key (Anthropic or OpenAI-compatible)',
88
+ 'w.brain.7ots': 'its 7ots home ({url})',
89
+ 'w.brain.batuta': 'Orquesta Batuta LLM',
90
+ 'w.brain.lines': 'no AI — built-in phrases',
91
+ 'w.model': 'Model (Enter = default)',
92
+ 'w.provider': 'Provider',
93
+ 'w.baseUrl': 'Base URL (Enter = official API)',
94
+ 'w.key': '{name} (hidden; Enter = skip)',
95
+ 'w.keyEnv': 'Using {name} from your environment.',
96
+ 'w.keySaved': 'Saved in ~/.7ots/keys.json (only you can read it).',
97
+ 'w.voice': 'How does it speak?',
98
+ 'w.voice.browser': 'browser / system voice (free)',
99
+ 'w.voice.apuchat': 'apuchat audio',
100
+ 'w.voice.elevenlabs': 'ElevenLabs',
101
+ 'w.voice.grok': 'Grok (xAI)',
102
+ 'w.voice.fish': 'Fish Audio',
103
+ 'w.voice.none': 'silent (text only)',
104
+ 'w.voiceId': 'Voice id (Enter = default)',
105
+ 'w.mode': 'Pet mode',
106
+ 'w.annoy': 'How chatty? 0 silent · 1 rarely · 2 normal · 3 annoying',
107
+ 'w.hooksClaude': 'Let it watch Claude Code?',
108
+ 'w.hooksClaude.project': 'yes, in this project',
109
+ 'w.hooksClaude.global': 'yes, everywhere',
110
+ 'w.hooksClaude.no': 'no',
111
+ 'w.hooksShell': 'Let it watch your shell commands (bash/zsh)?',
112
+ 'w.saved': 'Saved → {file}',
113
+ 'w.launch': 'Wake up {name} now?',
114
+ 'w.s.identity': 'Identity',
115
+ 'w.s.home': 'Home',
116
+ 'w.s.brain': 'Brain',
117
+ 'w.s.voice': 'Voice',
118
+ 'w.s.pet': 'Pet',
119
+ 'w.s.hooks': 'What it watches',
120
+ 'w.s.done': 'Ready',
121
+ 'w.idCard': 'your ot',
122
+ 'w.idLike': 'Do you like it?',
123
+ 'w.idThis': 'yes, this one',
124
+ 'w.idAgain': 'roll another one',
125
+ 'w.idName': 'same dice, my own name',
126
+ 'w.idNameQ': 'Name',
127
+ 'w.required': 'Required.',
128
+ 'w.h.local': 'private, nothing leaves this machine',
129
+ 'w.h.7ots': 'same ot on your website',
130
+ 'w.h.server': 'your 7ots-server',
131
+ 'w.h.orquesta': 'comments on the project agent',
132
+ 'w.badUrl': 'Must be an https:// URL.',
133
+ 'w.h.cli': 'uses your login, no extra key',
134
+ 'w.h.api': 'any model',
135
+ 'w.h.lines': 'offline and free',
136
+ 'w.voiceFallback': 'No key: using the browser voice.',
137
+ 'w.annoyQ': 'How chatty?',
138
+ 'w.annoy.0': 'silent — only when you call it',
139
+ 'w.annoy.1': 'quiet — now and then',
140
+ 'w.annoy.2': 'normal — comments on what matters',
141
+ 'w.annoy.3': 'annoying — comments on everything',
142
+ 'w.sum.title': 'summary',
143
+ 'w.sum.ot': 'ot',
144
+ 'w.sum.home': 'lives',
145
+ 'w.sum.brain': 'brain',
146
+ 'w.sum.voice': 'voice',
147
+ 'w.sum.pet': 'pet',
148
+ 'w.sum.hooks': 'watches',
149
+ 'w.sum.files': 'files',
150
+ 'w.next': 'Whenever you want: 7ots pet · 7ots install · 7ots meet --new',
151
+ 'w.waking': 'Waking up {name}…',
152
+ 'w.waitingBrowser': 'Waiting for the browser…',
153
+ 'w.loading': 'Loading…',
154
+ 'yes': 'yes',
155
+ 'no': 'no',
156
+ },
157
+ es: {
158
+ 'help': `7ots — dale a tu agente una cara, una voz y una mascota.
159
+
160
+ 7ots new [--seed s] [--name n] [--lang en|es|pt] [--global] [--force] crea una identidad aleatoria
161
+ 7ots show [--json|--prompt] muestra la identidad (la del proyecto o la global)
162
+ 7ots install [--target html|self|print] [archivo] pone la identidad en este proyecto
163
+ 7ots setup wizard: dónde vive, cerebro, voz, mascota, hooks
164
+ 7ots pet [--mode desktop|terminal|browser|auto] [--detach] despierta tu mascota virtual
165
+ 7ots feed | play | sleep | wake | status | say <texto> | stop
166
+ 7ots hooks install|remove [--claude] [--shell] [--global]
167
+ 7ots meet --new | "<invitación o enlace>" entra a apuchat meet con esta identidad
168
+ 7ots prompt [identity|install|pet|meet|all] prompts para agentes de código
169
+ 7ots login orquesta conecta una cuenta de getorquesta.com
170
+
171
+ Las identidades son reproducibles: la misma semilla da el mismo ot en todas partes (web, CLI, mascota, meet).
172
+ Docs: https://7ots.com/llms.txt`,
173
+ 'new.created': 'Nuevo ot creado → {file}',
174
+ 'new.kept': 'Ya hay una identidad en {file} (usa --force para reemplazarla):',
175
+ 'new.next': 'Sigue: «7ots install» (web) · «7ots pet» (mascota) · «7ots meet --new» (videollamada) · «7ots setup» (wizard)',
176
+ 'show.none': 'Aún no hay identidad. Crea una con: 7ots new',
177
+ 'install.html': 'Widget de 7ots añadido a {file}.',
178
+ 'install.htmlUpdated': 'Widget de 7ots actualizado en {file}.',
179
+ 'install.noHtml': 'No encontré un HTML. Pega esto antes de </body>:',
180
+ 'install.endpointLocal': 'El widget habla con un proxy 7ots en /api/agent. Levanta uno junto a tu web:\n IDENTITY_FILE={file} npx -p 7ots 7ots-server\n(o alójalo en 7ots.com: {app})',
181
+ 'install.self': 'Identidad lista para tu servidor:\n IDENTITY_FILE={file} npx -p 7ots 7ots-server',
182
+ 'install.platform': 'Alójalo en 7ots.com con esta misma identidad: {app}',
183
+ 'pet.food': 'Comida',
184
+ 'pet.energy': 'Energía',
185
+ 'pet.fun': 'Diversión',
186
+ 'pet.level': 'Nivel {level} · {xp}/{next} xp',
187
+ 'pet.keys': 'f comida · p jugar · s dormir/despertar · q salir',
188
+ 'pet.running': '{name} está despierto en {url}',
189
+ 'pet.already': '{name} ya está despierto (pid {pid}).',
190
+ 'pet.detached': '{name} vive ahora en segundo plano. Para dormirlo: 7ots stop',
191
+ 'pet.notRunning': 'La mascota no está corriendo (lánzala con: 7ots pet). Lo guardo para después.',
192
+ 'pet.stopped': 'Buenas noches, {name}.',
193
+ 'pet.electron': 'Abriendo la mascota de escritorio (la primera vez npx descarga Electron, ~100 MB)…',
194
+ 'pet.electronFail': 'No pude abrir la ventana de escritorio ({error}); abro el navegador.',
195
+ 'pet.chooseMode': '¿Cómo quieres que viva {name} en tu computador?',
196
+ 'pet.status': '{name} · nivel {level} · comida {food} · energía {energy} · diversión {fun}{asleep}',
197
+ 'pet.asleep': ' · durmiendo',
198
+ 'mode.desktop': 'escritorio — una ventanita que camina por el borde de la pantalla',
199
+ 'mode.terminal': 'terminal — dibujada aquí mismo',
200
+ 'mode.browser': 'navegador — una pestaña',
201
+ 'mode.ask': 'preguntarme cada vez',
202
+ 'hooks.claude': 'Hooks de Claude Code → {file}',
203
+ 'hooks.shell': 'Hook de la shell → {files}',
204
+ 'hooks.shellNone': 'No hay ~/.bashrc ni ~/.zshrc; carga ~/.7ots/shell.sh desde tu shell.',
205
+ 'hooks.removed': 'Hooks quitados.',
206
+ 'hooks.reload': 'Abre una terminal nueva (o «source ~/.bashrc») para activar el hook de la shell.',
207
+ 'meet.creating': 'Creando una llamada…',
208
+ 'meet.open': 'Abre este enlace para hablar con {name} (lleva las claves, no lo compartas):\n {url}',
209
+ 'meet.badInvite': 'Eso no es una invitación de meet. Usa el texto «Channel id / Token / PIN» o un enlace …/call#c=…&t=…, o --new.',
210
+ 'meet.joining': '{name} entra a la llamada como {avatar}…',
211
+ 'meet.noBrain': 'Ojo: sin cerebro el ot solo dice frases de serie. Corre «7ots setup» para darle uno.',
212
+ 'login.open': 'Autoriza 7ots en tu navegador:\n {url}',
213
+ 'login.ok': 'Conectado a Orquesta ({org}).',
214
+ 'err.unknown': 'Comando desconocido «{cmd}». Prueba: 7ots help',
215
+ 'err.noIdentity': 'Aún no hay identidad. Crea una con «7ots new» o corre «7ots setup».',
216
+ 'w.title': 'Configuración de 7ots — {name}',
217
+ 'w.intro': 'Tu ot en 7 pasos cortos. Las claves se quedan en este computador.',
218
+ 'w.lang': 'Idioma',
219
+ 'w.identity': 'Identidad',
220
+ 'w.idKeep': 'mantener a {name}',
221
+ 'w.idNew': 'una nueva al azar',
222
+ 'w.reroll': 'Enter = me quedo · r = otra · o escribe un nombre',
223
+ 'w.home': '¿Dónde vive el ot?',
224
+ 'w.home.local': 'solo en este computador',
225
+ 'w.home.7ots': 'alojado en 7ots.com (tu cuenta)',
226
+ 'w.home.server': 'mi propio servidor 7ots',
227
+ 'w.home.orquesta': 'un proyecto de getorquesta.com',
228
+ 'w.home7otsUrl': 'Créalo o ábrelo en {app}\ny pega su endpoint (https://7ots.com/api/o/…)',
229
+ 'w.homeServerUrl': 'Endpoint de tu servidor (https://tu.sitio/api/agent)',
230
+ 'w.originHint': 'Añade http://127.0.0.1:{port} a los dominios permitidos del ot para que la mascota pueda usarlo.',
231
+ 'w.orqLogin': '¿Entrar a Orquesta ahora?',
232
+ 'w.orqProject': '¿Qué proyecto?',
233
+ 'w.orqNone': 'No encontré proyectos en Orquesta; se queda local.',
234
+ 'w.brain': '¿Quién piensa por él?',
235
+ 'w.brain.cli': 'tu CLI {cli} (una instancia aparte)',
236
+ 'w.brain.api': 'mi propia API key (Anthropic o compatible con OpenAI)',
237
+ 'w.brain.7ots': 'su casa en 7ots ({url})',
238
+ 'w.brain.batuta': 'Batuta LLM de Orquesta',
239
+ 'w.brain.lines': 'sin IA — frases de serie',
240
+ 'w.model': 'Modelo (Enter = el de serie)',
241
+ 'w.provider': 'Proveedor',
242
+ 'w.baseUrl': 'Base URL (Enter = API oficial)',
243
+ 'w.key': '{name} (oculta; Enter = saltar)',
244
+ 'w.keyEnv': 'Uso {name} de tu entorno.',
245
+ 'w.keySaved': 'Guardada en ~/.7ots/keys.json (solo tú puedes leerla).',
246
+ 'w.voice': '¿Cómo habla?',
247
+ 'w.voice.browser': 'voz del navegador / sistema (gratis)',
248
+ 'w.voice.apuchat': 'audio de apuchat',
249
+ 'w.voice.elevenlabs': 'ElevenLabs',
250
+ 'w.voice.grok': 'Grok (xAI)',
251
+ 'w.voice.fish': 'Fish Audio',
252
+ 'w.voice.none': 'en silencio (solo texto)',
253
+ 'w.voiceId': 'Id de voz (Enter = la de serie)',
254
+ 'w.mode': 'Modo de la mascota',
255
+ 'w.annoy': '¿Cuánto habla? 0 nada · 1 poco · 2 normal · 3 molestoso',
256
+ 'w.hooksClaude': '¿Que mire Claude Code?',
257
+ 'w.hooksClaude.project': 'sí, en este proyecto',
258
+ 'w.hooksClaude.global': 'sí, en todos',
259
+ 'w.hooksClaude.no': 'no',
260
+ 'w.hooksShell': '¿Que mire tus comandos de la shell (bash/zsh)?',
261
+ 'w.saved': 'Guardado → {file}',
262
+ 'w.launch': '¿Despierto a {name} ahora?',
263
+ 'w.s.identity': 'Identidad',
264
+ 'w.s.home': 'Casa',
265
+ 'w.s.brain': 'Cerebro',
266
+ 'w.s.voice': 'Voz',
267
+ 'w.s.pet': 'Mascota',
268
+ 'w.s.hooks': 'Qué observa',
269
+ 'w.s.done': 'Listo',
270
+ 'w.idCard': 'tu ot',
271
+ 'w.idLike': '¿Te gusta?',
272
+ 'w.idThis': 'sí, este',
273
+ 'w.idAgain': 'tirar otra vez',
274
+ 'w.idName': 'mismo dado, mi propio nombre',
275
+ 'w.idNameQ': 'Nombre',
276
+ 'w.required': 'Obligatorio.',
277
+ 'w.h.local': 'privado, nada sale de esta máquina',
278
+ 'w.h.7ots': 'el mismo ot en tu web',
279
+ 'w.h.server': 'tu 7ots-server',
280
+ 'w.h.orquesta': 'comenta al agente del proyecto',
281
+ 'w.badUrl': 'Tiene que ser una URL https://.',
282
+ 'w.h.cli': 'usa tu sesión, sin otra clave',
283
+ 'w.h.api': 'cualquier modelo',
284
+ 'w.h.lines': 'sin conexión y gratis',
285
+ 'w.voiceFallback': 'Sin clave: uso la voz del navegador.',
286
+ 'w.annoyQ': '¿Cuánto habla?',
287
+ 'w.annoy.0': 'callado — solo cuando lo llamas',
288
+ 'w.annoy.1': 'tranquilo — de vez en cuando',
289
+ 'w.annoy.2': 'normal — comenta lo importante',
290
+ 'w.annoy.3': 'molestoso — lo comenta todo',
291
+ 'w.sum.title': 'resumen',
292
+ 'w.sum.ot': 'ot',
293
+ 'w.sum.home': 'vive',
294
+ 'w.sum.brain': 'cerebro',
295
+ 'w.sum.voice': 'voz',
296
+ 'w.sum.pet': 'mascota',
297
+ 'w.sum.hooks': 'observa',
298
+ 'w.sum.files': 'archivos',
299
+ 'w.next': 'Cuando quieras: 7ots pet · 7ots install · 7ots meet --new',
300
+ 'w.waking': 'Despertando a {name}…',
301
+ 'w.waitingBrowser': 'Esperando al navegador…',
302
+ 'w.loading': 'Cargando…',
303
+ 'yes': 'sí',
304
+ 'no': 'no',
305
+ },
306
+ pt: {
307
+ 'help': `7ots — dê ao seu agente um rosto, uma voz e um bichinho.
308
+
309
+ 7ots new [--seed s] [--name n] [--lang en|es|pt] [--global] [--force] cria uma identidade aleatória
310
+ 7ots show [--json|--prompt] mostra a identidade (do projeto ou a global)
311
+ 7ots install [--target html|self|print] [arquivo] coloca a identidade neste projeto
312
+ 7ots setup assistente: onde mora, cérebro, voz, bichinho, hooks
313
+ 7ots pet [--mode desktop|terminal|browser|auto] [--detach] acorda seu bichinho virtual
314
+ 7ots feed | play | sleep | wake | status | say <texto> | stop
315
+ 7ots hooks install|remove [--claude] [--shell] [--global]
316
+ 7ots meet --new | "<convite ou link>" entra no apuchat meet com esta identidade
317
+ 7ots prompt [identity|install|pet|meet|all] prompts para agentes de código
318
+ 7ots login orquesta conecta uma conta do getorquesta.com
319
+
320
+ As identidades são reproduzíveis: a mesma semente dá o mesmo ot em todo lugar (web, CLI, bichinho, meet).
321
+ Docs: https://7ots.com/llms.txt`,
322
+ 'new.created': 'Novo ot criado → {file}',
323
+ 'new.kept': 'Já existe uma identidade em {file} (use --force para trocar):',
324
+ 'new.next': 'Próximo: «7ots install» (site) · «7ots pet» (bichinho) · «7ots meet --new» (videochamada) · «7ots setup» (assistente)',
325
+ 'show.none': 'Ainda não há identidade. Crie uma com: 7ots new',
326
+ 'install.html': 'Widget do 7ots adicionado em {file}.',
327
+ 'install.htmlUpdated': 'Widget do 7ots atualizado em {file}.',
328
+ 'install.noHtml': 'Não achei um HTML. Cole isto antes de </body>:',
329
+ 'install.endpointLocal': 'O widget fala com um proxy 7ots em /api/agent. Suba um junto do seu site:\n IDENTITY_FILE={file} npx -p 7ots 7ots-server\n(ou hospede no 7ots.com: {app})',
330
+ 'install.self': 'Identidade pronta para o seu servidor:\n IDENTITY_FILE={file} npx -p 7ots 7ots-server',
331
+ 'install.platform': 'Hospede no 7ots.com com esta mesma identidade: {app}',
332
+ 'pet.food': 'Comida',
333
+ 'pet.energy': 'Energia',
334
+ 'pet.fun': 'Diversão',
335
+ 'pet.level': 'Nível {level} · {xp}/{next} xp',
336
+ 'pet.keys': 'f comida · p brincar · s dormir/acordar · q sair',
337
+ 'pet.running': '{name} está acordado em {url}',
338
+ 'pet.already': '{name} já está acordado (pid {pid}).',
339
+ 'pet.detached': '{name} agora mora em segundo plano. Para parar: 7ots stop',
340
+ 'pet.notRunning': 'O bichinho não está rodando (inicie com: 7ots pet). Guardado para depois.',
341
+ 'pet.stopped': 'Boa noite, {name}.',
342
+ 'pet.electron': 'Abrindo o bichinho de desktop (na primeira vez o npx baixa o Electron, ~100 MB)…',
343
+ 'pet.electronFail': 'Não consegui abrir a janela de desktop ({error}); abrindo o navegador.',
344
+ 'pet.chooseMode': 'Como {name} deve morar no seu computador?',
345
+ 'pet.status': '{name} · nível {level} · comida {food} · energia {energy} · diversão {fun}{asleep}',
346
+ 'pet.asleep': ' · dormindo',
347
+ 'mode.desktop': 'desktop — uma janelinha que anda pela borda da tela',
348
+ 'mode.terminal': 'terminal — desenhado aqui mesmo',
349
+ 'mode.browser': 'navegador — uma aba',
350
+ 'mode.ask': 'perguntar toda vez',
351
+ 'hooks.claude': 'Hooks do Claude Code → {file}',
352
+ 'hooks.shell': 'Hook do shell → {files}',
353
+ 'hooks.shellNone': 'Não há ~/.bashrc nem ~/.zshrc; carregue ~/.7ots/shell.sh no seu shell.',
354
+ 'hooks.removed': 'Hooks removidos.',
355
+ 'hooks.reload': 'Abra um terminal novo (ou «source ~/.bashrc») para ativar o hook do shell.',
356
+ 'meet.creating': 'Criando uma chamada…',
357
+ 'meet.open': 'Abra este link para falar com {name} (tem as chaves, não compartilhe):\n {url}',
358
+ 'meet.badInvite': 'Isso não é um convite do meet. Use o texto «Channel id / Token / PIN» ou um link …/call#c=…&t=…, ou --new.',
359
+ 'meet.joining': '{name} está entrando na chamada como {avatar}…',
360
+ 'meet.noBrain': 'Obs.: sem cérebro o ot só diz frases prontas. Rode «7ots setup» para dar um.',
361
+ 'login.open': 'Autorize o 7ots no navegador:\n {url}',
362
+ 'login.ok': 'Conectado ao Orquesta ({org}).',
363
+ 'err.unknown': 'Comando desconhecido «{cmd}». Tente: 7ots help',
364
+ 'err.noIdentity': 'Ainda não há identidade. Crie uma com «7ots new» ou rode «7ots setup».',
365
+ 'w.title': 'Configuração do 7ots — {name}',
366
+ 'w.intro': 'Seu ot em 7 passos curtos. As chaves ficam neste computador.',
367
+ 'w.lang': 'Idioma',
368
+ 'w.identity': 'Identidade',
369
+ 'w.idKeep': 'manter {name}',
370
+ 'w.idNew': 'uma nova aleatória',
371
+ 'w.reroll': 'Enter = fico com este · r = outro · ou digite um nome',
372
+ 'w.home': 'Onde o ot mora?',
373
+ 'w.home.local': 'só neste computador',
374
+ 'w.home.7ots': 'hospedado no 7ots.com (sua conta)',
375
+ 'w.home.server': 'meu próprio servidor 7ots',
376
+ 'w.home.orquesta': 'um projeto do getorquesta.com',
377
+ 'w.home7otsUrl': 'Crie ou abra em {app}\ne cole o endpoint (https://7ots.com/api/o/…)',
378
+ 'w.homeServerUrl': 'Endpoint do seu servidor (https://seu.site/api/agent)',
379
+ 'w.originHint': 'Adicione http://127.0.0.1:{port} aos domínios permitidos do ot para o bichinho poder usá-lo.',
380
+ 'w.orqLogin': 'Entrar no Orquesta agora?',
381
+ 'w.orqProject': 'Qual projeto?',
382
+ 'w.orqNone': 'Não achei projetos no Orquesta; fica local.',
383
+ 'w.brain': 'Quem pensa por ele?',
384
+ 'w.brain.cli': 'sua CLI {cli} (uma instância separada)',
385
+ 'w.brain.api': 'minha própria API key (Anthropic ou compatível com OpenAI)',
386
+ 'w.brain.7ots': 'a casa dele no 7ots ({url})',
387
+ 'w.brain.batuta': 'Batuta LLM do Orquesta',
388
+ 'w.brain.lines': 'sem IA — frases prontas',
389
+ 'w.model': 'Modelo (Enter = padrão)',
390
+ 'w.provider': 'Provedor',
391
+ 'w.baseUrl': 'Base URL (Enter = API oficial)',
392
+ 'w.key': '{name} (oculta; Enter = pular)',
393
+ 'w.keyEnv': 'Usando {name} do seu ambiente.',
394
+ 'w.keySaved': 'Salva em ~/.7ots/keys.json (só você pode ler).',
395
+ 'w.voice': 'Como ele fala?',
396
+ 'w.voice.browser': 'voz do navegador / sistema (grátis)',
397
+ 'w.voice.apuchat': 'áudio do apuchat',
398
+ 'w.voice.elevenlabs': 'ElevenLabs',
399
+ 'w.voice.grok': 'Grok (xAI)',
400
+ 'w.voice.fish': 'Fish Audio',
401
+ 'w.voice.none': 'em silêncio (só texto)',
402
+ 'w.voiceId': 'Id da voz (Enter = padrão)',
403
+ 'w.mode': 'Modo do bichinho',
404
+ 'w.annoy': 'Quanto fala? 0 nada · 1 pouco · 2 normal · 3 chato',
405
+ 'w.hooksClaude': 'Deixar ele olhar o Claude Code?',
406
+ 'w.hooksClaude.project': 'sim, neste projeto',
407
+ 'w.hooksClaude.global': 'sim, em todos',
408
+ 'w.hooksClaude.no': 'não',
409
+ 'w.hooksShell': 'Deixar ele olhar seus comandos do shell (bash/zsh)?',
410
+ 'w.saved': 'Salvo → {file}',
411
+ 'w.launch': 'Acordar {name} agora?',
412
+ 'w.s.identity': 'Identidade',
413
+ 'w.s.home': 'Casa',
414
+ 'w.s.brain': 'Cérebro',
415
+ 'w.s.voice': 'Voz',
416
+ 'w.s.pet': 'Mascote',
417
+ 'w.s.hooks': 'O que observa',
418
+ 'w.s.done': 'Pronto',
419
+ 'w.idCard': 'seu ot',
420
+ 'w.idLike': 'Gostou?',
421
+ 'w.idThis': 'sim, este',
422
+ 'w.idAgain': 'sortear outro',
423
+ 'w.idName': 'mesmo dado, meu próprio nome',
424
+ 'w.idNameQ': 'Nome',
425
+ 'w.required': 'Obrigatório.',
426
+ 'w.h.local': 'privado, nada sai desta máquina',
427
+ 'w.h.7ots': 'o mesmo ot no seu site',
428
+ 'w.h.server': 'seu 7ots-server',
429
+ 'w.h.orquesta': 'comenta o agente do projeto',
430
+ 'w.badUrl': 'Precisa ser uma URL https://.',
431
+ 'w.h.cli': 'usa seu login, sem outra chave',
432
+ 'w.h.api': 'qualquer modelo',
433
+ 'w.h.lines': 'offline e grátis',
434
+ 'w.voiceFallback': 'Sem chave: uso a voz do navegador.',
435
+ 'w.annoyQ': 'Quanto ele fala?',
436
+ 'w.annoy.0': 'calado — só quando você chama',
437
+ 'w.annoy.1': 'tranquilo — de vez em quando',
438
+ 'w.annoy.2': 'normal — comenta o que importa',
439
+ 'w.annoy.3': 'chato — comenta tudo',
440
+ 'w.sum.title': 'resumo',
441
+ 'w.sum.ot': 'ot',
442
+ 'w.sum.home': 'mora',
443
+ 'w.sum.brain': 'cérebro',
444
+ 'w.sum.voice': 'voz',
445
+ 'w.sum.pet': 'mascote',
446
+ 'w.sum.hooks': 'observa',
447
+ 'w.sum.files': 'arquivos',
448
+ 'w.next': 'Quando quiser: 7ots pet · 7ots install · 7ots meet --new',
449
+ 'w.waking': 'Acordando {name}…',
450
+ 'w.waitingBrowser': 'Esperando o navegador…',
451
+ 'w.loading': 'Carregando…',
452
+ 'yes': 'sim',
453
+ 'no': 'não',
454
+ },
455
+ };
456
+
457
+ let current = null;
458
+
459
+ export function cliLang() {
460
+ if (current) return current;
461
+ const pick = (v) => (['en', 'es', 'pt'].includes(v) ? v : '');
462
+ current =
463
+ pick(process.env.SEVENOTS_LANG) ||
464
+ pick(readJson(homeFile('config.json'), {})?.lang) ||
465
+ pick(String(process.env.LC_ALL || process.env.LC_MESSAGES || process.env.LANG || '').slice(0, 2).toLowerCase()) ||
466
+ 'en';
467
+ return current;
468
+ }
469
+
470
+ export function setCliLang(l) {
471
+ if (M[l]) current = l;
472
+ }
473
+
474
+ /** t(key, vars) or t(key, lang, vars). */
475
+ export function t(key, a, b) {
476
+ const lang = typeof a === 'string' ? a : cliLang();
477
+ const vars = (typeof a === 'string' ? b : a) || {};
478
+ const s = (M[lang] || M.en)[key] ?? M.en[key] ?? key;
479
+ return s.replace(/\{(\w+)\}/g, (_, k) => String(vars[k] ?? ''));
480
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The identity the CLI uses: the project's (./.7ots/identity.json) or else the global one
3
+ * (~/.7ots/identity.json). Both keep the full identity and its seed, so it can always be
4
+ * regenerated with `7ots new --seed <seed>`.
5
+ */
6
+
7
+ import { defineIdentity, identityPrompt } from '../../src/identity/schema.js';
8
+ import { randomIdentity, newSeed, cleanSeed } from '../../src/identity/random.js';
9
+ import { existsSync, homeFile, projectFile, readJson, writeJson } from './paths.mjs';
10
+ import { cliLang } from './i18n.mjs';
11
+
12
+ export { identityPrompt, randomIdentity, newSeed, cleanSeed };
13
+
14
+ export function identityPath({ global = false, cwd } = {}) {
15
+ return global ? homeFile('identity.json') : projectFile('identity.json', cwd);
16
+ }
17
+
18
+ /** The current identity and where it comes from: project → global → null. */
19
+ export function loadIdentity({ cwd } = {}) {
20
+ for (const file of [identityPath({ cwd }), identityPath({ global: true })]) {
21
+ if (!existsSync(file)) continue;
22
+ const raw = readJson(file);
23
+ if (raw) return { identity: defineIdentity(raw), file };
24
+ }
25
+ return { identity: null, file: null };
26
+ }
27
+
28
+ /** Creates (or recreates) a random identity and saves it. */
29
+ export function createIdentity({ seed, name, lang, global = false, force = false, cwd } = {}) {
30
+ const file = identityPath({ global, cwd });
31
+ const existed = existsSync(file);
32
+ if (existed && !force) return { identity: defineIdentity(readJson(file) || {}), file, existed, kept: true };
33
+ const identity = randomIdentity(cleanSeed(seed) || newSeed(), { lang: lang || guessLang(), name });
34
+ writeJson(file, identity);
35
+ return { identity, file, existed, kept: false };
36
+ }
37
+
38
+ /** en | es | pt from SEVENOTS_LANG / config / LANG (es_CL.UTF-8 → es). English by default. */
39
+ export const guessLang = cliLang;
40
+
41
+ /** Short card for the terminal. */
42
+ export function describe(id, color = (s) => s) {
43
+ const p = id.personality || {};
44
+ return [
45
+ `${color(id.name)} — ${id.role}`,
46
+ ` ${id.tagline}`,
47
+ ` ${p.tone}${p.traits?.length ? ` · ${p.traits.join(', ')}` : ''}`,
48
+ ` voice ${id.voice.lang} · meet ${id.look.meetAvatar}${id.look.meetScene ? ` @ ${id.look.meetScene}` : ''}`,
49
+ ` seed ${id.seed || '—'}`,
50
+ ].join('\n');
51
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * `7ots install`: puts the current identity into the project.
3
+ *
4
+ * html → finds index.html (., public/, src/, static/) and inserts the widget before </body>,
5
+ * between <!-- 7ots --> markers (re-running replaces the block)
6
+ * self → leaves the identity ready for your own 7ots server (absolute IDENTITY_FILE)
7
+ * print → only prints the snippet
8
+ *
9
+ * Where the widget talks to depends on the configured home: an ot on 7ots.com uses its embed.js,
10
+ * your own server uses its endpoint, otherwise a local proxy at /api/agent.
11
+ */
12
+
13
+ import { readFileSync, writeFileSync } from 'node:fs';
14
+ import { join, resolve, relative } from 'node:path';
15
+ import { existsSync } from './paths.mjs';
16
+ import { loadConfig } from './config.mjs';
17
+
18
+ const CDN = 'https://cdn.jsdelivr.net/npm/7ots/dist/7ots.iife.js';
19
+ const START = '<!-- 7ots -->';
20
+ const END = '<!-- /7ots -->';
21
+
22
+ export const platformLink = (seed) => `https://7ots.com/app/#new${seed ? `?seed=${encodeURIComponent(seed)}` : ''}`;
23
+
24
+ export function findHtml(cwd = process.cwd()) {
25
+ for (const dir of ['.', 'public', 'src', 'static', 'www', 'site']) {
26
+ const f = join(cwd, dir, 'index.html');
27
+ if (existsSync(f)) return f;
28
+ }
29
+ return null;
30
+ }
31
+
32
+ /** The HTML block for this identity and home. */
33
+ export function snippet(identity, { home = loadConfig().home, identityUrl } = {}) {
34
+ if (home?.kind === '7ots' && /\/api\/o\/[\w-]+\/?$/.test(home.url || '')) {
35
+ return `${START}\n<script src="${home.url.replace(/\/$/, '')}/embed.js" defer></script>\n${END}`;
36
+ }
37
+ const endpoint = home?.kind === 'server' && home.url ? home.url.replace(/\/$/, '') : '/api/agent';
38
+ // Own server or local proxy: they serve the identity at <endpoint>/identity, so `identity: true` is enough.
39
+ // Without a proxy that knows it, the identity goes inline (it's public data: name, look, voice).
40
+ const id = identityUrl ? JSON.stringify(identityUrl) : endpoint === '/api/agent' ? 'true' : JSON.stringify(inlineIdentity(identity));
41
+ return `${START}
42
+ <script src="${CDN}" defer></script>
43
+ <script>
44
+ addEventListener('DOMContentLoaded', () => SevenOts.init({ endpoint: ${JSON.stringify(endpoint)}, identity: ${id} }));
45
+ </script>
46
+ ${END}`;
47
+ }
48
+
49
+ /** Everything but the private instructions (the page's HTML is public). */
50
+ function inlineIdentity(id) {
51
+ const { personality = {}, ...rest } = id;
52
+ return { ...rest, personality: { tone: personality.tone, traits: personality.traits } };
53
+ }
54
+
55
+ /** Inserts or replaces the block. Returns { file, updated }. */
56
+ export function installHtml(file, block) {
57
+ const html = readFileSync(file, 'utf8');
58
+ const re = new RegExp(`${escape(START)}[\\s\\S]*?${escape(END)}`);
59
+ let out;
60
+ let updated = false;
61
+ if (re.test(html)) {
62
+ out = html.replace(re, block);
63
+ updated = true;
64
+ } else if (/<\/body>/i.test(html)) {
65
+ out = html.replace(/<\/body>/i, `${block}\n</body>`);
66
+ } else {
67
+ out = `${html.trimEnd()}\n${block}\n`;
68
+ }
69
+ writeFileSync(file, out);
70
+ return { file, updated };
71
+ }
72
+
73
+ const escape = (s) => s.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&');
74
+
75
+ export const absolute = (file) => resolve(file);
76
+ export const pretty = (file, cwd = process.cwd()) => {
77
+ const r = relative(cwd, file);
78
+ return r && !r.startsWith('..') ? r : file;
79
+ };