@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.
- package/.env.example +140 -0
- package/LICENSE +21 -0
- package/README.md +655 -0
- package/brand/apple-touch-icon.png +0 -0
- package/brand/favicon.svg +4 -0
- package/brand/icon-512.png +0 -0
- package/brand/mark-mono.svg +3 -0
- package/brand/mark.svg +4 -0
- package/brand/og-es.png +0 -0
- package/brand/og-pt.png +0 -0
- package/brand/og.png +0 -0
- package/brand/ots.json +11 -0
- package/brand/tokens.css +59 -0
- package/brand/wordmark.svg +5 -0
- package/cli/7ots.mjs +400 -0
- package/cli/lib/brain.mjs +143 -0
- package/cli/lib/config.mjs +70 -0
- package/cli/lib/hooks.mjs +151 -0
- package/cli/lib/i18n.mjs +480 -0
- package/cli/lib/identity.mjs +51 -0
- package/cli/lib/install.mjs +79 -0
- package/cli/lib/lines.mjs +73 -0
- package/cli/lib/meet.mjs +166 -0
- package/cli/lib/open.mjs +19 -0
- package/cli/lib/orquesta.mjs +84 -0
- package/cli/lib/paths.mjs +49 -0
- package/cli/lib/pet-core.mjs +184 -0
- package/cli/lib/pet-server.mjs +296 -0
- package/cli/lib/prompts.mjs +75 -0
- package/cli/lib/terminal.mjs +145 -0
- package/cli/lib/ui.mjs +234 -0
- package/cli/lib/wizard.mjs +279 -0
- package/cli/pet/electron/main.cjs +86 -0
- package/cli/pet/pet.html +404 -0
- package/dist/7ots.esm.js +627 -0
- package/dist/7ots.esm.js.map +7 -0
- package/dist/7ots.iife.js +627 -0
- package/dist/7ots.iife.js.map +7 -0
- package/llms.txt +57 -0
- package/package.json +73 -0
- package/server/admin.mjs +213 -0
- package/server/apuchat-relay.mjs +170 -0
- package/server/channels/agent.mjs +136 -0
- package/server/channels/apuchat.mjs +364 -0
- package/server/channels/apumail.mjs +211 -0
- package/server/channels/index.mjs +69 -0
- package/server/contact.mjs +151 -0
- package/server/demo.mjs +154 -0
- package/server/i18n.mjs +54 -0
- package/server/identity.mjs +108 -0
- package/server/llm.mjs +413 -0
- package/server/platform/auth.mjs +290 -0
- package/server/platform/crypto.mjs +80 -0
- package/server/platform/db.mjs +130 -0
- package/server/platform/routes.mjs +388 -0
- package/server/platform/runtime.mjs +180 -0
- package/server/platform/store.mjs +331 -0
- package/server/sdk.mjs +125 -0
- package/server/server.mjs +456 -0
- package/server/settings.mjs +177 -0
- package/server/tts.mjs +186 -0
- package/skills/7ots/SKILL.md +59 -0
- package/src/actions/ActionRegistry.js +254 -0
- package/src/actions/PageTools.js +270 -0
- package/src/actions/builtins.js +565 -0
- package/src/auth/AuthManager.js +128 -0
- package/src/avatar/AvatarStage.js +473 -0
- package/src/character/AvatarEditor.js +829 -0
- package/src/character/Character.js +1191 -0
- package/src/character/motion.js +794 -0
- package/src/character/parts.js +559 -0
- package/src/context/ContextManager.js +472 -0
- package/src/core/AgentBrain.js +341 -0
- package/src/core/AgentWidget.js +1059 -0
- package/src/core/EventBus.js +61 -0
- package/src/core/Proactivity.js +363 -0
- package/src/core/storage.js +35 -0
- package/src/i18n/index.js +193 -0
- package/src/i18n/messages/character.js +543 -0
- package/src/i18n/messages/identity.js +141 -0
- package/src/i18n/messages/platform.js +114 -0
- package/src/i18n/messages/server.js +630 -0
- package/src/i18n/messages/widget.js +362 -0
- package/src/identity/ContactCard.js +351 -0
- package/src/identity/Face.js +148 -0
- package/src/identity/random.js +112 -0
- package/src/identity/schema.js +262 -0
- package/src/index.js +117 -0
- package/src/integrations/apuchat.js +212 -0
- package/src/integrations/apumail.js +88 -0
- package/src/llm/ProxyLLM.js +87 -0
- package/src/mcp/McpClient.js +153 -0
- package/src/ui/Companion.js +498 -0
- package/src/ui/UIManager.js +425 -0
- package/src/ui/VirtualPointer.js +746 -0
- package/src/ui/markdown.js +94 -0
- package/src/voice/VoiceEngine.js +227 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
2
|
+
<style>.bg{fill:#5b47f0}.fg{fill:#fff}.dot{fill:#22d3ee}@media (prefers-color-scheme:dark){.bg{fill:#8b7cff}.fg{fill:#0b0a14}}</style>
|
|
3
|
+
<path class="bg" d="M64 32L63.98 41.83L63.93 44.97L63.84 47.24L63.72 49.07L63.56 50.64L63.36 52.01L63.13 53.23L62.86 54.33L62.56 55.33L62.21 56.25L61.83 57.1L61.4 57.87L60.93 58.59L60.42 59.25L59.86 59.86L59.25 60.42L58.59 60.93L57.87 61.4L57.1 61.83L56.25 62.21L55.33 62.56L54.33 62.86L53.23 63.13L52.01 63.36L50.64 63.56L49.07 63.72L47.24 63.84L44.97 63.93L41.83 63.98L32 64L22.17 63.98L19.03 63.93L16.76 63.84L14.93 63.72L13.36 63.56L11.99 63.36L10.77 63.13L9.67 62.86L8.67 62.56L7.75 62.21L6.9 61.83L6.13 61.4L5.41 60.93L4.75 60.42L4.14 59.86L3.58 59.25L3.07 58.59L2.6 57.87L2.17 57.1L1.79 56.25L1.44 55.33L1.14 54.33L0.87 53.23L0.64 52.01L0.44 50.64L0.28 49.07L0.16 47.24L0.07 44.97L0.02 41.83L0 32L0.02 22.17L0.07 19.03L0.16 16.76L0.28 14.93L0.44 13.36L0.64 11.99L0.87 10.77L1.14 9.67L1.44 8.67L1.79 7.75L2.17 6.9L2.6 6.13L3.07 5.41L3.58 4.75L4.14 4.14L4.75 3.58L5.41 3.07L6.13 2.6L6.9 2.17L7.75 1.79L8.67 1.44L9.67 1.14L10.77 0.87L11.99 0.64L13.36 0.44L14.93 0.28L16.76 0.16L19.03 0.07L22.17 0.02L32 0L41.83 0.02L44.97 0.07L47.24 0.16L49.07 0.28L50.64 0.44L52.01 0.64L53.23 0.87L54.33 1.14L55.33 1.44L56.25 1.79L57.1 2.17L57.87 2.6L58.59 3.07L59.25 3.58L59.86 4.14L60.42 4.75L60.93 5.41L61.4 6.13L61.83 6.9L62.21 7.75L62.56 8.67L62.86 9.67L63.13 10.77L63.36 11.99L63.56 13.36L63.72 14.93L63.84 16.76L63.93 19.03L63.98 22.17Z"/><path class="fg" d="M21.16 49Q21.16 45.4 22 41.8Q22.85 38.2 24.45 34.75Q26.04 31.31 28.33 28.12Q30.62 24.93 33.55 22.1V21.89H16.78V15H41.22V21.89Q39.31 23.9 37.38 26.88Q35.46 29.87 33.86 33.47Q32.27 37.07 31.31 41.03Q30.36 44.99 30.36 49Z"/><circle cx="44.5" cy="44.4" r="4.7" class="dot"/>
|
|
4
|
+
</svg>
|
|
Binary file
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="7ots" fill="currentColor">
|
|
2
|
+
<path fill-rule="evenodd" d="M64 32L63.98 41.83L63.93 44.97L63.84 47.24L63.72 49.07L63.56 50.64L63.36 52.01L63.13 53.23L62.86 54.33L62.56 55.33L62.21 56.25L61.83 57.1L61.4 57.87L60.93 58.59L60.42 59.25L59.86 59.86L59.25 60.42L58.59 60.93L57.87 61.4L57.1 61.83L56.25 62.21L55.33 62.56L54.33 62.86L53.23 63.13L52.01 63.36L50.64 63.56L49.07 63.72L47.24 63.84L44.97 63.93L41.83 63.98L32 64L22.17 63.98L19.03 63.93L16.76 63.84L14.93 63.72L13.36 63.56L11.99 63.36L10.77 63.13L9.67 62.86L8.67 62.56L7.75 62.21L6.9 61.83L6.13 61.4L5.41 60.93L4.75 60.42L4.14 59.86L3.58 59.25L3.07 58.59L2.6 57.87L2.17 57.1L1.79 56.25L1.44 55.33L1.14 54.33L0.87 53.23L0.64 52.01L0.44 50.64L0.28 49.07L0.16 47.24L0.07 44.97L0.02 41.83L0 32L0.02 22.17L0.07 19.03L0.16 16.76L0.28 14.93L0.44 13.36L0.64 11.99L0.87 10.77L1.14 9.67L1.44 8.67L1.79 7.75L2.17 6.9L2.6 6.13L3.07 5.41L3.58 4.75L4.14 4.14L4.75 3.58L5.41 3.07L6.13 2.6L6.9 2.17L7.75 1.79L8.67 1.44L9.67 1.14L10.77 0.87L11.99 0.64L13.36 0.44L14.93 0.28L16.76 0.16L19.03 0.07L22.17 0.02L32 0L41.83 0.02L44.97 0.07L47.24 0.16L49.07 0.28L50.64 0.44L52.01 0.64L53.23 0.87L54.33 1.14L55.33 1.44L56.25 1.79L57.1 2.17L57.87 2.6L58.59 3.07L59.25 3.58L59.86 4.14L60.42 4.75L60.93 5.41L61.4 6.13L61.83 6.9L62.21 7.75L62.56 8.67L62.86 9.67L63.13 10.77L63.36 11.99L63.56 13.36L63.72 14.93L63.84 16.76L63.93 19.03L63.98 22.17ZM21.16 49Q21.16 45.4 22 41.8Q22.85 38.2 24.45 34.75Q26.04 31.31 28.33 28.12Q30.62 24.93 33.55 22.1V21.89H16.78V15H41.22V21.89Q39.31 23.9 37.38 26.88Q35.46 29.87 33.86 33.47Q32.27 37.07 31.31 41.03Q30.36 44.99 30.36 49ZM39.8 44.4a4.7 4.7 0 1 0 9.4 0a4.7 4.7 0 1 0-9.4 0Z"/>
|
|
3
|
+
</svg>
|
package/brand/mark.svg
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="7ots">
|
|
2
|
+
<style>.bg{fill:#5b47f0}.fg{fill:#fff}.dot{fill:#22d3ee}</style>
|
|
3
|
+
<path class="bg" d="M64 32L63.98 41.83L63.93 44.97L63.84 47.24L63.72 49.07L63.56 50.64L63.36 52.01L63.13 53.23L62.86 54.33L62.56 55.33L62.21 56.25L61.83 57.1L61.4 57.87L60.93 58.59L60.42 59.25L59.86 59.86L59.25 60.42L58.59 60.93L57.87 61.4L57.1 61.83L56.25 62.21L55.33 62.56L54.33 62.86L53.23 63.13L52.01 63.36L50.64 63.56L49.07 63.72L47.24 63.84L44.97 63.93L41.83 63.98L32 64L22.17 63.98L19.03 63.93L16.76 63.84L14.93 63.72L13.36 63.56L11.99 63.36L10.77 63.13L9.67 62.86L8.67 62.56L7.75 62.21L6.9 61.83L6.13 61.4L5.41 60.93L4.75 60.42L4.14 59.86L3.58 59.25L3.07 58.59L2.6 57.87L2.17 57.1L1.79 56.25L1.44 55.33L1.14 54.33L0.87 53.23L0.64 52.01L0.44 50.64L0.28 49.07L0.16 47.24L0.07 44.97L0.02 41.83L0 32L0.02 22.17L0.07 19.03L0.16 16.76L0.28 14.93L0.44 13.36L0.64 11.99L0.87 10.77L1.14 9.67L1.44 8.67L1.79 7.75L2.17 6.9L2.6 6.13L3.07 5.41L3.58 4.75L4.14 4.14L4.75 3.58L5.41 3.07L6.13 2.6L6.9 2.17L7.75 1.79L8.67 1.44L9.67 1.14L10.77 0.87L11.99 0.64L13.36 0.44L14.93 0.28L16.76 0.16L19.03 0.07L22.17 0.02L32 0L41.83 0.02L44.97 0.07L47.24 0.16L49.07 0.28L50.64 0.44L52.01 0.64L53.23 0.87L54.33 1.14L55.33 1.44L56.25 1.79L57.1 2.17L57.87 2.6L58.59 3.07L59.25 3.58L59.86 4.14L60.42 4.75L60.93 5.41L61.4 6.13L61.83 6.9L62.21 7.75L62.56 8.67L62.86 9.67L63.13 10.77L63.36 11.99L63.56 13.36L63.72 14.93L63.84 16.76L63.93 19.03L63.98 22.17Z"/><path class="fg" d="M21.16 49Q21.16 45.4 22 41.8Q22.85 38.2 24.45 34.75Q26.04 31.31 28.33 28.12Q30.62 24.93 33.55 22.1V21.89H16.78V15H41.22V21.89Q39.31 23.9 37.38 26.88Q35.46 29.87 33.86 33.47Q32.27 37.07 31.31 41.03Q30.36 44.99 30.36 49Z"/><circle cx="44.5" cy="44.4" r="4.7" class="dot"/>
|
|
4
|
+
</svg>
|
package/brand/og-es.png
ADDED
|
Binary file
|
package/brand/og-pt.png
ADDED
|
Binary file
|
package/brand/og.png
ADDED
|
Binary file
|
package/brand/ots.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"v": 1,
|
|
3
|
+
"body": { "shape": "squircle", "color": "#6d5dfc", "pattern": "belly", "patternColor": "#8b7cff" },
|
|
4
|
+
"finish": "soft3d",
|
|
5
|
+
"outline": "auto",
|
|
6
|
+
"eyes": { "type": "round", "size": 1.05, "spacing": 1, "y": 0, "iris": "#22d3ee", "white": "#ffffff", "ink": "#16152a" },
|
|
7
|
+
"brows": { "type": "soft", "color": "#332a88" },
|
|
8
|
+
"mouth": { "type": "smile", "size": 1, "y": 0, "color": "", "lipColor": "#e11d48" },
|
|
9
|
+
"cheeks": { "type": "blush", "color": "#ff7eb6" },
|
|
10
|
+
"accessories": []
|
|
11
|
+
}
|
package/brand/tokens.css
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 7ots.com — tokens de marca (ver docs/BRAND.md).
|
|
3
|
+
* Escalas fijas + tokens semánticos con tema claro/oscuro.
|
|
4
|
+
* Tema: automático por prefers-color-scheme, o forzado con <html data-theme="light|dark">.
|
|
5
|
+
*/
|
|
6
|
+
:root {
|
|
7
|
+
/* violeta (primario) */
|
|
8
|
+
--v-50: #f3f1ff; --v-100: #e8e5ff; --v-200: #d3cdff; --v-300: #b3a9ff; --v-400: #8b7cff; --v-500: #6d5dfc;
|
|
9
|
+
--v-600: #5b47f0; --v-700: #4b36d6; --v-800: #3d2dad; --v-900: #332a88; --v-950: #1e1850;
|
|
10
|
+
/* cian (acento) */
|
|
11
|
+
--c-50: #ecfeff; --c-100: #cffafe; --c-200: #a5f3fc; --c-300: #67e8f9; --c-400: #22d3ee; --c-500: #06b6d4;
|
|
12
|
+
--c-600: #0891b2; --c-700: #0e7490; --c-800: #155e75; --c-900: #164e63; --c-950: #083344;
|
|
13
|
+
/* neutros con tinte violeta */
|
|
14
|
+
--n-0: #ffffff; --n-50: #f8f7fc; --n-100: #f0eff7; --n-200: #e2e0ee; --n-300: #c9c6db; --n-400: #9d9ab0;
|
|
15
|
+
--n-500: #6b6880; --n-600: #514e66; --n-700: #3b3950; --n-800: #25233a; --n-900: #16152a; --n-950: #0b0a14;
|
|
16
|
+
|
|
17
|
+
/* tipografía */
|
|
18
|
+
--font-display: 'Bricolage Grotesque', Inter, system-ui, sans-serif;
|
|
19
|
+
--font-body: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
20
|
+
--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
21
|
+
|
|
22
|
+
/* forma */
|
|
23
|
+
--radius-sm: 8px; --radius-md: 16px; --radius-lg: 24px; --radius-pill: 999px;
|
|
24
|
+
--container: 1200px; --measure: 660px;
|
|
25
|
+
|
|
26
|
+
/* semánticos: claro */
|
|
27
|
+
--bg: var(--n-50); --surface: var(--n-0); --surface-2: var(--n-100); --border: var(--n-200);
|
|
28
|
+
--text: var(--n-900); --text-muted: var(--n-500);
|
|
29
|
+
--primary: var(--v-600); --primary-hover: var(--v-700); --on-primary: #fff; --primary-soft: var(--v-100);
|
|
30
|
+
--link: var(--v-600); --accent: var(--c-400); --accent-text: var(--c-700);
|
|
31
|
+
--success: #16a34a; --warning: #d97706; --error: #dc2626;
|
|
32
|
+
--shadow: 0 1px 2px rgb(22 21 42 / .06), 0 8px 24px rgb(22 21 42 / .06);
|
|
33
|
+
--shadow-lg: 0 2px 4px rgb(22 21 42 / .05), 0 24px 60px rgb(22 21 42 / .12);
|
|
34
|
+
--glow: none;
|
|
35
|
+
color-scheme: light;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@media (prefers-color-scheme: dark) {
|
|
39
|
+
:root:not([data-theme='light']) {
|
|
40
|
+
--bg: var(--n-950); --surface: var(--n-900); --surface-2: #1d1c33; --border: #ffffff14;
|
|
41
|
+
--text: var(--n-50); --text-muted: var(--n-400);
|
|
42
|
+
--primary: var(--v-400); --primary-hover: var(--v-300); --on-primary: var(--n-950); --primary-soft: #221d4a;
|
|
43
|
+
--link: var(--v-300); --accent-text: var(--c-400);
|
|
44
|
+
--success: #4ade80; --warning: #fbbf24; --error: #f87171;
|
|
45
|
+
--shadow: inset 0 1px 0 #ffffff0f; --shadow-lg: inset 0 1px 0 #ffffff0f, 0 30px 80px rgb(0 0 0 / .45);
|
|
46
|
+
--glow: 0 0 120px 20px rgb(109 93 252 / .25);
|
|
47
|
+
color-scheme: dark;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
:root[data-theme='dark'] {
|
|
51
|
+
--bg: var(--n-950); --surface: var(--n-900); --surface-2: #1d1c33; --border: #ffffff14;
|
|
52
|
+
--text: var(--n-50); --text-muted: var(--n-400);
|
|
53
|
+
--primary: var(--v-400); --primary-hover: var(--v-300); --on-primary: var(--n-950); --primary-soft: #221d4a;
|
|
54
|
+
--link: var(--v-300); --accent-text: var(--c-400);
|
|
55
|
+
--success: #4ade80; --warning: #fbbf24; --error: #f87171;
|
|
56
|
+
--shadow: inset 0 1px 0 #ffffff0f; --shadow-lg: inset 0 1px 0 #ffffff0f, 0 30px 80px rgb(0 0 0 / .45);
|
|
57
|
+
--glow: 0 0 120px 20px rgb(109 93 252 / .25);
|
|
58
|
+
color-scheme: dark;
|
|
59
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 136.22 48" role="img" aria-label="7ots">
|
|
2
|
+
<style>.bg{fill:#5b47f0}.fg{fill:#fff}.dot{fill:#22d3ee}.tx{fill:#16152a}@media (prefers-color-scheme:dark){.tx{fill:#f8f7fc}}</style>
|
|
3
|
+
<g transform="scale(.75)"><path class="bg" d="M64 32L63.98 41.83L63.93 44.97L63.84 47.24L63.72 49.07L63.56 50.64L63.36 52.01L63.13 53.23L62.86 54.33L62.56 55.33L62.21 56.25L61.83 57.1L61.4 57.87L60.93 58.59L60.42 59.25L59.86 59.86L59.25 60.42L58.59 60.93L57.87 61.4L57.1 61.83L56.25 62.21L55.33 62.56L54.33 62.86L53.23 63.13L52.01 63.36L50.64 63.56L49.07 63.72L47.24 63.84L44.97 63.93L41.83 63.98L32 64L22.17 63.98L19.03 63.93L16.76 63.84L14.93 63.72L13.36 63.56L11.99 63.36L10.77 63.13L9.67 62.86L8.67 62.56L7.75 62.21L6.9 61.83L6.13 61.4L5.41 60.93L4.75 60.42L4.14 59.86L3.58 59.25L3.07 58.59L2.6 57.87L2.17 57.1L1.79 56.25L1.44 55.33L1.14 54.33L0.87 53.23L0.64 52.01L0.44 50.64L0.28 49.07L0.16 47.24L0.07 44.97L0.02 41.83L0 32L0.02 22.17L0.07 19.03L0.16 16.76L0.28 14.93L0.44 13.36L0.64 11.99L0.87 10.77L1.14 9.67L1.44 8.67L1.79 7.75L2.17 6.9L2.6 6.13L3.07 5.41L3.58 4.75L4.14 4.14L4.75 3.58L5.41 3.07L6.13 2.6L6.9 2.17L7.75 1.79L8.67 1.44L9.67 1.14L10.77 0.87L11.99 0.64L13.36 0.44L14.93 0.28L16.76 0.16L19.03 0.07L22.17 0.02L32 0L41.83 0.02L44.97 0.07L47.24 0.16L49.07 0.28L50.64 0.44L52.01 0.64L53.23 0.87L54.33 1.14L55.33 1.44L56.25 1.79L57.1 2.17L57.87 2.6L58.59 3.07L59.25 3.58L59.86 4.14L60.42 4.75L60.93 5.41L61.4 6.13L61.83 6.9L62.21 7.75L62.56 8.67L62.86 9.67L63.13 10.77L63.36 11.99L63.56 13.36L63.72 14.93L63.84 16.76L63.93 19.03L63.98 22.17Z"/><path class="fg" d="M21.16 49Q21.16 45.4 22 41.8Q22.85 38.2 24.45 34.75Q26.04 31.31 28.33 28.12Q30.62 24.93 33.55 22.1V21.89H16.78V15H41.22V21.89Q39.31 23.9 37.38 26.88Q35.46 29.87 33.86 33.47Q32.27 37.07 31.31 41.03Q30.36 44.99 30.36 49Z"/><circle cx="44.5" cy="44.4" r="4.7" class="dot"/></g>
|
|
4
|
+
<path class="tx" d="M64.47 37.5Q64.47 34.64 65.15 31.78Q65.82 28.92 67.09 26.19Q68.35 23.45 70.17 20.92Q71.99 18.38 74.32 16.14V15.97H61V10.5H80.4V15.97Q78.89 17.57 77.36 19.94Q75.83 22.3 74.56 25.16Q73.3 28.02 72.54 31.17Q71.78 34.31 71.78 37.5ZM90.94 38.07Q87.84 38.07 85.43 36.83Q83.02 35.58 81.67 33.07Q80.32 30.56 80.32 26.76Q80.32 22.8 81.73 20.28Q83.14 17.77 85.57 16.57Q88 15.36 91.06 15.36Q94.25 15.36 96.64 16.61Q99.03 17.85 100.36 20.36Q101.68 22.88 101.68 26.72Q101.68 30.68 100.27 33.19Q98.87 35.7 96.43 36.89Q94 38.07 90.94 38.07ZM91.1 33.21Q92.37 33.21 93.23 32.5Q94.09 31.78 94.54 30.39Q94.98 29 94.98 27.08Q94.98 24.96 94.49 23.51Q94 22.06 93.09 21.28Q92.17 20.51 90.86 20.51Q89.63 20.51 88.78 21.22Q87.92 21.94 87.47 23.33Q87.02 24.71 87.02 26.68Q87.02 29.78 88.1 31.5Q89.18 33.21 91.1 33.21ZM111.24 37.99Q107.44 37.99 105.58 36.05Q103.73 34.11 103.73 30.02V21.08H101.36V16.91H101.48Q104.01 16.59 105.07 15.22Q106.14 13.85 106.38 11.07V10.99H110.26V15.93H115.2V21.24H110.26V29.41Q110.26 30.96 111.02 31.62Q111.77 32.27 112.88 32.27Q113.45 32.27 114.06 32.11Q114.67 31.94 115.2 31.66V37.42Q113.94 37.79 112.96 37.89Q111.98 37.99 111.24 37.99ZM125.66 38.07Q123.5 38.07 121.64 37.64Q119.78 37.21 118.41 36.27Q117.04 35.34 116.33 33.76Q115.61 32.19 115.74 29.94L121.49 29.45Q121.58 30.8 122.11 31.68Q122.64 32.56 123.58 32.97Q124.52 33.37 125.87 33.37Q127.13 33.37 128.05 32.95Q128.97 32.52 128.97 31.5Q128.97 30.96 128.64 30.62Q128.32 30.27 127.44 29.96Q126.56 29.66 124.89 29.25Q122.8 28.72 121.13 28.19Q119.45 27.66 118.27 26.92Q117.08 26.19 116.47 25.1Q115.86 24.02 115.86 22.43Q115.86 20.18 117.06 18.61Q118.27 17.04 120.39 16.2Q122.52 15.36 125.29 15.36Q127.79 15.36 129.95 16.14Q132.11 16.91 133.36 18.67Q134.61 20.43 134.28 23.33L128.64 23.86Q128.77 22.67 128.32 21.84Q127.87 21 127.01 20.57Q126.15 20.14 124.93 20.14Q123.54 20.14 122.78 20.65Q122.03 21.16 122.03 21.94Q122.03 22.55 122.5 22.98Q122.97 23.41 123.97 23.71Q124.97 24.02 126.56 24.39Q127.95 24.67 129.46 25.12Q130.97 25.57 132.28 26.33Q133.59 27.08 134.4 28.31Q135.22 29.53 135.22 31.41Q135.22 33.42 134.12 34.93Q133.01 36.44 130.89 37.25Q128.77 38.07 125.66 38.07Z"/>
|
|
5
|
+
</svg>
|
package/cli/7ots.mjs
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 7ots CLI — identities, the desktop pet, hooks, apuchat meet and the setup wizard.
|
|
4
|
+
* `7ots help` lists the commands. Texts are English by default (es/pt: SEVENOTS_LANG, config or LANG).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import { openSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { createIdentity, describe, identityPrompt, loadIdentity, cleanSeed } from './lib/identity.mjs';
|
|
11
|
+
import { loadConfig } from './lib/config.mjs';
|
|
12
|
+
import { t, cliLang } from './lib/i18n.mjs';
|
|
13
|
+
import { ensureHome, homeFile, PET_PORT, PKG_ROOT } from './lib/paths.mjs';
|
|
14
|
+
import { applyEvent, loadPet, savePet, tick, view } from './lib/pet-core.mjs';
|
|
15
|
+
import { petAlive, petToken, postPet, startPetServer } from './lib/pet-server.mjs';
|
|
16
|
+
import { fromClaudeHook, installClaudeHooks, installShellHooks, redact, removeClaudeHooks, removeShellHooks } from './lib/hooks.mjs';
|
|
17
|
+
import { findHtml, installHtml, platformLink, pretty, snippet } from './lib/install.mjs';
|
|
18
|
+
import { promptText } from './lib/prompts.mjs';
|
|
19
|
+
import { openUrl, graphical } from './lib/open.mjs';
|
|
20
|
+
|
|
21
|
+
const ELECTRON = 'electron@37';
|
|
22
|
+
const tty = process.stdout.isTTY;
|
|
23
|
+
const bold = (s) => (tty ? `\x1b[1m${s}\x1b[0m` : s);
|
|
24
|
+
const dim = (s) => (tty ? `\x1b[2m${s}\x1b[0m` : s);
|
|
25
|
+
|
|
26
|
+
function parse(argv) {
|
|
27
|
+
const pos = [];
|
|
28
|
+
const flags = {};
|
|
29
|
+
for (let i = 0; i < argv.length; i++) {
|
|
30
|
+
const a = argv[i];
|
|
31
|
+
if (a === '--') {
|
|
32
|
+
pos.push(...argv.slice(i + 1));
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
if (a.startsWith('--')) {
|
|
36
|
+
const [k, v] = a.slice(2).split(/=(.*)/s);
|
|
37
|
+
if (v !== undefined) flags[k] = v;
|
|
38
|
+
else if (argv[i + 1] !== undefined && VALUED.has(k) && (!argv[i + 1].startsWith('-') || ['cmd', 'text', 'code'].includes(k))) flags[k] = argv[++i];
|
|
39
|
+
else flags[k] = true;
|
|
40
|
+
} else if (/^-[a-z]$/.test(a)) flags[{ h: 'help', g: 'global', f: 'force' }[a[1]] || a[1]] = true;
|
|
41
|
+
else pos.push(a);
|
|
42
|
+
}
|
|
43
|
+
return { pos, flags };
|
|
44
|
+
}
|
|
45
|
+
const VALUED = new Set(['seed', 'name', 'lang', 'target', 'mode', 'type', 'code', 'cmd', 'tool', 'from', 'port', 'text', 'minutes']);
|
|
46
|
+
|
|
47
|
+
const fail = (msg, code = 1) => {
|
|
48
|
+
console.error(msg);
|
|
49
|
+
process.exit(code);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function needIdentity() {
|
|
53
|
+
const { identity, file } = loadIdentity();
|
|
54
|
+
if (!identity) fail(t('err.noIdentity'));
|
|
55
|
+
return { identity, file };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* ───────────── identity ───────────── */
|
|
59
|
+
|
|
60
|
+
function cmdNew({ flags }) {
|
|
61
|
+
const r = createIdentity({ seed: flags.seed, name: typeof flags.name === 'string' ? flags.name : undefined, lang: flags.lang, global: !!flags.global, force: !!flags.force });
|
|
62
|
+
if (flags.json) return console.log(JSON.stringify({ ...r.identity, file: r.file, kept: r.kept }, null, 2));
|
|
63
|
+
console.log(r.kept ? t('new.kept', { file: pretty(r.file) }) : t('new.created', { file: pretty(r.file) }));
|
|
64
|
+
console.log('\n' + describe(r.identity, bold) + '\n');
|
|
65
|
+
console.log(dim(t('new.next')));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function cmdShow({ flags }) {
|
|
69
|
+
const { identity, file } = loadIdentity();
|
|
70
|
+
if (!identity) return fail(t('show.none'));
|
|
71
|
+
if (flags.json) return console.log(JSON.stringify(identity, null, 2));
|
|
72
|
+
if (flags.prompt) return console.log(identityPrompt(identity));
|
|
73
|
+
console.log(dim(pretty(file)) + '\n' + describe(identity, bold));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function cmdInstall({ pos, flags }) {
|
|
77
|
+
const { identity, file } = needIdentity();
|
|
78
|
+
const target = flags.target || (pos[0] ? 'html' : 'auto');
|
|
79
|
+
const app = platformLink(identity.seed);
|
|
80
|
+
const home = loadConfig().home;
|
|
81
|
+
if (target === 'self') {
|
|
82
|
+
console.log(t('install.self', { file: JSON.stringify(file) }));
|
|
83
|
+
return console.log(dim(t('install.platform', { app })));
|
|
84
|
+
}
|
|
85
|
+
const block = snippet(identity, { home });
|
|
86
|
+
const html = target === 'print' ? null : pos[0] || findHtml();
|
|
87
|
+
if (!html) {
|
|
88
|
+
console.log(t('install.noHtml') + '\n\n' + block + '\n');
|
|
89
|
+
} else {
|
|
90
|
+
const r = installHtml(html, block);
|
|
91
|
+
console.log(t(r.updated ? 'install.htmlUpdated' : 'install.html', { file: pretty(html) }));
|
|
92
|
+
}
|
|
93
|
+
if (home.kind === '7ots') return;
|
|
94
|
+
if (home.kind === 'server') return console.log(dim(t('install.platform', { app })));
|
|
95
|
+
console.log(dim(t('install.endpointLocal', { file: JSON.stringify(file), app })));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* ───────────── pet ───────────── */
|
|
99
|
+
|
|
100
|
+
async function chooseMode(identity, flags) {
|
|
101
|
+
let mode = flags.mode || loadConfig().pet.mode || 'ask';
|
|
102
|
+
if (mode === 'ask' && !(process.stdin.isTTY && tty)) mode = 'auto';
|
|
103
|
+
if (mode === 'ask') {
|
|
104
|
+
const { choose } = await import('./lib/wizard.mjs');
|
|
105
|
+
mode = await choose(t('pet.chooseMode', { name: identity.name }), [
|
|
106
|
+
['desktop', t('mode.desktop')],
|
|
107
|
+
['terminal', t('mode.terminal')],
|
|
108
|
+
['browser', t('mode.browser')],
|
|
109
|
+
], graphical() ? 'desktop' : 'terminal');
|
|
110
|
+
}
|
|
111
|
+
if (mode === 'auto') mode = graphical() ? 'desktop' : 'terminal';
|
|
112
|
+
if (!['desktop', 'terminal', 'browser'].includes(mode)) fail(`--mode desktop|terminal|browser|auto`);
|
|
113
|
+
return mode;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Starts the daemon in the background (it outlives this command). */
|
|
117
|
+
async function spawnDaemon() {
|
|
118
|
+
ensureHome();
|
|
119
|
+
const log = openSync(homeFile('pet.log'), 'a');
|
|
120
|
+
const child = spawn(process.execPath, [join(PKG_ROOT, 'cli', '7ots.mjs'), 'pet', '--daemon'], {
|
|
121
|
+
detached: true,
|
|
122
|
+
stdio: ['ignore', log, log],
|
|
123
|
+
env: process.env,
|
|
124
|
+
});
|
|
125
|
+
child.unref();
|
|
126
|
+
for (let i = 0; i < 40; i++) {
|
|
127
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
128
|
+
const h = await petAlive();
|
|
129
|
+
if (h) return h;
|
|
130
|
+
}
|
|
131
|
+
fail(`the pet did not start; see ${homeFile('pet.log')}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function petUrl(desktop = false) {
|
|
135
|
+
return `http://127.0.0.1:${PET_PORT}/pet?${desktop ? 'desktop=1&' : ''}token=${petToken()}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Electron window (detached). Resolves ok once it has stayed up for a few seconds. */
|
|
139
|
+
function openDesktop() {
|
|
140
|
+
console.log(dim(t('pet.electron')));
|
|
141
|
+
const child = spawn('npx', ['-y', ELECTRON, join(PKG_ROOT, 'cli', 'pet', 'electron', 'main.cjs')], {
|
|
142
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
143
|
+
detached: true,
|
|
144
|
+
env: { ...process.env, SEVENOTS_PET_URL: `http://127.0.0.1:${PET_PORT}`, SEVENOTS_PET_TOKEN: petToken() },
|
|
145
|
+
shell: process.platform === 'win32',
|
|
146
|
+
});
|
|
147
|
+
return new Promise((resolve) => {
|
|
148
|
+
let err = '';
|
|
149
|
+
child.stderr.on('data', (d) => (err = (err + d).slice(-400)));
|
|
150
|
+
child.once('error', (e) => resolve({ ok: false, error: e.message }));
|
|
151
|
+
// The first run downloads Electron: wait up to 3 min for it to either die or settle.
|
|
152
|
+
const began = Date.now();
|
|
153
|
+
let settled = began;
|
|
154
|
+
child.stderr.on('data', () => (settled = Date.now()));
|
|
155
|
+
const timer = setInterval(() => {
|
|
156
|
+
if (Date.now() - settled > 6000 || Date.now() - began > 180_000) {
|
|
157
|
+
clearInterval(timer);
|
|
158
|
+
child.stderr.destroy();
|
|
159
|
+
child.unref();
|
|
160
|
+
resolve({ ok: true });
|
|
161
|
+
}
|
|
162
|
+
}, 500);
|
|
163
|
+
child.once('exit', (code) => {
|
|
164
|
+
clearInterval(timer);
|
|
165
|
+
resolve(code ? { ok: false, error: (err.trim().split('\n').pop() || `exit ${code}`).slice(0, 160) } : { ok: true });
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function cmdPet({ flags }) {
|
|
171
|
+
if (flags.daemon) {
|
|
172
|
+
try {
|
|
173
|
+
await startPetServer({ port: PET_PORT, log: (s) => console.log(new Date().toISOString(), s) });
|
|
174
|
+
} catch (e) {
|
|
175
|
+
if (e.code === 'NO_IDENTITY') fail(t('err.noIdentity'));
|
|
176
|
+
if (e.code === 'EADDRINUSE') fail(`port ${PET_PORT} is busy (SEVENOTS_PET_PORT changes it)`);
|
|
177
|
+
throw e;
|
|
178
|
+
}
|
|
179
|
+
return; // the server keeps the process alive
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const { identity } = needIdentity();
|
|
183
|
+
const mode = await chooseMode(identity, flags);
|
|
184
|
+
const detach = !!flags.detach;
|
|
185
|
+
|
|
186
|
+
let alive = await petAlive();
|
|
187
|
+
let local = null;
|
|
188
|
+
if (alive) {
|
|
189
|
+
console.log(dim(t('pet.already', { name: alive.name, pid: alive.pid })));
|
|
190
|
+
} else if (detach || mode !== 'terminal') {
|
|
191
|
+
// Desktop and browser windows need a daemon that lives on its own.
|
|
192
|
+
alive = await spawnDaemon();
|
|
193
|
+
} else {
|
|
194
|
+
local = await startPetServer({ port: PET_PORT });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (mode === 'terminal') {
|
|
198
|
+
const { runTerminal } = await import('./lib/terminal.mjs');
|
|
199
|
+
const annoy = Number(loadConfig().pet.annoy ?? 2);
|
|
200
|
+
await runTerminal({ identity, lang: identity.language || cliLang(), port: PET_PORT, bell: annoy >= 3 });
|
|
201
|
+
if (local) local.stop();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (mode === 'desktop') {
|
|
206
|
+
const r = await openDesktop();
|
|
207
|
+
if (!r.ok) {
|
|
208
|
+
console.log(t('pet.electronFail', { error: r.error }));
|
|
209
|
+
openUrl(petUrl());
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
openUrl(petUrl());
|
|
213
|
+
console.log(t('pet.running', { name: identity.name, url: `http://127.0.0.1:${PET_PORT}/pet` }));
|
|
214
|
+
}
|
|
215
|
+
console.log(t('pet.detached', { name: identity.name }));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Events from hooks. Never fails loudly: a hook must not break the agent or the shell. */
|
|
219
|
+
async function cmdEvent({ flags }) {
|
|
220
|
+
let ev = null;
|
|
221
|
+
try {
|
|
222
|
+
if (flags.from === 'claude') {
|
|
223
|
+
let raw = '';
|
|
224
|
+
if (!process.stdin.isTTY) for await (const c of process.stdin) if ((raw += c).length > 512 * 1024) break;
|
|
225
|
+
ev = fromClaudeHook(JSON.parse(raw || '{}'));
|
|
226
|
+
} else if (flags.type) {
|
|
227
|
+
ev = { type: String(flags.type) };
|
|
228
|
+
if (flags.code !== undefined) ev.code = Number(flags.code) || 0;
|
|
229
|
+
if (typeof flags.cmd === 'string') ev.cmd = redact(flags.cmd);
|
|
230
|
+
if (typeof flags.tool === 'string') ev.tool = flags.tool.slice(0, 60);
|
|
231
|
+
if (typeof flags.text === 'string') ev.text = flags.text.slice(0, 300);
|
|
232
|
+
}
|
|
233
|
+
} catch {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (!ev) return;
|
|
237
|
+
if (await postPet('/event', ev)) return;
|
|
238
|
+
// No daemon: the state still changes (food, xp…), it just doesn't talk.
|
|
239
|
+
const p = tick(loadPet());
|
|
240
|
+
applyEvent(p, ev);
|
|
241
|
+
savePet(p);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const CARE = { feed: 'feed', play: 'play', sleep: 'sleep', wake: 'wake' };
|
|
245
|
+
|
|
246
|
+
async function cmdCare(cmd, { pos, flags }) {
|
|
247
|
+
const ev = cmd === 'say' ? { type: 'say', text: (flags.text || pos.join(' ')).toString().slice(0, 300) } : { type: CARE[cmd] };
|
|
248
|
+
if (await postPet('/event', ev)) return cmdStatus();
|
|
249
|
+
const p = tick(loadPet());
|
|
250
|
+
applyEvent(p, ev);
|
|
251
|
+
savePet(p);
|
|
252
|
+
console.log(dim(t('pet.notRunning')));
|
|
253
|
+
await cmdStatus();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function cmdStatus({ flags = {} } = {}) {
|
|
257
|
+
const { identity } = loadIdentity();
|
|
258
|
+
let state;
|
|
259
|
+
try {
|
|
260
|
+
const r = await fetch(`http://127.0.0.1:${PET_PORT}/state?token=${petToken()}`, { signal: AbortSignal.timeout(800) });
|
|
261
|
+
if (r.ok) state = (await r.json()).state;
|
|
262
|
+
} catch {}
|
|
263
|
+
state ||= view(tick(loadPet()));
|
|
264
|
+
if (flags.json) return console.log(JSON.stringify(state, null, 2));
|
|
265
|
+
console.log(
|
|
266
|
+
t('pet.status', {
|
|
267
|
+
name: identity?.name || '7ots',
|
|
268
|
+
level: state.level,
|
|
269
|
+
food: state.food,
|
|
270
|
+
energy: state.energy,
|
|
271
|
+
fun: state.fun,
|
|
272
|
+
asleep: state.asleep ? t('pet.asleep') : '',
|
|
273
|
+
}),
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function cmdStop() {
|
|
278
|
+
const alive = await petAlive();
|
|
279
|
+
if (!alive) return console.log(dim(t('pet.notRunning')));
|
|
280
|
+
await postPet('/quit');
|
|
281
|
+
console.log(t('pet.stopped', { name: alive.name }));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/* ───────────── hooks ───────────── */
|
|
285
|
+
|
|
286
|
+
function cmdHooks({ pos, flags }) {
|
|
287
|
+
const action = pos[0] || 'install';
|
|
288
|
+
const both = !flags.claude && !flags.shell;
|
|
289
|
+
if (action === 'remove' || action === 'uninstall') {
|
|
290
|
+
if (both || flags.claude) removeClaudeHooks({ global: !!flags.global });
|
|
291
|
+
if (both || flags.shell) removeShellHooks();
|
|
292
|
+
return console.log(t('hooks.removed'));
|
|
293
|
+
}
|
|
294
|
+
if (action !== 'install') return fail('7ots hooks install|remove [--claude] [--shell] [--global]');
|
|
295
|
+
if (both || flags.claude) console.log(t('hooks.claude', { file: pretty(installClaudeHooks({ global: !!flags.global })) }));
|
|
296
|
+
if (flags.shell) {
|
|
297
|
+
const files = installShellHooks();
|
|
298
|
+
console.log(files.length ? t('hooks.shell', { files: files.join(', ') }) : t('hooks.shellNone'));
|
|
299
|
+
console.log(dim(t('hooks.reload')));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/* ───────────── meet ───────────── */
|
|
304
|
+
|
|
305
|
+
async function cmdMeet({ pos, flags }) {
|
|
306
|
+
const { identity } = needIdentity();
|
|
307
|
+
const config = loadConfig();
|
|
308
|
+
const { newCall, parseInvite, joinCall } = await import('./lib/meet.mjs');
|
|
309
|
+
let invite;
|
|
310
|
+
if (flags.new) {
|
|
311
|
+
console.log(dim(t('meet.creating')));
|
|
312
|
+
const call = await newCall();
|
|
313
|
+
invite = { channel_id: call.channel_id, token: call.token, pin: call.pin };
|
|
314
|
+
console.log(t('meet.open', { name: identity.name, url: call.url }));
|
|
315
|
+
} else {
|
|
316
|
+
invite = parseInvite(pos.join(' '));
|
|
317
|
+
if (!invite?.channel_id || !invite?.token) fail(t('meet.badInvite'));
|
|
318
|
+
}
|
|
319
|
+
if (config.brain.kind === 'lines') console.log(dim(t('meet.noBrain')));
|
|
320
|
+
console.log(dim(t('meet.joining', { name: identity.name, avatar: identity.look.meetAvatar })));
|
|
321
|
+
await joinCall({ invite, identity, config, log: (s) => console.log(dim(s)), maxMinutes: Number(flags.minutes) || 30 });
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* ───────────── orquesta ───────────── */
|
|
325
|
+
|
|
326
|
+
async function cmdLogin({ pos }) {
|
|
327
|
+
if ((pos[0] || 'orquesta') !== 'orquesta') return fail('7ots login orquesta');
|
|
328
|
+
const { login } = await import('./lib/orquesta.mjs');
|
|
329
|
+
const r = await login({ open: openUrl, onWaiting: (url) => console.log(t('login.open', { url })) });
|
|
330
|
+
console.log(t('login.ok', { org: r.organizationName || r.organizationId }));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/* ───────────── main ───────────── */
|
|
334
|
+
|
|
335
|
+
async function main() {
|
|
336
|
+
const [cmd = 'help', ...rest] = process.argv.slice(2);
|
|
337
|
+
const args = parse(rest);
|
|
338
|
+
if (args.flags.lang && ['en', 'es', 'pt'].includes(args.flags.lang)) (await import('./lib/i18n.mjs')).setCliLang(args.flags.lang);
|
|
339
|
+
switch (cmd) {
|
|
340
|
+
case 'new':
|
|
341
|
+
case 'create':
|
|
342
|
+
return cmdNew(args);
|
|
343
|
+
case 'show':
|
|
344
|
+
case 'whoami':
|
|
345
|
+
return cmdShow(args);
|
|
346
|
+
case 'install':
|
|
347
|
+
return cmdInstall(args);
|
|
348
|
+
case 'setup':
|
|
349
|
+
case 'wizard':
|
|
350
|
+
case 'init': {
|
|
351
|
+
const { runWizard } = await import('./lib/wizard.mjs');
|
|
352
|
+
const r = await runWizard();
|
|
353
|
+
if (r?.launch) return cmdPet({ flags: { mode: r.mode, detach: r.mode !== 'terminal' } });
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
case 'pet':
|
|
357
|
+
return cmdPet(args);
|
|
358
|
+
case 'event':
|
|
359
|
+
return cmdEvent(args);
|
|
360
|
+
case 'feed':
|
|
361
|
+
case 'play':
|
|
362
|
+
case 'sleep':
|
|
363
|
+
case 'wake':
|
|
364
|
+
case 'say':
|
|
365
|
+
return cmdCare(cmd, args);
|
|
366
|
+
case 'status':
|
|
367
|
+
return cmdStatus(args);
|
|
368
|
+
case 'stop':
|
|
369
|
+
return cmdStop();
|
|
370
|
+
case 'hooks':
|
|
371
|
+
return cmdHooks(args);
|
|
372
|
+
case 'meet':
|
|
373
|
+
return cmdMeet(args);
|
|
374
|
+
case 'prompt':
|
|
375
|
+
case 'prompts':
|
|
376
|
+
return console.log(promptText(args.pos[0] || 'all'));
|
|
377
|
+
case 'login':
|
|
378
|
+
return cmdLogin(args);
|
|
379
|
+
case 'seed':
|
|
380
|
+
return console.log(cleanSeed(args.pos[0]) || '');
|
|
381
|
+
case 'help':
|
|
382
|
+
case '--help':
|
|
383
|
+
case '-h':
|
|
384
|
+
return console.log(t('help'));
|
|
385
|
+
case '--version':
|
|
386
|
+
case '-v':
|
|
387
|
+
case 'version': {
|
|
388
|
+
const { readFileSync } = await import('node:fs');
|
|
389
|
+
return console.log(JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf8')).version);
|
|
390
|
+
}
|
|
391
|
+
default:
|
|
392
|
+
fail(t('err.unknown', { cmd }));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
main().catch((e) => {
|
|
397
|
+
if (process.argv[2] === 'event') process.exit(0); // hooks never fail
|
|
398
|
+
console.error(e?.message || e);
|
|
399
|
+
process.exit(1);
|
|
400
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pet's brain. It turns "what just happened" into one short line in the ot's voice.
|
|
3
|
+
*
|
|
4
|
+
* cli the user's own AI CLI, as a separate instance: claude -p · codex exec · gemini -p · ollama run
|
|
5
|
+
* (runs in ~/.7ots with SEVENOTS_PET_BRAIN=1 so our hooks don't fire inside it)
|
|
6
|
+
* api own key (Anthropic or any OpenAI-compatible endpoint) through server/llm.mjs
|
|
7
|
+
* batuta Orquesta's Batuta LLM (OpenAI-compatible) with ORQUESTA_TOKEN
|
|
8
|
+
* 7ots the ot's home: POST <home.url>/chat (7ots.com or your own 7ots-server)
|
|
9
|
+
* lines built-in phrases (also the fallback when the AI is slow or fails)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
13
|
+
import { identityPrompt } from '../../src/identity/schema.js';
|
|
14
|
+
import { createLLM } from '../../server/llm.mjs';
|
|
15
|
+
import { env, ORQUESTA_URL } from './config.mjs';
|
|
16
|
+
import { ensureHome, PET_PORT } from './paths.mjs';
|
|
17
|
+
|
|
18
|
+
export const AI_CLIS = {
|
|
19
|
+
claude: { bin: 'claude', args: (p, m) => ['-p', p, ...(m ? ['--model', m] : [])] },
|
|
20
|
+
codex: { bin: 'codex', args: (p, m) => ['exec', '--skip-git-repo-check', ...(m ? ['-m', m] : []), p] },
|
|
21
|
+
gemini: { bin: 'gemini', args: (p, m) => ['-p', p, ...(m ? ['-m', m] : [])] },
|
|
22
|
+
ollama: { bin: 'ollama', args: (p, m) => ['run', m || 'llama3.2', p] },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const LANG_NAME = { en: 'English', es: 'Spanish', pt: 'Portuguese' };
|
|
26
|
+
|
|
27
|
+
/** Which AI CLIs are installed (for the wizard and `--mode auto`). */
|
|
28
|
+
export function detectClis() {
|
|
29
|
+
return Object.keys(AI_CLIS).filter((k) => {
|
|
30
|
+
const r = spawnSync(process.platform === 'win32' ? 'where' : 'which', [AI_CLIS[k].bin], { stdio: 'ignore' });
|
|
31
|
+
return r.status === 0;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** System prompt shared by every brain. */
|
|
36
|
+
export function petSystem(identity, { lang, role = 'pet' } = {}) {
|
|
37
|
+
const L = LANG_NAME[lang || identity.language] || 'English';
|
|
38
|
+
const base = identityPrompt(identity, 'en');
|
|
39
|
+
const job =
|
|
40
|
+
role === 'meet'
|
|
41
|
+
? `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
|
+
: `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
|
+
return `${base}\n${job}\nAlways answer in ${L}.`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function runCli({ cli, model, command }, prompt, timeoutMs) {
|
|
47
|
+
const spec = cli === 'custom' ? { bin: '/bin/sh', args: (p) => ['-c', command, 'sh', p] } : AI_CLIS[cli];
|
|
48
|
+
if (!spec) return Promise.reject(new Error(`unknown cli ${cli}`));
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const child = spawn(spec.bin, spec.args(prompt, model), {
|
|
51
|
+
cwd: ensureHome(),
|
|
52
|
+
env: { ...process.env, SEVENOTS_PET_BRAIN: '1', NO_COLOR: '1' },
|
|
53
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
54
|
+
});
|
|
55
|
+
let out = '';
|
|
56
|
+
const timer = setTimeout(() => {
|
|
57
|
+
child.kill('SIGTERM');
|
|
58
|
+
reject(new Error('timeout'));
|
|
59
|
+
}, timeoutMs);
|
|
60
|
+
child.stdout.on('data', (d) => (out += d).length > 20000 && child.kill('SIGTERM'));
|
|
61
|
+
child.on('error', (e) => {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
reject(e);
|
|
64
|
+
});
|
|
65
|
+
child.on('close', (code) => {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
code === 0 && out.trim() ? resolve(out) : reject(new Error(`exit ${code}`));
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Last meaningful paragraph of a CLI's output, cleaned of ANSI and quotes. */
|
|
73
|
+
export function cleanReply(text, max = 400) {
|
|
74
|
+
const s = String(text || '')
|
|
75
|
+
.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '')
|
|
76
|
+
.trim()
|
|
77
|
+
.split(/\n\s*\n/)
|
|
78
|
+
.filter((p) => p.trim())
|
|
79
|
+
.pop() || '';
|
|
80
|
+
return s.replace(/\s+/g, ' ').replace(/^["'«“]+|["'»”]+$/g, '').trim().slice(0, max);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {object} brain config.brain
|
|
85
|
+
* @param {object} home config.home
|
|
86
|
+
* @returns {{ kind: string, think: (o: { system: string, messages: {role,content}[] }) => Promise<string> }}
|
|
87
|
+
*/
|
|
88
|
+
export function createBrain(brain = { kind: 'lines' }, home = {}) {
|
|
89
|
+
const keys = env();
|
|
90
|
+
if (brain.kind === 'cli') {
|
|
91
|
+
return {
|
|
92
|
+
kind: 'cli',
|
|
93
|
+
async think({ system, messages, timeoutMs = 45000 }) {
|
|
94
|
+
const convo = messages.map((m) => `${m.role === 'user' ? 'Them' : 'You'}: ${m.content}`).join('\n');
|
|
95
|
+
return cleanReply(await runCli(brain, `${system}\n\n${convo}\nYou:`, timeoutMs));
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (brain.kind === 'api' || brain.kind === 'batuta') {
|
|
100
|
+
const llmEnv =
|
|
101
|
+
brain.kind === 'batuta'
|
|
102
|
+
? { LLM_PROVIDER: 'openai', LLM_BASE_URL: `${ORQUESTA_URL}/api/v1`, OPENAI_API_KEY: keys.ORQUESTA_TOKEN, LLM_MODEL: brain.model || 'batuta-auto' }
|
|
103
|
+
: {
|
|
104
|
+
LLM_PROVIDER: brain.provider || 'anthropic',
|
|
105
|
+
LLM_MODEL: brain.model || '',
|
|
106
|
+
LLM_BASE_URL: brain.baseUrl || '',
|
|
107
|
+
LLM_EFFORT: 'low',
|
|
108
|
+
LLM_MAX_TOKENS: '2000',
|
|
109
|
+
ANTHROPIC_API_KEY: keys.ANTHROPIC_API_KEY,
|
|
110
|
+
OPENAI_API_KEY: keys.OPENAI_API_KEY,
|
|
111
|
+
};
|
|
112
|
+
const llm = createLLM(llmEnv);
|
|
113
|
+
return {
|
|
114
|
+
kind: brain.kind,
|
|
115
|
+
async think({ system, messages }) {
|
|
116
|
+
const r = await llm.step({ system, messages, tools: [] });
|
|
117
|
+
return cleanReply(r.text);
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (brain.kind === '7ots' && home.url) {
|
|
122
|
+
const url = `${home.url.replace(/\/+$/, '')}/chat`;
|
|
123
|
+
return {
|
|
124
|
+
kind: '7ots',
|
|
125
|
+
async think({ system, messages, timeoutMs = 45000 }) {
|
|
126
|
+
// The ot's home only serves its own domains: add http://127.0.0.1:<port> to them in /app/.
|
|
127
|
+
const res = await fetch(url, {
|
|
128
|
+
method: 'POST',
|
|
129
|
+
headers: { 'Content-Type': 'application/json', Origin: `http://127.0.0.1:${PET_PORT}` },
|
|
130
|
+
body: JSON.stringify({ system, messages, tools: [] }),
|
|
131
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
132
|
+
});
|
|
133
|
+
const data = await res.json().catch(() => ({}));
|
|
134
|
+
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
|
|
135
|
+
return cleanReply(data.text);
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
kind: 'lines',
|
|
141
|
+
think: async () => '',
|
|
142
|
+
};
|
|
143
|
+
}
|