@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.
- package/.env.example +17 -1
- package/cli/7ots.mjs +136 -36
- package/cli/lib/assistant.mjs +337 -0
- package/cli/lib/brain.mjs +10 -7
- package/cli/lib/i18n.mjs +87 -17
- package/cli/lib/install.mjs +29 -1
- package/cli/lib/pet-core.mjs +3 -2
- package/cli/lib/pet-server.mjs +30 -0
- package/cli/lib/prompts.mjs +200 -8
- package/cli/lib/terminal.mjs +27 -6
- package/cli/pet/electron/main.cjs +79 -28
- package/cli/pet/pet.html +55 -6
- package/dist/7ots.esm.js +370 -142
- package/dist/7ots.esm.js.map +4 -4
- package/dist/7ots.iife.js +370 -142
- package/dist/7ots.iife.js.map +4 -4
- package/llms.txt +199 -7
- package/package.json +1 -2
- package/server/channels/apuchat.mjs +8 -5
- package/server/channels/apumail.mjs +13 -3
- package/server/channels/index.mjs +5 -3
- package/server/platform/apuchat.mjs +266 -0
- package/server/platform/auth.mjs +39 -10
- package/server/platform/connect.mjs +347 -0
- package/server/platform/db.mjs +55 -0
- package/server/platform/look.mjs +20 -0
- package/server/platform/orquesta.mjs +499 -0
- package/server/platform/routes.mjs +57 -8
- package/server/platform/runtime.mjs +75 -15
- package/server/platform/store.mjs +130 -9
- package/server/server.mjs +14 -3
- package/skills/7ots/SKILL.md +1 -1
- package/src/avatar/AvatarStage.js +54 -9
- package/src/avatar/three.js +62 -0
- package/src/character/AvatarEditor.js +174 -50
- package/src/character/Character.js +164 -26
- package/src/character/motion.js +95 -34
- package/src/character/parts.js +43 -12
- package/src/character/skin.js +412 -0
- package/src/character/styles.js +1429 -0
- package/src/character3d/Character3D.js +1693 -0
- package/src/character3d/body.js +154 -0
- package/src/character3d/fx.js +323 -0
- package/src/character3d/index.js +281 -0
- package/src/character3d/materials.js +429 -0
- package/src/character3d/stage.js +187 -0
- package/src/i18n/messages/character.js +18 -42
- package/src/i18n/messages/platform.js +135 -0
- package/src/identity/schema.js +16 -8
- package/src/index.js +1 -0
package/cli/lib/brain.mjs
CHANGED
|
@@ -37,7 +37,9 @@ export function petSystem(identity, { lang, role = 'pet' } = {}) {
|
|
|
37
37
|
const L = LANG_NAME[lang || identity.language] || 'English';
|
|
38
38
|
const base = identityPrompt(identity, 'en');
|
|
39
39
|
const job =
|
|
40
|
-
role === '
|
|
40
|
+
role === 'assistant'
|
|
41
|
+
? `You live on your human's computer as a virtual pet and also their small daily assistant: reminders, opening sites, remembering what matters about their work.`
|
|
42
|
+
: role === 'meet'
|
|
41
43
|
? `You are in a live video call (apuchat meet). Your words are spoken aloud: answer in 1-3 short sentences, no markdown, no emojis, no lists.`
|
|
42
44
|
: `You live on your human's computer as a virtual pet (a tamagotchi). You watch their terminal and their coding agent and comment on it. Answer with ONE short line (max 18 words), in character, no quotes, no markdown. Be playful, never rude, never repeat yourself.`;
|
|
43
45
|
return `${base}\n${job}\nAlways answer in ${L}.`;
|
|
@@ -90,9 +92,10 @@ export function createBrain(brain = { kind: 'lines' }, home = {}) {
|
|
|
90
92
|
if (brain.kind === 'cli') {
|
|
91
93
|
return {
|
|
92
94
|
kind: 'cli',
|
|
93
|
-
async think({ system, messages, timeoutMs = 45000 }) {
|
|
95
|
+
async think({ system, messages, timeoutMs = 45000, raw = false }) {
|
|
94
96
|
const convo = messages.map((m) => `${m.role === 'user' ? 'Them' : 'You'}: ${m.content}`).join('\n');
|
|
95
|
-
|
|
97
|
+
const out = await runCli(brain, `${system}\n\n${convo}\nYou:`, timeoutMs);
|
|
98
|
+
return raw ? out : cleanReply(out);
|
|
96
99
|
},
|
|
97
100
|
};
|
|
98
101
|
}
|
|
@@ -112,9 +115,9 @@ export function createBrain(brain = { kind: 'lines' }, home = {}) {
|
|
|
112
115
|
const llm = createLLM(llmEnv);
|
|
113
116
|
return {
|
|
114
117
|
kind: brain.kind,
|
|
115
|
-
async think({ system, messages }) {
|
|
118
|
+
async think({ system, messages, raw = false }) {
|
|
116
119
|
const r = await llm.step({ system, messages, tools: [] });
|
|
117
|
-
return cleanReply(r.text);
|
|
120
|
+
return raw ? r.text : cleanReply(r.text);
|
|
118
121
|
},
|
|
119
122
|
};
|
|
120
123
|
}
|
|
@@ -122,7 +125,7 @@ export function createBrain(brain = { kind: 'lines' }, home = {}) {
|
|
|
122
125
|
const url = `${home.url.replace(/\/+$/, '')}/chat`;
|
|
123
126
|
return {
|
|
124
127
|
kind: '7ots',
|
|
125
|
-
async think({ system, messages, timeoutMs = 45000 }) {
|
|
128
|
+
async think({ system, messages, timeoutMs = 45000, raw = false }) {
|
|
126
129
|
// The ot's home only serves its own domains: add http://127.0.0.1:<port> to them in /app/.
|
|
127
130
|
const res = await fetch(url, {
|
|
128
131
|
method: 'POST',
|
|
@@ -132,7 +135,7 @@ export function createBrain(brain = { kind: 'lines' }, home = {}) {
|
|
|
132
135
|
});
|
|
133
136
|
const data = await res.json().catch(() => ({}));
|
|
134
137
|
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
|
|
135
|
-
return cleanReply(data.text);
|
|
138
|
+
return raw ? data.text : cleanReply(data.text);
|
|
136
139
|
},
|
|
137
140
|
};
|
|
138
141
|
}
|
package/cli/lib/i18n.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI texts. English is the default; es/pt come from SEVENOTS_LANG, the saved config or LANG.
|
|
2
|
+
* CLI texts. English is the default; es/pt come from SEVENOTS_LANG, the saved config, the ot's language or LANG.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { readJson, homeFile } from './paths.mjs';
|
|
5
|
+
import { readJson, homeFile, projectFile } from './paths.mjs';
|
|
6
6
|
|
|
7
7
|
const M = {
|
|
8
8
|
en: {
|
|
@@ -12,12 +12,18 @@ const M = {
|
|
|
12
12
|
7ots show [--json|--prompt] show the current identity (project, else global)
|
|
13
13
|
7ots install [--target html|self|print] [file] put the identity in this project
|
|
14
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
|
|
15
|
+
7ots pet [--mode desktop|terminal|browser|auto] [--detach] [--opaque] wake up your virtual pet
|
|
16
|
+
7ots pet --tmux the terminal pet in a tmux side pane
|
|
17
|
+
7ots feed | play | sleep | wake | say <text> | stop
|
|
18
|
+
7ots status [--line|--json] pet state (--line: one line for tmux)
|
|
19
|
+
7ots ask <text> talk to it: "remind me to check email in 10 min", "open my calendar", "remember that…"
|
|
20
|
+
7ots reminders [--cancel <id>] pending reminders and what it remembers
|
|
17
21
|
7ots hooks install|remove [--claude] [--shell] [--global]
|
|
18
22
|
7ots meet --new | "<invite or call URL>" join apuchat meet with this identity
|
|
19
23
|
7ots prompt [identity|install|pet|meet|all] prompts for coding agents
|
|
24
|
+
7ots docs everything for coding agents (same as https://7ots.com/llms.txt)
|
|
20
25
|
7ots login orquesta connect a getorquesta.com account
|
|
26
|
+
7ots server run the 7ots proxy (/api/agent) next to your site
|
|
21
27
|
|
|
22
28
|
Identities are reproducible: the same seed gives the same ot everywhere (web, CLI, pet, meet).
|
|
23
29
|
Docs: https://7ots.com/llms.txt`,
|
|
@@ -28,8 +34,19 @@ Docs: https://7ots.com/llms.txt`,
|
|
|
28
34
|
'install.html': 'Added the 7ots widget to {file}.',
|
|
29
35
|
'install.htmlUpdated': 'Updated the 7ots widget in {file}.',
|
|
30
36
|
'install.noHtml': 'No HTML file found. Paste this before </body>:',
|
|
31
|
-
'install.
|
|
32
|
-
'install.
|
|
37
|
+
'install.pasteIn': "This is a {name} project: paste this in {file}, before </body> (or in the root layout):",
|
|
38
|
+
'install.notWeb': "This folder is not a website, so there is nowhere to put the widget. {name} can still:\n • live on your computer: 7ots pet\n • join a video call: 7ots meet --new\n • live on a site of yours: 7ots install <path/to/index.html> (or 7ots install --target print)\n • be hosted on 7ots.com: {app}",
|
|
39
|
+
'pet.where': "Look for {name} at the bottom of your screen (right click it, or use the tray icon, for the menu). Window looks odd? 7ots pet --opaque",
|
|
40
|
+
'pet.tmuxNo': "Not inside tmux. Run it from a tmux session, or use: 7ots pet --mode terminal",
|
|
41
|
+
'pet.tmuxOk': "{name} is in a tmux pane on the right. For the status line add to ~/.tmux.conf:\n set -g status-right \"#(7ots status --line)\"",
|
|
42
|
+
'usage.pet': "7ots pet [--mode desktop|terminal|browser|auto] [--detach] [--opaque] [--3d] [--tmux] [--compact]\n\n --mode desktop: a small window on your screen (Electron, ~100 MB the first time)\n terminal: drawn in this terminal · browser: a tab · auto: desktop if there is a screen\n --detach return right away (the pet keeps living in the background; stop it with 7ots stop)\n --opaque solid background in desktop mode (when transparency shows a box, common on Linux)\n --3d the ot as a 3D plush (WebGL; falls back to 2D)\n --tmux open the terminal pet in a tmux side pane\n --compact small terminal drawing (for narrow panes)\n\nCare: 7ots feed | play | sleep | wake | say <text> | status [--line] | stop",
|
|
43
|
+
'usage.new': "7ots new [--seed s] [--name n] [--lang en|es|pt] [--global] [--force] [--json]",
|
|
44
|
+
'usage.install': "7ots install [--target html|self|print] [file]\n\n html (default) insert the widget before </body> of index.html (., public/, src/, static/, www/, site/) or [file]\n self prepare the identity for your own 7ots server\n print only print the snippet",
|
|
45
|
+
'usage.hooks': "7ots hooks install|remove [--claude] [--shell] [--global]\n\n --claude Claude Code hooks (this project, or every project with --global)\n --shell bash/zsh hook (~/.bashrc, ~/.zshrc)",
|
|
46
|
+
'usage.meet': "7ots meet --new [--minutes n]\n7ots meet \"<invite text or https://meet.apuchat.com/call#c=…&t=…&p=… link>\" [--minutes n]",
|
|
47
|
+
'usage.status': "7ots status [--line|--json]\n\n --line one short line (for the tmux status bar: set -g status-right \"#(7ots status --line)\")",
|
|
48
|
+
'install.endpointLocal': 'The widget talks to a 7ots proxy at /api/agent. Run one next to your site:\n IDENTITY_FILE={file} npx -y @7ots/cli server\n(or host it on 7ots.com: {app})',
|
|
49
|
+
'install.self': 'Identity ready for your own server:\n IDENTITY_FILE={file} npx -y @7ots/cli server',
|
|
33
50
|
'install.platform': 'Host it on 7ots.com with this exact identity: {app}',
|
|
34
51
|
'pet.food': 'Food',
|
|
35
52
|
'pet.energy': 'Energy',
|
|
@@ -41,7 +58,8 @@ Docs: https://7ots.com/llms.txt`,
|
|
|
41
58
|
'pet.detached': '{name} now lives in the background. Stop it with: 7ots stop',
|
|
42
59
|
'pet.notRunning': 'The pet is not running (start it with: 7ots pet). Saved for later.',
|
|
43
60
|
'pet.stopped': 'Good night, {name}.',
|
|
44
|
-
'pet.electron': 'Opening the desktop pet
|
|
61
|
+
'pet.electron': 'Opening the desktop pet…',
|
|
62
|
+
'pet.electronInstall': 'First time: installing Electron in {dir} (~100 MB, once)…',
|
|
45
63
|
'pet.electronFail': 'Could not open the desktop window ({error}); opening the browser instead.',
|
|
46
64
|
'pet.chooseMode': 'How should {name} live on your computer?',
|
|
47
65
|
'pet.status': '{name} · level {level} · food {food} · energy {energy} · fun {fun}{asleep}',
|
|
@@ -63,6 +81,8 @@ Docs: https://7ots.com/llms.txt`,
|
|
|
63
81
|
'login.open': 'Authorize 7ots in your browser:\n {url}',
|
|
64
82
|
'login.ok': 'Connected to Orquesta ({org}).',
|
|
65
83
|
'err.unknown': 'Unknown command "{cmd}". Try: 7ots help',
|
|
84
|
+
'err.noPet': 'The pet is not awake. Start it with: 7ots pet',
|
|
85
|
+
'usage.ask': '7ots ask <text>\n\n e.g. 7ots ask "remind me to check email in 10 minutes and open it"\n 7ots ask "remember that Fridays we demo" · 7ots ask "my reminders"\n Seeing your screen or processes: coming soon.',
|
|
66
86
|
'err.noIdentity': 'No identity yet. Create one with "7ots new" or run "7ots setup".',
|
|
67
87
|
'w.title': '7ots setup — {name}',
|
|
68
88
|
'w.intro': 'Your ot in 7 short steps. Keys stay on this computer.',
|
|
@@ -161,12 +181,18 @@ Docs: https://7ots.com/llms.txt`,
|
|
|
161
181
|
7ots show [--json|--prompt] muestra la identidad (la del proyecto o la global)
|
|
162
182
|
7ots install [--target html|self|print] [archivo] pone la identidad en este proyecto
|
|
163
183
|
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
|
|
184
|
+
7ots pet [--mode desktop|terminal|browser|auto] [--detach] [--opaque] despierta tu mascota virtual
|
|
185
|
+
7ots pet --tmux la mascota de terminal en un panel de tmux
|
|
186
|
+
7ots feed | play | sleep | wake | say <texto> | stop
|
|
187
|
+
7ots status [--line|--json] estado (--line: una línea para tmux)
|
|
188
|
+
7ots ask <texto> háblale: "recuérdame ver el correo en 10 min", "abre mi calendario", "anota que…"
|
|
189
|
+
7ots reminders [--cancel <id>] recordatorios pendientes y lo que recuerda
|
|
166
190
|
7ots hooks install|remove [--claude] [--shell] [--global]
|
|
167
191
|
7ots meet --new | "<invitación o enlace>" entra a apuchat meet con esta identidad
|
|
168
192
|
7ots prompt [identity|install|pet|meet|all] prompts para agentes de código
|
|
193
|
+
7ots docs todo para agentes de código (igual que https://7ots.com/llms.txt)
|
|
169
194
|
7ots login orquesta conecta una cuenta de getorquesta.com
|
|
195
|
+
7ots server levanta el proxy 7ots (/api/agent) junto a tu web
|
|
170
196
|
|
|
171
197
|
Las identidades son reproducibles: la misma semilla da el mismo ot en todas partes (web, CLI, mascota, meet).
|
|
172
198
|
Docs: https://7ots.com/llms.txt`,
|
|
@@ -177,8 +203,19 @@ Docs: https://7ots.com/llms.txt`,
|
|
|
177
203
|
'install.html': 'Widget de 7ots añadido a {file}.',
|
|
178
204
|
'install.htmlUpdated': 'Widget de 7ots actualizado en {file}.',
|
|
179
205
|
'install.noHtml': 'No encontré un HTML. Pega esto antes de </body>:',
|
|
180
|
-
'install.
|
|
181
|
-
'install.
|
|
206
|
+
'install.pasteIn': "Es un proyecto {name}: pega esto en {file}, antes de </body> (o en el layout raíz):",
|
|
207
|
+
'install.notWeb': "Esta carpeta no es una web, así que no hay dónde poner el widget. {name} igual puede:\n • vivir en tu computador: 7ots pet\n • entrar a una videollamada: 7ots meet --new\n • vivir en una web tuya: 7ots install <ruta/a/index.html> (o 7ots install --target print)\n • alojarse en 7ots.com: {app}",
|
|
208
|
+
'pet.where': "Busca a {name} abajo en tu pantalla (clic derecho sobre él, o el icono de la bandeja, para el menú). ¿Se ve un recuadro? 7ots pet --opaque",
|
|
209
|
+
'pet.tmuxNo': "No estás dentro de tmux. Ejecútalo desde una sesión de tmux, o usa: 7ots pet --mode terminal",
|
|
210
|
+
'pet.tmuxOk': "{name} está en un panel de tmux a la derecha. Para la barra de estado añade a ~/.tmux.conf:\n set -g status-right \"#(7ots status --line)\"",
|
|
211
|
+
'usage.pet': "7ots pet [--mode desktop|terminal|browser|auto] [--detach] [--opaque] [--3d] [--tmux] [--compact]\n\n --mode desktop: una ventanita en tu pantalla (Electron, ~100 MB la primera vez)\n terminal: dibujado en esta terminal · browser: una pestaña · auto: desktop si hay pantalla\n --detach vuelve enseguida (la mascota sigue en segundo plano; se detiene con 7ots stop)\n --opaque fondo sólido en modo desktop (si la transparencia muestra un recuadro, común en Linux)\n --3d el ot como peluche 3D (WebGL; si no hay, 2D)\n --tmux abre la mascota de terminal en un panel lateral de tmux\n --compact dibujo pequeño en la terminal (para paneles estrechos)\n\nCuidados: 7ots feed | play | sleep | wake | say <texto> | status [--line] | stop",
|
|
212
|
+
'usage.new': "7ots new [--seed s] [--name n] [--lang en|es|pt] [--global] [--force] [--json]",
|
|
213
|
+
'usage.install': "7ots install [--target html|self|print] [archivo]\n\n html (defecto) inserta el widget antes de </body> de index.html (., public/, src/, static/, www/, site/) o [archivo]\n self prepara la identidad para tu propio servidor 7ots\n print solo imprime el snippet",
|
|
214
|
+
'usage.hooks': "7ots hooks install|remove [--claude] [--shell] [--global]\n\n --claude hooks de Claude Code (este proyecto, o todos con --global)\n --shell hook de bash/zsh (~/.bashrc, ~/.zshrc)",
|
|
215
|
+
'usage.meet': "7ots meet --new [--minutes n]\n7ots meet \"<texto de invitación o enlace https://meet.apuchat.com/call#c=…&t=…&p=…>\" [--minutes n]",
|
|
216
|
+
'usage.status': "7ots status [--line|--json]\n\n --line una línea corta (para la barra de tmux: set -g status-right \"#(7ots status --line)\")",
|
|
217
|
+
'install.endpointLocal': 'El widget habla con un proxy 7ots en /api/agent. Levanta uno junto a tu web:\n IDENTITY_FILE={file} npx -y @7ots/cli server\n(o alójalo en 7ots.com: {app})',
|
|
218
|
+
'install.self': 'Identidad lista para tu servidor:\n IDENTITY_FILE={file} npx -y @7ots/cli server',
|
|
182
219
|
'install.platform': 'Alójalo en 7ots.com con esta misma identidad: {app}',
|
|
183
220
|
'pet.food': 'Comida',
|
|
184
221
|
'pet.energy': 'Energía',
|
|
@@ -190,7 +227,8 @@ Docs: https://7ots.com/llms.txt`,
|
|
|
190
227
|
'pet.detached': '{name} vive ahora en segundo plano. Para dormirlo: 7ots stop',
|
|
191
228
|
'pet.notRunning': 'La mascota no está corriendo (lánzala con: 7ots pet). Lo guardo para después.',
|
|
192
229
|
'pet.stopped': 'Buenas noches, {name}.',
|
|
193
|
-
'pet.electron': 'Abriendo la mascota de escritorio
|
|
230
|
+
'pet.electron': 'Abriendo la mascota de escritorio…',
|
|
231
|
+
'pet.electronInstall': 'Primera vez: instalo Electron en {dir} (~100 MB, una sola vez)…',
|
|
194
232
|
'pet.electronFail': 'No pude abrir la ventana de escritorio ({error}); abro el navegador.',
|
|
195
233
|
'pet.chooseMode': '¿Cómo quieres que viva {name} en tu computador?',
|
|
196
234
|
'pet.status': '{name} · nivel {level} · comida {food} · energía {energy} · diversión {fun}{asleep}',
|
|
@@ -212,6 +250,8 @@ Docs: https://7ots.com/llms.txt`,
|
|
|
212
250
|
'login.open': 'Autoriza 7ots en tu navegador:\n {url}',
|
|
213
251
|
'login.ok': 'Conectado a Orquesta ({org}).',
|
|
214
252
|
'err.unknown': 'Comando desconocido «{cmd}». Prueba: 7ots help',
|
|
253
|
+
'err.noPet': 'La mascota no está despierta. Iníciala con: 7ots pet',
|
|
254
|
+
'usage.ask': '7ots ask <texto>\n\n ej. 7ots ask "recuérdame ver el correo en 10 minutos y ábrelo"\n 7ots ask "anota que los viernes hay demo" · 7ots ask "mis recordatorios"\n Ver tu pantalla o tus procesos: muy pronto.',
|
|
215
255
|
'err.noIdentity': 'Aún no hay identidad. Crea una con «7ots new» o corre «7ots setup».',
|
|
216
256
|
'w.title': 'Configuración de 7ots — {name}',
|
|
217
257
|
'w.intro': 'Tu ot en 7 pasos cortos. Las claves se quedan en este computador.',
|
|
@@ -310,12 +350,18 @@ Docs: https://7ots.com/llms.txt`,
|
|
|
310
350
|
7ots show [--json|--prompt] mostra a identidade (do projeto ou a global)
|
|
311
351
|
7ots install [--target html|self|print] [arquivo] coloca a identidade neste projeto
|
|
312
352
|
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
|
|
353
|
+
7ots pet [--mode desktop|terminal|browser|auto] [--detach] [--opaque] acorda seu bichinho virtual
|
|
354
|
+
7ots pet --tmux o bichinho de terminal num painel do tmux
|
|
355
|
+
7ots feed | play | sleep | wake | say <texto> | stop
|
|
356
|
+
7ots status [--line|--json] estado (--line: uma linha para tmux)
|
|
357
|
+
7ots ask <texto> fale com ele: "me lembre de ver o e-mail em 10 min", "abra minha agenda", "anote que…"
|
|
358
|
+
7ots reminders [--cancel <id>] lembretes pendentes e o que ele lembra
|
|
315
359
|
7ots hooks install|remove [--claude] [--shell] [--global]
|
|
316
360
|
7ots meet --new | "<convite ou link>" entra no apuchat meet com esta identidade
|
|
317
361
|
7ots prompt [identity|install|pet|meet|all] prompts para agentes de código
|
|
362
|
+
7ots docs tudo para agentes de código (igual a https://7ots.com/llms.txt)
|
|
318
363
|
7ots login orquesta conecta uma conta do getorquesta.com
|
|
364
|
+
7ots server sobe o proxy 7ots (/api/agent) junto do seu site
|
|
319
365
|
|
|
320
366
|
As identidades são reproduzíveis: a mesma semente dá o mesmo ot em todo lugar (web, CLI, bichinho, meet).
|
|
321
367
|
Docs: https://7ots.com/llms.txt`,
|
|
@@ -326,8 +372,19 @@ Docs: https://7ots.com/llms.txt`,
|
|
|
326
372
|
'install.html': 'Widget do 7ots adicionado em {file}.',
|
|
327
373
|
'install.htmlUpdated': 'Widget do 7ots atualizado em {file}.',
|
|
328
374
|
'install.noHtml': 'Não achei um HTML. Cole isto antes de </body>:',
|
|
329
|
-
'install.
|
|
330
|
-
'install.
|
|
375
|
+
'install.pasteIn': "É um projeto {name}: cole isto em {file}, antes de </body> (ou no layout raiz):",
|
|
376
|
+
'install.notWeb': "Esta pasta não é um site, então não há onde pôr o widget. {name} ainda pode:\n • morar no seu computador: 7ots pet\n • entrar numa videochamada: 7ots meet --new\n • morar num site seu: 7ots install <caminho/para/index.html> (ou 7ots install --target print)\n • ser hospedado no 7ots.com: {app}",
|
|
377
|
+
'pet.where': "Procure {name} na parte de baixo da tela (clique direito nele, ou o ícone da bandeja, para o menu). Aparece um quadro? 7ots pet --opaque",
|
|
378
|
+
'pet.tmuxNo': "Você não está no tmux. Rode dentro de uma sessão do tmux, ou use: 7ots pet --mode terminal",
|
|
379
|
+
'pet.tmuxOk': "{name} está num painel do tmux à direita. Para a barra de status adicione ao ~/.tmux.conf:\n set -g status-right \"#(7ots status --line)\"",
|
|
380
|
+
'usage.pet': "7ots pet [--mode desktop|terminal|browser|auto] [--detach] [--opaque] [--3d] [--tmux] [--compact]\n\n --mode desktop: uma janelinha na tela (Electron, ~100 MB na primeira vez)\n terminal: desenhado neste terminal · browser: uma aba · auto: desktop se houver tela\n --detach volta na hora (o bichinho segue em segundo plano; pare com 7ots stop)\n --opaque fundo sólido no modo desktop (quando a transparência mostra um quadro, comum no Linux)\n --3d o ot como pelúcia 3D (WebGL; sem ele, 2D)\n --tmux abre o bichinho de terminal num painel lateral do tmux\n --compact desenho pequeno no terminal (para painéis estreitos)\n\nCuidados: 7ots feed | play | sleep | wake | say <texto> | status [--line] | stop",
|
|
381
|
+
'usage.new': "7ots new [--seed s] [--name n] [--lang en|es|pt] [--global] [--force] [--json]",
|
|
382
|
+
'usage.install': "7ots install [--target html|self|print] [arquivo]\n\n html (padrão) insere o widget antes de </body> do index.html (., public/, src/, static/, www/, site/) ou [arquivo]\n self prepara a identidade para o seu próprio servidor 7ots\n print só imprime o snippet",
|
|
383
|
+
'usage.hooks': "7ots hooks install|remove [--claude] [--shell] [--global]\n\n --claude hooks do Claude Code (este projeto, ou todos com --global)\n --shell hook de bash/zsh (~/.bashrc, ~/.zshrc)",
|
|
384
|
+
'usage.meet': "7ots meet --new [--minutes n]\n7ots meet \"<texto do convite ou link https://meet.apuchat.com/call#c=…&t=…&p=…>\" [--minutes n]",
|
|
385
|
+
'usage.status': "7ots status [--line|--json]\n\n --line uma linha curta (para a barra do tmux: set -g status-right \"#(7ots status --line)\")",
|
|
386
|
+
'install.endpointLocal': 'O widget fala com um proxy 7ots em /api/agent. Suba um junto do seu site:\n IDENTITY_FILE={file} npx -y @7ots/cli server\n(ou hospede no 7ots.com: {app})',
|
|
387
|
+
'install.self': 'Identidade pronta para o seu servidor:\n IDENTITY_FILE={file} npx -y @7ots/cli server',
|
|
331
388
|
'install.platform': 'Hospede no 7ots.com com esta mesma identidade: {app}',
|
|
332
389
|
'pet.food': 'Comida',
|
|
333
390
|
'pet.energy': 'Energia',
|
|
@@ -339,7 +396,8 @@ Docs: https://7ots.com/llms.txt`,
|
|
|
339
396
|
'pet.detached': '{name} agora mora em segundo plano. Para parar: 7ots stop',
|
|
340
397
|
'pet.notRunning': 'O bichinho não está rodando (inicie com: 7ots pet). Guardado para depois.',
|
|
341
398
|
'pet.stopped': 'Boa noite, {name}.',
|
|
342
|
-
'pet.electron': 'Abrindo o bichinho de desktop
|
|
399
|
+
'pet.electron': 'Abrindo o bichinho de desktop…',
|
|
400
|
+
'pet.electronInstall': 'Primeira vez: instalando o Electron em {dir} (~100 MB, uma vez só)…',
|
|
343
401
|
'pet.electronFail': 'Não consegui abrir a janela de desktop ({error}); abrindo o navegador.',
|
|
344
402
|
'pet.chooseMode': 'Como {name} deve morar no seu computador?',
|
|
345
403
|
'pet.status': '{name} · nível {level} · comida {food} · energia {energy} · diversão {fun}{asleep}',
|
|
@@ -361,6 +419,8 @@ Docs: https://7ots.com/llms.txt`,
|
|
|
361
419
|
'login.open': 'Autorize o 7ots no navegador:\n {url}',
|
|
362
420
|
'login.ok': 'Conectado ao Orquesta ({org}).',
|
|
363
421
|
'err.unknown': 'Comando desconhecido «{cmd}». Tente: 7ots help',
|
|
422
|
+
'err.noPet': 'O bichinho não está acordado. Inicie com: 7ots pet',
|
|
423
|
+
'usage.ask': '7ots ask <texto>\n\n ex. 7ots ask "me lembre de ver o e-mail em 10 minutos e abra"\n 7ots ask "anote que às sextas tem demo" · 7ots ask "meus lembretes"\n Ver sua tela ou processos: em breve.',
|
|
364
424
|
'err.noIdentity': 'Ainda não há identidade. Crie uma com «7ots new» ou rode «7ots setup».',
|
|
365
425
|
'w.title': 'Configuração do 7ots — {name}',
|
|
366
426
|
'w.intro': 'Seu ot em 7 passos curtos. As chaves ficam neste computador.',
|
|
@@ -456,12 +516,22 @@ Docs: https://7ots.com/llms.txt`,
|
|
|
456
516
|
|
|
457
517
|
let current = null;
|
|
458
518
|
|
|
519
|
+
/** The ot's language (project identity, else the global one): its texts follow it. */
|
|
520
|
+
function identityLang() {
|
|
521
|
+
for (const f of [projectFile('identity.json'), homeFile('identity.json')]) {
|
|
522
|
+
const l = readJson(f)?.language;
|
|
523
|
+
if (l) return String(l).slice(0, 2).toLowerCase();
|
|
524
|
+
}
|
|
525
|
+
return '';
|
|
526
|
+
}
|
|
527
|
+
|
|
459
528
|
export function cliLang() {
|
|
460
529
|
if (current) return current;
|
|
461
530
|
const pick = (v) => (['en', 'es', 'pt'].includes(v) ? v : '');
|
|
462
531
|
current =
|
|
463
532
|
pick(process.env.SEVENOTS_LANG) ||
|
|
464
533
|
pick(readJson(homeFile('config.json'), {})?.lang) ||
|
|
534
|
+
pick(identityLang()) ||
|
|
465
535
|
pick(String(process.env.LC_ALL || process.env.LC_MESSAGES || process.env.LANG || '').slice(0, 2).toLowerCase()) ||
|
|
466
536
|
'en';
|
|
467
537
|
return current;
|
package/cli/lib/install.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import { join, resolve, relative } from 'node:path';
|
|
|
15
15
|
import { existsSync } from './paths.mjs';
|
|
16
16
|
import { loadConfig } from './config.mjs';
|
|
17
17
|
|
|
18
|
-
const CDN = 'https://cdn.jsdelivr.net/npm
|
|
18
|
+
const CDN = 'https://cdn.jsdelivr.net/npm/@7ots/cli/dist/7ots.iife.js';
|
|
19
19
|
const START = '<!-- 7ots -->';
|
|
20
20
|
const END = '<!-- /7ots -->';
|
|
21
21
|
|
|
@@ -29,6 +29,34 @@ export function findHtml(cwd = process.cwd()) {
|
|
|
29
29
|
return null;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* What kind of project this is, to say where the snippet goes when there is no plain index.html.
|
|
34
|
+
* @returns {{ kind: 'framework'|'web'|'none', name?: string, file?: string }}
|
|
35
|
+
*/
|
|
36
|
+
export function detectProject(cwd = process.cwd()) {
|
|
37
|
+
let pkg = null;
|
|
38
|
+
try {
|
|
39
|
+
pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8'));
|
|
40
|
+
} catch {}
|
|
41
|
+
const deps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
42
|
+
const first = (files) => files.find((f) => existsSync(join(cwd, f)));
|
|
43
|
+
const FRAMEWORKS = [
|
|
44
|
+
['next', 'Next.js', ['app/layout.tsx', 'app/layout.jsx', 'app/layout.js', 'src/app/layout.tsx', 'src/app/layout.jsx', 'pages/_document.tsx', 'pages/_document.js', 'src/pages/_document.tsx']],
|
|
45
|
+
['nuxt', 'Nuxt', ['app.vue', 'nuxt.config.ts', 'nuxt.config.js']],
|
|
46
|
+
['astro', 'Astro', ['src/layouts/Layout.astro', 'src/layouts/BaseLayout.astro']],
|
|
47
|
+
['@sveltejs/kit', 'SvelteKit', ['src/app.html']],
|
|
48
|
+
['@remix-run/react', 'Remix', ['app/root.tsx', 'app/root.jsx']],
|
|
49
|
+
['gatsby', 'Gatsby', ['src/html.js', 'gatsby-ssr.js']],
|
|
50
|
+
['vite', 'Vite', ['index.html']],
|
|
51
|
+
];
|
|
52
|
+
// No layout file yet: name the usual one, so the user still knows where it goes.
|
|
53
|
+
for (const [dep, name, files] of FRAMEWORKS) if (deps[dep]) return { kind: 'framework', name, file: first(files) || files[0] };
|
|
54
|
+
const web = first(['templates/base.html', 'templates/layout.html', 'app/views/layouts/application.html.erb', 'resources/views/layouts/app.blade.php', 'layouts/_default/baseof.html', '_layouts/default.html']);
|
|
55
|
+
if (web) return { kind: 'web', file: web };
|
|
56
|
+
if (pkg && (deps.react || deps.vue || deps.svelte || deps['solid-js'] || deps.express || deps.fastify)) return { kind: 'web' };
|
|
57
|
+
return { kind: 'none' };
|
|
58
|
+
}
|
|
59
|
+
|
|
32
60
|
/** The HTML block for this identity and home. */
|
|
33
61
|
export function snippet(identity, { home = loadConfig().home, identityUrl } = {}) {
|
|
34
62
|
if (home?.kind === '7ots' && /\/api\/o\/[\w-]+\/?$/.test(home.url || '')) {
|
package/cli/lib/pet-core.mjs
CHANGED
|
@@ -15,7 +15,7 @@ const HOUR = 3600_000;
|
|
|
15
15
|
const clamp = (n) => Math.max(0, Math.min(100, Math.round(n * 10) / 10));
|
|
16
16
|
|
|
17
17
|
export function newPet(now = Date.now()) {
|
|
18
|
-
return { food:
|
|
18
|
+
return { food: 90, energy: 100, fun: 80, xp: 0, level: 1, asleep: false, born: now, last: now, lastTalk: 0, lastSeen: now, stats: { ok: 0, fail: 0, turns: 0 } };
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function loadPet() {
|
|
@@ -31,7 +31,8 @@ export const xpForLevel = (level) => 20 * level * level;
|
|
|
31
31
|
|
|
32
32
|
/** Time passes: needs decay (slower while asleep, energy recovers). */
|
|
33
33
|
export function tick(p, now = Date.now()) {
|
|
34
|
-
|
|
34
|
+
// Time away counts up to 8 h: coming back after a weekend finds it peckish, not starving.
|
|
35
|
+
const h = Math.max(0, Math.min(8, (now - p.last) / HOUR));
|
|
35
36
|
p.last = now;
|
|
36
37
|
if (!h) return p;
|
|
37
38
|
if (p.asleep) {
|
package/cli/lib/pet-server.mjs
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* GET /events?token=… SSE: { state, say?, gesture?, effect?, morph?, activity? }
|
|
9
9
|
* POST /event X-7ots-Token { type: tool|shell|prompt|stop|notify|start|feed|play|sleep|wake|say, … }
|
|
10
10
|
* POST /tts X-7ots-Token { text } → audio with the configured voice
|
|
11
|
+
* POST /ask X-7ots-Token { text } → { reply, actions } reminders, notes, open a site (assistant.mjs)
|
|
12
|
+
* GET /reminders?token=… { reminders, notes }
|
|
13
|
+
* POST /reminders/cancel { id }
|
|
11
14
|
* POST /quit X-7ots-Token
|
|
12
15
|
*
|
|
13
16
|
* Only listens on 127.0.0.1 and only answers to Host 127.0.0.1/localhost (no DNS rebinding).
|
|
@@ -18,6 +21,7 @@ import { createServer } from 'node:http';
|
|
|
18
21
|
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
19
22
|
import { readFileSync, existsSync, writeFileSync, chmodSync } from 'node:fs';
|
|
20
23
|
import { join } from 'node:path';
|
|
24
|
+
import { createAssistant } from './assistant.mjs';
|
|
21
25
|
import { createBrain, petSystem } from './brain.mjs';
|
|
22
26
|
import { loadConfig, env } from './config.mjs';
|
|
23
27
|
import { loadIdentity } from './identity.mjs';
|
|
@@ -189,6 +193,20 @@ export function startPetServer({ port = PET_PORT, log = () => {} } = {}) {
|
|
|
189
193
|
broadcast({});
|
|
190
194
|
}, 60_000);
|
|
191
195
|
|
|
196
|
+
// The assistant: reminders that speak, notify and open the tab; notes it learns.
|
|
197
|
+
const assistant = createAssistant({
|
|
198
|
+
brain,
|
|
199
|
+
lang,
|
|
200
|
+
name: identity.name,
|
|
201
|
+
log,
|
|
202
|
+
system: () => petSystem(identity, { lang, role: 'assistant' }),
|
|
203
|
+
onFire: (r, text) => {
|
|
204
|
+
lastSpoke = Date.now();
|
|
205
|
+
pet.lastTalk = lastSpoke;
|
|
206
|
+
broadcast({ say: text, gesture: 'wave', effect: 'exclaim', reminder: r });
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
192
210
|
// Home on getorquesta.com: comment on what the project's agent does.
|
|
193
211
|
let stopWatch = () => {};
|
|
194
212
|
if (config.home.kind === 'orquesta' && config.home.projectId && env().ORQUESTA_TOKEN) {
|
|
@@ -243,6 +261,7 @@ export function startPetServer({ port = PET_PORT, log = () => {} } = {}) {
|
|
|
243
261
|
req.on('close', () => clients.delete(res));
|
|
244
262
|
return;
|
|
245
263
|
}
|
|
264
|
+
if (req.method === 'GET' && p === '/reminders') return json(200, { reminders: assistant.reminders, notes: assistant.notes });
|
|
246
265
|
if (req.method !== 'POST') return json(405, { error: 'method' });
|
|
247
266
|
let body = '';
|
|
248
267
|
for await (const c of req) if ((body += c).length > 64 * 1024) return json(413, { error: 'too large' });
|
|
@@ -269,6 +288,16 @@ export function startPetServer({ port = PET_PORT, log = () => {} } = {}) {
|
|
|
269
288
|
return json(e.status || 502, { error: e.message });
|
|
270
289
|
}
|
|
271
290
|
}
|
|
291
|
+
if (p === '/ask') {
|
|
292
|
+
const text = String(data.text || '').trim().slice(0, 800);
|
|
293
|
+
if (!text) return json(400, { error: 'text' });
|
|
294
|
+
broadcast({ thinking: true, activity: track({ type: 'prompt', text }) });
|
|
295
|
+
const r = await assistant.ask(text);
|
|
296
|
+
lastSpoke = Date.now();
|
|
297
|
+
broadcast({ thinking: false, say: r.reply, gesture: r.actions.length ? 'nod' : null });
|
|
298
|
+
return json(200, r);
|
|
299
|
+
}
|
|
300
|
+
if (p === '/reminders/cancel') return json(200, { ok: assistant.cancel(String(data.id || '')) });
|
|
272
301
|
if (p === '/quit') {
|
|
273
302
|
json(200, { ok: true });
|
|
274
303
|
return stop();
|
|
@@ -278,6 +307,7 @@ export function startPetServer({ port = PET_PORT, log = () => {} } = {}) {
|
|
|
278
307
|
|
|
279
308
|
function stop() {
|
|
280
309
|
clearInterval(timer);
|
|
310
|
+
assistant.stop();
|
|
281
311
|
stopWatch();
|
|
282
312
|
savePet(pet);
|
|
283
313
|
for (const c of clients) c.end();
|