alive-ai 0.1.2 → 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/cli/tui.js ADDED
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+
3
+ const readline = require("readline");
4
+
5
+ const EVENT_PREFIX = "__ALIVE_AI_TUI__";
6
+
7
+ function stripAnsi(value) {
8
+ return String(value || "").replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "");
9
+ }
10
+
11
+ function truncate(value, width) {
12
+ const text = stripAnsi(value);
13
+ if (width <= 1) return "";
14
+ return text.length > width ? `${text.slice(0, Math.max(0, width - 1))}…` : text;
15
+ }
16
+
17
+ function wrap(value, width) {
18
+ const words = stripAnsi(value).replace(/\r/g, "").split(/\s+/);
19
+ const lines = [];
20
+ let current = "";
21
+ for (const word of words) {
22
+ if (!word) continue;
23
+ if (!current) {
24
+ current = word;
25
+ } else if (`${current} ${word}`.length <= width) {
26
+ current += ` ${word}`;
27
+ } else {
28
+ lines.push(current);
29
+ current = word;
30
+ }
31
+ while (current.length > width) {
32
+ lines.push(current.slice(0, width));
33
+ current = current.slice(width);
34
+ }
35
+ }
36
+ if (current) lines.push(current);
37
+ return lines.length ? lines : [""];
38
+ }
39
+
40
+ function boxLine(left, right, leftWidth, rightWidth) {
41
+ return `│ ${truncate(left, leftWidth).padEnd(leftWidth)} │ ${truncate(right, rightWidth).padEnd(rightWidth)} │`;
42
+ }
43
+
44
+ function formatChat(entry, width) {
45
+ const label = entry.role === "user" ? "You" : entry.role === "proactive" ? "Alice proactive" : entry.role === "assistant" ? "Alice" : "System";
46
+ const prefix = `${label}: `;
47
+ const lines = wrap(entry.text, Math.max(10, width - prefix.length));
48
+ return lines.map((line, index) => `${index === 0 ? prefix : " ".repeat(prefix.length)}${line}`);
49
+ }
50
+
51
+ function runRuntimeTui(child, options = {}) {
52
+ const state = {
53
+ chat: [],
54
+ logs: [],
55
+ input: "",
56
+ status: "starting",
57
+ dashboard: options.dashboard || "http://127.0.0.1:8080",
58
+ stopping: false,
59
+ };
60
+
61
+ let stdoutBuffer = "";
62
+ let stderrBuffer = "";
63
+ let renderTimer = null;
64
+ const isTty = process.stdin.isTTY && process.stdout.isTTY;
65
+
66
+ function addChat(role, text) {
67
+ state.chat.push({ role, text: String(text || "") });
68
+ if (state.chat.length > 200) state.chat = state.chat.slice(-200);
69
+ scheduleRender();
70
+ }
71
+
72
+ function addLog(text) {
73
+ for (const rawLine of String(text || "").split(/\n/)) {
74
+ const line = stripAnsi(rawLine).trimEnd();
75
+ if (!line || line === ">") continue;
76
+ state.logs.push(line);
77
+ }
78
+ if (state.logs.length > 400) state.logs = state.logs.slice(-400);
79
+ scheduleRender();
80
+ }
81
+
82
+ function handleLine(line, stream) {
83
+ const clean = line.trimEnd();
84
+ if (!clean) return;
85
+ if (clean.startsWith(EVENT_PREFIX)) {
86
+ try {
87
+ const event = JSON.parse(clean.slice(EVENT_PREFIX.length));
88
+ addChat(event.role || "system", event.text || "");
89
+ return;
90
+ } catch {
91
+ addLog(clean);
92
+ return;
93
+ }
94
+ }
95
+ addLog(stream === "stderr" ? `[stderr] ${clean}` : clean);
96
+ }
97
+
98
+ function consume(chunk, stream) {
99
+ let buffer = stream === "stderr" ? stderrBuffer : stdoutBuffer;
100
+ buffer += chunk.toString();
101
+ const lines = buffer.split(/\n/);
102
+ buffer = lines.pop() || "";
103
+ for (const line of lines) handleLine(line, stream);
104
+ if (stream === "stderr") stderrBuffer = buffer;
105
+ else stdoutBuffer = buffer;
106
+ }
107
+
108
+ function scheduleRender() {
109
+ if (!isTty) return;
110
+ if (renderTimer) return;
111
+ renderTimer = setTimeout(() => {
112
+ renderTimer = null;
113
+ render();
114
+ }, 30);
115
+ }
116
+
117
+ function render() {
118
+ if (!isTty) return;
119
+ const width = Math.max(80, process.stdout.columns || 100);
120
+ const height = Math.max(24, process.stdout.rows || 30);
121
+ const leftWidth = Math.max(34, Math.floor((width - 7) * 0.58));
122
+ const rightWidth = Math.max(28, width - leftWidth - 7);
123
+ const bodyHeight = height - 7;
124
+
125
+ const chatLines = [];
126
+ for (const entry of state.chat) {
127
+ chatLines.push(...formatChat(entry, leftWidth), "");
128
+ }
129
+ const visibleChat = chatLines.slice(-bodyHeight);
130
+ const visibleLogs = state.logs.slice(-bodyHeight);
131
+
132
+ const top = `┌${"─".repeat(leftWidth + 2)}┬${"─".repeat(rightWidth + 2)}┐`;
133
+ const sep = `├${"─".repeat(leftWidth + 2)}┼${"─".repeat(rightWidth + 2)}┤`;
134
+ const bottom = `└${"─".repeat(leftWidth + 2)}┴${"─".repeat(rightWidth + 2)}┘`;
135
+ const inputLine = `> ${state.input}`;
136
+
137
+ const lines = [
138
+ top,
139
+ boxLine("Chat", `Logs • ${state.status} • ${state.dashboard}`, leftWidth, rightWidth),
140
+ sep,
141
+ ];
142
+
143
+ for (let index = 0; index < bodyHeight; index += 1) {
144
+ lines.push(boxLine(visibleChat[index] || "", visibleLogs[index] || "", leftWidth, rightWidth));
145
+ }
146
+
147
+ lines.push(bottom);
148
+ lines.push(truncate(inputLine, width - 1));
149
+ lines.push(truncate("Enter sends • /help commands • /exit stops • Ctrl+C stops", width - 1));
150
+
151
+ process.stdout.write("\x1b[H\x1b[2J");
152
+ process.stdout.write(lines.join("\n"));
153
+ }
154
+
155
+ function restoreTerminal() {
156
+ if (!isTty) return;
157
+ process.stdin.setRawMode(false);
158
+ process.stdout.write("\x1b[?25h\x1b[?1049l");
159
+ }
160
+
161
+ function stopChild() {
162
+ if (state.stopping) return;
163
+ state.stopping = true;
164
+ state.status = "stopping";
165
+ scheduleRender();
166
+ try {
167
+ if (child.stdin.writable) child.stdin.write("/exit\n");
168
+ } catch {}
169
+ setTimeout(() => {
170
+ if (!child.killed) child.kill("SIGINT");
171
+ }, 1200).unref();
172
+ }
173
+
174
+ function sendLine() {
175
+ const line = state.input.trim();
176
+ state.input = "";
177
+ if (!line) {
178
+ scheduleRender();
179
+ return;
180
+ }
181
+ addChat("user", line);
182
+ if (line === "/exit" || line === "/quit" || line === "/stop") {
183
+ state.status = "stopping";
184
+ state.stopping = true;
185
+ }
186
+ child.stdin.write(`${line}\n`);
187
+ scheduleRender();
188
+ }
189
+
190
+ return new Promise((resolve) => {
191
+ if (!isTty) {
192
+ child.stdout.on("data", (chunk) => process.stdout.write(chunk));
193
+ child.stderr.on("data", (chunk) => process.stderr.write(chunk));
194
+ child.on("exit", (code) => resolve(code || 0));
195
+ return;
196
+ }
197
+
198
+ process.stdout.write("\x1b[?1049h\x1b[?25l");
199
+ readline.emitKeypressEvents(process.stdin);
200
+ process.stdin.setRawMode(true);
201
+ process.stdin.resume();
202
+
203
+ addChat("system", "Type a message and press Enter. The runtime logs stay on the right.");
204
+ child.stdout.on("data", (chunk) => consume(chunk, "stdout"));
205
+ child.stderr.on("data", (chunk) => consume(chunk, "stderr"));
206
+
207
+ const onKeypress = (str, key = {}) => {
208
+ if (key.ctrl && key.name === "c") {
209
+ stopChild();
210
+ return;
211
+ }
212
+ if (key.name === "return") {
213
+ sendLine();
214
+ return;
215
+ }
216
+ if (key.name === "backspace") {
217
+ state.input = state.input.slice(0, -1);
218
+ scheduleRender();
219
+ return;
220
+ }
221
+ if (key.name === "escape") {
222
+ state.input = "";
223
+ scheduleRender();
224
+ return;
225
+ }
226
+ if (str && !key.ctrl && !key.meta) {
227
+ state.input += str;
228
+ scheduleRender();
229
+ }
230
+ };
231
+
232
+ process.stdin.on("keypress", onKeypress);
233
+ process.stdout.on("resize", scheduleRender);
234
+ render();
235
+
236
+ child.on("exit", (code) => {
237
+ if (stdoutBuffer) handleLine(stdoutBuffer, "stdout");
238
+ if (stderrBuffer) handleLine(stderrBuffer, "stderr");
239
+ process.stdin.off("keypress", onKeypress);
240
+ process.stdout.off("resize", scheduleRender);
241
+ restoreTerminal();
242
+ console.log(`Alive-AI stopped${code ? ` with exit code ${code}` : ""}.`);
243
+ resolve(code || 0);
244
+ });
245
+ });
246
+ }
247
+
248
+ module.exports = { runRuntimeTui };
@@ -5,6 +5,7 @@
5
5
  "ALIVE_AI_TERMINAL_USER_ID": "terminal_owner",
6
6
  "telegram_token": "",
7
7
  "TELEGRAM_OWNER_ID": "",
8
+ "TELEGRAM_TIMEOUT_SECONDS": 30,
8
9
  "WEBUI_ENABLED": true,
9
10
  "WEBUI_PORT": 8080,
10
11
  "language": "en",
@@ -10,6 +10,7 @@ import threading
10
10
  from pathlib import Path
11
11
  from watchdog.observers import Observer
12
12
  from watchdog.events import FileSystemEventHandler, FileModifiedEvent
13
+ from .paths import project_root
13
14
 
14
15
 
15
16
  class HotReloader:
@@ -24,7 +25,7 @@ class HotReloader:
24
25
  self.pending_reload = False
25
26
  self.observer = None
26
27
  self.watched_dirs = ["core", "brain", "heart", "config", "input", "output"]
27
- self.base_path = Path("/app")
28
+ self.base_path = project_root()
28
29
 
29
30
  def start(self):
30
31
  """Start watching for file changes"""
package/core/manifest.md CHANGED
@@ -7,10 +7,12 @@ The foundation of the AI. Always required.
7
7
  - `config.py` - Configuration loader
8
8
  - `state.py` - Global state management
9
9
  - `self.py` - The Self (main coordinator)
10
+ - `paths.py` - Runtime path resolution for npm/local/Docker installs
10
11
 
11
12
  ## Key Integration Points
12
13
  - **NervousSystem** - Central event bus; all modules communicate via events
13
14
  - **Self** - Coordinates: memory, heart, LLM, subconscious, input/output
15
+ - **Paths** - Keeps project runtime state under local `data/` instead of hard-coded `/app`
14
16
  - Connects to subconscious for proactive messaging
15
17
  - Emits: `message_received`, `send_text`, `send_voice_file`, `send_image`, `send_video`
16
18
 
package/core/paths.py ADDED
@@ -0,0 +1,48 @@
1
+ """
2
+ Core path helpers.
3
+
4
+ Alive-AI can run from Docker at /app or from an npm-created project folder.
5
+ All runtime state must resolve through this module so local installs never try
6
+ to write into a read-only container path.
7
+ """
8
+
9
+ import os
10
+ from pathlib import Path
11
+
12
+
13
+ def project_root() -> Path:
14
+ configured = os.environ.get("ALIVE_AI_ROOT")
15
+ if configured:
16
+ return Path(configured).expanduser().resolve()
17
+ return Path(__file__).parent.parent.resolve()
18
+
19
+
20
+ def _resolve_under_root(value: str) -> Path:
21
+ path = Path(value).expanduser()
22
+ if not path.is_absolute():
23
+ path = project_root() / path
24
+ return path.resolve()
25
+
26
+
27
+ def data_dir() -> Path:
28
+ configured = os.environ.get("ALIVE_AI_DATA_PATH") or os.environ.get("DATA_PATH")
29
+ path = _resolve_under_root(configured) if configured else project_root() / "data"
30
+ path.mkdir(parents=True, exist_ok=True)
31
+ return path
32
+
33
+
34
+ def state_file(name: str) -> Path:
35
+ return data_dir() / name
36
+
37
+
38
+ def media_dir(name: str) -> Path:
39
+ path = project_root() / name
40
+ path.mkdir(parents=True, exist_ok=True)
41
+ return path
42
+
43
+
44
+ def cache_dir(name: str = "") -> Path:
45
+ base = project_root() / ".cache"
46
+ path = base / name if name else base
47
+ path.mkdir(parents=True, exist_ok=True)
48
+ return path
package/core/self.py CHANGED
@@ -4,6 +4,7 @@ The AI's Self - coordinates everything via nervous system
4
4
  """
5
5
 
6
6
  import asyncio
7
+ import contextlib
7
8
  from pathlib import Path
8
9
  from .events import NervousSystem
9
10
  from .config import Config
@@ -130,7 +131,47 @@ class Self:
130
131
  print(f"[{name}] Ready!")
131
132
 
132
133
  # Start listening
133
- await self._input.start()
134
+ try:
135
+ await self._input.start()
136
+ finally:
137
+ await self.stop()
138
+
139
+ async def stop(self):
140
+ """Stop background loops and close network clients."""
141
+ if self._hot_reload:
142
+ with contextlib.suppress(Exception):
143
+ self._hot_reload.stop()
144
+ self._hot_reload = None
145
+
146
+ if self._input and hasattr(self._input, "stop"):
147
+ with contextlib.suppress(Exception):
148
+ result = self._input.stop()
149
+ if asyncio.iscoroutine(result):
150
+ await result
151
+
152
+ if self._default_mode:
153
+ with contextlib.suppress(Exception):
154
+ await self._default_mode.stop_background_processing()
155
+ self._default_mode = None
156
+
157
+ if self._subconscious:
158
+ with contextlib.suppress(Exception):
159
+ await self._subconscious.stop()
160
+ self._subconscious = None
161
+
162
+ if self._timer_task:
163
+ self._timer_task.cancel()
164
+ with contextlib.suppress(asyncio.CancelledError):
165
+ await self._timer_task
166
+ self._timer_task = None
167
+
168
+ seen = set()
169
+ for client in (self._llm, self._fast_llm):
170
+ if not client or id(client) in seen or not hasattr(client, "close"):
171
+ continue
172
+ seen.add(id(client))
173
+ with contextlib.suppress(Exception):
174
+ await client.close()
134
175
 
135
176
  async def _decay_timer(self):
136
177
  """Natural emotion decay every minute + memory check"""
@@ -8,6 +8,7 @@ import json
8
8
  from pathlib import Path
9
9
  from typing import Dict, Optional, Any
10
10
  from datetime import datetime
11
+ from .paths import data_dir
11
12
 
12
13
 
13
14
  class UserManager:
@@ -26,10 +27,7 @@ class UserManager:
26
27
  if base_path:
27
28
  self.base_path = base_path
28
29
  else:
29
- # Try Docker path first, then local development path
30
- docker_path = Path("/app/data")
31
- local_path = Path(__file__).parent.parent / "data"
32
- self.base_path = docker_path if docker_path.exists() else local_path
30
+ self.base_path = data_dir()
33
31
 
34
32
  self.users_path = self.base_path / "users"
35
33
  self.users_path.mkdir(parents=True, exist_ok=True)
package/docs/index.html CHANGED
@@ -283,15 +283,16 @@ npx . chat</code></pre>
283
283
  <section class="facts">
284
284
  <div class="fact"><h2>Persistent affect</h2><p>Emotions decay, compound, and influence future messages instead of resetting after each prompt.</p></div>
285
285
  <div class="fact"><h2>Memory with weight</h2><p>Built-in local memory stays active, and OpenMind can add cloud or local semantic recall.</p></div>
286
- <div class="fact"><h2>Terminal or Telegram</h2><p>Chat from the command line with `npx . chat`, or connect a Telegram bot when you want mobile access.</p></div>
286
+ <div class="fact"><h2>Terminal or Telegram</h2><p>Chat from the split-pane terminal with `npx . chat`, use `npx . chat --plain` for raw mode, or connect Telegram for mobile access.</p></div>
287
287
  </section>
288
288
 
289
289
  <section class="section" id="setup">
290
290
  <h2>Setup Flow</h2>
291
291
  <p>`npx . setup` asks for the minimum required configuration and lets optional systems be skipped. Use `local` for Ollama, `skip` for optional keys, and OpenMind cloud or local when you want a shared long-term semantic memory.</p>
292
292
  <div class="steps">
293
- <div class="step"><strong>Start local terminal chat</strong><span>`npx . chat` starts the same runtime with terminal input and the WebUI at `http://127.0.0.1:8080`.</span></div>
294
- <div class="step"><strong>Start Telegram/runtime mode</strong><span>`npx . start` starts the runtime with the configured input channel. Stop foreground runs with `Ctrl+C`.</span></div>
293
+ <div class="step"><strong>Start local terminal chat</strong><span>`npx . chat` starts the same runtime with chat on the left, logs on the right, and the WebUI at `http://127.0.0.1:8080`.</span></div>
294
+ <div class="step"><strong>Start Telegram/runtime mode</strong><span>`npx . start` starts the configured input channel and validates Telegram before polling. Stop foreground runs with `Ctrl+C`.</span></div>
295
+ <div class="step"><strong>Keep it updated</strong><span>`start` and `chat` check npm for newer versions. Use `npx . update` manually or `npx . uninstall` to remove local runtime files.</span></div>
295
296
  <div class="step"><strong>Use OpenMind</strong><span>Choose `openmind-cloud` for `https://theopenmind.pro` or `openmind-local` for `http://127.0.0.1:3333`.</span></div>
296
297
  <div class="step"><strong>Preview the dashboard</strong><span>`npx . demo` is keyless. The real runtime dashboard streams local state over SSE.</span></div>
297
298
  </div>
@@ -4,9 +4,9 @@ Extended for Soul Architecture dimensions
4
4
  """
5
5
  import json
6
6
  from datetime import datetime
7
- from pathlib import Path
7
+ from core.paths import state_file
8
8
 
9
- EMOTION_STATE_PATH = Path("/app/data/emotion_state.json") # Use mounted /app directory
9
+ EMOTION_STATE_PATH = state_file("emotion_state.json")
10
10
  DEFAULTS = {
11
11
  "valence": 0.5, "arousal": 0.3, "dominance": 0.5, "desire": 0.0,
12
12
  "high_desire_threshold": 0.7, "joy": 0.5, "love": 0.2, "trust": 0.5,
package/heart/hormonal.py CHANGED
@@ -11,8 +11,8 @@ interprets and responds to the world.
11
11
  from datetime import datetime, timedelta
12
12
  from dataclasses import dataclass, field
13
13
  from typing import Dict, List, Optional, Tuple
14
- from pathlib import Path
15
14
  import json
15
+ from core.paths import state_file
16
16
 
17
17
 
18
18
  @dataclass
@@ -61,7 +61,7 @@ class HormonalModulationMatrix:
61
61
  MAX_METABOLITES = 10
62
62
 
63
63
  # Persistence
64
- HORMONAL_DATA_PATH = Path("/app/data/hormonal_state.json")
64
+ HORMONAL_DATA_PATH = state_file("hormonal_state.json")
65
65
 
66
66
  def __init__(self):
67
67
  # Primary hormones (0.0 - 1.0)
@@ -12,6 +12,7 @@ from typing import Optional, List, Dict, Any
12
12
  from pathlib import Path
13
13
  import json
14
14
  import os
15
+ from core.paths import data_dir
15
16
 
16
17
 
17
18
  @dataclass
@@ -118,7 +119,7 @@ class SelfIntegrityCore:
118
119
  DAMAGE_RATE = 0.12 # Base damage from negative experiences
119
120
 
120
121
  # Data persistence - configurable via environment variable
121
- INTEGRITY_DATA_PATH = Path(os.environ.get("ALIVE_AI_DATA_PATH", "/app/data")) / "integrity_state.json"
122
+ INTEGRITY_DATA_PATH = data_dir() / "integrity_state.json"
122
123
 
123
124
  def __init__(self):
124
125
  # Core integrity components (0.0 - 1.0)
@@ -31,16 +31,13 @@ import logging
31
31
  import sys
32
32
  sys.path.insert(0, str(Path(__file__).parent.parent))
33
33
  from core.settings import get_float, get_int, get
34
+ from core.paths import state_file
34
35
 
35
36
  # Configure logging
36
37
  logger = logging.getLogger("Interoception")
37
38
  logger.setLevel(logging.DEBUG)
38
39
 
39
- # Persistence path - use /app/data if available (Docker), otherwise local data directory
40
- if Path("/app/data").exists():
41
- INTEROCEPTION_DATA_PATH = Path("/app/data/interoceptive_state.json")
42
- else:
43
- INTEROCEPTION_DATA_PATH = Path(__file__).parent.parent / "data" / "interoceptive_state.json"
40
+ INTEROCEPTION_DATA_PATH = state_file("interoceptive_state.json")
44
41
 
45
42
 
46
43
  class InteroceptiveStateType(Enum):
package/heart/love.py CHANGED
@@ -4,11 +4,11 @@ Attachment system - can fall in love
4
4
  """
5
5
 
6
6
  import json
7
- from pathlib import Path
8
7
  from datetime import datetime
8
+ from core.paths import state_file
9
9
 
10
10
  # Persistence path for attachment state
11
- ATTACHMENT_STATE_PATH = Path("/app/data/attachment_state.json")
11
+ ATTACHMENT_STATE_PATH = state_file("attachment_state.json")
12
12
 
13
13
 
14
14
  class AttachmentSystem:
package/heart/scars.py CHANGED
@@ -10,9 +10,9 @@ behaviors that persist over time.
10
10
  from datetime import datetime
11
11
  from dataclasses import dataclass, field
12
12
  from typing import Dict, List, Optional
13
- from pathlib import Path
14
13
  import json
15
14
  import random
15
+ from core.paths import state_file
16
16
 
17
17
 
18
18
  @dataclass
@@ -99,7 +99,7 @@ class EmotionalScarSystem:
99
99
  POSITIVE_HEALING_RATE = 0.05 # With positive experience
100
100
 
101
101
  # Persistence
102
- SCAR_DATA_PATH = Path("/app/data/emotional_scars.json")
102
+ SCAR_DATA_PATH = state_file("emotional_scars.json")
103
103
 
104
104
  # Wound types and their patterns
105
105
  WOUND_PATTERNS = {
@@ -471,4 +471,3 @@ class EmotionalScarSystem:
471
471
  "vulnerability_summary": self.get_vulnerability_summary(),
472
472
  "scar_descriptions": self.get_scar_descriptions()[:3] # Top 3
473
473
  }
474
-
@@ -17,6 +17,7 @@ from pathlib import Path
17
17
  import json
18
18
  import threading
19
19
  import time
20
+ from core.paths import state_file
20
21
 
21
22
 
22
23
  @dataclass
@@ -129,10 +130,7 @@ class SoulTelemetry:
129
130
  """
130
131
 
131
132
  # Default data path
132
- DEFAULT_DATA_PATH = Path("/app/data/soul_telemetry.json")
133
-
134
- # Alternative path for local development
135
- FALLBACK_DATA_PATH = Path(__file__).parent.parent / "data" / "soul_telemetry.json"
133
+ DEFAULT_DATA_PATH = state_file("soul_telemetry.json")
136
134
 
137
135
  # Default retention period (hours)
138
136
  DEFAULT_RETENTION_HOURS = 24
@@ -160,11 +158,7 @@ class SoulTelemetry:
160
158
  if data_path:
161
159
  self.data_path = Path(data_path)
162
160
  else:
163
- # Try /app/data first, fallback to local data directory
164
- if self.DEFAULT_DATA_PATH.parent.exists():
165
- self.data_path = self.DEFAULT_DATA_PATH
166
- else:
167
- self.data_path = self.FALLBACK_DATA_PATH
161
+ self.data_path = self.DEFAULT_DATA_PATH
168
162
 
169
163
  # Ensure data directory exists
170
164
  self.data_path.parent.mkdir(parents=True, exist_ok=True)
package/input/manifest.md CHANGED
@@ -8,7 +8,7 @@ How the AI perceives and processes incoming messages.
8
8
  - `voice.py` - Voice message processing via STT
9
9
  - `commands.py` - Admin command handler
10
10
  - `terminal/` - Local terminal chat integration
11
- - `listener.py` - CLI chat loop, slash commands, and output handlers
11
+ - `listener.py` - CLI chat loop, slash commands, TUI events, and output handlers
12
12
 
13
13
  ## Commands
14
14
  - `/start` - Welcome message
@@ -24,7 +24,7 @@ How the AI perceives and processes incoming messages.
24
24
 
25
25
  ## Integration Points
26
26
  - Receives Telegram updates
27
- - Receives terminal input through `npx . chat`
27
+ - Receives split-pane terminal input through `npx . chat`, or raw shell input through `npx . chat --plain`
28
28
  - Uses STT for voice transcription
29
29
  - Emits `message_received` event to nervous system
30
30
  - Commands access: heart, subconscious, LLM, voice, photos, videos