alive-ai 0.1.0 → 0.1.2

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/index.js CHANGED
@@ -45,12 +45,15 @@ Usage:
45
45
  alive-ai setup [--yes] Create local config from templates
46
46
  alive-ai demo [--port 8080] Run the animated dashboard demo
47
47
  alive-ai start [--skip-install] Install Python deps if needed and start runtime
48
+ alive-ai chat [--skip-install] Start runtime with terminal chat input
48
49
  alive-ai doctor Check local prerequisites
49
50
 
50
51
  Quick start:
51
- npx github:vindepemarte/alive-ai init my-ai
52
+ npx alive-ai@latest init my-ai
52
53
  cd my-ai
53
54
  npx . setup
55
+ npx . doctor
56
+ npx . chat
54
57
  npx . demo
55
58
  npx . start`);
56
59
  }
@@ -114,6 +117,8 @@ function initProject(args) {
114
117
  console.log("Next:");
115
118
  console.log(` cd ${target}`);
116
119
  console.log(" npx . setup");
120
+ console.log(" npx . doctor");
121
+ console.log(" npx . chat");
117
122
  console.log(" npx . demo");
118
123
  }
119
124
 
@@ -138,6 +143,28 @@ function ask(question, fallback, assumeYes) {
138
143
  });
139
144
  }
140
145
 
146
+ function normalizeChoice(value, fallback = "") {
147
+ return String(value || fallback).trim().toLowerCase();
148
+ }
149
+
150
+ function isSkipped(value) {
151
+ return normalizeChoice(value) === "skip";
152
+ }
153
+
154
+ function emptyIfSkipped(value) {
155
+ return isSkipped(value) ? "" : value;
156
+ }
157
+
158
+ function readProjectSettings() {
159
+ const settingsPath = path.join(process.cwd(), "config", "settings.json");
160
+ if (!fs.existsSync(settingsPath)) return {};
161
+ try {
162
+ return readJson(settingsPath);
163
+ } catch {
164
+ return {};
165
+ }
166
+ }
167
+
141
168
  async function setupProject(args) {
142
169
  const dryRun = hasFlag(args, "--dry-run");
143
170
  const assumeYes = hasFlag(args, "--yes") || hasFlag(args, "-y") || dryRun;
@@ -165,25 +192,68 @@ async function setupProject(args) {
165
192
  return;
166
193
  }
167
194
 
168
- const displayName = await ask("Agent display name", "Nova", assumeYes);
169
- const ownerId = await ask("Telegram owner ID (optional)", "", assumeYes);
170
- const telegramToken = await ask("Telegram bot token (optional for demo)", "", assumeYes);
171
- const provider = await ask("LLM provider: ollama, openrouter, or zai", "ollama", assumeYes);
195
+ const displayNameAnswer = await ask("Agent display name", "Nova", assumeYes);
196
+ const displayName = emptyIfSkipped(displayNameAnswer) || "Nova";
197
+ const ownerId = emptyIfSkipped(await ask("Telegram owner ID (optional, use skip to leave blank)", "", assumeYes));
198
+ const telegramToken = emptyIfSkipped(await ask("Telegram bot token (optional, use skip to leave blank)", "", assumeYes));
199
+ const providerChoice = normalizeChoice(
200
+ await ask("LLM provider: local, openrouter, zai, or skip", "local", assumeYes),
201
+ "local"
202
+ );
203
+ const provider = providerChoice === "local" ? "ollama" : providerChoice;
172
204
  const openRouterKey = provider === "openrouter"
173
- ? await ask("OpenRouter API key", "", assumeYes)
205
+ ? emptyIfSkipped(await ask("OpenRouter API key (or skip)", "", assumeYes))
174
206
  : "";
175
207
  const zaiKey = provider === "zai"
176
- ? await ask("ZAI API key", "", assumeYes)
208
+ ? emptyIfSkipped(await ask("ZAI API key (or skip)", "", assumeYes))
209
+ : "";
210
+ const ttsChoice = normalizeChoice(
211
+ await ask("Voice provider: gtts, google, vibe, or skip", "gtts", assumeYes),
212
+ "gtts"
213
+ );
214
+ const googleTtsKey = ttsChoice === "google"
215
+ ? emptyIfSkipped(await ask("Google TTS API key (optional, use skip for ADC)", "", assumeYes))
216
+ : "";
217
+ const vibeTtsUrl = ttsChoice === "vibe"
218
+ ? emptyIfSkipped(await ask("VibeVoice URL (or skip)", "http://127.0.0.1:8088", assumeYes))
219
+ : "";
220
+ const falKey = emptyIfSkipped(await ask("Fal.ai image API key (optional, use skip to disable)", "", assumeYes));
221
+ const memoryChoice = normalizeChoice(
222
+ await ask("Memory mode: built-in, openmind-cloud, or openmind-local", "built-in", assumeYes),
223
+ "built-in"
224
+ );
225
+ const openmindEnabled = memoryChoice.startsWith("openmind");
226
+ const openmindBaseUrl = openmindEnabled
227
+ ? memoryChoice === "openmind-local"
228
+ ? emptyIfSkipped(await ask("OpenMind local URL", "http://127.0.0.1:3333", assumeYes))
229
+ : emptyIfSkipped(await ask("OpenMind cloud URL", "https://theopenmind.pro", assumeYes))
230
+ : "";
231
+ const openmindKey = openmindEnabled
232
+ ? emptyIfSkipped(await ask("OpenMind API key (om_..., optional for unauthenticated local dev)", "", assumeYes))
177
233
  : "";
178
234
 
179
235
  const settings = readJson(settingsExample);
180
236
  settings.AGENT_NAME = displayName;
237
+ settings.INPUT_CHANNEL = "telegram";
181
238
  settings.telegram_token = telegramToken;
182
239
  settings.TELEGRAM_OWNER_ID = ownerId;
183
- settings.LLM_PROVIDER = provider;
240
+ settings.LLM_PROVIDER = provider === "skip" ? "ollama" : provider;
184
241
  settings.OPENROUTER_API_KEY = openRouterKey;
185
242
  settings.ZAI_API_KEY = zaiKey;
186
- settings.LLM_FALLBACK.ORDER = provider === "ollama" ? ["ollama"] : [provider, "ollama"];
243
+ settings.LLM_FALLBACK.ENABLED = provider !== "skip";
244
+ settings.LLM_FALLBACK.ORDER = provider === "skip"
245
+ ? ["ollama"]
246
+ : provider === "ollama"
247
+ ? ["ollama"]
248
+ : [provider, "ollama"];
249
+ settings.TTS_PROVIDER = ttsChoice === "skip" ? "none" : ttsChoice;
250
+ settings.GOOGLE_TTS_API_KEY = googleTtsKey;
251
+ settings.vibe_tts_url = vibeTtsUrl;
252
+ settings.FAL_API_KEY = falKey;
253
+ settings.OPENMIND_ENABLED = openmindEnabled;
254
+ settings.OPENMIND_MODE = openmindEnabled ? "hybrid" : "built-in";
255
+ settings.OPENMIND_BASE_URL = openmindBaseUrl || "https://theopenmind.pro";
256
+ settings.OPENMIND_API_KEY = openmindKey;
187
257
 
188
258
  const self = readJson(selfExample);
189
259
  self.who_i_am.name = displayName;
@@ -201,7 +271,7 @@ async function setupProject(args) {
201
271
  ensureDir(path.join(process.cwd(), "myvids"));
202
272
 
203
273
  console.log("Alive-AI config created.");
204
- console.log("Run `npx . demo` to preview the dashboard or `npx . start` to start the runtime.");
274
+ console.log("Run `npx . chat` for terminal chat, `npx . demo` for the dashboard preview, or `npx . start` for Telegram/runtime mode.");
205
275
  }
206
276
 
207
277
  function findCommand(candidates) {
@@ -212,19 +282,47 @@ function findCommand(candidates) {
212
282
  return null;
213
283
  }
214
284
 
215
- function doctor() {
285
+ async function doctor() {
216
286
  const python = findCommand(["python3.11", "python3", "python"]);
217
287
  const uv = findCommand(["uv"]);
218
288
  const ffmpeg = findCommand(["ffmpeg"]);
219
289
  const docker = findCommand(["docker"]);
220
290
  const node = process.version;
291
+ const settings = readProjectSettings();
221
292
 
222
293
  console.log("Alive-AI doctor");
294
+ console.log(` system: ${os.platform()} ${os.arch()}`);
223
295
  console.log(` node: ${node}`);
224
296
  console.log(` python: ${python || "missing"}`);
225
297
  console.log(` uv: ${uv || "missing, will use venv + pip"}`);
226
298
  console.log(` ffmpeg: ${ffmpeg || "missing, voice conversion may be limited"}`);
227
299
  console.log(` docker: ${docker || "missing, Redis can still be external"}`);
300
+ console.log(` input: ${settings.INPUT_CHANNEL || "telegram"}`);
301
+
302
+ if (!python) {
303
+ console.log("");
304
+ console.log("Install Python 3.11+ first:");
305
+ if (process.platform === "darwin") console.log(" brew install python@3.11");
306
+ else if (process.platform === "win32") console.log(" winget install Python.Python.3.11");
307
+ else console.log(" sudo apt install python3.11 python3.11-venv");
308
+ }
309
+
310
+ if (settings.OPENMIND_ENABLED) {
311
+ const baseUrl = String(settings.OPENMIND_BASE_URL || "https://theopenmind.pro").replace(/\/$/, "");
312
+ const headers = {};
313
+ if (settings.OPENMIND_API_KEY) headers.authorization = `Bearer ${settings.OPENMIND_API_KEY}`;
314
+ try {
315
+ const controller = new AbortController();
316
+ const timer = setTimeout(() => controller.abort(), 4000);
317
+ const response = await fetch(`${baseUrl}/health`, { headers, signal: controller.signal });
318
+ clearTimeout(timer);
319
+ console.log(` OpenMind: ${response.ok ? "reachable" : `HTTP ${response.status}`} (${baseUrl})`);
320
+ } catch (error) {
321
+ console.log(` OpenMind: unreachable (${baseUrl})`);
322
+ }
323
+ } else {
324
+ console.log(" OpenMind: disabled");
325
+ }
228
326
 
229
327
  if (!python) process.exitCode = 1;
230
328
  }
@@ -267,14 +365,23 @@ function ensurePythonEnv(skipInstall) {
267
365
 
268
366
  function startRuntime(args) {
269
367
  if (!fs.existsSync(path.join(process.cwd(), "config", "settings.json"))) {
270
- console.error("Missing config/settings.json. Run `npx . setup` first.");
271
- process.exit(1);
368
+ console.log("Missing config/settings.json. Starting onboarding first.");
369
+ const setupArgs = process.stdin.isTTY ? [] : ["--yes"];
370
+ setupProject(setupArgs).then(() => startRuntime(args));
371
+ return;
272
372
  }
273
373
  const pythonBin = ensurePythonEnv(hasFlag(args, "--skip-install"));
274
- const child = spawn(pythonBin, ["main.py"], { stdio: "inherit", cwd: process.cwd() });
374
+ const extraArgs = [];
375
+ const inputChannel = argValue(args, "--input", null);
376
+ if (inputChannel) extraArgs.push("--input", inputChannel);
377
+ const child = spawn(pythonBin, ["main.py", ...extraArgs], { stdio: "inherit", cwd: process.cwd() });
275
378
  child.on("exit", (code) => process.exit(code || 0));
276
379
  }
277
380
 
381
+ function startTerminalChat(args) {
382
+ return startRuntime(["--input", "terminal", ...args]);
383
+ }
384
+
278
385
  function demoHtml() {
279
386
  return fs.readFileSync(path.join(PACKAGE_ROOT, "demo", "index.html"), "utf8");
280
387
  }
@@ -359,6 +466,7 @@ async function main() {
359
466
  if (command === "setup") return setupProject(args);
360
467
  if (command === "demo") return startDemo(args);
361
468
  if (command === "start") return startRuntime(args);
469
+ if (command === "chat") return startTerminalChat(args);
362
470
  if (command === "doctor") return doctor();
363
471
  console.error(`Unknown command: ${command}`);
364
472
  usage();
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "_comment": "Alive-AI settings. Copy to config/settings.json with `npx alive-ai setup`.",
3
3
  "AGENT_NAME": "Nova",
4
+ "INPUT_CHANNEL": "telegram",
5
+ "ALIVE_AI_TERMINAL_USER_ID": "terminal_owner",
4
6
  "telegram_token": "",
5
7
  "TELEGRAM_OWNER_ID": "",
6
8
  "WEBUI_ENABLED": true,
@@ -31,7 +33,12 @@
31
33
  "TTS_PROVIDER": "gtts",
32
34
  "vibe_tts_url": "",
33
35
  "GOOGLE_TTS_API_KEY": "",
36
+ "FAL_API_KEY": "",
34
37
  "HF_TOKEN": "",
38
+ "OPENMIND_ENABLED": false,
39
+ "OPENMIND_MODE": "built-in",
40
+ "OPENMIND_BASE_URL": "https://theopenmind.pro",
41
+ "OPENMIND_API_KEY": "",
35
42
  "EMOTION_RATE_LOVE": 65,
36
43
  "EMOTION_RATE_DESIRE": 45,
37
44
  "EMOTION_RATE_AROUSAL": 40,
@@ -6,7 +6,7 @@ Module loading and startup logic for Self
6
6
  import os
7
7
 
8
8
 
9
- async def load_modules(self):
9
+ async def load_modules(self, input_channel: str = "telegram"):
10
10
  """Load all modules and initialize the AI system"""
11
11
  name = self.config.identity.get("name", "AI")
12
12
  print(f"[{name}] Waking up...")
@@ -21,7 +21,6 @@ async def load_modules(self):
21
21
  from brain.stt import GoogleSTT
22
22
  from brain.embeddings import get_embedding_service
23
23
  from heart.core import Heart
24
- from input.telegram.listener import TelegramListener
25
24
  from output.text.sender import TextSender
26
25
  from skills.photo_manager.scanner import PhotoScanner
27
26
  from skills.video_manager.scanner import VideoScanner
@@ -41,7 +40,14 @@ async def load_modules(self):
41
40
  _init_photos(self, name)
42
41
  _init_videos(self, name)
43
42
 
44
- self._input = TelegramListener(self.nervous, self.config, stt=self._stt, heart=self._heart)
43
+ input_channel = (input_channel or "telegram").lower()
44
+ if input_channel == "terminal":
45
+ from input.terminal.listener import TerminalListener
46
+ self._input = TerminalListener(self.nervous, self.config, stt=self._stt, heart=self._heart)
47
+ else:
48
+ from input.telegram.listener import TelegramListener
49
+ self._input = TelegramListener(self.nervous, self.config, stt=self._stt, heart=self._heart)
50
+ print(f"[{name}] Input channel: {input_channel}")
45
51
  self._output = TextSender(self.nervous, self.config)
46
52
  self.nervous.heart = self._heart
47
53
 
@@ -99,6 +105,10 @@ async def _init_voice(self, name: str):
99
105
 
100
106
  # Get TTS provider from settings (default to vibe)
101
107
  tts_provider = get("TTS_PROVIDER", "vibe").lower()
108
+ if tts_provider in ("none", "skip", "disabled", "off"):
109
+ print(f"[{name}] Voice disabled")
110
+ self._voice = None
111
+ return
102
112
 
103
113
  # Provider-specific configuration
104
114
  if tts_provider == "google":
package/core/self.py CHANGED
@@ -52,7 +52,7 @@ class Self:
52
52
  # Default Mode Network (background idle processing)
53
53
  self._default_mode = None
54
54
 
55
- async def start(self):
55
+ async def start(self, input_channel: str = "telegram"):
56
56
  """Start the AI system"""
57
57
  # Set the active settings path for this async context
58
58
  settings_path = self.base / "config" / "settings.json"
@@ -71,7 +71,7 @@ class Self:
71
71
  name = self.config.identity.get("name", "AI")
72
72
 
73
73
  # Load modules via initialization module
74
- await load_modules(self)
74
+ await load_modules(self, input_channel=input_channel)
75
75
 
76
76
  # Init Subconscious
77
77
  from brain.subconscious import SubconsciousLoop
@@ -1,15 +1,8 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-labelledby="title desc">
2
- <title id="title">Alive-AI logo</title>
3
- <desc id="desc">A neural core wrapped by a heartbeat pulse.</desc>
4
- <rect width="512" height="512" rx="112" fill="#080b0f"/>
5
- <circle cx="256" cy="256" r="154" fill="none" stroke="#f5f7fb" stroke-width="12" opacity="0.18"/>
6
- <path d="M86 274h72l30-70 47 148 50-201 40 123h101" fill="none" stroke="#41f0a1" stroke-width="18" stroke-linecap="round" stroke-linejoin="round"/>
7
- <path d="M151 335c35 48 91 77 153 69 83-11 145-84 138-168-7-91-89-160-180-150-51 6-96 36-121 80" fill="none" stroke="#ff5c8a" stroke-width="14" stroke-linecap="round"/>
8
- <circle cx="256" cy="256" r="57" fill="#101820" stroke="#f5f7fb" stroke-width="10"/>
9
- <circle cx="256" cy="256" r="15" fill="#41f0a1"/>
10
- <circle cx="216" cy="224" r="10" fill="#ffcf5a"/>
11
- <circle cx="301" cy="223" r="10" fill="#ff5c8a"/>
12
- <circle cx="221" cy="295" r="10" fill="#f5f7fb"/>
13
- <circle cx="300" cy="294" r="10" fill="#41f0a1"/>
14
- <path d="M226 229l30 27 45-31M256 256l-35 39M256 256l44 38" fill="none" stroke="#f5f7fb" stroke-width="7" stroke-linecap="round"/>
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" role="img" aria-labelledby="title desc">
2
+ <title id="title">Alive-AI</title>
3
+ <desc id="desc">A heart with a live pulse line.</desc>
4
+ <rect width="96" height="96" rx="22" fill="#070a0f"/>
5
+ <path d="M48 76S19 59 19 35c0-10 8-18 18-18 6 0 11 3 14 8 3-5 8-8 14-8 10 0 18 8 18 18 0 24-35 41-35 41Z" fill="#ff5c8a"/>
6
+ <path d="M20 50h15l7-17 11 30 8-23h15" fill="none" stroke="#41f0a1" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
7
+ <circle cx="48" cy="48" r="34" fill="none" stroke="#253445" stroke-width="4"/>
15
8
  </svg>