diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..76cd8af Binary files /dev/null and b/.DS_Store differ diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4a32ab4..ee299ae 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -16,10 +16,12 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@1.90.0 with: - toolchain: 1.90.0 components: rustfmt, clippy + - name: Cache cargo uses: actions/cache@v4 with: @@ -37,3 +39,18 @@ jobs: - name: Run check run: just check + + - name: Install ISO tooling + run: | + sudo apt-get update + sudo apt-get install -y xorriso + + - name: Build installer ISO + run: ./scripts/make_iso.sh + + - name: Upload installer ISO artifact + uses: actions/upload-artifact@v4 + with: + name: tiles-installer-iso + path: dist/*.iso + if-no-files-found: error diff --git a/.gitignore b/.gitignore index e533825..1038942 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target .tiles_dev +dist/ diff --git a/README.md b/README.md index 5143bb3..9c7c70f 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,11 @@ Modelfile-based SDK that lets developers to lets developers customize local mode - Go to root and run `just serve` in another terminal to run the server - Run the rust cli using cargo as usual +### Packaging installers + +- `just bundle` creates a tarball that includes the CLI binary and Python server. +- `just iso` wraps the bundle and installer script into an ISO that can be flashed with tools like Balena Etcher. + ## License This project is dual-licensed under MIT and Apache 2.0 terms: diff --git a/ascii-art.txt b/ascii-art.txt new file mode 100644 index 0000000..c57ccdb --- /dev/null +++ b/ascii-art.txt @@ -0,0 +1,55 @@ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░▓█████████████████████████████████████████████▓░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░█████████████████████████████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░▒███▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓██████▒░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░▒███▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░███▓░▒███▒░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░▒███▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒███▒░░░▒███▒░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░▓███░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒███▒░░░░░▒███▒░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░▓██████████████████████▒░░░░░░░▓████████████████▒░░░░░░▒███░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░▓███▓▒▒▒▒▒▓▓▓▓▓▓▓▓███▒░░░░░░░▒█████▓▓▓▓▓▒▒▒▒▓███▒░░░░▒███░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░▒███░░░░░░░░░░░░▓██▓░░░░░░░░██████▒░░░░░░░░░▒███▒░░▒███▒░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░▒███░░░░░░░░░░▒███░░░░░░░░███▒░███▒░░░░░░░░░▒███▒▒███▒░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░▒███░░░░░░░░▒███▒░░░░░░░▓██▓░░░▓██▓░░░░░░░░░░██████▒░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░▒█████████████▒░░░░░░░▒███░░░░░██████████████████▒░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░▒███████████▓░░░░░░░▒███░░░░░░▓████████████████▒░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒██▓░░░░░░░▒███▒░░░░░▒██▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒███░░░░░░░░▓██▒░░░░░▒███▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░███▒░░░░░░░▓██▓░░░░░░███▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓██▒░░░░░░░▒███░░░░░░▓██▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓██▓░░░░░░░▒███░░░░░░▓██▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒██▓░░░░░░░░███▒░░░░░▒███░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▒███▒░░░░░░░▓██▒░░░░░░███▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░███▒░░░░░░░▓██▓░░░░░░███▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░███▒░░░░░░░▒███░░░░░░▓██▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░▓██▓░░░░░░░▒███░░░░░░▒██▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░▒███░░░░░░░░███▒░░░░░▒███▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░███▒▒▒▒▒▒▒▒▓██▓░░░░░░███▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░███████████████░░░░░░███▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░▒███▓░░░░░░░▓██▓░░░░▓██▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░▒███▒░░░░░░░▓██▓░░▒███░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░▒███▒░░░░░░░▒███▒███▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░███▓░░░░░░░▒█████▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓██▓▒▒▒▒▒▒▒▓███▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█████████████▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ \ No newline at end of file diff --git a/justfile b/justfile index ea145c6..1a9ec6d 100644 --- a/justfile +++ b/justfile @@ -19,3 +19,6 @@ bundle: install: ./scripts/install.sh + +iso: + ./scripts/make_iso.sh diff --git a/memgpt.modelfile b/memgpt.modelfile deleted file mode 100644 index b990e82..0000000 --- a/memgpt.modelfile +++ /dev/null @@ -1 +0,0 @@ -FROM driaforall/mem-agent diff --git a/registry/memgpt/Modelfile b/registry/memgpt/Modelfile new file mode 100644 index 0000000..19c52e8 --- /dev/null +++ b/registry/memgpt/Modelfile @@ -0,0 +1 @@ +FROM driaforall/mem-agent-mlx-4bit \ No newline at end of file diff --git a/scripts/bundler.sh b/scripts/bundler.sh index d31dd87..5dd83cd 100755 --- a/scripts/bundler.sh +++ b/scripts/bundler.sh @@ -11,7 +11,7 @@ OS=$(uname -s | tr '[:upper:]' '[:lower:]') ARCH=$(uname -m) OUT_NAME="${BINARY_NAME}-v${VERSION}-${ARCH}-${OS}" -echo "🚀 Building ${BINARY_NAME} (${TARGET} mode)..." +echo "Building ${BINARY_NAME} (${TARGET} mode)..." cargo build --${TARGET} mkdir -p "${DIST_DIR}/tmp" @@ -21,9 +21,9 @@ cp -r "${SERVER_DIR}" "${DIST_DIR}/tmp/" rm -rf "${DIST_DIR}/tmp/server/__pycache__" rm -rf "${DIST_DIR}/tmp/server/.venv" -echo "📦 Creating ${OUT_NAME}.tar.gz..." +echo "Creating ${OUT_NAME}.tar.gz..." tar -czf "${DIST_DIR}/${OUT_NAME}.tar.gz" -C "${DIST_DIR}/tmp" . rm -rf "${DIST_DIR}/tmp" -echo "✅ Bundle created: ${DIST_DIR}/${OUT_NAME}.tar.gz" +echo "Bundle created: ${DIST_DIR}/${OUT_NAME}.tar.gz" diff --git a/scripts/install.sh b/scripts/install.sh index 7330009..1edef14 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,70 +1,252 @@ #!/usr/bin/env bash set -euo pipefail -ENV="prod" # prod is another env, try taking it from github env -REPO="tilesprivacy/tilekit" -# VERSION="${TILES_VERSION:-latest}" -VERSION="0.1.0" -INSTALL_DIR="$HOME/.local/bin" # CLI install location -SERVER_DIR="$HOME/.local/share/tiles/server" # Python server folder +# ============================================================================ +# Tiles Installer +# ============================================================================ + +# Terminal setup +TERM_WIDTH=${COLUMNS:-80} +if command -v tput >/dev/null 2>&1; then + TERM_WIDTH=$(tput cols 2>/dev/null || echo 80) +fi +[[ $TERM_WIDTH -lt 60 ]] && TERM_WIDTH=80 + +# Display ASCII art if available +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ASCII_ART_FILE="${SCRIPT_DIR}/../ascii-art.txt" +if [[ ! -f "${ASCII_ART_FILE}" ]]; then + VOLUME_ROOT="$(dirname "${SCRIPT_DIR}")" + ASCII_ART_FILE="${VOLUME_ROOT}/ascii-art.txt" +fi +if [[ -f "${ASCII_ART_FILE}" ]]; then + cat "${ASCII_ART_FILE}" + echo "" +fi + +# Configuration +ENV="${TILES_INSTALL_ENV:-prod}" +REPO="tilesprivacy/tilekit" +VERSION="0.1.0" +INSTALL_DIR="$HOME/.local/bin" +SERVER_DIR="$HOME/.local/share/tiles/server" TMPDIR="$(mktemp -d)" OS=$(uname -s | tr '[:upper:]' '[:lower:]') ARCH=$(uname -m) +# ============================================================================ +# UI Functions +# ============================================================================ + +# Print functions with color support +print_header() { + echo "" + echo -e "\033[1;36m================================================================================\033[0m" + echo -e "\033[1;37m$*\033[0m" + echo -e "\033[1;36m================================================================================\033[0m" + echo "" +} + +print_step() { + echo -e "\033[1;36m>\033[0m $*" +} + +print_success() { + echo -e "\033[1;32m[OK]\033[0m $*" +} + +print_warning() { + echo -e "\033[1;33m[!]\033[0m $*" +} + +print_error() { + echo -e "\033[1;31m[ERROR]\033[0m $*" >&2 +} + +print_info() { + echo -e "\033[0;90m $*\033[0m" +} -log() { echo -e "\033[1;36m$*\033[0m"; } -err() { echo -e "\033[1;31m$*\033[0m" >&2; exit 1; } +wrap_text() { + local text="$1" + local prefix="${2:- }" + local width=$((TERM_WIDTH - ${#prefix})) + echo "$text" | fold -s -w "$width" | sed "s/^/$prefix/" +} -echo "🔍 Checking Python..." -if ! command -v python3 >/dev/null 2>&1; then - log "⚠️ Python 3.10+ not found." +print_section() { + echo "" + echo -e "\033[1;37m[ $* ]\033[0m" +} + +print_section_end() { + echo -e "\033[1;37m------------------------------------------------------------\033[0m" + echo "" +} + +log() { + echo -e "\033[1;36m$*\033[0m" +} + +err() { + print_error "$*" + echo "" + exit 1 +} + +# ============================================================================ +# Main Installation +# ============================================================================ + +print_header "Tiles Installer v${VERSION}" + +wrap_text "This installer will set up Tiles on your system, including the CLI tool and Python server environment." " " +echo "" + +# ---------------------------------------------------------------------------- +# Check Dependencies +# ---------------------------------------------------------------------------- + +print_section "Checking Dependencies" + +print_step "Checking Python installation..." +if command -v python3 >/dev/null 2>&1; then + PY_VERSION=$(python3 --version 2>&1 | awk '{print $2}') + print_success "Python ${PY_VERSION} found" +else + print_warning "Python 3.10+ not found" + if [[ "$OS" == "darwin" ]]; then - log "Installing via Homebrew..." - brew install python || err "Could not install Python automatically. Please install manually." + print_info "Installing Python via Homebrew..." + brew install python || err "Could not install Python. Please install manually from https://www.python.org" elif [[ -f /etc/debian_version ]]; then - log "Installing via apt..." + print_info "Installing Python via apt..." sudo apt-get update -y && sudo apt-get install -y python3 python3-venv else err "Please install Python manually: https://www.python.org/downloads/" fi + + print_success "Python installed" fi -echo "🔍 Checking uv..." -if ! command -v uv >/dev/null 2>&1; then - log "⬇️ Installing uv..." +print_step "Checking uv package manager..." +if command -v uv >/dev/null 2>&1; then + UV_VERSION=$(uv --version 2>&1 | awk '{print $2}') + print_success "uv ${UV_VERSION} found" +else + print_info "Installing uv..." curl -LsSf https://astral.sh/uv/install.sh | sh export PATH="$HOME/.local/bin:$PATH" + print_success "uv installed" fi -log "⬇️ Downloading Tiles (${VERSION}) for ${ARCH}-${OS}..." +print_section_end +# ---------------------------------------------------------------------------- +# Download/Locate Tiles +# ---------------------------------------------------------------------------- -if [[ "$ENV" == "prod" ]]; then +print_section "Gathering Tiles Bundle" + +print_step "Looking for Tiles v${VERSION} (${ARCH}-${OS})..." + +LOCAL_BUNDLE="${SCRIPT_DIR}/tiles-v${VERSION}-${ARCH}-${OS}.tar.gz" +ROOT_BUNDLE="${SCRIPT_DIR}/../dist/tiles-v${VERSION}-${ARCH}-${OS}.tar.gz" + +if [[ -f "${LOCAL_BUNDLE}" ]]; then + print_info "Found local bundle" + cp "${LOCAL_BUNDLE}" "${TMPDIR}/tiles.tar.gz" + print_success "Bundle located" +elif [[ -f "${ROOT_BUNDLE}" ]]; then + print_info "Found bundle in repository" + cp "${ROOT_BUNDLE}" "${TMPDIR}/tiles.tar.gz" + print_success "Bundle located" +elif [[ "${ENV}" == "prod" ]]; then + print_info "Downloading from GitHub releases..." TAR_URL="https://github.com/${REPO}/releases/download/${VERSION}/tiles-v${VERSION}-${ARCH}-${OS}.tar.gz" - curl -fsSL -o "${TMPDIR}/tiles.tar.gz" "$TAR_URL" + + if curl -fsSL -o "${TMPDIR}/tiles.tar.gz" "$TAR_URL"; then + print_success "Bundle downloaded" + else + err "Failed to download bundle from ${TAR_URL}" + fi else - # Installer suppose to ran from tilekit root folder after running the bundler - mv "dist/tiles-v${VERSION}-${ARCH}-${OS}.tar.gz" "${TMPDIR}/tiles.tar.gz" + err "Could not locate bundle tiles-v${VERSION}-${ARCH}-${OS}.tar.gz" fi -echo "⬇️ Installing tiles..." -# Lets point to tile repo +print_step "Extracting bundle..." tar -xzf "${TMPDIR}/tiles.tar.gz" -C "${TMPDIR}" +print_success "Bundle extracted" + +print_section_end -log "📦 Installing tiles binary to ${INSTALL_DIR}..." +# ---------------------------------------------------------------------------- +# Install Tiles +# ---------------------------------------------------------------------------- + +print_section "Installing Components" + +print_step "Installing CLI binary..." +print_info "Location: ${INSTALL_DIR}/tiles" mkdir -p "${INSTALL_DIR}" install -m 755 "${TMPDIR}/tiles" "${INSTALL_DIR}/tiles" +print_success "CLI binary installed" -log "📦 Installing Python server to ${SERVER_DIR}..." +print_step "Installing Python server..." +print_info "Location: ${SERVER_DIR}" mkdir -p "${SERVER_DIR}" cp -r "${TMPDIR}/server"/* "${SERVER_DIR}/" +print_success "Server files installed" -log "🔧 Setting up Python environment..." +print_step "Setting up Python environment..." +print_info "Installing dependencies with uv..." cd "${SERVER_DIR}" -uv sync --frozen || err "Dependency setup failed." +if uv sync --frozen; then + print_success "Python environment ready" +else + err "Failed to set up Python environment" +fi + +print_section_end +# Cleanup rm -rf "${TMPDIR}" -log "✅ Tiles installed successfully!" -log "" -log "👉 Make sure ${INSTALL_DIR} is in your PATH." +# ============================================================================ +# Installation Complete +# ============================================================================ + +print_header "Installation Complete" + +print_success "Tiles v${VERSION} installed successfully!" +echo "" + +wrap_text "The Tiles CLI has been installed to ${INSTALL_DIR}. Make sure this directory is in your PATH to use the 'tiles' command." " " +echo "" + +# Display PATH setup instructions if needed +if [[ ":$PATH:" != *":${INSTALL_DIR}:"* ]]; then + print_warning "Setup Required" + echo "" + wrap_text "Add the following line to your shell configuration file (~/.bashrc, ~/.zshrc, or ~/.profile):" " " + echo "" + echo -e "\033[1;37m export PATH=\"\$HOME/.local/bin:\$PATH\"\033[0m" + echo "" + wrap_text "Then reload your shell or run: source ~/.zshrc" " " + echo "" +fi + +# Display help +print_section "Getting Started" + +export PATH="${INSTALL_DIR}:${PATH}" +if command -v tiles >/dev/null 2>&1; then + tiles --help 2>/dev/null || "${INSTALL_DIR}/tiles" --help 2>/dev/null || true +else + "${INSTALL_DIR}/tiles" --help 2>/dev/null || true +fi + +print_section_end + +wrap_text "For more information, visit the documentation or run 'tiles --help' at any time." " " +echo "" diff --git a/scripts/make_iso.sh b/scripts/make_iso.sh new file mode 100755 index 0000000..e229d61 --- /dev/null +++ b/scripts/make_iso.sh @@ -0,0 +1,292 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +DIST_DIR="${ROOT_DIR}/dist" +BINARY_NAME="tiles" +VERSION=$(grep '^version' "${ROOT_DIR}/Cargo.toml" | head -1 | awk -F'"' '{print $2}') +OS=$(uname -s | tr '[:upper:]' '[:lower:]') +ARCH=$(uname -m) +BUNDLE_NAME="${BINARY_NAME}-v${VERSION}-${ARCH}-${OS}.tar.gz" +BUNDLE_PATH="${DIST_DIR}/${BUNDLE_NAME}" +ISO_NAME="${BINARY_NAME}-installer-v${VERSION}-${ARCH}-${OS}.iso" +ISO_PATH="${DIST_DIR}/${ISO_NAME}" + +log() { echo -e "\033[1;36m$*\033[0m"; } +err() { echo -e "\033[1;31m$*\033[0m" >&2; exit 1; } + +log "Preparing ${BINARY_NAME} bundle (${VERSION})..." +"${ROOT_DIR}/scripts/bundler.sh" + +if [[ ! -f "${BUNDLE_PATH}" ]]; then + err "Expected bundle ${BUNDLE_PATH} was not created." +fi + +TMPDIR=$(mktemp -d) +trap 'rm -rf "${TMPDIR}"' EXIT + +# Create a hidden subfolder for all installer files (except the app) +INSTALLER_FILES_DIR="${TMPDIR}/.tiles-installer" +mkdir -p "${INSTALLER_FILES_DIR}" + +# Copy ASCII art to root if it exists +if [[ -f "${ROOT_DIR}/ascii-art.txt" ]]; then + cp "${ROOT_DIR}/ascii-art.txt" "${TMPDIR}/ascii-art.txt" + log "Added ASCII art to ISO root" +fi + +# Copy installer files to the subfolder +cp "${ROOT_DIR}/scripts/install.sh" "${INSTALLER_FILES_DIR}/install.sh" +chmod +x "${INSTALLER_FILES_DIR}/install.sh" +cp "${BUNDLE_PATH}" "${INSTALLER_FILES_DIR}/${BUNDLE_NAME}" + +# For macOS builds, create a .command file and an .app bundle that auto-launches +if [[ "${OS}" == "darwin" ]]; then + log "Creating macOS auto-launch installer..." + + # Create the .command file with logging in the subfolder + cat > "${INSTALLER_FILES_DIR}/Install Tiles.command" << 'EOF' +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +LOG_DIR="${HOME}/Library/Logs/tiles" +LOG_FILE="${LOG_DIR}/install-$(date +%Y%m%d-%H%M%S).log" + +# Create log directory if it doesn't exist +mkdir -p "${LOG_DIR}" + +# Logging function +log_to_file() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "${LOG_FILE}" +} + +# Log script start +log_to_file "=== Tiles Installer Started ===" +log_to_file "Script directory: ${SCRIPT_DIR}" +log_to_file "Log file: ${LOG_FILE}" +log_to_file "User: $(whoami)" +log_to_file "System: $(uname -a)" + +# Change to script directory +cd "${SCRIPT_DIR}" +log_to_file "Changed directory to: ${SCRIPT_DIR}" + +# Check if install.sh exists +if [[ ! -f "./install.sh" ]]; then + log_to_file "ERROR: install.sh not found in ${SCRIPT_DIR}" + echo "Error: install.sh not found!" | tee -a "${LOG_FILE}" + exit 1 +fi + +log_to_file "Found install.sh, starting installation..." + +# Run installer and capture all output +set +e # Temporarily disable exit on error to capture exit code +./install.sh 2>&1 | tee -a "${LOG_FILE}" +EXIT_CODE=${PIPESTATUS[0]} +set -e # Re-enable exit on error + +if [[ ${EXIT_CODE} -eq 0 ]]; then + log_to_file "=== Installation Completed Successfully ===" + echo "" + echo "Installation complete! Log saved to: ${LOG_FILE}" +else + log_to_file "=== Installation Failed with exit code: ${EXIT_CODE} ===" + echo "" + echo "Installation failed. Check log: ${LOG_FILE}" + exit ${EXIT_CODE} +fi +EOF + chmod +x "${INSTALLER_FILES_DIR}/Install Tiles.command" + + # Create AppleScript application bundle at the root level + log "Creating AppleScript application bundle..." + APP_DIR="${TMPDIR}/Install Tiles.app" + mkdir -p "${APP_DIR}/Contents/MacOS" + mkdir -p "${APP_DIR}/Contents/Resources" + + # Copy icon if it exists + if [[ -f "${ROOT_DIR}/tiles_icon.icns" ]]; then + cp "${ROOT_DIR}/tiles_icon.icns" "${APP_DIR}/Contents/Resources/tiles_icon.icns" + log "Added icon to app bundle" + fi + + # Create Info.plist + cat > "${APP_DIR}/Contents/Info.plist" << 'PLIST' + + + + + CFBundleExecutable + installer + CFBundleIdentifier + com.tiles.installer + CFBundleName + Install Tiles + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + CFBundleIconFile + tiles_icon + LSMinimumSystemVersion + 10.13 + NSHighResolutionCapable + + + +PLIST + + # Create the executable script that runs the installer automatically + # The app bundle will be in the same directory as Install Tiles.command + cat > "${APP_DIR}/Contents/MacOS/installer" << 'APPSCRIPT' +#!/usr/bin/env bash +set -euo pipefail + +# Find the directory containing this app bundle (the mounted volume root) +# App bundle structure: Install Tiles.app/Contents/MacOS/installer +# So we go up 2 levels to get to the app bundle, then up 1 more to get to volume root +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # .../Install Tiles.app/Contents/MacOS +APP_BUNDLE_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" # .../Install Tiles.app +VOLUME_DIR="$(dirname "${APP_BUNDLE_DIR}")" # .../ (volume root) +# Look for Install Tiles.command in the hidden installer subfolder +COMMAND_FILE="${VOLUME_DIR}/.tiles-installer/Install Tiles.command" + +# Log directory for app launches +LOG_DIR="${HOME}/Library/Logs/tiles" +mkdir -p "${LOG_DIR}" +APP_LOG="${LOG_DIR}/app-launch-$(date +%Y%m%d-%H%M%S).log" + +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Install Tiles.app launched" >> "${APP_LOG}" +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Volume directory: ${VOLUME_DIR}" >> "${APP_LOG}" +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Command file: ${COMMAND_FILE}" >> "${APP_LOG}" + +# If command file exists in the hidden installer folder, run it automatically +if [[ -f "${COMMAND_FILE}" ]]; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Found Install Tiles.command, launching installer..." >> "${APP_LOG}" + # Use AppleScript to open Terminal and run the installer automatically + INSTALLER_DIR="${VOLUME_DIR}/.tiles-installer" + osascript <> "${APP_LOG}" +else + # Fallback: try to find it in mounted volumes + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Command file not found, searching mounted volumes..." >> "${APP_LOG}" + FOUND=0 + for vol in /Volumes/tiles-*; do + if [[ -d "$vol/.tiles-installer" ]] && [[ -f "$vol/.tiles-installer/Install Tiles.command" ]]; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Found installer in ${vol}/.tiles-installer" >> "${APP_LOG}" + osascript <> "${APP_LOG}" + osascript <> "${APP_LOG}" + osascript < "${TMPDIR}/README.txt" << EOF +$(cat "${ROOT_DIR}/ascii-art.txt") + +Welcome to Tiles Installer! + +INSTALLATION: +Double-click "Install Tiles.app" to start the installation. + +The installer will automatically: +- Check for Python and uv dependencies +- Download and install the Tiles binary +- Set up the Python server environment +- Save installation logs to ~/Library/Logs/tiles/ + +Installation logs are saved with timestamps for troubleshooting. + +For manual installation, open Terminal and run: + cd /Volumes/tiles-${VERSION}/.tiles-installer + ./Install\ Tiles.command +EOF + log "Created README.txt with ASCII art at root" +else + # Fallback README without ASCII art if file not found + cat > "${TMPDIR}/README.txt" << 'README' +Welcome to Tiles Installer! + +INSTALLATION: +Double-click "Install Tiles.app" to start the installation. + +The installer will automatically: +- Check for Python and uv dependencies +- Download and install the Tiles binary +- Set up the Python server environment +- Save installation logs to ~/Library/Logs/tiles/ + +Installation logs are saved with timestamps for troubleshooting. + +For manual installation, open Terminal and run: + cd /Volumes/tiles-/.tiles-installer + ./Install\ Tiles.command +README +fi + +log "Creating ISO layout..." + +ISO_LABEL="tiles-${VERSION}" + +create_iso() { + local src_dir="$1" + local out_path="$2" + local label="$3" + + if command -v xorriso >/dev/null 2>&1; then + xorriso -as mkisofs -quiet -o "${out_path}" -V "${label}" "${src_dir}" + elif command -v genisoimage >/dev/null 2>&1; then + genisoimage -quiet -V "${label}" -o "${out_path}" "${src_dir}" + elif command -v mkisofs >/dev/null 2>&1; then + mkisofs -quiet -V "${label}" -o "${out_path}" "${src_dir}" + else + err "Could not find xorriso, genisoimage, or mkisofs. Please install one of them to build the ISO." + fi +} + +mkdir -p "${DIST_DIR}" +create_iso "${TMPDIR}" "${ISO_PATH}" "${ISO_LABEL}" + +log "ISO created: ${ISO_PATH}" diff --git a/server/config.py b/server/config.py index f9629a9..32d29f1 100644 --- a/server/config.py +++ b/server/config.py @@ -1,7 +1,7 @@ from pathlib import Path import os PORT = 6969 -MODEL_ID = "driaforall/mem-agent" +MODEL_ID = "driaforall/mem-agent-mlx-4bit" prompt_path = Path(__file__).parent / "system_prompt.txt" MEMORY_PATH = os.path.expanduser("~") + "/tiles_memory" diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 465c872..ce4fce2 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,5 +1,6 @@ // Module that handles CLI commands +use std::path::Path; use tiles::{ core::{ health, @@ -8,8 +9,17 @@ use tiles::{ runner::mlx, }; -pub async fn run(modelfile: &str) { - match modelfile::parse_from_file(modelfile) { +pub async fn run(modelfile_path: &str) { + // Resolve the modelfile path - check if it's a model name or file path + let resolved_path = match resolve_modelfile_path(modelfile_path) { + Ok(path) => path, + Err(err) => { + println!("Error resolving modelfile: {}", err); + return; + } + }; + + match modelfile::parse_from_file(&resolved_path) { Ok(modelfile) => { mlx::run(modelfile).await; } @@ -17,6 +27,40 @@ pub async fn run(modelfile: &str) { } } +fn resolve_modelfile_path(input: &str) -> Result { + let path = Path::new(input); + + // If the input is a file path that exists, canonicalize it to get absolute path + // This prevents path traversal attacks and ensures consistent path handling + if path.exists() { + return path + .canonicalize() + .map(|p| p.to_string_lossy().to_string()) + .map_err(|e| format!("Failed to canonicalize path '{}': {}", input, e)); + } + + // Otherwise, treat it as a model name and look for it in the registry + match mlx::get_registry_dir() { + Ok(registry_dir) => { + let modelfile_path = registry_dir.join(input).join("Modelfile"); + if modelfile_path.exists() { + // Registry paths are already absolute, but canonicalize for consistency + modelfile_path + .canonicalize() + .map(|p| p.to_string_lossy().to_string()) + .map_err(|e| format!("Failed to canonicalize registry path: {}", e)) + } else { + Err(format!( + "Modelfile not found for '{}'. Expected at: {}\nTry: tiles run or tiles run ", + input, + modelfile_path.display() + )) + } + } + Err(err) => Err(format!("Failed to get registry directory: {}", err)), + } +} + pub fn check_health() { health::check_health(); } diff --git a/src/main.rs b/src/main.rs index 0625378..4872f84 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use clap::{Args, Parser, Subcommand}; mod commands; #[derive(Debug, Parser)] #[command(name = "tiles")] -#[command(version, about = "Run, fine-tune models locally with Modelfile", long_about = None)] +#[command(version, about = "Private, on-device AI memory that personalizes the agents you use, on your terms. Works with Obsidian.", long_about = None)] struct Cli { #[command(subcommand)] command: Commands, @@ -12,7 +12,11 @@ struct Cli { #[derive(Subcommand, Debug)] enum Commands { - /// Runs the given modelfile Path + /// Runs a model by name (e.g., 'memgpt') or by Modelfile path + /// + /// Examples: + /// tiles run memgpt # Runs registry/memgpt/Modelfile + /// tiles run ./path/to/my.modelfile # Runs specific file Run { modelfile_path: String }, /// Checks the status of dependencies diff --git a/src/runner/mlx.rs b/src/runner/mlx.rs index adb2aed..2606cbe 100644 --- a/src/runner/mlx.rs +++ b/src/runner/mlx.rs @@ -270,3 +270,10 @@ fn get_data_dir() -> Result { Ok(data_dir.join("tiles")) } } + +pub fn get_registry_dir() -> Result { + let tiles_data_dir = get_data_dir()?; + let registry_dir = tiles_data_dir.join("registry"); + fs::create_dir_all(®istry_dir).context("Failed to create tiles registry directory")?; + Ok(registry_dir) +} diff --git a/tiles_icon.icns b/tiles_icon.icns new file mode 100644 index 0000000..5c535e2 Binary files /dev/null and b/tiles_icon.icns differ