the static git browser that builds this site
git clone https://git.lucas.co/gitsite.git
Mirror from GitHub; retire the ~/git bare layer
GitHub is canonical now: every work tree's origin is
https://github.com/lsgalante/<name>.git and this site mirrors from there.
repos.conf carries the GitHub URLs, autodeploy.sh asks GitHub for each
HEAD with ls-remote instead of reading a local bare repo, and build.sh
re-points an existing mirror's origin at whatever repos.conf says so the
cached mirrors follow the move without a re-clone.
git-bare-sync.sh goes with the bare layer it maintained; the readme's
publishing model is rewritten and keeps the history, including the three
repos whose hashes changed on the way to GitHub.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
autodeploy.sh | 4 +-
build.sh | 4 +
git-bare-sync.sh | 242 -------------------------------------------------------
readme.txt | 79 +++++++-----------
repos.conf | 77 +++++++++---------
5 files changed, 76 insertions(+), 330 deletions(-)
diff --git a/autodeploy.sh b/autodeploy.sh
index abb6829..7fdb61d 100755
--- a/autodeploy.sh
+++ b/autodeploy.sh
@@ -8,7 +8,9 @@ STATE="$HOME/.cache/gitsite/last-publish-heads"
current=$(grep -v '^#' "$BASE/repos.conf" | grep -v '^$' | \
while IFS='|' read -r name path desc mode; do
- echo "$name $(git -C "$path" rev-parse HEAD 2>/dev/null || echo empty)"
+ # path is a URL (GitHub); ls-remote is one round trip per repo. A
+ # repo with no commits yet answers nothing, hence "empty".
+ echo "$name $(git ls-remote "$path" HEAD 2>/dev/null | cut -f1 | grep . || echo empty)"
done | sha256sum | cut -d' ' -f1)
if [ -f "$STATE" ] && [ "$(cat "$STATE")" = "$current" ]; then
diff --git a/build.sh b/build.sh
index 6152124..721f8d1 100755
--- a/build.sh
+++ b/build.sh
@@ -16,6 +16,10 @@ grep -v '^#' "$BASE/repos.conf" | grep -v '^$' | while IFS='|' read -r name path
# small packfiles: Cloudflare Pages rejects files over 25MB
git -C "$m" repack -a -d -q --max-pack-size=20m
else
+ # repos.conf is the source of truth for where a repo lives; a mirror
+ # made when it pointed elsewhere (the ~/git bare layer, before
+ # 2026-09-20) follows it here rather than fetching from a stale path.
+ git -C "$m" remote set-url origin "$path"
git -C "$m" fetch --quiet --prune origin
fi
done
diff --git a/git-bare-sync.sh b/git-bare-sync.sh
deleted file mode 100755
index d290295..0000000
--- a/git-bare-sync.sh
+++ /dev/null
@@ -1,242 +0,0 @@
-#!/bin/sh
-# Create and maintain the local bare-repo layer under ~/git.
-#
-# ~/git/<name>.git bare, pushable -- the canonical copy
-# ^ git push
-# working tree gitsite mirrors from the bare repo -> git.lucas.co
-#
-# Why this exists: nothing in the setup could be pushed to. The cce repos'
-# origin was https://git.lucas.co/<name>.git, which is static dumb-http -- you
-# can clone from it, you cannot push to it, because there is no receive-pack
-# behind static hosting. So "published" meant "a timer happened to run", with
-# no signal either way. gitsite's own readme names the hazard: if the timer
-# stops, local commits look published but aren't.
-#
-# With a bare remote, `git push` succeeds or fails now, and the bare repo is a
-# second real copy on disk -- independent of the working tree, so an rm -rf no
-# longer costs everything since the last nightly restic run.
-#
-# Safe to re-run. Existing bare repos are fetched into, not recreated, and an
-# existing origin is preserved under a descriptive name rather than dropped.
-#
-# Which repos: every line of repos.conf, by NAME (field 1). Field 2 is the BARE
-# path -- what gitsite reads HEAD from -- not a work tree, so work trees are
-# resolved by name under $WORK_ROOTS. A name that resolves to nothing is
-# reported and fails the run; it is never silently skipped.
-#
-# Run it only on the machine that owns the bare layer. The bare repos are
-# canonical and live on exactly one host; on any other machine a work tree's
-# origin is a URL pointing back at that host over the network, and this script
-# -- which computes bare="$BARE_ROOT/$name.git" and rewrites origin to that
-# local path -- would hand the second machine its own divergent bare repos and
-# file the real remote away under "previous". It would then look like it was
-# publishing while pushing to its own disk, which is the failure this whole
-# arrangement exists to prevent. A remote-looking origin is therefore refused.
-# On a second machine just use git: `git push origin <branch>`.
-#
-# GIT_BARE_HOST=1 overrides the refusal. That is the migration case this script
-# was written for, where an old fetch-only origin (git.lucas.co, github) is
-# deliberately replaced by a local bare repo.
-#
-# Usage: git-bare-sync.sh [--dry-run]
-# env: GIT_BARE_ROOT (default ~/git), GIT_WORK_ROOTS, REPOS_CONF,
-# GIT_BARE_HOST
-
-set -eu
-
-BARE_ROOT="${GIT_BARE_ROOT:-$HOME/git}"
-REPOS_CONF="${REPOS_CONF:-$HOME/projects/gitsite/repos.conf}"
-DRY=""
-[ "${1:-}" = "--dry-run" ] && DRY=1
-
-# Where work trees live. repos.conf does NOT record them -- its field 2 is the
-# BARE path, because that is what gitsite reads HEAD from -- so a repo's work
-# tree is found by NAME under these roots, first match winning.
-WORK_ROOTS="${GIT_WORK_ROOTS:-$HOME/projects/cce $HOME/projects $HOME/Dropbox/src}"
-
-# Repos that are not published through gitsite and so are absent from
-# repos.conf. Add a line here (a work-tree path), or add them to repos.conf to
-# publish them too. Empty now that hou-control and gitsite are both listed
-# there -- they were carried here with a path that had since gone stale.
-EXTRA_REPOS=""
-
-# The work tree for a repo name, or nothing if it is not under WORK_ROOTS.
-find_worktree() {
- _name=$1
- for _root in $WORK_ROOTS; do
- if [ -d "$_root/$_name/.git" ]; then
- printf '%s\n' "$_root/$_name"
- return 0
- fi
- done
- return 1
-}
-
-say() { printf '%s\n' "$*"; }
-run() {
- if [ -n "$DRY" ]; then
- printf ' would: %s\n' "$*"
- else
- "$@"
- fi
-}
-
-# Does this origin point at a bare layer on another machine? A local bare repo
-# is an absolute path; a URL scheme or an scp-style host:path is somewhere
-# else. file:// is a URL but still local, so it is not remote.
-is_remote_url() {
- case "$1" in
- ''|/*|./*|../*|~*) return 1 ;;
- file://*) return 1 ;;
- *://*) return 0 ;;
- *:*) return 0 ;;
- *) return 1 ;;
- esac
-}
-
-# A name for an existing origin, so replacing it loses no information.
-preserved_name() {
- case "$1" in
- *git.lucas.co*) echo "published" ;; # static mirror: fetch-only
- *codeberg.org*) echo "codeberg" ;; # no longer used
- *github.com*) echo "github" ;;
- *) echo "previous" ;;
- esac
-}
-
-# Runs in a subshell so a failure inside it is returnable rather than fatal
-# under `set -e`. fail() records the first error and keeps going, so a repo
-# that cannot be cloned still gets reported rather than silently skipped.
-sync_one() (
- set +e
- rc=0
- fail() { rc=1; }
- path=$1
- [ -d "$path/.git" ] || { say " skip $path (not a git work tree)"; exit 0; }
- name=$(basename "$path")
- bare="$BARE_ROOT/$name.git"
- current=$(git -C "$path" remote get-url origin 2>/dev/null || true)
-
- # Client-mode guard, before anything is written: creating the bare repo is
- # itself a mutation, so the origin has to be inspected first.
- if [ -z "${GIT_BARE_HOST:-}" ] && is_remote_url "$current"; then
- say " !! $name: origin is $current"
- say " That is a bare layer on another machine. Running here would"
- say " replace it with $bare, giving this machine its own divergent"
- say " bare repos while the real remote is filed away as 'previous'."
- say " Push from here with git directly: git push origin <branch>."
- say " Run this only on the host that owns $BARE_ROOT."
- say " GIT_BARE_HOST=1 overrides, for replacing a legacy origin."
- fail
- exit $rc
- fi
-
- if [ -d "$bare" ]; then
- say " $name: bare exists"
- else
- say " $name: creating $bare"
- run git clone --bare --quiet "$path" "$bare" || { fail; exit $rc; }
- # clone --bare leaves a back-reference to the work tree; the bare repo
- # is the upstream, so it should not point anywhere.
- run git -C "$bare" remote remove origin 2>/dev/null || true
- run git -C "$bare" config gc.auto 6700
- fi
-
- if [ -n "$current" ]; then
- case "$current" in
- "$bare") : ;; # already pointed at the bare repo
- *)
- keep=$(preserved_name "$current")
- if git -C "$path" remote get-url "$keep" >/dev/null 2>&1; then
- run git -C "$path" remote remove origin
- else
- say " keeping old origin as '$keep' ($current)"
- run git -C "$path" remote rename origin "$keep"
- fi
- run git -C "$path" remote add origin "$bare"
- ;;
- esac
- else
- run git -C "$path" remote add origin "$bare"
- fi
-
- # A repo with no commits yet has nothing to push and is not a failure.
- if ! git -C "$path" rev-parse --quiet --verify HEAD >/dev/null 2>&1; then
- say " no commits yet -- bare repo created, nothing to push"
- exit 0
- fi
-
- # Push every branch and tag. --all covers main/master without caring which.
- run git -C "$path" push --quiet origin --all || fail
- run git -C "$path" push --quiet origin --tags || fail
-
- branch=$(git -C "$path" symbolic-ref --short HEAD 2>/dev/null || true)
- if [ -n "$branch" ]; then
- run git -C "$path" branch --quiet --set-upstream-to="origin/$branch" "$branch" 2>/dev/null || true
- # Point the bare repo's HEAD at the same branch, so cloning it checks
- # out the right thing and gitsite shows the right default.
- run git -C "$bare" symbolic-ref HEAD "refs/heads/$branch" || fail
- fi
- exit $rc
-)
-
-[ -n "$DRY" ] && say "DRY RUN -- nothing will be written"
-run mkdir -p "$BARE_ROOT"
-
-FAILED=""
-
-# One damaged repo must not abort the other thirty-three. A repo whose refs
-# Dropbox has mangled makes git exit non-zero here; record it and carry on.
-attempt() {
- if sync_one "$1"; then
- :
- else
- say " !! $(basename "$1") FAILED -- see above"
- FAILED="$FAILED $(basename "$1")"
- fi
-}
-
-say "from $REPOS_CONF:"
-# Read the whole list first: the loop body runs in this shell, not a subshell,
-# so a failure is visible to the caller rather than swallowed by a pipeline.
-# Fields 1|2 are name|bare-path (no spaces around the separators, per the file's
-# own header). This used to iterate field 2 and hand it to sync_one as though it
-# were a work tree; every repo then reported "not a git work tree" and was
-# skipped, so nothing was pushed for weeks while commits piled up looking
-# published. Hence: resolve by NAME, and treat an unresolvable one as a failure
-# rather than a skip.
-list=$(grep -v '^#' "$REPOS_CONF" | grep -v '^[[:space:]]*$' | cut -d'|' -f1,2)
-for entry in $list; do
- name=${entry%%|*}
- conf_bare=${entry#*|}
- if ! path=$(find_worktree "$name"); then
- say " !! $name: no work tree under $WORK_ROOTS -- NOT synced"
- FAILED="$FAILED $name"
- continue
- fi
- # gitsite publishes whatever field 2 points at, while this script maintains
- # $BARE_ROOT/<name>.git. If the two ever diverge, pushes land in a repo the
- # site never reads -- silent non-publishing again, from the other end.
- if [ "$BARE_ROOT" = "$HOME/git" ] && [ "$conf_bare" != "$BARE_ROOT/$name.git" ]; then
- say " !! $name: repos.conf publishes $conf_bare, but this syncs"
- say " $BARE_ROOT/$name.git -- pushes would not reach the site"
- FAILED="$FAILED $name"
- continue
- fi
- attempt "$path"
-done
-
-if [ -n "$EXTRA_REPOS" ]; then
- say "extras:"
- for path in $EXTRA_REPOS; do
- attempt "$path"
- done
-fi
-
-say ""
-if [ -n "$FAILED" ]; then
- say "done, with failures:$FAILED"
- say "bare repos in $BARE_ROOT"
- exit 1
-fi
-say "done. bare repos in $BARE_ROOT"
diff --git a/readme.txt b/readme.txt
index 9c6c93c..a311cd3 100644
--- a/readme.txt
+++ b/readme.txt
@@ -9,12 +9,9 @@ repos.conf, plus dumb-http clone support so
Files
-----
repos.conf which repos to publish: name|path|description|mode
- path is the BARE repo in ~/git -- what this reads HEAD from,
- not a work tree
+ path is the GitHub URL the repo is mirrored from -- what
+ this reads HEAD from, not a work tree
mode "clone" = browse + clonable, "browse" = browse only
-git-bare-sync.sh
- creates/maintains the ~/git bare repos and pushes every work
- tree into them -- see "Publishing model" below
generate.py the HTML generator
build.sh syncs mirrors, runs generate.py, adds clone files
deploy.sh pushes the built site to Cloudflare Pages
@@ -52,53 +49,36 @@ Log: ~/.cache/gitsite/autodeploy.log
Publishing model
----------------
-Committing is NOT publishing; pushing is. Each repo's origin is a local
-bare repo under ~/git (real, pushable, and a second copy on disk); this
-site mirrors from those, and repos.conf lists them. So:
+Committing is NOT publishing; pushing is. Each repo's origin is
+https://github.com/lsgalante/<name>.git, GitHub is canonical, and this
+site is a mirror of it: repos.conf lists the GitHub URLs, build.sh keeps a
+bare mirror of each in ~/.cache/gitsite/mirrors, and autodeploy.sh asks
+GitHub (git ls-remote) whether any HEAD moved. So:
git commit ... # local only
- git push origin <branch> # the publishing step
+ git push # the publishing step
# gitsite.timer then deploys it
-That replaced an older arrangement where origin was this site itself --
-fetch-only, static, no receive-pack -- and "published" meant "a timer
-happened to run", with no signal either way.
-
-git-bare-sync.sh creates any missing bare repos and pushes every repo in
-repos.conf into its own, so it is the bulk version of that push step:
-
- git-bare-sync.sh --dry-run # show what would be pushed
- git-bare-sync.sh # do it
-
-It finds each repo's work tree by NAME under ~/projects/cce, ~/projects
-and ~/Dropbox/src (override with GIT_WORK_ROOTS), because repos.conf
-records only the bare path. A name it cannot resolve fails the run rather
-than being skipped: it previously read repos.conf's bare path AS a work
-tree, found no .git, and silently skipped all 34 repos -- which left 21
-of them holding unpushed commits (2026-09-18) while the docs still said a
-commit was enough.
-
-Run it only on the machine that owns ~/git. The bare repos are canonical
-and live on exactly one host; on a second machine a work tree's origin is
-a URL pointing back here over the network, and this script -- which
-rewrites origin to $BARE_ROOT/<name>.git -- would hand that machine its
-own divergent bare layer and file the real remote away under "previous".
-It would then look like it was publishing while pushing to its own disk,
-which is the failure the bare layer was introduced to eliminate. So a
-remote-looking origin (a URL scheme, or scp-style host:path) is refused,
-and refused before anything is created, since creating a bare repo is
-already a mutation. On a second machine, just use git:
-
- git push origin <branch> # same publishing step, no bulk tool
-
-GIT_BARE_HOST=1 overrides the refusal. That is the migration case this
-script was written for: an old fetch-only origin deliberately replaced by
-a local bare repo.
-
-It lives here rather than loose in ~/.local/bin, where it was unversioned
-and one rm from gone; ~/.local/bin/git-bare-sync.sh is a symlink to this
-copy, so PATH and the docs that name that path keep working and there is
-only one file to edit.
+The 27 crates pinned to https://git.lucas.co/<name>.git?rev=... keep
+working because the dumb-http clone dirs are built from the same mirrors;
+a rev exists here as long as it is reachable on GitHub.
+
+History, for when something looks odd:
+
+- Until 2026-09-18 each repo's origin was this site itself: fetch-only,
+ static, no receive-pack, so "published" meant "a timer happened to run".
+- 2026-09-18..20 a local bare-repo layer under ~/git was the origin and
+ this site mirrored from it (git-bare-sync.sh, now deleted). A Forgejo
+ instance at forge.lucas.co briefly mirrored the same layer.
+- 2026-09-20 everything moved to GitHub. Three repos were rewritten on the
+ way, so their hashes before that date do not match the ones here:
+ cce-fonts and cce-gallery had a committed target/ directory (blobs up to
+ 403MB; GitHub refuses anything over 100MB), and cce-system-interface had
+ a Google OAuth client ID and secret compiled into
+ src/pages/accounts.rs. Both values are scrubbed from every commit; the
+ secret was public here and had to be rotated regardless.
+- hou-control already existed on GitHub with an unrelated 2024-2025
+ history; that is preserved there as branch main-2024.
One-time Cloudflare setup
-------------------------
@@ -133,4 +113,5 @@ Notes
- Mirrors live in ~/.cache/gitsite/mirrors, repacked into <=20MB packs
(Cloudflare rejects files over 25MB). Delete a mirror dir to force a
fresh re-mirror.
-- To add a repo: add a line to repos.conf, run build.sh + deploy.sh.
+- To add a repo: create it on GitHub (gh repo create lsgalante/<name>
+ --public), push, add a line to repos.conf, run build.sh + deploy.sh.
diff --git a/repos.conf b/repos.conf
index 1aa8d1b..c57e9df 100644
--- a/repos.conf
+++ b/repos.conf
@@ -1,41 +1,42 @@
# name|path|description|mode
-# path: the bare repo in ~/git -- publishing follows what was PUSHED,
-# not merely committed. See git-bare-sync.sh.
+# path: where the repo is fetched from. GitHub is canonical and this site
+# is a mirror of it: publishing follows what was PUSHED to GitHub,
+# not merely committed.
# mode: "clone" = browsable + clonable via dumb http, "browse" = browsable only
# no spaces around the | separators
-website|/home/lsgalante/git/website.git|source for lucas.co|browse
-hou-control|/home/lsgalante/git/hou-control.git|SideFX Houdini customization package|clone
-gitsite|/home/lsgalante/git/gitsite.git|the static git browser that builds this site|clone
-cce|/home/lsgalante/git/cce.git|cce workspace root: member list, lockfile and dependency pins|clone
-cce-authenticator|/home/lsgalante/git/cce-authenticator.git|login authentication (PAM + fingerprint)|clone
-cce-browser|/home/lsgalante/git/cce-browser.git|web browser (Servo)|clone
-cce-calendar|/home/lsgalante/git/cce-calendar.git|calendar|clone
-cce-cloud|/home/lsgalante/git/cce-cloud.git|cloud storage client|clone
-cce-color-editor|/home/lsgalante/git/cce-color-editor.git|color picker and palette editor|clone
-cce-compositor|/home/lsgalante/git/cce-compositor.git|Wayland compositor (wlroots)|clone
-cce-data-editor|/home/lsgalante/git/cce-data-editor.git|structured data editor|clone
-cce-designer|/home/lsgalante/git/cce-designer.git|graphic design tool|clone
-cce-display-manager|/home/lsgalante/git/cce-display-manager.git|login greeter|clone
-cce-files|/home/lsgalante/git/cce-files.git|file manager|clone
-cce-fonts|/home/lsgalante/git/cce-fonts.git|font browser|browse
-cce-graph|/home/lsgalante/git/cce-graph.git|node-based graph editor|clone
-cce-icons|/home/lsgalante/git/cce-icons.git|SVG icon set|clone
-cce-layout-interface|/home/lsgalante/git/cce-layout-interface.git|page layout tool|clone
-cce-lock|/home/lsgalante/git/cce-lock.git|session locker (ext-session-lock + PAM)|clone
-cce-mail|/home/lsgalante/git/cce-mail.git|mail client (IMAP/SMTP)|clone
-cce-grid|/home/lsgalante/git/cce-grid.git|desktop grid client|clone
-cce-map|/home/lsgalante/git/cce-map.git|map viewer|clone
-cce-notifier|/home/lsgalante/git/cce-notifier.git|notification daemon|clone
-cce-preview|/home/lsgalante/git/cce-preview.git|document and image viewer|clone
-cce-remote|/home/lsgalante/git/cce-remote.git|remote trackpad and keyboard server|clone
-cce-screenaver|/home/lsgalante/git/cce-screenaver.git|screensaver|clone
-cce-secrets|/home/lsgalante/git/cce-secrets.git|secrets manager|clone
-cce-status-interface|/home/lsgalante/git/cce-status-interface.git|status bar|clone
-cce-system-interface|/home/lsgalante/git/cce-system-interface.git|system settings|clone
-cce-terminal|/home/lsgalante/git/cce-terminal.git|terminal emulator|clone
-cce-gallery|/home/lsgalante/git/cce-gallery.git|widget gallery and compositor test bench|browse
-cce-text-editor|/home/lsgalante/git/cce-text-editor.git|text editor|clone
-cce-ui|/home/lsgalante/git/cce-ui.git|GPU-accelerated UI toolkit (Vulkan)|clone
-cce-window-manager|/home/lsgalante/git/cce-window-manager.git|window management library|clone
-clear-typeface|/home/lsgalante/git/clear-typeface.git|typeface (empty for now)|clone
-cce-list|/home/lsgalante/git/cce-list.git|things-to-remember checklist|clone
+website|https://github.com/lsgalante/website.git|source for lucas.co|browse
+hou-control|https://github.com/lsgalante/hou-control.git|SideFX Houdini customization package|clone
+gitsite|https://github.com/lsgalante/gitsite.git|the static git browser that builds this site|clone
+cce|https://github.com/lsgalante/cce.git|cce workspace root: member list, lockfile and dependency pins|clone
+cce-authenticator|https://github.com/lsgalante/cce-authenticator.git|login authentication (PAM + fingerprint)|clone
+cce-browser|https://github.com/lsgalante/cce-browser.git|web browser (Servo)|clone
+cce-calendar|https://github.com/lsgalante/cce-calendar.git|calendar|clone
+cce-cloud|https://github.com/lsgalante/cce-cloud.git|cloud storage client|clone
+cce-color-editor|https://github.com/lsgalante/cce-color-editor.git|color picker and palette editor|clone
+cce-compositor|https://github.com/lsgalante/cce-compositor.git|Wayland compositor (wlroots)|clone
+cce-data-editor|https://github.com/lsgalante/cce-data-editor.git|structured data editor|clone
+cce-designer|https://github.com/lsgalante/cce-designer.git|graphic design tool|clone
+cce-display-manager|https://github.com/lsgalante/cce-display-manager.git|login greeter|clone
+cce-files|https://github.com/lsgalante/cce-files.git|file manager|clone
+cce-fonts|https://github.com/lsgalante/cce-fonts.git|font browser|browse
+cce-graph|https://github.com/lsgalante/cce-graph.git|node-based graph editor|clone
+cce-icons|https://github.com/lsgalante/cce-icons.git|SVG icon set|clone
+cce-layout-interface|https://github.com/lsgalante/cce-layout-interface.git|page layout tool|clone
+cce-lock|https://github.com/lsgalante/cce-lock.git|session locker (ext-session-lock + PAM)|clone
+cce-mail|https://github.com/lsgalante/cce-mail.git|mail client (IMAP/SMTP)|clone
+cce-grid|https://github.com/lsgalante/cce-grid.git|desktop grid client|clone
+cce-map|https://github.com/lsgalante/cce-map.git|map viewer|clone
+cce-notifier|https://github.com/lsgalante/cce-notifier.git|notification daemon|clone
+cce-preview|https://github.com/lsgalante/cce-preview.git|document and image viewer|clone
+cce-remote|https://github.com/lsgalante/cce-remote.git|remote trackpad and keyboard server|clone
+cce-screenaver|https://github.com/lsgalante/cce-screenaver.git|screensaver|clone
+cce-secrets|https://github.com/lsgalante/cce-secrets.git|secrets manager|clone
+cce-status-interface|https://github.com/lsgalante/cce-status-interface.git|status bar|clone
+cce-system-interface|https://github.com/lsgalante/cce-system-interface.git|system settings|clone
+cce-terminal|https://github.com/lsgalante/cce-terminal.git|terminal emulator|clone
+cce-gallery|https://github.com/lsgalante/cce-gallery.git|widget gallery and compositor test bench|browse
+cce-text-editor|https://github.com/lsgalante/cce-text-editor.git|text editor|clone
+cce-ui|https://github.com/lsgalante/cce-ui.git|GPU-accelerated UI toolkit (Vulkan)|clone
+cce-window-manager|https://github.com/lsgalante/cce-window-manager.git|window management library|clone
+clear-typeface|https://github.com/lsgalante/clear-typeface.git|typeface (empty for now)|clone
+cce-list|https://github.com/lsgalante/cce-list.git|things-to-remember checklist|clone