skill
Adom Theme
Public Made by Adomby adom
The canonical Adom application theme spec: five schemes, exact tokens, font pairings, and the implementation contract.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
#!/usr/bin/env bash
# Install the Adom theme system onto this machine:
# 1. the skill (always) -> ~/.claude/skills/adom-theme/
# 2. the VS Code theme pack (when code-server is present) -> makes the five
# "Adom ..." themes selectable in the editor. Does NOT change the active
# theme; that stays the user's choice.
# 3. the brand fonts:
# - JetBrains Mono + Familjen Grotesk ship IN this package (OFL 1.1).
# - Satoshi is fetched from Fontshare's OFFICIAL servers at install time;
# its EULA forbids re-hosting the files publicly but blesses delivery
# from Indian Type Foundry's own servers. See fonts/satoshi/README.md.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
log() { echo "[adom-theme] $*"; }
# --- 1. the skill ------------------------------------------------------------
DEST="$HOME/.claude/skills/adom-theme"
# migrate: drop the pre-rename skill dir so only one copy exists
rm -rf "$HOME/.claude/skills/adom-theme-system"
mkdir -p "$DEST"
cp -f "$HERE/SKILL.md" "$DEST/SKILL.md"
log "skill -> $DEST"
# --- 2. the VS Code theme pack ----------------------------------------------
# The pack MUST be installed through code-server's own CLI (`--install-extension
# <vsix>`) and NOT by copying the folder into extensions/. A copied folder is
# absent from extensions/extensions.json, and code-server PRUNES unregistered
# folders on its next launch: the pack silently vanishes and the theme picker
# shows no "Adom ..." entries at all, so a settings.json asking for "Adom Studio"
# falls back to stock Light. That bug shipped in a golden image; see SKILL.md
# "Delivering the VS Code pack". Do not "simplify" this back into a cp -r.
PACK_VERSION="$(python3 -c "import json;print(json.load(open('$HERE/vscode/package.json'))['version'])" 2>/dev/null || echo unknown)"
EXT_DIR="$HOME/.local/share/code-server/extensions"
EXT_ID="adom.adom-theme"
# legacy id from before the 2026-08-02 rename; always purge so the picker never doubles up
LEGACY_EXT_ID="adom.adom-themes"
# The `code-server` on PATH inside an editor terminal is the remote-cli shim; it
# cannot install extensions. Resolve the real launcher instead.
find_code_server() {
local c
for c in /usr/lib/code-server/bin/code-server \
/usr/local/lib/code-server/bin/code-server \
"$HOME/.local/lib/code-server/bin/code-server"; do
[ -x "$c" ] && { printf '%s\n' "$c"; return 0; }
done
c="$(command -v code-server 2>/dev/null || true)"
case "$c" in
""|*remote-cli*) return 1 ;;
*) printf '%s\n' "$c" ;;
esac
}
# code-server's CLI refuses to run ("error not spawned with IPC") when it inherits
# CODE_SERVER_PARENT_PID / VSCODE_IPC_HOOK_CLI from the editor terminal that is
# running this installer. Strip them.
run_code_server() {
env -u CODE_SERVER_PARENT_PID -u VSCODE_IPC_HOOK_CLI "$@"
}
install_theme_pack() {
local cs vsix
cs="$(find_code_server)" || {
log "no code-server launcher found; skipped the editor theme pack"
return 0
}
command -v python3 >/dev/null 2>&1 || {
log "python3 not available to package the pack; skipped the editor theme pack"
return 0
}
vsix="$(mktemp /tmp/adom-themes-XXXXXX.vsix)"
python3 - "$HERE/vscode" "$vsix" <<'PYEOF'
import sys, os, json, zipfile
src, out = sys.argv[1], sys.argv[2]
m = json.load(open(os.path.join(src, "package.json")))
pub, name, ver = m["publisher"], m["name"], m["version"]
esc = lambda s: (s.replace("&", "&").replace("<", "<").replace(">", ">"))
manifest = f'''<?xml version="1.0" encoding="utf-8"?>
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011">
<Metadata>
<Identity Language="en-US" Id="{name}" Version="{ver}" Publisher="{pub}" />
<DisplayName>{esc(m.get("displayName", name))}</DisplayName>
<Description xml:space="preserve">{esc(m.get("description", ""))}</Description>
<Categories>Themes</Categories>
<Properties>
<Property Id="Microsoft.VisualStudio.Code.Engine" Value="{m.get("engines", {}).get("vscode", "^1.70.0")}" />
<Property Id="Microsoft.VisualStudio.Code.ExtensionDependencies" Value="" />
<Property Id="Microsoft.VisualStudio.Code.ExtensionPack" Value="" />
</Properties>
</Metadata>
<Installation><InstallationTarget Id="Microsoft.VisualStudio.Code" /></Installation>
<Dependencies />
<Assets>
<Asset Type="Microsoft.VisualStudio.Code.Manifest" Path="extension/package.json" Addressable="true" />
</Assets>
</PackageManifest>
'''
content_types = '''<?xml version="1.0" encoding="utf-8"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="json" ContentType="application/json" />
<Default Extension="vsixmanifest" ContentType="text/xml" />
<Default Extension="md" ContentType="text/markdown" />
<Default Extension="png" ContentType="image/png" />
</Types>
'''
with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as z:
z.writestr("extension.vsixmanifest", manifest)
z.writestr("[Content_Types].xml", content_types)
for root, _, files in os.walk(src):
for f in files:
p = os.path.join(root, f)
z.write(p, "extension/" + os.path.relpath(p, src).replace(os.sep, "/"))
PYEOF
mkdir -p "$EXT_DIR"
# Uninstall first so a stale entry is dropped from extensions.json too; a bare
# `rm -rf` of the folder would leave the registration behind and dangling.
run_code_server "$cs" --extensions-dir "$EXT_DIR" \
--uninstall-extension "$EXT_ID" >/dev/null 2>&1 || true
run_code_server "$cs" --extensions-dir "$EXT_DIR" \
--uninstall-extension "$LEGACY_EXT_ID" >/dev/null 2>&1 || true
if run_code_server "$cs" --extensions-dir "$EXT_DIR" \
--install-extension "$vsix" --force >/dev/null 2>&1; then
log "VS Code theme pack v$PACK_VERSION installed via code-server -> $EXT_ID"
else
log "WARNING: code-server refused the theme pack vsix; the editor will show no Adom themes"
rm -f "$vsix"
return 0
fi
rm -f "$vsix"
# Registration is the whole point of this section -- assert it, loudly.
if python3 - "$EXT_DIR/extensions.json" "$EXT_ID" <<'PYEOF'
import sys, json
try:
entries = json.load(open(sys.argv[1]))
except Exception:
sys.exit(1)
sys.exit(0 if any(e.get("identifier", {}).get("id") == sys.argv[2] for e in entries) else 1)
PYEOF
then
log " registered in extensions.json (survives the next editor launch)"
log " (five 'Adom ...' themes appear in the theme picker after the editor reloads)"
else
log "WARNING: $EXT_ID is NOT in extensions.json -- code-server will prune the pack"
fi
}
if [ -d "$HERE/vscode/themes" ]; then
install_theme_pack
fi
# --- 3a. the OFL fonts (bundled) ----------------------------------------------
FONT_DEST="$HOME/.local/share/fonts/adom-theme"
if [ -d "$HERE/fonts" ]; then
mkdir -p "$FONT_DEST"
cp -f "$HERE"/fonts/jetbrains-mono/*.woff2 "$FONT_DEST/" 2>/dev/null || true
cp -f "$HERE"/fonts/familjen-grotesk/*.woff2 "$FONT_DEST/" 2>/dev/null || true
cp -f "$HERE"/fonts/jetbrains-mono/OFL.txt "$FONT_DEST/JetBrainsMono-OFL.txt" 2>/dev/null || true
cp -f "$HERE"/fonts/familjen-grotesk/OFL.txt "$FONT_DEST/FamiljenGrotesk-OFL.txt" 2>/dev/null || true
log "OFL fonts (JetBrains Mono, Familjen Grotesk) -> $FONT_DEST"
fi
# --- 3b. Satoshi (fetched from Fontshare's official servers) -------------------
# Skipped silently when offline or already present; the theme's prose stacks all
# carry system fallbacks, so nothing breaks without it.
SATOSHI_URL="https://api.fontshare.com/v2/fonts/download/satoshi"
if [ "${ADOM_THEME_SKIP_SATOSHI:-0}" = "1" ]; then
# Image bakes and other PUBLIC artifacts must not carry Satoshi (Fontshare EULA
# forbids public redistribution). Set ADOM_THEME_SKIP_SATOSHI=1 and let each
# machine fetch it at first setup. See fonts/LICENSES.md.
log "Satoshi fetch SKIPPED (ADOM_THEME_SKIP_SATOSHI=1) - fetch per-machine at setup"
elif [ -f "$FONT_DEST/Satoshi-Regular.woff2" ]; then
log "Satoshi already present -> $FONT_DEST"
elif command -v curl >/dev/null 2>&1 && command -v python3 >/dev/null 2>&1; then
TMPZ="$(mktemp /tmp/satoshi-XXXXXX.zip)"
if curl -fsSL --compressed -A "Mozilla/5.0" -o "$TMPZ" "$SATOSHI_URL" 2>/dev/null; then
python3 - "$TMPZ" "$FONT_DEST" <<'PYEOF'
import sys, zipfile, os
zpath, dest = sys.argv[1], sys.argv[2]
WANT = ['Satoshi-Regular.woff2','Satoshi-Medium.woff2','Satoshi-Bold.woff2','Satoshi-Variable.woff2']
got = 0
with zipfile.ZipFile(zpath) as z:
for name in z.namelist():
base = os.path.basename(name)
if base in WANT and '/WEB/' in name:
data = z.read(name)
if data[:4] != b'wOF2':
print(f"[adom-theme] {base}: not wOF2, skipped"); continue
open(os.path.join(dest, base),'wb').write(data)
got += 1
print(f"[adom-theme] Satoshi: {got}/{len(WANT)} faces from Fontshare (official servers, per its EULA)")
sys.exit(0 if got else 1)
PYEOF
rm -f "$TMPZ"
else
rm -f "$TMPZ"
log "Satoshi fetch skipped (offline?) - get it later: see fonts/satoshi/README.md on the page"
fi
else
log "Satoshi fetch skipped (needs curl+python3) - see fonts/satoshi/README.md on the page"
fi
command -v fc-cache >/dev/null 2>&1 && fc-cache -f "$FONT_DEST" >/dev/null 2>&1 || true
log "done"