Files
stack/packages/mosaic/framework/bin/mosaic-projects
Jason Woltje b38cfac760
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
feat: integrate framework files into monorepo under packages/mosaic/framework/
Moves all Mosaic framework runtime files from the separate bootstrap repo
into the monorepo as canonical source. The @mosaic/mosaic npm package now
ships the complete framework — bin scripts, runtime configs, tools, and
templates — enabling standalone installation via npm install.

Structure:
  packages/mosaic/framework/
  ├── bin/          28 CLI scripts (mosaic, mosaic-doctor, mosaic-sync-skills, etc.)
  ├── runtime/      Runtime adapters (claude, codex, opencode, pi, mcp)
  ├── tools/        Shell tooling (git, prdy, orchestrator, quality, etc.)
  ├── templates/    Agent and repo templates
  ├── defaults/     Default identity files (AGENTS.md, STANDARDS.md, SOUL.md, etc.)
  ├── install.sh    Legacy bash installer
  └── remote-install.sh  One-liner remote installer

Key files with Pi support and recent fixes:
- bin/mosaic: launch_pi() with skills-local loop
- bin/mosaic-doctor: --fix auto-wiring for all 4 harnesses
- bin/mosaic-sync-skills: Pi as 4th link target, symlink-aware find
- bin/mosaic-link-runtime-assets: Pi settings.json patching
- bin/mosaic-migrate-local-skills: Pi skill roots, symlink find
- runtime/pi/RUNTIME.md + mosaic-extension.ts

Package ships 251 framework files in the npm tarball (278KB compressed).
2026-04-01 21:19:21 -05:00

219 lines
5.8 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}"
PROJECTS_FILE="$MOSAIC_HOME/projects.txt"
usage() {
cat <<USAGE
Usage: $(basename "$0") <command> [options]
Commands:
init
Create projects registry file at ~/.config/mosaic/projects.txt
add <repo-path> [repo-path...]
Add one or more repos to the registry
remove <repo-path> [repo-path...]
Remove one or more repos from the registry
list
Show registered repos
bootstrap [--all|repo-path...] [--force] [--quality-template <name>]
Bootstrap registered repos or explicit repo paths
orchestrate <drain|start|status|stop> [--all|repo-path...] [--poll-sec N] [--no-sync] [--worker-cmd "cmd"]
Run orchestrator actions across repos from one command
Examples:
mosaic-projects init
mosaic-projects add ~/src/syncagent ~/src/inventory-stickers
mosaic-projects bootstrap --all
mosaic-projects orchestrate drain --all --worker-cmd "codex -p"
mosaic-projects orchestrate start ~/src/syncagent --worker-cmd "opencode -p"
USAGE
}
ensure_registry() {
mkdir -p "$MOSAIC_HOME"
if [[ ! -f "$PROJECTS_FILE" ]]; then
cat > "$PROJECTS_FILE" <<EOF
# Mosaic managed projects (one absolute path per line)
# Lines starting with # are ignored.
EOF
fi
}
norm_path() {
local p="$1"
if [[ -d "$p" ]]; then
(cd "$p" && pwd)
else
return 1
fi
}
read_registry() {
ensure_registry
grep -vE '^\s*#|^\s*$' "$PROJECTS_FILE" | while read -r p; do
[[ -d "$p" ]] && echo "$p"
done
}
add_repo() {
local p="$1"
ensure_registry
local np
np="$(norm_path "$p")" || { echo "[mosaic-projects] skip missing dir: $p" >&2; return 1; }
if grep -Fxq "$np" "$PROJECTS_FILE"; then
echo "[mosaic-projects] already registered: $np"
return 0
fi
echo "$np" >> "$PROJECTS_FILE"
echo "[mosaic-projects] added: $np"
}
remove_repo() {
local p="$1"
ensure_registry
local np
np="$(norm_path "$p" 2>/dev/null || echo "$p")"
tmp="$(mktemp)"
grep -vFx "$np" "$PROJECTS_FILE" > "$tmp" || true
mv "$tmp" "$PROJECTS_FILE"
echo "[mosaic-projects] removed: $np"
}
resolve_targets() {
local use_all="$1"
shift
if [[ "$use_all" == "1" ]]; then
read_registry
return 0
fi
if [[ $# -gt 0 ]]; then
for p in "$@"; do
norm_path "$p" || { echo "[mosaic-projects] missing target: $p" >&2; exit 1; }
done
return 0
fi
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
git rev-parse --show-toplevel
return 0
fi
echo "[mosaic-projects] no targets provided. Use --all or pass repo paths." >&2
exit 1
}
cmd="${1:-}"
if [[ -z "$cmd" ]]; then
usage
exit 1
fi
shift || true
case "$cmd" in
init)
ensure_registry
echo "[mosaic-projects] registry ready: $PROJECTS_FILE"
;;
add)
[[ $# -gt 0 ]] || { echo "[mosaic-projects] add requires repo path(s)" >&2; exit 1; }
for p in "$@"; do add_repo "$p"; done
;;
remove)
[[ $# -gt 0 ]] || { echo "[mosaic-projects] remove requires repo path(s)" >&2; exit 1; }
for p in "$@"; do remove_repo "$p"; done
;;
list)
read_registry
;;
bootstrap)
use_all=0
force=0
quality_template=""
targets=()
while [[ $# -gt 0 ]]; do
case "$1" in
--all) use_all=1; shift ;;
--force) force=1; shift ;;
--quality-template) quality_template="${2:-}"; shift 2 ;;
*) targets+=("$1"); shift ;;
esac
done
mapfile -t repos < <(resolve_targets "$use_all" "${targets[@]}")
[[ ${#repos[@]} -gt 0 ]] || { echo "[mosaic-projects] no repos resolved"; exit 1; }
for repo in "${repos[@]}"; do
args=()
[[ $force -eq 1 ]] && args+=(--force)
[[ -n "$quality_template" ]] && args+=(--quality-template "$quality_template")
args+=("$repo")
echo "[mosaic-projects] bootstrap: $repo"
"$MOSAIC_HOME/bin/mosaic-bootstrap-repo" "${args[@]}"
add_repo "$repo" || true
done
;;
orchestrate)
action="${1:-}"
[[ -n "$action" ]] || { echo "[mosaic-projects] orchestrate requires action: drain|start|status|stop" >&2; exit 1; }
shift || true
use_all=0
poll_sec=15
no_sync=0
worker_cmd=""
targets=()
while [[ $# -gt 0 ]]; do
case "$1" in
--all) use_all=1; shift ;;
--poll-sec) poll_sec="${2:-15}"; shift 2 ;;
--no-sync) no_sync=1; shift ;;
--worker-cmd) worker_cmd="${2:-}"; shift 2 ;;
*) targets+=("$1"); shift ;;
esac
done
mapfile -t repos < <(resolve_targets "$use_all" "${targets[@]}")
[[ ${#repos[@]} -gt 0 ]] || { echo "[mosaic-projects] no repos resolved"; exit 1; }
for repo in "${repos[@]}"; do
echo "[mosaic-projects] orchestrate:$action -> $repo"
(
cd "$repo"
if [[ -n "$worker_cmd" ]]; then
export MOSAIC_WORKER_EXEC="$worker_cmd"
fi
if [[ -x "scripts/agent/orchestrator-daemon.sh" ]]; then
args=()
[[ "$action" == "start" || "$action" == "drain" ]] && args+=(--poll-sec "$poll_sec")
[[ $no_sync -eq 1 ]] && args+=(--no-sync)
bash scripts/agent/orchestrator-daemon.sh "$action" "${args[@]}"
else
case "$action" in
drain)
args=(--poll-sec "$poll_sec")
[[ $no_sync -eq 1 ]] && args+=(--no-sync)
"$MOSAIC_HOME/bin/mosaic-orchestrator-drain" "${args[@]}"
;;
status)
echo "[mosaic-projects] no daemon script in repo; run from bootstrapped repo or re-bootstrap"
;;
start|stop)
echo "[mosaic-projects] action '$action' requires scripts/agent/orchestrator-daemon.sh (run bootstrap first)" >&2
exit 1
;;
*)
echo "[mosaic-projects] unsupported action: $action" >&2
exit 1
;;
esac
fi
)
done
;;
*)
usage
exit 1
;;
esac