@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,70 @@
1
+ /**
2
+ * Configuration written by the wizard (`7ots setup`) and the local keys.
3
+ *
4
+ * ~/.7ots/config.json
5
+ * lang 'en' | 'es' | 'pt'
6
+ * home where the ot lives: { kind: 'local' }
7
+ * { kind: '7ots', url: 'https://7ots.com/api/o/<id>' } hosted on 7ots.com
8
+ * { kind: 'server', url: 'https://your.site/api/agent' } your own 7ots-server
9
+ * { kind: 'orquesta', projectId, projectName } a getorquesta.com project
10
+ * brain who thinks: { kind: 'lines' } built-in phrases, no AI
11
+ * { kind: 'cli', cli: 'claude'|'codex'|'gemini'|'ollama'|'custom', model?, command? }
12
+ * { kind: 'api', provider: 'anthropic'|'openai', model?, baseUrl? }
13
+ * { kind: '7ots' } the home's /chat (7ots or own server)
14
+ * { kind: 'batuta', model? } Orquesta's Batuta LLM
15
+ * voice how it speaks: { kind: 'none'|'browser'|'apuchat'|'elevenlabs'|'grok'|'fish'|'openai', voiceId? }
16
+ * pet { mode: 'ask'|'desktop'|'terminal'|'browser', annoy: 0..3 }
17
+ *
18
+ * ~/.7ots/keys.json (0600) keeps the keys typed in the wizard under their usual env-var names.
19
+ * Variables already set in the environment always win over the file.
20
+ */
21
+
22
+ import { homeFile, readJson, writeJson } from './paths.mjs';
23
+
24
+ export const KEY_NAMES = ['ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'ORQUESTA_TOKEN', 'APUCHAT_VOICE_TOKEN', 'ELEVENLABS_API_KEY', 'XAI_API_KEY', 'FISH_API_KEY'];
25
+ export const ORQUESTA_URL = (process.env.ORQUESTA_URL || 'https://getorquesta.com').replace(/\/+$/, '');
26
+
27
+ export const CONFIG_DEFAULTS = Object.freeze({
28
+ lang: '',
29
+ home: { kind: 'local' },
30
+ brain: { kind: 'lines' },
31
+ voice: { kind: 'browser' },
32
+ pet: { mode: 'ask', annoy: 2 },
33
+ });
34
+
35
+ export function loadConfig() {
36
+ const c = readJson(homeFile('config.json'), {}) || {};
37
+ return {
38
+ ...CONFIG_DEFAULTS,
39
+ ...c,
40
+ home: { ...CONFIG_DEFAULTS.home, ...c.home },
41
+ brain: { ...CONFIG_DEFAULTS.brain, ...c.brain },
42
+ voice: { ...CONFIG_DEFAULTS.voice, ...c.voice },
43
+ pet: { ...CONFIG_DEFAULTS.pet, ...c.pet },
44
+ };
45
+ }
46
+
47
+ export function saveConfig(config) {
48
+ writeJson(homeFile('config.json'), config);
49
+ }
50
+
51
+ export function loadKeys() {
52
+ return readJson(homeFile('keys.json'), {}) || {};
53
+ }
54
+
55
+ export function saveKey(name, value) {
56
+ if (!KEY_NAMES.includes(name)) throw new Error(`unknown key ${name}`);
57
+ const keys = loadKeys();
58
+ if (value) keys[name] = String(value).trim();
59
+ else delete keys[name];
60
+ writeJson(homeFile('keys.json'), keys, { secret: true });
61
+ }
62
+
63
+ /** keys.json + process.env (the environment wins). Never print this object. */
64
+ export function env() {
65
+ const out = { ...loadKeys() };
66
+ for (const k of KEY_NAMES) if (process.env[k]) out[k] = process.env[k];
67
+ return out;
68
+ }
69
+
70
+ export const hasKey = (name) => Boolean(env()[name]);
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Hooks that tell the pet what happens.
3
+ *
4
+ * Claude Code .claude/settings.local.json (or ~/.claude/settings.json with --global):
5
+ * PostToolUse · UserPromptSubmit · Stop · Notification · SessionStart → `7ots event --from claude`
6
+ * (the hook JSON arrives on stdin).
7
+ * Shell ~/.7ots/shell.sh sourced from ~/.bashrc / ~/.zshrc: after each command,
8
+ * `7ots event --type shell --code <exit> --cmd <command>` in the background.
9
+ *
10
+ * Every entry we add runs "7ots … event", so `7ots hooks remove` finds and removes only ours.
11
+ */
12
+
13
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
14
+ import { spawnSync } from 'node:child_process';
15
+ import { homedir } from 'node:os';
16
+ import { join } from 'node:path';
17
+ import { homeFile, PKG_ROOT, readJson, writeJson, ensureHome } from './paths.mjs';
18
+
19
+ /** Our entries: `7ots event …`, `npx -y @7ots/cli event …` or `node ".../cli/7ots.mjs" event …`. */
20
+ const isOurs = (h) => /(7ots(\.mjs"?)?|@7ots\/cli)\s+event\b/.test(String(h?.command || ''));
21
+ const CLAUDE_EVENTS = ['PostToolUse', 'UserPromptSubmit', 'Stop', 'Notification', 'SessionStart'];
22
+ const RC_LINE = '[ -f "$HOME/.7ots/shell.sh" ] && . "$HOME/.7ots/shell.sh" # 7ots pet';
23
+
24
+ /** How hooks should call us: the global `7ots`, this checkout, or npx. */
25
+ export function selfCommand() {
26
+ const which = spawnSync('which', ['7ots'], { encoding: 'utf8' });
27
+ if (which.status === 0 && which.stdout.trim()) return '7ots';
28
+ if (!/[\\/]_npx[\\/]/.test(PKG_ROOT)) return `node ${JSON.stringify(join(PKG_ROOT, 'cli', '7ots.mjs'))}`;
29
+ return 'npx -y @7ots/cli';
30
+ }
31
+
32
+ function claudeSettingsFile({ global = false, cwd = process.cwd() } = {}) {
33
+ return global ? join(homedir(), '.claude', 'settings.json') : join(cwd, '.claude', 'settings.local.json');
34
+ }
35
+
36
+ export function installClaudeHooks(opts = {}) {
37
+ const file = claudeSettingsFile(opts);
38
+ const s = readJson(file, {}) || {};
39
+ s.hooks ||= {};
40
+ const cmd = `${selfCommand()} event --from claude`;
41
+ for (const ev of CLAUDE_EVENTS) {
42
+ const list = (s.hooks[ev] ||= []);
43
+ const ours = list.some((g) => (g.hooks || []).some(isOurs));
44
+ if (!ours) list.push({ ...(ev === 'PostToolUse' ? { matcher: '*' } : {}), hooks: [{ type: 'command', command: cmd, timeout: 5 }] });
45
+ }
46
+ writeJson(file, s);
47
+ return file;
48
+ }
49
+
50
+ export function removeClaudeHooks(opts = {}) {
51
+ const file = claudeSettingsFile(opts);
52
+ if (!existsSync(file)) return null;
53
+ const s = readJson(file, {}) || {};
54
+ for (const ev of Object.keys(s.hooks || {})) {
55
+ s.hooks[ev] = s.hooks[ev]
56
+ .map((g) => ({ ...g, hooks: (g.hooks || []).filter((h) => !isOurs(h)) }))
57
+ .filter((g) => g.hooks.length);
58
+ if (!s.hooks[ev].length) delete s.hooks[ev];
59
+ }
60
+ if (s.hooks && !Object.keys(s.hooks).length) delete s.hooks;
61
+ writeJson(file, s);
62
+ return file;
63
+ }
64
+
65
+ function shellScript() {
66
+ const cmd = selfCommand();
67
+ return `# 7ots pet — tells your pet how your commands go. Remove with: 7ots hooks remove
68
+ # (generated; runs in the background and never changes $?)
69
+ __7ots_send() {
70
+ [ -n "$SEVENOTS_PET_BRAIN" ] && return
71
+ ( ${cmd} event --type shell --code "$1" --cmd "$2" >/dev/null 2>&1 & ) 2>/dev/null
72
+ }
73
+ if [ -n "$ZSH_VERSION" ]; then
74
+ __7ots_preexec() { __7ots_cmd="$1"; }
75
+ __7ots_precmd() {
76
+ local code=$?
77
+ [ -n "$__7ots_cmd" ] && __7ots_send "$code" "$__7ots_cmd"
78
+ __7ots_cmd=""
79
+ return $code
80
+ }
81
+ autoload -Uz add-zsh-hook 2>/dev/null && add-zsh-hook preexec __7ots_preexec && add-zsh-hook precmd __7ots_precmd
82
+ elif [ -n "$BASH_VERSION" ]; then
83
+ __7ots_prompt() {
84
+ local code=$?
85
+ local h; h=$(HISTTIMEFORMAT= history 1)
86
+ local n=\${h%%[^ 0-9]*}
87
+ if [ -n "$__7ots_n" ] && [ "$n" != "$__7ots_n" ]; then
88
+ __7ots_send "$code" "$(printf '%s' "$h" | sed 's/^ *[0-9]* *//')"
89
+ fi
90
+ __7ots_n=$n
91
+ return $code
92
+ }
93
+ case ";$PROMPT_COMMAND;" in *";__7ots_prompt;"*) ;; *) PROMPT_COMMAND="__7ots_prompt\${PROMPT_COMMAND:+;$PROMPT_COMMAND}" ;; esac
94
+ fi
95
+ `;
96
+ }
97
+
98
+ export function installShellHooks() {
99
+ ensureHome();
100
+ writeFileSync(homeFile('shell.sh'), shellScript());
101
+ const touched = [];
102
+ for (const rc of ['.bashrc', '.zshrc']) {
103
+ const f = join(homedir(), rc);
104
+ if (!existsSync(f)) continue;
105
+ const text = readFileSync(f, 'utf8');
106
+ if (!text.includes(RC_LINE)) writeFileSync(f, `${text.replace(/\n*$/, '\n')}${RC_LINE}\n`);
107
+ touched.push(f);
108
+ }
109
+ return touched;
110
+ }
111
+
112
+ export function removeShellHooks() {
113
+ const touched = [];
114
+ for (const rc of ['.bashrc', '.zshrc']) {
115
+ const f = join(homedir(), rc);
116
+ if (!existsSync(f)) continue;
117
+ const text = readFileSync(f, 'utf8');
118
+ if (!text.includes(RC_LINE)) continue;
119
+ writeFileSync(f, text.split('\n').filter((l) => l !== RC_LINE).join('\n'));
120
+ touched.push(f);
121
+ }
122
+ return touched;
123
+ }
124
+
125
+ /** Hides what looks like a secret before a command reaches the pet (and maybe an AI). */
126
+ export function redact(cmd) {
127
+ return String(cmd || '')
128
+ .replace(/((?:key|token|secret|password|passwd|pwd|auth|bearer)[\w-]*\s*[=:]\s*|--(?:key|token|secret|password)[\w-]*[ =])\S+/gi, '$1***')
129
+ .replace(/\b(?:sk|pk|rk|ghp|gho|xox[abp]|oak|oclt|oat|acct)[-_][A-Za-z0-9_-]{8,}/g, '***')
130
+ .replace(/[A-Za-z0-9+/_-]{32,}={0,2}/g, '***')
131
+ .replace(/https?:\/\/[^\s@/]+:[^\s@/]+@/g, 'https://***@')
132
+ .slice(0, 120);
133
+ }
134
+
135
+ /** Claude Code hook JSON → pet event. */
136
+ export function fromClaudeHook(h = {}) {
137
+ const ev = h.hook_event_name;
138
+ if (ev === 'UserPromptSubmit') return { type: 'prompt', text: redact(h.prompt).slice(0, 120) };
139
+ if (ev === 'Stop' || ev === 'SubagentStop') return { type: 'stop' };
140
+ if (ev === 'Notification') return { type: 'notify', message: String(h.message || '').slice(0, 160) };
141
+ if (ev === 'SessionStart') return { type: 'start' };
142
+ if (ev === 'PostToolUse') {
143
+ const r = h.tool_response;
144
+ const failed =
145
+ r && typeof r === 'object' && (r.is_error === true || r.success === false || (r.exitCode ?? r.exit_code ?? 0) !== 0 || r.interrupted === true);
146
+ const input = h.tool_input || {};
147
+ const detail = redact(input.command || input.file_path || input.pattern || input.url || input.description || '');
148
+ return { type: 'tool', tool: String(h.tool_name || 'tool').slice(0, 40), ok: !failed, detail };
149
+ }
150
+ return null;
151
+ }