One command to start: introducing the LibxaFrame installer
libxa new my-app asks which database you want, installs the skeleton, configures it and makes the first commit. A self-contained executable with no dependencies, because the first thing someone runs is the worst place for an install to fail. Plus an agent skill covering what differs from Laravel.
Starting a LibxaFrame project used to mean remembering a Composer incantation,
then copying .env.example, then generating a key, then editing three lines to
point at the database you actually use. Now:
libxa new my-app
What it does
It asks which database you want, runs composer create-project libxa/libxa,
and then does the parts everyone does by hand afterwards: .env configured for
the driver you chose, the application key generated, front-end dependencies
installed if you want them, a git repository with a first commit, and a list of
what to run next.
LibxaFrame installer 1.0.0
Which database will it use?
1. SQLite (no server required) (default)
2. MySQL
3. MariaDB
4. PostgreSQL
› 2
Creating the application…
Configured .env for mysql.
Initialised a git repository on main.
DONE Your application is ready.
Flags for everything, so it fits a script as readily as a terminal:
libxa new my-app --database=mysql --git --npm
libxa new my-app --github=public --organization=my-team
libxa new my-app --dev
libxa new .
Only three lines of .env change
This one is worth explaining, because getting it wrong is invisible until it matters.
composer create-project runs the skeleton's post-install scripts, and one of
them is php libxa key:generate, which writes APP_KEY into the .env it has
just copied. If the installer then wrote a fresh .env for your chosen
database, that key would be gone. Nothing would complain. The application would
boot, serve pages, and fail the first time it touched an encrypted cookie or a
session.
So only DB_DRIVER, DB_PORT and DB_DATABASE are rewritten, in place, and
everything else in the file is left exactly as it was. There is a test whose
only job is to assert that the generated key is still there afterwards.
Installed, not pip-installed
The installer is a single self-contained executable. It needs nothing on your machine to run: no Python, no runtime, nothing to resolve.
irm https://raw.githubusercontent.com/libxa-framework/libxa-installer/main/scripts/install.ps1 | iex
curl -fsSL https://raw.githubusercontent.com/libxa-framework/libxa-installer/main/scripts/install.sh | sh
Both scripts install for the current user and add the command to your PATH. Neither asks for administrator rights or sudo, and neither writes outside your home directory: the program goes in your local application directory and the PATH entry is the per-user one, never the machine-wide one that everything else depends on.
Downloads land in a temporary file and are moved into place only once complete. An interrupted download replacing a working install with a truncated one is the kind of failure that surfaces later as something that looks nothing like a network problem.
No dependencies, on purpose
The installer has none at all.
It is the first thing someone runs, on a machine nobody has tested, in whatever state that machine happens to be in. Every dependency is another way for that first step to fail, and a failure there is a failure to start at all rather than a failure in something you were already invested in.
It also means the whole tool freezes cleanly into one executable with nothing to resolve at install time.
And a skill for the framework
Also published: an agent skill for LibxaFrame.
It exists because LibxaFrame looks enough like Laravel that an assistant will write Laravel confidently and produce code that parses and then fails at runtime. So the skill is mostly the differences:
src/app/andsrc/public/, notapp/andpublic/php libxa, notphp artisanResponse::getStatus(), notgetStatusCode()@extendstakes exactly one argument; a second is a parse error, not an ignored argument{{ }}is inert inside a@phpblock- routes match in registration order, with no specificity sorting, so
/posts/{slug}registered first makes/posts/newunreachable - a migration whose class name does not match its filename is skipped in silence
- and the one that catches everyone: the home page loads, every other route 404s, and only once deployed
Every item in it caused a real bug while this ecosystem was being built. That is the test for whether something belongs there: not that it is a difference, but that the difference has cost someone an afternoon.