Establishes the deploy baseline on main so the admin agent's publish/rollback has a clean starting point. Everything built to date: sumi-e brand system, homepage, projects (DB-driven case studies), resume, about, services + website-design detail, contact form + DB, changelog, favicon + share card. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DoFYZY9gkGPNDqZ7NuEa9a
373 lines
16 KiB
PHP
373 lines
16 KiB
PHP
<?php
|
|
/**
|
|
* Seed / refresh cja_projects.
|
|
*
|
|
* Idempotent: upserts on slug, so it is safe to re-run after editing the
|
|
* content below. Existing rows keep their project_id, so anything pointing at
|
|
* them stays valid.
|
|
*
|
|
* php api/cli/seed-projects.php
|
|
*/
|
|
|
|
require __DIR__ . '/../vendor/autoload.php';
|
|
require __DIR__ . '/../config.php';
|
|
|
|
$pdo = new PDO(
|
|
sprintf('mysql:host=%s;dbname=%s;charset=utf8mb4', DB_HOST, DB_NAME),
|
|
DB_USER,
|
|
DB_PASS,
|
|
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
|
);
|
|
|
|
$projects = [
|
|
[
|
|
'slug' => 'agentic-marketing-platform',
|
|
'title' => 'Agentic Marketing Platform',
|
|
'kind' => 'SaaS',
|
|
'period_label' => 'In development',
|
|
'started_on' => '2025-01-01',
|
|
'ended_on' => null,
|
|
'status' => 'building',
|
|
'summary' => 'An autonomous digital marketing platform where collaborative agents run technical SEO, competitor research, content planning, local SEO, analytics, and reporting — the full process, run by agents.',
|
|
'lede' => 'Most marketing tools give you a dashboard and leave the work to you. This one does the work.',
|
|
'role' => 'Solo — architecture, agents, product',
|
|
'categories' => ['Agentic', 'SaaS', 'Tools'],
|
|
'stack' => ['Astro', 'PHP', 'MariaDB', 'LLM orchestration', 'MCP'],
|
|
'skills' => [
|
|
['group' => 'Engineering', 'items' => ['Multi-agent architecture', 'Astro + PHP', 'MCP tooling']],
|
|
['group' => 'AI', 'items' => ['LLM orchestration', 'Agent handoffs', 'Autonomous QA']],
|
|
['group' => 'Marketing', 'items' => ['Technical SEO', 'Competitor research', 'Content planning']],
|
|
],
|
|
'metrics' => [
|
|
['label' => 'Agents', 'value' => '8', 'note' => 'research, plan, write, review, publish'],
|
|
['label' => 'Human steps', 'value' => '1', 'note' => 'approve, or let it run'],
|
|
],
|
|
'links' => [],
|
|
'body' => <<<'MD'
|
|
## The problem
|
|
|
|
Digital marketing is a pipeline of repetitive judgement calls. Audit the site,
|
|
find the gaps, research what competitors rank for, plan the content, write it,
|
|
review it, publish it, measure it, and start again. Every step is
|
|
well-understood. Almost none of it is automated, because each step needs
|
|
context from the one before it.
|
|
|
|
Tools solve this by giving you a dashboard. You still do the work.
|
|
|
|
## The approach
|
|
|
|
A set of specialist agents, each with a narrow job and a defined handoff, that
|
|
pass context down the pipeline rather than dumping it into one prompt.
|
|
|
|
Research agents build the picture. Planning agents decide what to make.
|
|
Writing agents draft against a brief. Review agents check the draft against the
|
|
brief rather than against taste. A publishing agent ships it.
|
|
|
|
The interesting problem is not any single agent — it is the handoff. Most
|
|
multi-agent systems fail because step four has forgotten why step two made the
|
|
decision it made.
|
|
|
|
## Where it is
|
|
|
|
In development. Running against live properties, not test data.
|
|
MD,
|
|
],
|
|
[
|
|
'slug' => 'medellin-co',
|
|
'title' => 'Medellin.co',
|
|
'kind' => 'SaaS / Media',
|
|
'period_label' => '2020 — present',
|
|
'started_on' => '2022-10-01',
|
|
'ended_on' => null,
|
|
'status' => 'live',
|
|
'summary' => 'An AI-powered city platform using RAG, intelligent search, autonomous content generation, and AI assistants for travelers, expats, and local businesses.',
|
|
'lede' => 'The largest resource hub for Medellín, Colombia — built because the information simply did not exist in one place.',
|
|
'role' => 'Founder — product, engineering, SEO, partnerships',
|
|
'categories' => ['Websites', 'SaaS', 'Agentic'],
|
|
'stack' => ['WordPress', 'Custom PHP', 'OpenAI', 'Zilliz', 'Stripe'],
|
|
'skills' => [
|
|
['group' => 'Engineering', 'items' => ['Custom PHP', 'Third-party integrations', 'Payment systems']],
|
|
['group' => 'AI', 'items' => ['RAG', 'Vector search', 'Embeddings', 'Chatbot design']],
|
|
['group' => 'Marketing', 'items' => ['Technical SEO', 'Content strategy', 'Link building']],
|
|
['group' => 'Leadership', 'items' => ['Managing an overseas team', 'Partnerships & advertising sales']],
|
|
],
|
|
'metrics' => [
|
|
['label' => 'Pages ranked', 'value' => '200+', 'note' => 'roughly 70% on page one'],
|
|
['label' => 'Domain authority', 'value' => '10 → 30', 'note' => 'over three years'],
|
|
],
|
|
// Placeholders — set src to a real path once photos exist. Empty src
|
|
// renders a labelled frame so the gallery layout is visible either way.
|
|
'gallery' => [
|
|
['src' => '', 'alt' => 'Medellin.co homepage', 'caption' => 'Homepage'],
|
|
['src' => '', 'alt' => 'AI assistant interface', 'caption' => 'AI assistant'],
|
|
['src' => '', 'alt' => 'Business listing page', 'caption' => 'Business listings'],
|
|
['src' => '', 'alt' => 'Advertiser dashboard', 'caption' => 'Advertiser dashboard'],
|
|
],
|
|
'links' => [
|
|
'live' => 'https://medellin.co',
|
|
'instagram' => 'https://instagram.com/medellin.co',
|
|
],
|
|
'body' => <<<'MD'
|
|
## The problem
|
|
|
|
Medellín has a large and growing population of tourists and expats, and almost
|
|
no reliable central resource written for them. Information was scattered across
|
|
outdated blogs, closed Facebook groups, and word of mouth.
|
|
|
|
The domain had recently been released by the city. I negotiated with the owner
|
|
and acquired it in October 2022.
|
|
|
|
## What it does
|
|
|
|
A content platform with an AI layer on top. Visitors ask questions in natural
|
|
language and get answers grounded in the site's own content rather than a
|
|
general-purpose model's guesses — retrieval-augmented generation over an
|
|
embedded corpus of everything the site knows.
|
|
|
|
Beyond content, it integrates directly with local systems: a path for users to
|
|
file criminal reports with law enforcement, and Stripe checkout for advertisers.
|
|
|
|
## Engineering notes
|
|
|
|
- Chatbot on an OpenAI LLM with a Zilliz vector database and OpenAI embeddings
|
|
- Custom PHP integrating directly with TikTok and Instagram
|
|
- Custom WordPress build using Elementor and Crocoblock
|
|
- Overseas team managed on design and development
|
|
|
|
## Result
|
|
|
|
The largest resource hub for the city, used by locals, expats, and tourists
|
|
before they arrive. 200+ pages optimised, roughly 70% ranking on page one, and
|
|
domain authority grown from 10 to 30.
|
|
MD,
|
|
],
|
|
[
|
|
'slug' => 'verificaa',
|
|
'title' => 'Verificaa.com',
|
|
'kind' => 'SaaS',
|
|
'period_label' => '2021 — present',
|
|
'started_on' => '2021-06-01',
|
|
'ended_on' => null,
|
|
'status' => 'live',
|
|
'summary' => 'ID verification and fraud prevention for Latin America — letting people verify businesses and individuals before doing business with them.',
|
|
'lede' => 'Built because fraud in Latin America is common, and foreigners had no way to check who they were dealing with.',
|
|
'role' => 'Founder — architecture, data pipeline, AI',
|
|
'categories' => ['SaaS', 'Tools', 'Agentic'],
|
|
'stack' => ['PHP', 'REST API', 'LLM', 'Data mining', 'Stripe'],
|
|
'skills' => [
|
|
['group' => 'Engineering', 'items' => ['REST API design', 'API key management', 'Custom PHP dashboard']],
|
|
['group' => 'Data', 'items' => ['Data mining', 'Crawling', 'Normalisation pipelines']],
|
|
['group' => 'AI', 'items' => ['LLM fraud detection', 'Pattern analysis']],
|
|
],
|
|
'metrics' => [
|
|
['label' => 'Lives saved', 'value' => '2', 'note' => 'reported by users'],
|
|
['label' => 'Sources', 'value' => 'Police, AG, business registries'],
|
|
],
|
|
'links' => ['live' => 'https://verificaa.com'],
|
|
'body' => <<<'MD'
|
|
## The problem
|
|
|
|
Fraud is common across Latin America, and foreigners doing business there have
|
|
almost no way to check who they are dealing with. The data exists — police
|
|
records, attorney general filings, business registries — but it is scattered,
|
|
inconsistently formatted, and effectively unsearchable.
|
|
|
|
## What it does
|
|
|
|
Verificaa normalises that data and puts a single lookup in front of it. Enter a
|
|
person or a business, get back what the public record actually says.
|
|
|
|
An AI layer sits on top to flag patterns a human would miss: identities that
|
|
appear across unrelated filings, businesses registered days before a
|
|
transaction, documents that do not reconcile.
|
|
|
|
## Engineering notes
|
|
|
|
- Data-mining pipeline that crawls open Colombian and LATAM data and normalises it
|
|
- Integrations with police, attorney general, and business databases
|
|
- REST API with key management for developer access
|
|
- Custom PHP dashboard with a complex user management system
|
|
- LLM-driven fraud detection infrastructure
|
|
|
|
## Result
|
|
|
|
Users have reported the platform helping in two cases where someone's safety was
|
|
at risk, alongside a steady stream of prevented financial loss.
|
|
MD,
|
|
],
|
|
[
|
|
'slug' => 'snoopi-io',
|
|
'title' => 'Snoopi.io',
|
|
'kind' => 'SaaS',
|
|
'period_label' => '2015 — present',
|
|
'started_on' => '2015-03-01',
|
|
'ended_on' => null,
|
|
'status' => 'live',
|
|
'summary' => 'A geolocation SaaS platform delivering IP intelligence, APIs, and location services for developers and enterprise applications.',
|
|
'lede' => 'Started as a way to learn PHP OOP properly. Ten years later it is still running, and still shipping.',
|
|
'role' => 'Founder — everything',
|
|
'categories' => ['SaaS', 'Tools'],
|
|
'stack' => ['PHP', 'REST API', 'WordPress', 'Stripe'],
|
|
'skills' => [
|
|
['group' => 'Engineering', 'items' => ['PHP OOP', 'REST API design', 'Real-time tracking']],
|
|
['group' => 'Product', 'items' => ['Subscription billing', 'Invoice management', 'Custom dashboard']],
|
|
['group' => 'Marketing', 'items' => ['GeoIP landing-page optimisation']],
|
|
],
|
|
'metrics' => [
|
|
['label' => 'Running since', 'value' => '2015', 'note' => 'ten years in production'],
|
|
],
|
|
'links' => ['live' => 'https://snoopi.io'],
|
|
'body' => <<<'MD'
|
|
## Origin
|
|
|
|
This one started as a learning project in 2015. I wanted to understand PHP
|
|
object-oriented programming properly, and building a real REST API that other
|
|
developers could actually use seemed like a better teacher than a tutorial.
|
|
|
|
Ten years later it is still in production.
|
|
|
|
## What it does
|
|
|
|
GeoIP location and IP fraud prevention. Developers hit the API to resolve an IP
|
|
to a location; marketers use the tracking script to understand where landing
|
|
page traffic actually comes from.
|
|
|
|
## Engineering notes
|
|
|
|
- Real-time tracking script for sites and landing pages
|
|
- GeoIP-driven landing page optimisation
|
|
- Custom dashboard with invoice and subscription management
|
|
- Stripe integration using a custom card form
|
|
|
|
## What is next
|
|
|
|
AI analysis, to help marketers identify their best-performing pages rather than
|
|
just reporting where visitors came from.
|
|
MD,
|
|
],
|
|
[
|
|
'slug' => 'crawllr',
|
|
'title' => 'Crawllr',
|
|
'kind' => 'Tooling',
|
|
'period_label' => '2025 — present',
|
|
'started_on' => '2025-01-01',
|
|
'ended_on' => null,
|
|
'status' => 'live',
|
|
'summary' => 'An intelligent web crawler built for large-scale data collection, knowledge extraction, and AI indexing.',
|
|
'lede' => 'Crawling is a solved problem. Crawling in a way an LLM can actually use is not.',
|
|
'role' => 'Solo — architecture and build',
|
|
'categories' => ['Tools', 'Agentic'],
|
|
'stack' => ['PHP', 'MCP', 'LLM', 'Vector storage'],
|
|
'skills' => [
|
|
['group' => 'Engineering', 'items' => ['Crawl architecture', 'MCP integration', 'Vector storage']],
|
|
['group' => 'AI', 'items' => ['Knowledge extraction', 'Content chunking', 'Retrieval indexing']],
|
|
],
|
|
'metrics' => [],
|
|
'links' => [],
|
|
'body' => <<<'MD'
|
|
## The problem
|
|
|
|
Fetching pages is trivial. Turning a site into something a language model can
|
|
reason over is not. Boilerplate has to go, structure has to survive, and the
|
|
result has to be chunked in a way that keeps meaning intact across boundaries.
|
|
|
|
Most crawlers optimise for coverage. This one optimises for what comes after.
|
|
|
|
## What it does
|
|
|
|
Large-scale collection with knowledge extraction built into the pipeline rather
|
|
than bolted on afterwards. Output is indexed for retrieval, not just archived.
|
|
|
|
It is exposed to agents over MCP, so an agent can crawl and reason in one loop
|
|
instead of waiting on a separate ingestion step.
|
|
|
|
## Where it is
|
|
|
|
Running in production, feeding the other projects on this list.
|
|
MD,
|
|
],
|
|
[
|
|
'slug' => 'gabii',
|
|
'title' => 'Gabii',
|
|
'kind' => 'SaaS',
|
|
'period_label' => 'In development',
|
|
'started_on' => '2025-06-01',
|
|
'ended_on' => null,
|
|
'status' => 'building',
|
|
'summary' => 'An AI-powered relationship platform using intelligent profiling, compatibility analysis, and autonomous matchmaking.',
|
|
'lede' => 'Matching is a ranking problem that most platforms solve with engagement metrics rather than compatibility.',
|
|
'role' => 'Founder — product and engineering',
|
|
'categories' => ['SaaS', 'Agentic'],
|
|
'stack' => ['LLM', 'Embeddings', 'PHP'],
|
|
'skills' => [
|
|
['group' => 'Engineering', 'items' => ['PHP', 'Embedding pipelines']],
|
|
['group' => 'AI', 'items' => ['Intelligent profiling', 'Compatibility modelling', 'Autonomous matching']],
|
|
],
|
|
'metrics' => [],
|
|
'links' => [],
|
|
'body' => <<<'MD'
|
|
## The idea
|
|
|
|
Most matching platforms optimise for engagement, because engagement is what
|
|
they are measured on. That produces a product that works best when it does not
|
|
quite work.
|
|
|
|
Gabii treats compatibility as the objective rather than time-on-app.
|
|
|
|
## Approach
|
|
|
|
Intelligent profiling built from how people actually describe themselves rather
|
|
than from checkbox attributes, compatibility analysis over embeddings, and
|
|
autonomous matchmaking that improves as it observes outcomes.
|
|
|
|
## Where it is
|
|
|
|
In development.
|
|
MD,
|
|
],
|
|
];
|
|
|
|
$sql = <<<'SQL'
|
|
INSERT INTO cja_projects
|
|
(slug, title, kind, period_label, started_on, ended_on, status, summary, lede,
|
|
body, role, categories, stack, skills, metrics, gallery, links, published, published_at, sort_order)
|
|
VALUES
|
|
(:slug, :title, :kind, :period_label, :started_on, :ended_on, :status, :summary, :lede,
|
|
:body, :role, :categories, :stack, :skills, :metrics, :gallery, :links, 1, NOW(), :sort_order)
|
|
ON DUPLICATE KEY UPDATE
|
|
title = VALUES(title), kind = VALUES(kind), period_label = VALUES(period_label),
|
|
started_on = VALUES(started_on), ended_on = VALUES(ended_on), status = VALUES(status),
|
|
summary = VALUES(summary), lede = VALUES(lede), body = VALUES(body), role = VALUES(role),
|
|
categories = VALUES(categories), stack = VALUES(stack), skills = VALUES(skills), metrics = VALUES(metrics), gallery = VALUES(gallery),
|
|
links = VALUES(links), sort_order = VALUES(sort_order)
|
|
SQL;
|
|
|
|
$stmt = $pdo->prepare($sql);
|
|
$json = static fn($v) => json_encode($v, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
|
|
|
foreach ($projects as $i => $p) {
|
|
$stmt->execute([
|
|
':slug' => $p['slug'],
|
|
':title' => $p['title'],
|
|
':kind' => $p['kind'],
|
|
':period_label' => $p['period_label'],
|
|
':started_on' => $p['started_on'],
|
|
':ended_on' => $p['ended_on'],
|
|
':status' => $p['status'],
|
|
':summary' => $p['summary'],
|
|
':lede' => $p['lede'],
|
|
':body' => $p['body'],
|
|
':role' => $p['role'],
|
|
':categories' => $json($p['categories']),
|
|
':stack' => $json($p['stack']),
|
|
':skills' => $json($p['skills'] ?? []),
|
|
':metrics' => $json($p['metrics']),
|
|
':gallery' => $json($p['gallery'] ?? []),
|
|
':links' => $json($p['links']),
|
|
':sort_order' => $i * 10,
|
|
]);
|
|
printf(" %-30s %s\n", $p['slug'], 'ok');
|
|
}
|
|
|
|
$n = $pdo->query('SELECT COUNT(*) FROM cja_projects')->fetchColumn();
|
|
echo "\n {$n} projects in cja_projects\n";
|