Compare commits

..

1 Commits

Author SHA1 Message Date
Jarvis
7e95107ed5 fix(mosaic): resumable gateway install + prominent admin token
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
The `mosaic gateway install` command had three UX problems:

1. Admin API token was silently saved to meta.json and never shown to
   the user — no way to retrieve it without reading the file.
2. If install crashed mid-way (e.g. daemon health-check failure), the
   next run prompted "Reinstall? [y/N]" and aborted on N, leaving a
   half-configured install with no resume path.
3. Health-check failures only pointed at `mosaic gateway logs` — user
   had to run a separate command to see the actual error.

Changes:

- **Prominent admin token banner** printed immediately after bootstrap
  creates the first admin user. Clear "save now, won't be shown again"
  warning.
- **Resumable install state machine.** Detects partial installs from
  meta.json + .env + mosaic.config.json + daemon state, and picks up
  where a prior attempt stopped instead of prompting. Fully set up
  installs now offer "re-run config wizard" with explicit warnings
  about what it does (regenerates .env, clears admin token, may change
  backend storage).
- **Inline log tail on health failure.** Last 30 non-empty lines of
  gateway.log printed automatically when the daemon fails to become
  healthy, so the user sees the underlying error without running a
  second command.
- **Corrupt-state detection.** If exactly one of .env / mosaic.config.json
  exists (from an interrupted prior install), refuses to guess and
  directs the user to `mosaic gateway uninstall`.
- **BETTER_AUTH_SECRET preservation** across config regeneration so
  existing Better Auth sessions aren't silently invalidated.
- **Admin token dropped on any config regeneration** (the wizard may
  point at a different backend; the old token is unverifiable).
- **Daemon stopped before config rewrite** so the live process never
  serves stale config.
- Bump `@mosaic/mosaic` 0.0.19 → 0.0.20.

Known follow-ups (noted in review):
- `--port 14242` as an explicit override cannot be distinguished from
  commander's default value; requires plumbing an `explicit` flag.
- No automated test coverage for the new state branches; requires
  mocking fs/readline/fetch/spawn.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 22:13:46 -05:00
26 changed files with 66 additions and 113 deletions

View File

@@ -35,42 +35,13 @@ steps:
- |
echo "//git.mosaicstack.dev/api/packages/mosaicstack/npm/:_authToken=$NPM_TOKEN" > ~/.npmrc
echo "@mosaicstack:registry=https://git.mosaicstack.dev/api/packages/mosaicstack/npm/" >> ~/.npmrc
# Publish non-private packages to Gitea.
#
# The only publish failure we tolerate is "version already exists" —
# that legitimately happens when only some packages were bumped in
# the merge. Any other failure (registry 404, auth error, network
# error) MUST fail the pipeline loudly: the previous
# `|| echo "... continuing"` fallback silently hid a 404 from the
# Gitea org rename and caused every @mosaicstack/* publish to fall
# on the floor while CI still reported green.
- |
# Portable sh (Alpine ash) — avoid bashisms like PIPESTATUS.
set +e
pnpm --filter "@mosaicstack/*" --filter "!@mosaicstack/web" publish --no-git-checks --access public >/tmp/publish.log 2>&1
EXIT=$?
set -e
cat /tmp/publish.log
if [ "$EXIT" -eq 0 ]; then
echo "[publish] all packages published successfully"
exit 0
fi
# Hard registry / auth / network errors → fatal. Match npm's own
# error lines specifically to avoid false positives on arbitrary
# log text that happens to contain "E404" etc.
if grep -qE "npm (error|ERR!) code (E404|E401|ENEEDAUTH|ECONNREFUSED|ETIMEDOUT|ENOTFOUND)" /tmp/publish.log; then
echo "[publish] FATAL: registry/auth/network error detected — failing pipeline" >&2
exit 1
fi
# Only tolerate the explicit "version already published" case.
# npm returns this as E403 with body "You cannot publish over..."
# or EPUBLISHCONFLICT depending on version.
if grep -qE "EPUBLISHCONFLICT|You cannot publish over|previously published" /tmp/publish.log; then
echo "[publish] some packages already at this version — continuing (non-fatal)"
exit 0
fi
echo "[publish] FATAL: publish failed with unrecognized error — failing pipeline" >&2
exit 1
# Publish non-private packages to Gitea (--no-git-checks skips dirty/branch checks in CI)
# --filter excludes web (private)
- >
pnpm --filter "@mosaicstack/*"
--filter "!@mosaicstack/web"
publish --no-git-checks --access public
|| echo "[publish] Some packages may already exist at this version — continuing"
depends_on:
- build
@@ -103,12 +74,12 @@ steps:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
- |
DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/mosaic-stack/gateway:sha-${CI_COMMIT_SHA:0:7}"
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:sha-${CI_COMMIT_SHA:0:7}"
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/mosaic-stack/gateway:latest"
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:latest"
fi
if [ -n "$CI_COMMIT_TAG" ]; then
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/mosaic-stack/gateway:$CI_COMMIT_TAG"
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/gateway:$CI_COMMIT_TAG"
fi
/kaniko/executor --context . --dockerfile docker/gateway.Dockerfile $DESTINATIONS
depends_on:
@@ -128,12 +99,12 @@ steps:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
- |
DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/mosaic-stack/web:sha-${CI_COMMIT_SHA:0:7}"
DESTINATIONS="--destination git.mosaicstack.dev/mosaic/mosaic-stack/web:sha-${CI_COMMIT_SHA:0:7}"
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/mosaic-stack/web:latest"
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/web:latest"
fi
if [ -n "$CI_COMMIT_TAG" ]; then
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/mosaic-stack/web:$CI_COMMIT_TAG"
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaic/mosaic-stack/web:$CI_COMMIT_TAG"
fi
/kaniko/executor --context . --dockerfile docker/web.Dockerfile $DESTINATIONS
depends_on:

View File

@@ -3,7 +3,7 @@
"version": "0.0.6",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "apps/gateway"
},
"type": "module",
@@ -15,7 +15,7 @@
"dist"
],
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"scripts": {

View File

@@ -3,7 +3,7 @@
"version": "0.0.2",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/agent"
},
"main": "dist/index.js",
@@ -28,7 +28,7 @@
"vitest": "^2.0.0"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.2",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/auth"
},
"type": "module",
@@ -32,7 +32,7 @@
"better-auth": "^1.5.5"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.2",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/brain"
},
"main": "dist/index.js",
@@ -29,7 +29,7 @@
"vitest": "^2.0.0"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.17",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/cli"
},
"type": "module",
@@ -47,7 +47,7 @@
"vitest": "^2.0.0"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.2",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/config"
},
"type": "module",
@@ -32,7 +32,7 @@
"vitest": "^2.0.0"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.2",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/coord"
},
"main": "dist/index.js",
@@ -29,7 +29,7 @@
"vitest": "^2.0.0"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.3",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/db"
},
"type": "module",
@@ -38,7 +38,7 @@
"postgres": "^3.4.8"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.2",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/design-tokens"
},
"type": "module",
@@ -26,7 +26,7 @@
"vitest": "^2.0.0"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.2",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/forge"
},
"type": "module",
@@ -35,7 +35,7 @@
"vitest": "^2.0.0"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
}
}

View File

@@ -3,7 +3,7 @@
"version": "0.0.2",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/log"
},
"type": "module",
@@ -30,7 +30,7 @@
"vitest": "^2.0.0"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.2",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/macp"
},
"type": "module",
@@ -28,7 +28,7 @@
"vitest": "^2.0.0"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.3",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/memory"
},
"type": "module",
@@ -32,7 +32,7 @@
"vitest": "^2.0.0"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -1,9 +1,9 @@
{
"name": "@mosaicstack/mosaic",
"version": "0.0.21",
"version": "0.0.20",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/mosaic"
},
"description": "Mosaic agent framework — installation wizard and meta package",
@@ -52,7 +52,7 @@
"vitest": "^2.0.0"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -127,28 +127,10 @@ export function semverLt(a: string, b: string): boolean {
// ─── Known packages for checkForAllUpdates() ──────────────────────────────
const KNOWN_PACKAGES = [
'@mosaicstack/agent',
'@mosaicstack/auth',
'@mosaicstack/brain',
'@mosaicstack/config',
'@mosaicstack/coord',
'@mosaicstack/db',
'@mosaicstack/design-tokens',
'@mosaicstack/discord-plugin',
'@mosaicstack/forge',
'@mosaicstack/gateway',
'@mosaicstack/log',
'@mosaicstack/macp',
'@mosaicstack/memory',
'@mosaicstack/mosaic',
'@mosaicstack/oc-framework-plugin',
'@mosaicstack/oc-macp-plugin',
'@mosaicstack/prdy',
'@mosaicstack/cli',
'@mosaicstack/gateway',
'@mosaicstack/quality-rails',
'@mosaicstack/queue',
'@mosaicstack/storage',
'@mosaicstack/telegram-plugin',
'@mosaicstack/types',
];
// ─── Multi-package types ──────────────────────────────────────────────────

View File

@@ -3,7 +3,7 @@
"version": "0.0.2",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/prdy"
},
"main": "dist/index.js",
@@ -33,7 +33,7 @@
"vitest": "^2.0.0"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.3",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/quality-rails"
},
"type": "module",
@@ -30,7 +30,7 @@
"vitest": "^2.0.0"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.3",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/queue"
},
"main": "dist/index.js",
@@ -29,7 +29,7 @@
"vitest": "^2.0.0"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.3",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/storage"
},
"main": "dist/index.js",
@@ -30,7 +30,7 @@
"vitest": "^2.0.0"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.2",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "packages/types"
},
"main": "dist/index.js",
@@ -29,7 +29,7 @@
"class-validator": "^0.15.1"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.2",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "plugins/discord"
},
"main": "dist/index.js",
@@ -31,7 +31,7 @@
"vitest": "^2.0.0"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.2",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "plugins/macp"
},
"type": "module",
@@ -23,7 +23,7 @@
"openclaw": "*"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.2",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "plugins/mosaic-framework"
},
"type": "module",
@@ -18,7 +18,7 @@
"openclaw": "*"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -3,7 +3,7 @@
"version": "0.0.2",
"repository": {
"type": "git",
"url": "https://git.mosaicstack.dev/mosaicstack/mosaic-stack.git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",
"directory": "plugins/telegram"
},
"main": "dist/index.js",
@@ -29,7 +29,7 @@
"telegraf": "^4.16.3"
},
"publishConfig": {
"registry": "https://git.mosaicstack.dev/api/packages/mosaicstack/npm/",
"registry": "https://git.mosaicstack.dev/api/packages/mosaic/npm/",
"access": "public"
},
"files": [

View File

@@ -6,10 +6,10 @@
# 2. @mosaicstack/mosaic (npm) → ~/.npm-global/ (CLI, TUI, gateway client, wizard)
#
# Remote install (recommended):
# bash <(curl -fsSL https://git.mosaicstack.dev/mosaicstack/mosaic-stack/raw/branch/main/tools/install.sh)
# bash <(curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh)
#
# Remote install (alternative — use -s -- to pass flags):
# curl -fsSL https://git.mosaicstack.dev/mosaicstack/mosaic-stack/raw/branch/main/tools/install.sh | bash -s --
# curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh | bash -s --
#
# Flags:
# --check Version check only, no install
@@ -50,11 +50,11 @@ done
# ─── constants ────────────────────────────────────────────────────────────────
MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}"
REGISTRY="${MOSAIC_REGISTRY:-https://git.mosaicstack.dev/api/packages/mosaicstack/npm/}"
REGISTRY="${MOSAIC_REGISTRY:-https://git.mosaicstack.dev/api/packages/mosaic/npm/}"
SCOPE="${MOSAIC_SCOPE:-@mosaicstack}"
PREFIX="${MOSAIC_PREFIX:-$HOME/.npm-global}"
CLI_PKG="${SCOPE}/mosaic"
REPO_BASE="https://git.mosaicstack.dev/mosaicstack/mosaic-stack"
REPO_BASE="https://git.mosaicstack.dev/mosaic/mosaic-stack"
ARCHIVE_URL="${REPO_BASE}/archive/${GIT_REF}.tar.gz"
# ─── colours ──────────────────────────────────────────────────────────────────