alive-ai 0.1.3 → 0.1.4

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/README.md CHANGED
@@ -55,6 +55,7 @@ alive-ai init my-ai
55
55
  | `npx alive-ai@latest init my-ai` | Scaffold a clean local Alive-AI project. |
56
56
  | `npx . setup` | Guided onboarding for local config, providers, Telegram, voice, images, and memory. |
57
57
  | `npx . doctor` | Check OS, Node, Python, uv, ffmpeg, Docker, and OpenMind reachability. |
58
+ | `npx . doctor --fix` | Ask `y/N` for each missing installable tool and run the platform installer if approved. |
58
59
  | `npx . chat` | Start the real runtime with split-pane terminal chat and logs. |
59
60
  | `npx . chat --plain` | Start raw terminal chat without the TUI. |
60
61
  | `npx . demo` | Run a keyless animated dashboard demo. |
@@ -65,6 +66,8 @@ alive-ai init my-ai
65
66
 
66
67
  `start` and `chat` check npm for a newer Alive-AI version. You can update, skip once, or skip that specific version. Stop terminal chat with `/exit` or `Ctrl+C`.
67
68
 
69
+ `doctor --fix` is conservative: it prints the exact install command before running anything and asks separately for each missing tool. On macOS it uses Homebrew, on Windows it uses winget, and on Linux it supports apt, dnf, and pacman where possible.
70
+
68
71
  If you use Docker:
69
72
 
70
73
  ```bash
@@ -286,6 +289,7 @@ Implemented:
286
289
  - [x] Optional hybrid OpenMind cloud/local semantic memory
287
290
  - [x] npm/npx CLI scaffold, setup, doctor, demo, chat, and start commands
288
291
  - [x] Update prompt and project uninstall command
292
+ - [x] `doctor --fix` guided system dependency installer
289
293
  - [x] Clean public repo with private personas, media, runtime data, and multi-AI orchestration removed
290
294
  - [x] GitHub Pages site and full static WebUI export
291
295
 
package/cli/index.js CHANGED
@@ -49,7 +49,7 @@ Usage:
49
49
  alive-ai start [--skip-install] Install Python deps if needed and start runtime
50
50
  alive-ai chat [--skip-install] Start split-pane terminal chat and logs
51
51
  alive-ai chat --plain Start raw terminal chat without the TUI
52
- alive-ai doctor Check local prerequisites
52
+ alive-ai doctor [--fix] Check local prerequisites and optionally install missing tools
53
53
  alive-ai uninstall Remove Alive-AI runtime files from this project
54
54
 
55
55
  Quick start:
@@ -479,6 +479,10 @@ function findCommand(candidates) {
479
479
  return null;
480
480
  }
481
481
 
482
+ function majorVersion(version) {
483
+ return Number.parseInt(String(version || "0").split(".")[0], 10) || 0;
484
+ }
485
+
482
486
  function pythonVersion(command) {
483
487
  const result = spawnSync(command, ["-c", "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')"], {
484
488
  encoding: "utf8",
@@ -487,6 +491,130 @@ function pythonVersion(command) {
487
491
  return result.stdout.trim();
488
492
  }
489
493
 
494
+ function hasCommand(command) {
495
+ return spawnSync(command, ["--version"], { stdio: "ignore" }).status === 0;
496
+ }
497
+
498
+ function commandLine(command) {
499
+ if (!command) return "";
500
+ return command.map((part) => /[\s"']/.test(part) ? JSON.stringify(part) : part).join(" ");
501
+ }
502
+
503
+ function runInstallCommand(command) {
504
+ const [bin, ...args] = command;
505
+ return spawnSync(bin, args, { stdio: "inherit", shell: false });
506
+ }
507
+
508
+ function packageManager() {
509
+ if (process.platform === "darwin" && hasCommand("brew")) return "brew";
510
+ if (process.platform === "win32" && hasCommand("winget")) return "winget";
511
+ if (process.platform === "linux") {
512
+ if (hasCommand("apt-get")) return "apt";
513
+ if (hasCommand("dnf")) return "dnf";
514
+ if (hasCommand("pacman")) return "pacman";
515
+ }
516
+ return null;
517
+ }
518
+
519
+ function installPlan(tool) {
520
+ const manager = packageManager();
521
+ if (process.platform === "darwin") {
522
+ if (manager !== "brew") return null;
523
+ return {
524
+ node: ["brew", "install", "node"],
525
+ python: ["brew", "install", "python@3.12"],
526
+ uv: ["brew", "install", "uv"],
527
+ ffmpeg: ["brew", "install", "ffmpeg"],
528
+ docker: ["brew", "install", "--cask", "docker"],
529
+ ollama: ["brew", "install", "ollama"],
530
+ }[tool] || null;
531
+ }
532
+
533
+ if (process.platform === "win32") {
534
+ if (manager !== "winget") return null;
535
+ return {
536
+ node: ["winget", "install", "-e", "--id", "OpenJS.NodeJS.LTS"],
537
+ python: ["winget", "install", "-e", "--id", "Python.Python.3.12"],
538
+ uv: ["winget", "install", "-e", "--id", "astral-sh.uv"],
539
+ ffmpeg: ["winget", "install", "-e", "--id", "Gyan.FFmpeg"],
540
+ docker: ["winget", "install", "-e", "--id", "Docker.DockerDesktop"],
541
+ ollama: ["winget", "install", "-e", "--id", "Ollama.Ollama"],
542
+ }[tool] || null;
543
+ }
544
+
545
+ if (process.platform === "linux") {
546
+ const plans = {
547
+ apt: {
548
+ node: ["sudo", "apt-get", "install", "-y", "nodejs", "npm"],
549
+ python: ["sudo", "apt-get", "install", "-y", "python3", "python3-venv"],
550
+ uv: ["sh", "-c", "curl -LsSf https://astral.sh/uv/install.sh | sh"],
551
+ ffmpeg: ["sudo", "apt-get", "install", "-y", "ffmpeg"],
552
+ docker: ["sudo", "apt-get", "install", "-y", "docker.io"],
553
+ ollama: ["sh", "-c", "curl -fsSL https://ollama.com/install.sh | sh"],
554
+ },
555
+ dnf: {
556
+ node: ["sudo", "dnf", "install", "-y", "nodejs", "npm"],
557
+ python: ["sudo", "dnf", "install", "-y", "python3"],
558
+ uv: ["sh", "-c", "curl -LsSf https://astral.sh/uv/install.sh | sh"],
559
+ ffmpeg: ["sudo", "dnf", "install", "-y", "ffmpeg"],
560
+ docker: ["sudo", "dnf", "install", "-y", "docker"],
561
+ ollama: ["sh", "-c", "curl -fsSL https://ollama.com/install.sh | sh"],
562
+ },
563
+ pacman: {
564
+ node: ["sudo", "pacman", "-S", "--needed", "nodejs", "npm"],
565
+ python: ["sudo", "pacman", "-S", "--needed", "python"],
566
+ uv: ["sudo", "pacman", "-S", "--needed", "uv"],
567
+ ffmpeg: ["sudo", "pacman", "-S", "--needed", "ffmpeg"],
568
+ docker: ["sudo", "pacman", "-S", "--needed", "docker"],
569
+ ollama: ["sudo", "pacman", "-S", "--needed", "ollama"],
570
+ },
571
+ };
572
+ return plans[manager]?.[tool] || null;
573
+ }
574
+
575
+ return null;
576
+ }
577
+
578
+ function manualInstallHint(tool) {
579
+ if (process.platform === "darwin" && !hasCommand("brew")) {
580
+ return "Install Homebrew from https://brew.sh, then rerun `npx . doctor --fix`.";
581
+ }
582
+ if (process.platform === "win32" && !hasCommand("winget")) {
583
+ return "Install Windows App Installer/winget, then rerun `npx . doctor --fix`.";
584
+ }
585
+ if (process.platform === "linux" && !packageManager()) {
586
+ return `No supported Linux package manager detected for ${tool}. Install it manually with your distro package manager.`;
587
+ }
588
+ return `No automatic installer is configured for ${tool} on this system.`;
589
+ }
590
+
591
+ async function maybeInstallTool(item, assumeYes = false) {
592
+ const command = installPlan(item.id);
593
+ if (!command) {
594
+ console.log(` ${item.name}: ${manualInstallHint(item.id)}`);
595
+ return false;
596
+ }
597
+
598
+ console.log("");
599
+ console.log(`${item.name} is missing.`);
600
+ console.log(`Command: ${commandLine(command)}`);
601
+ const answer = assumeYes
602
+ ? "y"
603
+ : normalizeChoice(await ask(`Install ${item.name}? y/N`, "n", false), "n");
604
+ if (!["y", "yes"].includes(answer)) {
605
+ console.log(`Skipped ${item.name}.`);
606
+ return false;
607
+ }
608
+
609
+ const result = runInstallCommand(command);
610
+ if (result.status === 0) {
611
+ console.log(`${item.name} install command completed.`);
612
+ return true;
613
+ }
614
+ console.log(`${item.name} install command failed with exit code ${result.status || 1}.`);
615
+ return false;
616
+ }
617
+
490
618
  function findPython() {
491
619
  const preferred = ["python3.12", "python3.11", "python3.13", "python3", "python"];
492
620
  for (const command of preferred) {
@@ -499,12 +627,24 @@ function findPython() {
499
627
  return null;
500
628
  }
501
629
 
502
- async function doctor() {
630
+ function wantsOllama(settings) {
631
+ const provider = String(settings.LLM_PROVIDER || "").toLowerCase();
632
+ const order = Array.isArray(settings.LLM_FALLBACK?.ORDER)
633
+ ? settings.LLM_FALLBACK.ORDER.map((item) => String(item).toLowerCase())
634
+ : [];
635
+ return provider === "ollama" || order.includes("ollama");
636
+ }
637
+
638
+ async function doctor(args = []) {
639
+ const shouldFix = hasFlag(args, "--fix");
640
+ const assumeYes = hasFlag(args, "--yes") || hasFlag(args, "-y");
503
641
  const python = findPython();
504
642
  const uv = findCommand(["uv"]);
505
643
  const ffmpeg = findCommand(["ffmpeg"]);
506
644
  const docker = findCommand(["docker"]);
645
+ const ollama = findCommand(["ollama"]);
507
646
  const node = process.version;
647
+ const nodeMajor = majorVersion(process.versions.node);
508
648
  const settings = readProjectSettings();
509
649
  const venvPython = process.platform === "win32"
510
650
  ? path.join(process.cwd(), ".alive-ai", "venv", "Scripts", "python.exe")
@@ -512,7 +652,7 @@ async function doctor() {
512
652
 
513
653
  console.log("Alive-AI doctor");
514
654
  console.log(` system: ${os.platform()} ${os.arch()}`);
515
- console.log(` node: ${node}`);
655
+ console.log(` node: ${nodeMajor >= 18 ? node : `${node} (Node 18+ required)`}`);
516
656
  console.log(` python: ${python ? `${python.command} ${python.version}` : "missing"}`);
517
657
  if (fs.existsSync(venvPython)) {
518
658
  console.log(` venv: ${pythonVersion(venvPython) || "unknown"} (${path.relative(process.cwd(), venvPython)})`);
@@ -520,8 +660,19 @@ async function doctor() {
520
660
  console.log(` uv: ${uv || "missing, will use venv + pip"}`);
521
661
  console.log(` ffmpeg: ${ffmpeg || "missing, voice conversion may be limited"}`);
522
662
  console.log(` docker: ${docker || "missing, Redis can still be external"}`);
663
+ if (wantsOllama(settings)) {
664
+ console.log(` ollama: ${ollama || "missing, local LLM unavailable until installed"}`);
665
+ }
523
666
  console.log(` input: ${settings.INPUT_CHANNEL || "telegram"}`);
524
667
 
668
+ const missing = [];
669
+ if (nodeMajor < 18) missing.push({ id: "node", name: "Node.js 18+" });
670
+ if (!python) missing.push({ id: "python", name: "Python 3.11+" });
671
+ if (!uv) missing.push({ id: "uv", name: "uv" });
672
+ if (!ffmpeg) missing.push({ id: "ffmpeg", name: "ffmpeg" });
673
+ if (!docker) missing.push({ id: "docker", name: "Docker" });
674
+ if (wantsOllama(settings) && !ollama) missing.push({ id: "ollama", name: "Ollama" });
675
+
525
676
  if (!python) {
526
677
  console.log("");
527
678
  console.log("Install Python 3.11+ first:");
@@ -547,7 +698,25 @@ async function doctor() {
547
698
  console.log(" OpenMind: disabled");
548
699
  }
549
700
 
550
- if (!python) process.exitCode = 1;
701
+ if (shouldFix) {
702
+ if (!missing.length) {
703
+ console.log("");
704
+ console.log("Nothing missing. No fixes needed.");
705
+ } else {
706
+ console.log("");
707
+ console.log("Doctor fix mode: each installer is optional and will ask before running.");
708
+ for (const item of missing) {
709
+ await maybeInstallTool(item, assumeYes);
710
+ }
711
+ console.log("");
712
+ console.log("Run `npx . doctor` again to verify the final state.");
713
+ }
714
+ } else if (missing.length) {
715
+ console.log("");
716
+ console.log("Run `npx . doctor --fix` to install missing tools one by one.");
717
+ }
718
+
719
+ if (!python || nodeMajor < 18) process.exitCode = 1;
551
720
  }
552
721
 
553
722
  function ensurePythonEnv(skipInstall) {
@@ -735,7 +904,7 @@ async function main() {
735
904
  if (command === "demo") return startDemo(args);
736
905
  if (command === "start") return startRuntime(args);
737
906
  if (command === "chat") return startTerminalChat(args);
738
- if (command === "doctor") return doctor();
907
+ if (command === "doctor") return doctor(args);
739
908
  if (command === "uninstall") return uninstallProject(args);
740
909
  console.error(`Unknown command: ${command}`);
741
910
  usage();
package/docs/index.html CHANGED
@@ -292,6 +292,7 @@ npx . chat</code></pre>
292
292
  <div class="steps">
293
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
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>Install missing tools</strong><span>`npx . doctor --fix` asks `y/N` for each missing tool, then uses Homebrew, winget, apt, dnf, or pacman where available.</span></div>
295
296
  <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>
296
297
  <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>
297
298
  <div class="step"><strong>Preview the dashboard</strong><span>`npx . demo` is keyless. The real runtime dashboard streams local state over SSE.</span></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alive-ai",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Local-first emotional AI runtime with memory, impulses, and a live dashboard.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://vindepemarte.github.io/alive-ai/",
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "alive-ai-runtime"
3
- version = "0.1.3"
3
+ version = "0.1.4"
4
4
  description = "Local-first emotional AI runtime with memory, impulses, and a live dashboard."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"