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/Cargo.lock b/Cargo.lock index 8ca5da0..d174ed5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.21" @@ -64,6 +73,12 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "base64" version = "0.22.1" @@ -104,6 +119,19 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link 0.2.1", +] + [[package]] name = "clap" version = "4.5.50" @@ -468,6 +496,30 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.0.0" @@ -696,6 +748,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1126,6 +1187,7 @@ name = "tiles" version = "0.1.0" dependencies = [ "anyhow", + "chrono", "clap", "nom", "reqwest", @@ -1421,6 +1483,41 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-link" version = "0.1.3" @@ -1440,8 +1537,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ "windows-link 0.1.3", - "windows-result", - "windows-strings", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -1453,6 +1550,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -1462,6 +1568,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 80faa4b..360876b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,10 @@ name = "tiles" version = "0.1.0" edition = "2024" +[[bin]] +name = "Tiles" +path = "src/main.rs" + [dependencies] clap = { version = "4.5.48", features = ["derive"] } nom = "8" @@ -11,3 +15,4 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" anyhow = "1.0" tokio = { version = "1" , features = ["macros", "rt-multi-thread"]} +chrono = "0.4" diff --git a/README.md b/README.md index 5143bb3..3240e91 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,40 @@ # Tilekit Modelfile-based SDK that lets developers to lets developers customize local models and agent experiences within [Tiles](https://www.tiles.run/). +## Quick Start + +### Installation + +Download and run the installer for your platform from the [releases page](https://github.com/tilesprivacy/tilekit/releases). + +On macOS, double-click `Tiles.app` from the mounted ISO to start installation. + +### Basic Usage + +```bash +# Start a model (runs in background) +Tiles run memgpt + +# List running models +Tiles ls + +# Stop a model +Tiles stop memgpt + +# View help +Tiles --help +``` + +### Recommended Setup + +**For the best experience with Tiles, we recommend:** + +- **[Tailscale](https://tailscale.com)** - Access your Tiles instance securely from anywhere, on any device +- **[Amphetamine](https://amphetamine.en.softonic.com/mac)** (macOS) - Keep your Mac awake when running models +- **[rsync](https://rsync.samba.org/)** - Sync your memory and data across devices + +These tools ensure your models stay accessible, responsive, and synced 24/7. + ## Dev setup - Clone the repo @@ -15,6 +49,94 @@ 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 +### Directory Structure + +Tiles uses a single `~/.tiles/` directory for all data, making it portable and environment-agnostic: + +``` +~/.tiles/ +├── registry/ # Model definitions +│ ├── memgpt/ +│ │ └── Modelfile +│ └── other-model/ +│ └── Modelfile +├── memory/ # AI memory storage +├── server/ # Python server (production only) +├── server.pid # Server process ID +└── models.json # Running models state +``` + +Models are organized in the `registry/` folder. Each model has its own folder with a `Modelfile` inside. + +To run a model: +```bash +cargo run run memgpt # Runs the model defined in ~/.tiles/registry/memgpt/Modelfile +``` + +The folder name becomes the model name that you use with the CLI. + +### Running Models + +Models run in the background and persist after the CLI exits: + +```bash +# Start a model (runs in background) +cargo run run memgpt # Dev +Tiles run memgpt # Production + +# List running models +cargo run ls # Dev +Tiles ls # Production + +# Stop a specific model +cargo run stop memgpt # Dev +Tiles stop memgpt # Production +``` + +**How it works:** +1. When you start a model with `Tiles run`, it loads into the server and runs in the background +2. The CLI exits immediately - your model keeps running +3. On macOS (production), Tiles Agent keeps a dock icon visible while models are active +4. Use `Tiles ls` anytime to see what's running +5. When you stop the last model, the server and agent shut down automatically + +**Production Features (macOS):** +- Tiles Agent.app appears in dock while models are running +- Models persist even if you close Terminal +- Access via `Tiles` command from anywhere + +When you stop the last running model, the server automatically stops as well. + +### Server Management + +**The server starts automatically** when you run a model and stops when no models are running. + +**Manual server control (if needed):** +```bash +# Start server manually +cargo run start # Dev +tiles start # Production + +# Stop server manually (only if no models running) +cargo run stop --server # Dev +tiles stop --server # Production +``` + +**Both development and production builds:** +- Use the same `~/.tiles/` directory structure (environment-agnostic) +- Server starts automatically on first model run +- Models run in background and persist after CLI exits +- Use `Tiles ls` to see running models +- Use `Tiles stop ` to stop individual models +- Server auto-starts and auto-stops based on running models + +**Note:** Development builds use the local `server/` directory for the Python server, while production installs it to `~/.tiles/server/`. + +### 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/build-agent-app.sh b/scripts/build-agent-app.sh new file mode 100755 index 0000000..9e50d9d --- /dev/null +++ b/scripts/build-agent-app.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# Build Tiles.app bundle with agent functionality +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(dirname "${SCRIPT_DIR}")" +DIST_DIR="${ROOT_DIR}/dist" +APP_NAME="Tiles" +APP_DIR="${DIST_DIR}/${APP_NAME}.app" + +echo "Building ${APP_NAME}.app..." + +# Clean and create app bundle structure +rm -rf "${APP_DIR}" +mkdir -p "${APP_DIR}/Contents/MacOS" +mkdir -p "${APP_DIR}/Contents/Resources" + +# Copy icon if available +if [[ -f "${ROOT_DIR}/tiles_icon.icns" ]]; then + cp "${ROOT_DIR}/tiles_icon.icns" "${APP_DIR}/Contents/Resources/AppIcon.icns" +fi + +# Create Info.plist +cat > "${APP_DIR}/Contents/Info.plist" << 'PLIST' + + + + + CFBundleExecutable + Tiles + CFBundleIdentifier + com.tiles.app + CFBundleName + Tiles + CFBundleDisplayName + Tiles + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.1.0 + CFBundleVersion + 1 + CFBundleIconFile + AppIcon + LSMinimumSystemVersion + 10.13 + LSUIElement + + NSHighResolutionCapable + + LSBackgroundOnly + + + +PLIST + +# Create the executable that runs the agent script +cat > "${APP_DIR}/Contents/MacOS/Tiles" << 'AGENT' +#!/usr/bin/env bash +set -euo pipefail + +# Find the agent script +TILES_DIR="$HOME/.tiles" +AGENT_SCRIPT="${TILES_DIR}/tiles-agent.sh" + +# Log file +LOG_FILE="${TILES_DIR}/agent.log" +mkdir -p "${TILES_DIR}" + +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Tiles.app launched" >> "${LOG_FILE}" + +# Check if agent script exists +if [[ ! -f "${AGENT_SCRIPT}" ]]; then + osascript -e 'display alert "Tiles Error" message "Agent script not found. Please reinstall Tiles." as critical' + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: Agent script not found at ${AGENT_SCRIPT}" >> "${LOG_FILE}" + exit 1 +fi + +# Run the agent script +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting agent script" >> "${LOG_FILE}" +bash "${AGENT_SCRIPT}" +AGENT + +chmod +x "${APP_DIR}/Contents/MacOS/Tiles" + +# Copy the agent script to dist for installation +cp "${SCRIPT_DIR}/tiles-agent.sh" "${DIST_DIR}/tiles-agent.sh" +chmod +x "${DIST_DIR}/tiles-agent.sh" + +echo "✓ ${APP_NAME}.app created at ${APP_DIR}" +echo "✓ Agent script ready at ${DIST_DIR}/tiles-agent.sh" + diff --git a/scripts/bundler.sh b/scripts/bundler.sh index d31dd87..d1c2ee8 100755 --- a/scripts/bundler.sh +++ b/scripts/bundler.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -BINARY_NAME="tiles" +BINARY_NAME="Tiles" DIST_DIR="dist" SERVER_DIR="server" TARGET="release" @@ -11,19 +11,31 @@ 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} +# Build Tiles.app for macOS +if [[ "${OS}" == "darwin" ]]; then + echo "Building Tiles.app..." + bash scripts/build-agent-app.sh +fi + mkdir -p "${DIST_DIR}/tmp" cp "target/${TARGET}/${BINARY_NAME}" "${DIST_DIR}/tmp/" cp -r "${SERVER_DIR}" "${DIST_DIR}/tmp/" +# Add Tiles.app for macOS +if [[ "${OS}" == "darwin" ]] && [[ -d "${DIST_DIR}/Tiles.app" ]]; then + cp -r "${DIST_DIR}/Tiles.app" "${DIST_DIR}/tmp/" + cp "${DIST_DIR}/tiles-agent.sh" "${DIST_DIR}/tmp/" +fi + 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..1f4505e 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,70 +1,291 @@ #!/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)" +# Look in the current directory (hidden installer folder) +ASCII_ART_FILE="${SCRIPT_DIR}/ascii-art.txt" +if [[ ! -f "${ASCII_ART_FILE}" ]]; then + # Fallback to parent directory + ASCII_ART_FILE="${SCRIPT_DIR}/../ascii-art.txt" +fi +if [[ ! -f "${ASCII_ART_FILE}" ]]; then + # Fallback to volume root + 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/.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 +# ---------------------------------------------------------------------------- +print_section "Gathering Tiles Bundle" -if [[ "$ENV" == "prod" ]]; then - 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" +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" + + 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 -tar -xzf "${TMPDIR}/tiles.tar.gz" -C "${TMPDIR}" +print_step "Extracting bundle..." +tar -xzf "${TMPDIR}/Tiles.tar.gz" -C "${TMPDIR}" +print_success "Bundle extracted" + +print_section_end + +# ---------------------------------------------------------------------------- +# Install Tiles +# ---------------------------------------------------------------------------- -log "📦 Installing tiles binary to ${INSTALL_DIR}..." +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" +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 + +# Install Tiles Agent for macOS +if [[ "${OS}" == "darwin" ]] && [[ -d "${TMPDIR}/Tiles Agent.app" ]]; then + print_step "Installing Tiles Agent..." + mkdir -p "${HOME}/Applications" + rm -rf "${HOME}/Applications/Tiles Agent.app" + cp -r "${TMPDIR}/Tiles Agent.app" "${HOME}/Applications/" + + # Install agent script + mkdir -p "${HOME}/.tiles" + cp "${TMPDIR}/tiles-agent.sh" "${HOME}/.tiles/tiles-agent.sh" + chmod +x "${HOME}/.tiles/tiles-agent.sh" + + print_success "Tiles Agent installed to ~/Applications" +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 "" + +# Show recommended tools +print_section "Recommended Setup" +echo "" +wrap_text "For the best experience with Tiles, we recommend:" " " +echo "" +echo -e " \033[1;36m•\033[0m \033[1;37mTailscale\033[0m - Access your Tiles instance securely from anywhere" +echo -e " https://tailscale.com" +echo "" +echo -e " \033[1;36m•\033[0m \033[1;37mAmphetamine\033[0m (macOS) - Keep your Mac awake when running models" +echo -e " https://amphetamine.en.softonic.com/mac" +echo "" +echo -e " \033[1;36m•\033[0m \033[1;37mrsync\033[0m - Sync your memory and data across devices" +echo -e " https://rsync.samba.org" +echo "" +wrap_text "These tools ensure your models stay accessible, responsive, and synced 24/7." " " +echo "" +print_section_end diff --git a/scripts/make_iso.sh b/scripts/make_iso.sh new file mode 100755 index 0000000..67b4a69 --- /dev/null +++ b/scripts/make_iso.sh @@ -0,0 +1,304 @@ +#!/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="Tiles-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 hidden installer folder if it exists +if [[ -f "${ROOT_DIR}/ascii-art.txt" ]]; then + cp "${ROOT_DIR}/ascii-art.txt" "${INSTALLER_FILES_DIR}/ascii-art.txt" + log "Added ASCII art to hidden installer folder" +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}" + +# Extract and add Tiles.app to the ISO root +log "Extracting Tiles.app from bundle..." +EXTRACT_DIR=$(mktemp -d) +tar -xzf "${BUNDLE_PATH}" -C "${EXTRACT_DIR}" +if [[ -d "${EXTRACT_DIR}/Tiles.app" ]]; then + cp -r "${EXTRACT_DIR}/Tiles.app" "${TMPDIR}/Tiles.app" + log "Added Tiles.app to ISO root" +else + log "Warning: Tiles.app not found in bundle" +fi +rm -rf "${EXTRACT_DIR}" + +# 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}/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}/Tiles.command" + + # Create AppleScript application bundle at the root level + log "Creating AppleScript application bundle..." + APP_DIR="${TMPDIR}/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 + 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 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: 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)" # .../Tiles.app/Contents/MacOS +APP_BUNDLE_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" # .../Tiles.app +VOLUME_DIR="$(dirname "${APP_BUNDLE_DIR}")" # .../ (volume root) +# Look for Tiles.command in the hidden installer subfolder +COMMAND_FILE="${VOLUME_DIR}/.tiles-installer/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')] 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 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/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 <