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