refactor(standard): granularity, XDG compliance, and literate thinking medium
Some checks failed
Deploy-Agent-V15-Stdin / JOB-V15-STDIN (push) Failing after 2s

This commit is contained in:
2026-04-27 19:53:45 -04:00
parent 215fe0eae7
commit 41e25d091e
5 changed files with 432 additions and 456 deletions

168
harness/doctor.org Normal file
View File

@@ -0,0 +1,168 @@
#+TITLE: System Diagnostic Doctor (doctor.org)
#+AUTHOR: Agent
#+FILETAGS: :harness:setup:diagnostic:
#+STARTUP: content
* Overview
The *System Doctor* is the primary diagnostic utility for the OpenCortex. Its purpose is to transform opaque startup failures into actionable engineering reports.
By centralizing environment validation, we ensure that the "Brain" never attempts to boot in a compromised or incomplete state.
* Phase A: Demand (Thinking)
** The XDG Standard Rationale
To ensure OpenCortex behaves as a first-class POSIX citizen, we adopt the **XDG Base Directory Specification**. This separates the system into four logical layers:
1. **Configuration (`~/.config/opencortex`)**: User-editable settings and secrets.
2. **Data (`~/.local/share/opencortex`)**: Tangled Lisp engine artifacts (immutable by user).
3. **State (`~/.local/state/opencortex`)**: Dynamic persistence like brain snapshots.
4. **Bin (`~/.local/bin`)**: The CLI shim for global invocation.
** The Detection Invariant: Shell Probing
Common Lisp's `uiop:getenv` is strictly typed in SBCL. The Doctor must ensure that missing variables are handled as logic failures, not type crashes. Furthermore, binary detection must use a shell probe (`command -v` or `which`) to account for varying `$PATH` inheritance between interactive and headless sessions.
* Phase B: Protocol (Success Criteria)
** Package Context
#+begin_src lisp :tangle (expand-file-name "doctor-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(defpackage :opencortex-doctor-tests
(:use :cl :fiveam :opencortex)
(:export #:doctor-suite))
#+end_src
#+begin_src lisp :tangle (expand-file-name "doctor-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(in-package :opencortex-doctor-tests)
#+end_src
#+begin_src lisp :tangle (expand-file-name "doctor-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(def-suite doctor-suite :description "Verification of the System Doctor diagnostic logic")
#+end_src
#+begin_src lisp :tangle (expand-file-name "doctor-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(in-suite doctor-suite)
#+end_src
** Dependency Tests
#+begin_src lisp :tangle (expand-file-name "doctor-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(test test-dependency-check-fail
"Verify that missing binaries are correctly identified as failures."
(let ((opencortex::*doctor-required-binaries* '("non-existent-binary-123")))
(is (null (opencortex:doctor-check-dependencies)))))
#+end_src
** Environment Tests
#+begin_src lisp :tangle (expand-file-name "doctor-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(test test-env-validation-fail
"Verify that an invalid MEMEX_DIR triggers a critical failure."
(let ((old-m (uiop:getenv "MEMEX_DIR"))
(old-s (uiop:getenv "SKILLS_DIR")))
(unwind-protect
(progn
(setf (uiop:getenv "MEMEX_DIR") "/non/existent/path/999")
(is (null (opencortex:doctor-check-env))))
(setf (uiop:getenv "MEMEX_DIR") (or old-m ""))
(setf (uiop:getenv "SKILLS_DIR") (or old-s "")))))
#+end_src
* Phase C: Implementation (Build)
** Package Context
#+begin_src lisp :tangle (expand-file-name "doctor.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(in-package :opencortex)
#+end_src
** Global Configuration
#+begin_src lisp :tangle (expand-file-name "doctor.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defvar *doctor-required-binaries* '("sbcl" "emacs" "git" "socat" "nc")
"List of external binaries required for full system operation.")
#+end_src
** Dependency Verification
#+begin_src lisp :tangle (expand-file-name "doctor.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun doctor-check-dependencies ()
"Verifies that required external binaries are available in the PATH via a shell probe."
(let ((all-ok t))
(harness-log "DOCTOR: Checking system dependencies...")
(dolist (dep *doctor-required-binaries*)
(let ((path (ignore-errors
(uiop:run-program (list "which" dep)
:output :string :ignore-error-status t))))
(if (and path (> (length path) 0))
(harness-log " [OK] Found ~a" dep)
(progn
(harness-log " [FAIL] Missing binary: ~a" dep)
(setf all-ok nil)))))
all-ok))
#+end_src
** Environment & XDG Validation
#+begin_src lisp :tangle (expand-file-name "doctor.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun doctor-check-env ()
"Validates XDG directories and environment configuration against the POSIX standard."
(harness-log "DOCTOR: Checking XDG environment...")
(let ((all-ok t)
(config-dir (uiop:getenv "OC_CONFIG_DIR"))
(data-dir (uiop:getenv "OC_DATA_DIR"))
(state-dir (uiop:getenv "OC_STATE_DIR"))
(memex-dir (uiop:getenv "MEMEX_DIR")))
(flet ((check-dir (name path critical)
(if (and path (> (length path) 0))
(if (uiop:directory-exists-p path)
(harness-log " [OK] ~a: ~a" name path)
(progn
(harness-log " [FAIL] ~a directory missing: ~a" name path)
(when critical (setf all-ok nil))))
(progn
(harness-log " [FAIL] ~a variable not set." name)
(when critical (setf all-ok nil))))))
(check-dir "Config (OC_CONFIG_DIR)" config-dir t)
(check-dir "Data (OC_DATA_DIR)" data-dir t)
(check-dir "State (OC_STATE_DIR)" state-dir t)
(check-dir "Memex (MEMEX_DIR)" memex-dir t))
all-ok))
#+end_src
** LLM Connectivity
#+begin_src lisp :tangle (expand-file-name "doctor.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun doctor-check-llm ()
"Tests connectivity to primary LLM providers. Non-critical fallback allowed."
(harness-log "DOCTOR: Checking LLM connectivity...")
(let ((openrouter-key (uiop:getenv "OPENROUTER_API_KEY")))
(if (and openrouter-key (> (length openrouter-key) 0))
(progn
(harness-log " [OK] OpenRouter API Key detected.")
t)
(progn
(harness-log " [WARN] No OpenRouter API Key. Falling back to local inference only.")
t))))
#+end_src
** Orchestration
#+begin_src lisp :tangle (expand-file-name "doctor.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun doctor-run-all ()
"Executes the full diagnostic suite and returns T if system is healthy."
(harness-log "==================================================")
(harness-log " OPENCORTEX DOCTOR: Commencing Health Check")
(harness-log "==================================================")
(let ((dep-ok (doctor-check-dependencies))
(env-ok (doctor-check-env))
(llm-ok (doctor-check-llm)))
(harness-log "==================================================")
(if (and dep-ok env-ok)
(progn
(harness-log " ✓ SYSTEM HEALTHY: Ready for ignition.")
t)
(progn
(harness-log " ✗ SYSTEM UNHEALTHY: Fix the errors above.")
nil))))
#+end_src
** CLI Entry Point
#+begin_src lisp :tangle (expand-file-name "doctor.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun doctor-main ()
"Entry point for the 'doctor' CLI command."
(if (doctor-run-all)
(uiop:quit 0)
(uiop:quit 1)))
#+end_src

View File

@@ -32,6 +32,21 @@ The ~package.lisp~ file defines the public API of the ~opencortex~ harness. It s
#:harness-log #:harness-log
#:main #:main
;; --- Diagnostic Doctor ---
#:doctor-run-all
#:doctor-main
#:doctor-check-dependencies
#:doctor-check-env
;; --- Setup Wizard ---
#:register-provider
#:system-ready-p
#:run-setup-wizard
;; --- Diagnostic Doctor ---
#:doctor-run-all
#:doctor-main
;; --- Memory (CLOSOS) --- ;; --- Memory (CLOSOS) ---
#:ingest-ast #:ingest-ast
#:lookup-object #:lookup-object

View File

@@ -6,275 +6,196 @@
* Zero-to-One Setup (setup.org) * Zero-to-One Setup (setup.org)
The ~setup.org~ file defines the automated installation and initialization sequence for the OpenCortex. The ~setup.org~ file defines the automated installation and initialization sequence for the OpenCortex.
** The Installer Script (opencortex.sh) * Phase A: Demand (Thinking)
#+begin_src bash :tangle (expand-file-name "../opencortex.sh" (concat (or (getenv "INSTALL_DIR") ".") "/harness")) ** The Agnostic LLM Provider Registry
#!/bin/bash To fulfill the mandate of sovereignty and extensibility, the setup process must move away from a single hardcoded LLM provider (like OpenRouter).
set -e
PORT=9105 ** Design Goals:
HOST="localhost" 1. **Modular Adapters:** Each provider (Ollama, Groq, OpenAI, etc.) is a data-driven structure defining its required fields (API_KEY, BASE_URL) and its "ping" validation logic.
RED='\033[0;31m'; GREEN='\033[0;32m'; BLUE='\033[0;34m'; YELLOW='\033[0;33m'; NC='\033[0m' 2. **Interactive Selection:** The user should be presented with a multi-select list of providers.
3. **Local-First Default:** If no cloud keys are provided, the system must default to a local Ollama/llama.cpp configuration.
4. **State Persistence:** Configuration is saved to `providers.lisp` in the XDG Config directory.
5. **Secret Splitting:** Sensitive keys go to `.env`, while metadata (models, URLs) lives in `state/providers.lisp`.
command_exists() { command -v "$1" >/dev/null 2>&1; } * Phase B: Protocol (Success Criteria)
# Resolve symlinks to find the actual repository location ** Test Suite Context
SOURCE="${BASH_SOURCE[0]}" #+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
while [ -h "$SOURCE" ]; do (defpackage :opencortex-setup-tests
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" (:use :cl :fiveam :opencortex)
SOURCE="$(readlink "$SOURCE")" (:export #:setup-suite))
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
done
export SCRIPT_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
# Load environment variables if they exist
if [ -f "$SCRIPT_DIR/.env" ]; then
while IFS="=" read -r key value || [ -n "$key" ]; do
if [[ $key =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
val=$(echo "$value" | sed "s/^\"//;s/\"$//")
export "$key=$val"
fi
done < "$SCRIPT_DIR/.env"
[ -n "$ORG_AGENT_DAEMON_PORT" ] && PORT=$ORG_AGENT_DAEMON_PORT
[ -n "$DAEMON_HOST" ] && HOST=$DAEMON_HOST
fi
# --- 1. BOOTSTRAP ---
# If the script is run standalone, it clones the full repo and restarts itself.
if [ ! -d "$SCRIPT_DIR/.git" ] && [ ! -d "$HOME/.opencortex" ] && [[ ! "$(pwd)" =~ "opencortex" ]]; then
echo -e "${BLUE}=== OpenCortex: Zero-to-One Bootstrapper ===${NC}"
git clone ssh://git@10.10.10.201:2222/amr/opencortex.git ~/.opencortex
cd ~/.opencortex && git submodule update --init --recursive
exec ./opencortex.sh "$@"
fi
# --- 2. SETUP ---
setup_system() {
NON_INTERACTIVE=false
for arg in "$@"; do
if [ "$arg" == "--non-interactive" ]; then NON_INTERACTIVE=true; fi
done
echo -e "${BLUE}=== OpenCortex: Initializing System ===${NC}"
echo -e "${YELLOW}--- Installing System Dependencies ---${NC}"
if command_exists apt-get; then
sudo apt-get update && sudo apt-get install -y sbcl emacs-nox rlwrap netcat-openbsd curl git socat libssl-dev libncurses5-dev libffi-dev zlib1g-dev libsqlite3-dev
fi
if [ ! -d "$HOME/quicklisp" ]; then
curl -O https://beta.quicklisp.org/quicklisp.lisp
sbcl --non-interactive --load quicklisp.lisp --eval "(quicklisp-quickstart:install)" --eval "(ql-util:without-prompting (ql:add-to-init-file))"
rm quicklisp.lisp
fi
cd "$SCRIPT_DIR"
if [ ! -f .env ]; then
if [ "$NON_INTERACTIVE" = true ]; then
echo "Non-interactive mode: Using environment variables for .env creation."
cp .env.example .env
[ -n "$MEMEX_USER" ] && sed -i "s|MEMEX_USER=.*|MEMEX_USER=\"$MEMEX_USER\"|" .env
[ -n "$MEMEX_ASSISTANT" ] && sed -i "s|MEMEX_ASSISTANT=.*|MEMEX_ASSISTANT=\"$MEMEX_ASSISTANT\"|" .env
[ -n "$OPENROUTER_API_KEY" ] && sed -i "s|OPENROUTER_API_KEY=.*|OPENROUTER_API_KEY=\"$OPENROUTER_API_KEY\"|" .env
[ -n "$MEMEX_DIR" ] && sed -i "s|MEMEX_DIR=.*|MEMEX_DIR=\"$MEMEX_DIR\"|" .env
else
cp .env.example .env
echo -e "\n${YELLOW}--- Identity Configuration ---${NC}"
read -p "Your Name [User]: " user_name < /dev/tty
user_name=${user_name:-User}
sed -i "s|MEMEX_USER=.*|MEMEX_USER=\"$user_name\"|" .env
read -p "Agent Name [OpenCortex]: " agent_name < /dev/tty
agent_name=${agent_name:-OpenCortex}
sed -i "s|MEMEX_ASSISTANT=.*|MEMEX_ASSISTANT=\"$agent_name\"|" .env
echo -e "\n${YELLOW}--- LLM Configuration ---${NC}"
read -p "OpenRouter API Key: " openrouter_key < /dev/tty
[ -n "$openrouter_key" ] && sed -i "s|OPENROUTER_API_KEY=.*|OPENROUTER_API_KEY=\"$openrouter_key\"|" .env
echo -e "\n${YELLOW}--- Memex Folder Structure ---${NC}"
read -p "Memex Root [\$HOME/memex]: " memex_dir < /dev/tty
memex_dir=${memex_dir:-\$HOME/memex}
sed -i "s|MEMEX_DIR=.*|MEMEX_DIR=\"$memex_dir\"|" .env
fi
# Hydrate default paths
M_DIR=$(grep MEMEX_DIR .env | cut -d'"' -f2 | sed "s|\$HOME|$HOME|")
sed -i "s|SKILLS_DIR=.*|SKILLS_DIR=\"$SCRIPT_DIR/skills\"|" .env
sed -i "s|ZETTELKASTEN_DIR=.*|ZETTELKASTEN_DIR=\"$M_DIR/notes\"|" .env
mkdir -p "$M_DIR" "$M_DIR/notes" "$M_DIR/areas" "$M_DIR/resources" "$M_DIR/archives" "$M_DIR/system" "$M_DIR/inbox" "$M_DIR/daily" "$M_DIR/projects"
fi
mkdir -p library
for f in harness/*.org skills/*.org; do
emacs -Q --batch --eval "(require 'org)" --eval "(org-babel-tangle-file \"$f\")" >/dev/null 2>&1 || true
done
mkdir -p "$HOME/.local/bin"
ln -sf "$SCRIPT_DIR/opencortex.sh" "$HOME/.local/bin/opencortex"
for shell_config in "$HOME/.bashrc" "$HOME/.profile"; do
if [ -f "$shell_config" ]; then
if ! grep -q ".local/bin" "$shell_config"; then
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$shell_config"
fi
fi
done
export PATH="$HOME/.local/bin:$PATH"
echo -e "${YELLOW}--- Compiling and Loading OpenCortex ---${NC}"
sbcl --non-interactive --eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' --eval '(push (truename (uiop:getenv "SCRIPT_DIR")) asdf:*central-registry*)' --eval "(ql:quickload '(:opencortex :croatoan))"
if [ $? -ne 0 ]; then
echo -e "${RED}✗ Compilation failed.${NC}"
exit 1
fi
if [ "$NON_INTERACTIVE" = true ]; then
echo "Setup complete (Non-interactive)."
exit 0
fi
echo -e "${YELLOW}--- Finalizing: Awakening the Brain ---${NC}"
"$SCRIPT_DIR/opencortex.sh" --boot > "$SCRIPT_DIR/brain.log" 2>&1 &
success=false
for i in {1..30}; do
if nc -z localhost $PORT 2>/dev/null; then success=true; break; fi
sleep 2
echo -n "."
done
if [ "$success" = true ]; then
echo -e "\n${GREEN}✓ Brain is alive on port $PORT.${NC}"
exit 0
else
echo -e "\n${RED}✗ Brain failed to wake up.${NC}"
exit 1
fi
}
# --- 3. COMMAND ROUTER ---
COMMAND=$1
[ -z "$COMMAND" ] && COMMAND="cli"
shift || true
DEFAULT_PORT=9105
DEFAULT_HOST="localhost"
TARGET_PORT=${PORT:-$DEFAULT_PORT}
TARGET_HOST=${HOST:-$DEFAULT_HOST}
# If uninitialized, force setup.
if [ ! -f "$SCRIPT_DIR/library/package.lisp" ] || [ ! -f "$SCRIPT_DIR/.env" ]; then
COMMAND="setup"
fi
case "$COMMAND" in
setup)
setup_system "$@"
;;
--boot|boot)
export SKILLS_DIR="${SCRIPT_DIR}/skills"
[ -z "$MEMEX_DIR" ] && export MEMEX_DIR="$HOME/memex"
if [ -f "$SCRIPT_DIR/.env" ]; then
export OPENROUTER_API_KEY=$(grep OPENROUTER_API_KEY "$SCRIPT_DIR/.env" | cut -d'"' -f2)
fi
exec sbcl --non-interactive --eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' --eval '(setf *debugger-hook* (lambda (c h) (declare (ignore h)) (format *error-output* "FATAL LISP ERROR: ~a~%" c) (uiop:print-backtrace :stream *error-output*) (uiop:quit 1)))' --eval '(push (truename (uiop:getenv "SCRIPT_DIR")) asdf:*central-registry*)' --eval '(format t "--- Quickloading OpenCortex ---~%")' --eval "(ql:quickload '(:opencortex :croatoan))" --eval '(opencortex:main)'
;;
tui)
if ! nc -z $TARGET_HOST $TARGET_PORT 2>/dev/null; then
echo -e "Brain is offline. Awakening..."
"$SCRIPT_DIR/opencortex.sh" --boot > "$SCRIPT_DIR/brain.log" 2>&1 &
for i in {1..15}; do
sleep 2
if nc -z $TARGET_HOST $TARGET_PORT 2>/dev/null; then break; fi
echo -n "."
done
echo ""
fi
echo -e "Launching Croatoan TUI..."
export SKILLS_DIR="${SCRIPT_DIR}/skills"
[ -z "$MEMEX_DIR" ] && export MEMEX_DIR="$HOME/memex"
exec sbcl --eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' --eval '(push (truename (uiop:getenv "SCRIPT_DIR")) asdf:*central-registry*)' --eval '(ql:quickload :opencortex/tui)' --eval '(opencortex.tui:main)'
;;
cli)
if ! nc -z $TARGET_HOST $TARGET_PORT 2>/dev/null; then
echo -e "Brain is offline. Awakening..."
"$SCRIPT_DIR/opencortex.sh" --boot > "$SCRIPT_DIR/brain.log" 2>&1 &
for i in {1..15}; do
sleep 2
if nc -z $TARGET_HOST $TARGET_PORT 2>/dev/null; then break; fi
echo -n "."
done
echo ""
fi
if command_exists socat; then
echo -e "Connected to OpenCortex on $TARGET_HOST:$TARGET_PORT (Channel: CLI)"
while true; do
read -p "User: " MESSAGE
if [ -z "$MESSAGE" ]; then continue; fi
if [ "$MESSAGE" = "/exit" ]; then break; fi
# Frame the message
PAYLOAD="(:TYPE :EVENT :META (:SOURCE :CLI) :PAYLOAD (:SENSOR :USER-INPUT :TEXT \"$MESSAGE\"))"
LEN=$(printf "%s" "$PAYLOAD" | wc -c)
HEXLEN=$(printf "%06x" $LEN)
# Send and read response
(printf "%s%s" "$HEXLEN" "$PAYLOAD" | nc -N $TARGET_HOST $TARGET_PORT) | while read -r LINE; do
CLEAN=$(echo "$LINE" | sed 's/^......//')
if [[ "$CLEAN" == *":TEXT"* ]]; then
TEXT=$(echo "$CLEAN" | sed -n 's/.*:TEXT "\([^"]*\)".*/\1/p')
echo -e "Agent: $TEXT"
fi
done
done
else
echo "Error: socat required for CLI interaction."
exit 1
fi
;;
*)
echo -e "Unknown command: $COMMAND"
echo "Available commands: setup, boot, tui, cli"
exit 1
;;
esac
#+end_src #+end_src
** Metabolic Docker Infrastructure (Dockerfile) #+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
#+begin_src dockerfile :tangle (expand-file-name "../infrastructure/docker/Dockerfile" (concat (or (getenv "INSTALL_DIR") ".") "/harness")) (in-package :opencortex-setup-tests)
FROM debian:bullseye-slim #+end_src
ENV DEBIAN_FRONTEND=noninteractive #+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(def-suite setup-suite :description "Verification of the Lisp Setup Wizard")
RUN apt-get update && apt-get install -y \ #+end_src
sbcl \
emacs-nox \ #+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
curl \ (in-suite setup-suite)
git \ #+end_src
socat \
netcat-openbsd \ ** Persistence Tests
libssl-dev \ #+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
libncurses5-dev \ (test test-provider-registry-persistence
libffi-dev \ "Verify that multiple providers can be registered and saved."
zlib1g-dev \ (let ((opencortex::*providers* nil))
libsqlite3-dev \ (opencortex:register-provider :ollama '(:url "http://localhost:11434" :model "llama3"))
&& rm -rf /var/lib/apt/lists/* (opencortex:register-provider :groq '(:key "gsk_123" :model "mixtral-8x7b"))
(is (equal "gsk_123" (getf (getf opencortex::*providers* :groq) :key)))))
# Install Quicklisp #+end_src
RUN curl -O https://beta.quicklisp.org/quicklisp.lisp \
&& sbcl --non-interactive --load quicklisp.lisp --eval "(quicklisp-quickstart:install)" --eval "(ql-util:without-prompting (ql:add-to-init-file))" \ ** Fallback Tests
&& rm quicklisp.lisp #+begin_src lisp :tangle (expand-file-name "setup-wizard-tests.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/tests"))
(test test-sovereign-fallback-logic
WORKDIR /app "Verify that the system identifies as healthy with only local providers."
COPY . . (let ((opencortex::*providers* (list :ollama '(:url "http://localhost:11434"))))
(is (opencortex:system-ready-p))))
# Initialize system in non-interactive mode #+end_src
RUN mkdir -p /root/memex && ./opencortex.sh setup --non-interactive
* Phase C: Implementation (Build)
EXPOSE 9105
** Package Context
CMD ["./opencortex.sh", "boot"] #+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(in-package :opencortex)
#+end_src
** Global Provider Registry
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defvar *providers* nil "Global registry of configured LLM providers.")
#+end_src
** Provider Templates
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defvar *provider-templates*
'((:ollama . (:name "Ollama (Local)" :fields ((:url :label "URL") (:model :label "Model")) :default-url "http://localhost:11434" :default-model "llama3"))
(:openrouter . (:name "OpenRouter" :fields ((:key :label "API Key" :secret t) (:model :label "Model")) :default-model "anthropic/claude-3-opus-20240229"))
(:openai . (:name "OpenAI" :fields ((:key :label "API Key" :secret t) (:model :label "Model")) :default-model "gpt-4-turbo"))
(:groq . (:name "Groq" :fields ((:key :label "API Key" :secret t) (:model :label "Model")) :default-model "mixtral-8x7b-32768"))
(:gemini . (:name "Google Gemini" :fields ((:key :label "API Key" :secret t) (:model :label "Model")) :default-model "gemini-1.5-pro"))
(:anthropic . (:name "Anthropic" :fields ((:key :label "API Key" :secret t) (:model :label "Model")) :default-model "claude-3-5-sonnet-20240620")))
"Templates for supported LLM providers. Fields marked :secret go to .env.")
#+end_src
** XDG Configuration Utilities
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun get-oc-config-dir ()
"Resolves the OpenCortex configuration directory following XDG standards."
(let ((env (uiop:getenv "OC_CONFIG_DIR")))
(if (and env (> (length env) 0))
(uiop:ensure-directory-pathname env)
(merge-pathnames ".config/opencortex/" (user-homedir-pathname)))))
#+end_src
** Secret Persistence
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun save-secret (id key value)
"Appends a secret to the XDG config .env file and updates the current environment."
(let* ((env-key (format nil "~:@(~a_~a~)" id key))
(path (merge-pathnames ".env" (get-oc-config-dir))))
(ensure-directories-exist path)
(with-open-file (s path :direction :output :if-exists :append :if-does-not-exist :create)
(format s "~%~a=\"~a\"" env-key value))
(setf (uiop:getenv env-key) value)))
#+end_src
** Provider Metadata Persistence
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun save-providers ()
"Persist provider configuration to XDG config directory."
(let ((path (merge-pathnames "providers.lisp" (get-oc-config-dir))))
(ensure-directories-exist path)
(with-open-file (s path :direction :output :if-exists :supersede)
(format s ";;; OpenCortex Provider Metadata~%~s~%" *providers*))))
#+end_src
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun load-providers ()
"Load provider configuration from XDG config directory."
(let ((path (merge-pathnames "providers.lisp" (get-oc-config-dir))))
(when (uiop:file-exists-p path)
(with-open-file (s path)
(setf *providers* (read s))))))
#+end_src
** Registry API
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun register-provider (id config)
"Update the global provider registry."
(setf (getf *providers* id) config))
#+end_src
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun system-ready-p ()
"Predicate verifying if at least one provider is configured."
(and *providers* (> (length *providers*) 0)))
#+end_src
** User Interface Primitives
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun prompt-for (label &optional default)
"Interactively prompt the user for input with an optional default."
(format t "~a~@[ [~a]~]: " label default)
(finish-output)
(let ((input (read-line)))
(if (and (string= input "") default)
default
input)))
#+end_src
** Provider Configuration Loop
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun configure-provider (id)
"Guided configuration for a specific LLM provider template."
(let* ((template (cdr (assoc id *provider-templates*)))
(fields (getf template :fields))
(config nil))
(format t "~%--- Configuring ~a ---~%" (getf template :name))
(dolist (field-spec fields)
(let* ((field (first field-spec))
(label (getf (rest field-spec) :label))
(is-secret (getf (rest field-spec) :secret))
(default-key (intern (format nil "DEFAULT-~a" field) :keyword))
(default (getf template default-key))
(val (prompt-for label default)))
(if is-secret
(save-secret id field val)
(setf (getf config field) val))))
(register-provider id config)
(format t "✓ ~a metadata registered.~%" (getf template :name))))
#+end_src
** Main Setup Orchestrator
#+begin_src lisp :tangle (expand-file-name "setup-wizard.lisp" (concat (or (getenv "INSTALL_DIR") ".") "/harness"))
(defun run-setup-wizard ()
"Entry point for the interactive OpenCortex Lisp Setup Wizard."
(format t "=== OpenCortex: Advanced Setup Wizard ===~%")
;; 1. Identity
(let ((user (prompt-for "Your Name" "User"))
(agent (prompt-for "Agent Name" "OpenCortex")))
(format t "Welcome, ~a. I am ~a.~%" user agent))
;; 2. Providers
(format t "~%Available Providers:~%")
(loop for (id . data) in *provider-templates*
do (format t " ~a: ~a~%" id (getf data :name)))
(format t "~%Enter provider IDs to configure (comma separated, or 'all'): ")
(finish-output)
(let* ((input (read-line))
(ids (if (string= input "all")
(mapcar #'car *provider-templates*)
(mapcar (lambda (s) (intern (string-upcase (string-trim " " s)) :keyword))
(uiop:split-string input :separator ",")))))
(dolist (id ids)
(when (assoc id *provider-templates*)
(configure-provider id))))
(save-providers)
(format t "~%Setup complete. Running doctor check...~%")
(doctor-run-all))
#+end_src #+end_src

View File

@@ -19,6 +19,8 @@
(:file "harness/reason") (:file "harness/reason")
(:file "harness/act") (:file "harness/act")
(:file "harness/loop") (:file "harness/loop")
(:file "harness/doctor")
(:file "harness/setup-wizard")
(:file "skills/org-skill-policy") (:file "skills/org-skill-policy")
(:file "skills/org-skill-bouncer") (:file "skills/org-skill-bouncer")
@@ -31,7 +33,6 @@
(:file "skills/org-skill-emacs-edit") (:file "skills/org-skill-emacs-edit")
(:file "skills/org-skill-tool-permissions") (:file "skills/org-skill-tool-permissions")
(:file "skills/org-skill-self-fix") (:file "skills/org-skill-self-fix")
(:file "skills/org-skill-lisp-validator")
(:file "skills/org-skill-peripheral-vision")) (:file "skills/org-skill-peripheral-vision"))
:build-operation "program-op" :build-operation "program-op"
@@ -53,7 +54,9 @@
(:file "tests/lisp-validator-tests") (:file "tests/lisp-validator-tests")
(:file "tests/literate-programming-tests") (:file "tests/literate-programming-tests")
(:file "tests/self-edit-tests") (:file "tests/self-edit-tests")
(:file "tests/tool-permissions-tests"))) (:file "tests/tool-permissions-tests")
(:file "tests/doctor-tests")
(:file "tests/setup-wizard-tests")))
(defsystem :opencortex/tui (defsystem :opencortex/tui
:depends-on (:opencortex :croatoan :usocket :bordeaux-threads) :depends-on (:opencortex :croatoan :usocket :bordeaux-threads)

View File

@@ -7,7 +7,8 @@ RED='\033[0;31m'; GREEN='\033[0;32m'; BLUE='\033[0;34m'; YELLOW='\033[0;33m'; NC
command_exists() { command -v "$1" >/dev/null 2>&1; } command_exists() { command -v "$1" >/dev/null 2>&1; }
# Resolve symlinks to find the actual repository location # 1. XDG PATH RESOLUTION
# SCRIPT_DIR is the immutable source (where the git repo lives)
SOURCE="${BASH_SOURCE[0]}" SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do while [ -h "$SOURCE" ]; do
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
@@ -16,25 +17,15 @@ while [ -h "$SOURCE" ]; do
done done
export SCRIPT_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" export SCRIPT_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
# Load environment variables if they exist # XDG Defaults
if [ -f "$SCRIPT_DIR/.env" ]; then export OC_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/opencortex"
while IFS="=" read -r key value || [ -n "$key" ]; do export OC_DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/opencortex"
if [[ $key =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then export OC_STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/opencortex"
val=$(echo "$value" | sed "s/^\"//;s/\"$//") export OC_BIN_DIR="${XDG_BIN_HOME:-$HOME/.local/bin}"
export "$key=$val"
fi
done < "$SCRIPT_DIR/.env"
[ -n "$ORG_AGENT_DAEMON_PORT" ] && PORT=$ORG_AGENT_DAEMON_PORT
[ -n "$DAEMON_HOST" ] && HOST=$DAEMON_HOST
fi
# --- 1. BOOTSTRAP --- # Load environment variables from the standard config location
# If the script is run standalone, it clones the full repo and restarts itself. if [ -f "$OC_CONFIG_DIR/.env" ]; then
if [ ! -d "$SCRIPT_DIR/.git" ] && [ ! -f "$SCRIPT_DIR/harness/package.org" ] && [ ! -d "$HOME/.opencortex" ] && [[ ! "$(pwd)" =~ "opencortex" ]]; then source "$OC_CONFIG_DIR/.env"
echo -e "${BLUE}=== OpenCortex: Zero-to-One Bootstrapper ===${NC}"
git clone ssh://git@10.10.10.201:2222/amr/opencortex.git ~/.opencortex
cd ~/.opencortex && git submodule update --init --recursive
exec ./opencortex.sh "$@"
fi fi
# --- 2. SETUP --- # --- 2. SETUP ---
@@ -44,10 +35,15 @@ setup_system() {
if [ "$arg" == "--non-interactive" ]; then NON_INTERACTIVE=true; fi if [ "$arg" == "--non-interactive" ]; then NON_INTERACTIVE=true; fi
done done
echo -e "${BLUE}=== OpenCortex: Initializing System ===${NC}" echo -e "${BLUE}=== OpenCortex: Initializing XDG-Compliant System ===${NC}"
# Create standard directories
mkdir -p "$OC_CONFIG_DIR" "$OC_DATA_DIR" "$OC_STATE_DIR" "$OC_BIN_DIR"
mkdir -p "$OC_DATA_DIR/harness" "$OC_DATA_DIR/tests" "$OC_DATA_DIR/skills" "$OC_DATA_DIR/library"
echo -e "${YELLOW}--- Installing System Dependencies ---${NC}" echo -e "${YELLOW}--- Installing System Dependencies ---${NC}"
if command_exists apt-get; then if command_exists apt-get; then
sudo apt-get update && sudo apt-get install -y sbcl emacs-nox rlwrap netcat-openbsd curl git socat libssl-dev libncurses5-dev libffi-dev zlib1g-dev libsqlite3-dev sudo apt-get update && sudo apt-get install -y sbcl emacs-nox rlwrap netcat-openbsd curl git socat libssl-dev libncurses-dev libffi-dev zlib1g-dev libsqlite3-dev
fi fi
if [ ! -d "$HOME/quicklisp" ]; then if [ ! -d "$HOME/quicklisp" ]; then
curl -O https://beta.quicklisp.org/quicklisp.lisp curl -O https://beta.quicklisp.org/quicklisp.lisp
@@ -55,111 +51,33 @@ setup_system() {
rm quicklisp.lisp rm quicklisp.lisp
fi fi
# Tangle the literate source from SCRIPT_DIR to OC_DATA_DIR (The Engine)
echo -e "${YELLOW}--- Deploying Engine to $OC_DATA_DIR ---${NC}"
cp "$SCRIPT_DIR/opencortex.asd" "$OC_DATA_DIR/"
cd "$SCRIPT_DIR" cd "$SCRIPT_DIR"
if [ ! -f .env ]; then export INSTALL_DIR="$OC_DATA_DIR"
if [ "$NON_INTERACTIVE" = true ]; then for f in harness/*.org skills/*.org; do
echo "Non-interactive mode: Using environment variables for .env creation." echo "Tangling $f..."
cp .env.example .env emacs -Q --batch --eval "(require 'org)" --eval "(org-babel-tangle-file \"$f\")" >/dev/null 2>&1 || true
[ -n "$MEMEX_USER" ] && sed -i "s|MEMEX_USER=.*|MEMEX_USER=\"$MEMEX_USER\"|" .env
[ -n "$MEMEX_ASSISTANT" ] && sed -i "s|MEMEX_ASSISTANT=.*|MEMEX_ASSISTANT=\"$MEMEX_ASSISTANT\"|" .env
[ -n "$OPENROUTER_API_KEY" ] && sed -i "s|OPENROUTER_API_KEY=.*|OPENROUTER_API_KEY=\"$OPENROUTER_API_KEY\"|" .env
[ -n "$MEMEX_DIR" ] && sed -i "s|MEMEX_DIR=.*|MEMEX_DIR=\"$MEMEX_DIR\"|" .env
else
cp .env.example .env
echo -e "\n${YELLOW}--- Identity Configuration ---${NC}"
read -p "Your Name [User]: " user_name < /dev/tty
user_name=${user_name:-User}
sed -i "s|MEMEX_USER=.*|MEMEX_USER=\"$user_name\"|" .env
read -p "Agent Name [OpenCortex]: " agent_name < /dev/tty
agent_name=${agent_name:-OpenCortex}
sed -i "s|MEMEX_ASSISTANT=.*|MEMEX_ASSISTANT=\"$agent_name\"|" .env
echo -e "\n${YELLOW}--- LLM Configuration ---${NC}"
read -p "OpenRouter API Key: " openrouter_key < /dev/tty
[ -n "$openrouter_key" ] && sed -i "s|OPENROUTER_API_KEY=.*|OPENROUTER_API_KEY=\"$openrouter_key\"|" .env
echo -e "\n${YELLOW}--- Memex Folder Structure ---${NC}"
read -p "Memex Root [\$HOME/memex]: " memex_dir < /dev/tty
memex_dir=${memex_dir:-\$HOME/memex}
sed -i "s|MEMEX_DIR=.*|MEMEX_DIR=\"$memex_dir\"|" .env
fi
# Hydrate default paths
M_DIR=$(grep MEMEX_DIR .env | cut -d'"' -f2 | sed "s|\$HOME|$HOME|")
I_DIR=$(grep INSTALL_DIR .env | cut -d'"' -f2 | sed "s|\$HOME|$HOME|")
if [ -z "$I_DIR" ]; then I_DIR="$HOME/opencortex"; fi
sed -i "s|SKILLS_DIR=.*|SKILLS_DIR=\"$I_DIR/skills\"|" .env
sed -i "s|ZETTELKASTEN_DIR=.*|ZETTELKASTEN_DIR=\"$M_DIR/notes\"|" .env
mkdir -p "$M_DIR" "$M_DIR/notes" "$M_DIR/areas" "$M_DIR/resources" "$M_DIR/archives" "$M_DIR/system" "$M_DIR/inbox" "$M_DIR/daily" "$M_DIR/projects"
fi
I_DIR=$(grep INSTALL_DIR .env | cut -d'"' -f2 | sed "s|\$HOME|$HOME|")
if [ -z "$I_DIR" ]; then I_DIR="$HOME/opencortex"; fi
if [ "$SCRIPT_DIR" != "$I_DIR" ]; then
echo -e "${YELLOW}--- Deploying to Instance Directory ($I_DIR) ---${NC}"
mkdir -p "$I_DIR/harness" "$I_DIR/skills" "$I_DIR/tests"
cp "$SCRIPT_DIR/opencortex.asd" "$I_DIR/"
cp "$SCRIPT_DIR/opencortex.sh" "$I_DIR/"
cp "$SCRIPT_DIR/.env" "$I_DIR/"
echo -e "${BLUE}Tangling Lisp files to instance directory...${NC}"
export INSTALL_DIR="$I_DIR"
for f in harness/*.org skills/*.org; do
emacs -Q --batch --eval "(require 'org)" --eval "(org-babel-tangle-file \"$f\")" >/dev/null 2>&1 || true
done
else
echo -e "${BLUE}--- Running in Local Mode (Source == Instance) ---${NC}"
echo -e "${BLUE}Tangling Lisp files...${NC}"
export INSTALL_DIR="$I_DIR"
for f in harness/*.org skills/*.org; do
emacs -Q --batch --eval "(require 'org)" --eval "(org-babel-tangle-file \"$f\")" >/dev/null 2>&1 || true
done
fi
mkdir -p "$HOME/.local/bin"
ln -sf "$I_DIR/opencortex.sh" "$HOME/.local/bin/opencortex"
for shell_config in "$HOME/.bashrc" "$HOME/.profile"; do
if [ -f "$shell_config" ]; then
if ! grep -q ".local/bin" "$shell_config"; then
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$shell_config"
fi
fi
done done
export PATH="$HOME/.local/bin:$PATH"
echo -e "${YELLOW}--- Compiling and Loading OpenCortex ---${NC}" # Create the bin shim
sbcl --non-interactive --eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' --eval "(push (truename \"$I_DIR/\") asdf:*central-registry*)" --eval "(ql:quickload '(:opencortex :croatoan))" echo -e "${YELLOW}--- Creating Bin Shim in $OC_BIN_DIR/opencortex ---${NC}"
ln -sf "$SCRIPT_DIR/opencortex.sh" "$OC_BIN_DIR/opencortex"
if [ $? -ne 0 ]; then
echo -e "${RED}✗ Compilation failed.${NC}"
exit 1
fi
if [ "$NON_INTERACTIVE" = true ]; then if [ "$NON_INTERACTIVE" = true ]; then
echo "Setup complete (Non-interactive)." echo "Setup complete (Non-interactive)."
exit 0 exit 0
fi fi
echo -e "${YELLOW}--- Finalizing: Awakening the Brain ---${NC}" echo -e "${YELLOW}--- Launching Lisp Setup Wizard ---${NC}"
export I_DIR="$I_DIR"; "$I_DIR/opencortex.sh" --boot > "$I_DIR/brain.log" 2>&1 & # Use OC_DATA_DIR for the Lisp registry
exec sbcl --non-interactive \
success=false --eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' \
for i in {1..30}; do --eval "(push (truename \"$OC_DATA_DIR/\") asdf:*central-registry*)" \
if nc -z localhost $PORT 2>/dev/null; then success=true; break; fi --eval '(ql:quickload :opencortex)' \
sleep 2 --eval '(opencortex:run-setup-wizard)'
echo -n "."
done
if [ "$success" = true ]; then
echo -e "\n${GREEN}✓ Brain is alive on port $PORT.${NC}"
exit 0
else
echo -e "\n${RED}✗ Brain failed to wake up.${NC}"
exit 1
fi
} }
# --- 3. COMMAND ROUTER --- # --- 3. COMMAND ROUTER ---
@@ -167,88 +85,39 @@ COMMAND=$1
[ -z "$COMMAND" ] && COMMAND="cli" [ -z "$COMMAND" ] && COMMAND="cli"
shift || true shift || true
DEFAULT_PORT=9105
DEFAULT_HOST="localhost"
TARGET_PORT=${PORT:-$DEFAULT_PORT}
TARGET_HOST=${HOST:-$DEFAULT_HOST}
# If uninitialized, force setup.
if [ ! -f "$SCRIPT_DIR/harness/package.lisp" ] || [ ! -f "$SCRIPT_DIR/.env" ]; then
COMMAND="setup"
fi
case "$COMMAND" in case "$COMMAND" in
doctor)
export SKILLS_DIR="${OC_DATA_DIR}/skills"
[ -z "$MEMEX_DIR" ] && export MEMEX_DIR="$HOME/memex"
exec sbcl --non-interactive \
--eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' \
--eval "(push (truename \"$OC_DATA_DIR/\") asdf:*central-registry*)" \
--eval '(ql:quickload :opencortex)' \
--eval '(opencortex:doctor-main)'
;;
setup) setup)
setup_system "$@" setup_system "$@"
;; ;;
--boot|boot) boot|--boot)
export SKILLS_DIR="${SCRIPT_DIR}/skills" exec sbcl --non-interactive \
[ -z "$MEMEX_DIR" ] && export MEMEX_DIR="$HOME/memex" --eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' \
if [ -f "$SCRIPT_DIR/.env" ]; then --eval "(push (truename \"$OC_DATA_DIR/\") asdf:*central-registry*)" \
export OPENROUTER_API_KEY=$(grep OPENROUTER_API_KEY "$SCRIPT_DIR/.env" | cut -d'"' -f2) --eval "(ql:quickload '(:opencortex :croatoan))" \
fi --eval '(opencortex:main)'
exec sbcl --non-interactive --eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' --eval '(setf *debugger-hook* (lambda (c h) (declare (ignore h)) (format *error-output* "FATAL LISP ERROR: ~a~%" c) (uiop:print-backtrace :stream *error-output*) (uiop:quit 1)))' --eval '(let ((path (or (uiop:getenv "I_DIR") (uiop:getenv "SCRIPT_DIR")))) (when path (push (truename path) asdf:*central-registry*)))' --eval '(format t "--- Quickloading OpenCortex ---~%")' --eval "(ql:quickload '(:opencortex :croatoan))" --eval '(opencortex:main)'
;; ;;
tui) tui)
if ! nc -z $TARGET_HOST $TARGET_PORT 2>/dev/null; then exec sbcl \
echo -e "Brain is offline. Awakening..." --eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' \
"$SCRIPT_DIR/opencortex.sh" --boot > "$SCRIPT_DIR/brain.log" 2>&1 & --eval "(push (truename \"$OC_DATA_DIR/\") asdf:*central-registry*)" \
for i in {1..15}; do --eval '(ql:quickload :opencortex/tui)' \
sleep 2 --eval '(opencortex.tui:main)'
if nc -z $TARGET_HOST $TARGET_PORT 2>/dev/null; then break; fi
echo -n "."
done
echo ""
fi
echo -e "Launching Croatoan TUI..."
export SKILLS_DIR="${SCRIPT_DIR}/skills"
[ -z "$MEMEX_DIR" ] && export MEMEX_DIR="$HOME/memex"
exec sbcl --eval '(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))' --eval '(let ((path (or (uiop:getenv "I_DIR") (uiop:getenv "SCRIPT_DIR")))) (when path (push (truename path) asdf:*central-registry*)))' --eval '(ql:quickload :opencortex/tui)' --eval '(opencortex.tui:main)'
;;
cli)
if ! nc -z $TARGET_HOST $TARGET_PORT 2>/dev/null; then
echo -e "Brain is offline. Awakening..."
"$SCRIPT_DIR/opencortex.sh" --boot > "$SCRIPT_DIR/brain.log" 2>&1 &
for i in {1..15}; do
sleep 2
if nc -z $TARGET_HOST $TARGET_PORT 2>/dev/null; then break; fi
echo -n "."
done
echo ""
fi
if command_exists socat; then
echo -e "Connected to OpenCortex on $TARGET_HOST:$TARGET_PORT (Channel: CLI)"
while true; do
read -p "User: " MESSAGE
if [ -z "$MESSAGE" ]; then continue; fi
if [ "$MESSAGE" = "/exit" ]; then break; fi
# Frame the message
PAYLOAD="(:TYPE :EVENT :META (:SOURCE :CLI) :PAYLOAD (:SENSOR :USER-INPUT :TEXT \"$MESSAGE\"))"
LEN=$(printf "%s" "$PAYLOAD" | wc -c)
HEXLEN=$(printf "%06x" $LEN)
# Send and read response
(printf "%s%s" "$HEXLEN" "$PAYLOAD" | nc -N $TARGET_HOST $TARGET_PORT) | while read -r LINE; do
CLEAN=$(echo "$LINE" | sed 's/^......//')
if [[ "$CLEAN" == *":TEXT"* ]]; then
TEXT=$(echo "$CLEAN" | sed -n 's/.*:TEXT "\([^"]*\)".*/\1/p')
echo -e "Agent: $TEXT"
fi
done
done
else
echo "Error: socat required for CLI interaction."
exit 1
fi
;; ;;
*) *)
echo -e "Unknown command: $COMMAND" echo "Available commands: setup, doctor, boot, tui"
echo "Available commands: setup, boot, tui, cli"
exit 1 exit 1
;; ;;
esac esac