unoverse 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/unoverse.mjs CHANGED
@@ -27,7 +27,11 @@ import { create } from "../lib/create.mjs";
27
27
  // file), so the code is global and the data is local.
28
28
  const VENDORED = join(dirname(fileURLToPath(import.meta.url)), "../operator/operator.sh");
29
29
  const IN_REPO = join(dirname(fileURLToPath(import.meta.url)), "../../../scripts/operator.sh");
30
- const OPERATOR = existsSync(VENDORED) ? VENDORED : IN_REPO;
30
+ // IN-REPO WINS. The vendored copy is a build artifact, refreshed only by prepack, so
31
+ // preferring it meant every edit to scripts/lib was invisible while developing here:
32
+ // the CLI kept running whatever the last `npm pack` froze. A published package has no
33
+ // ../../../scripts, so there the vendored copy is the only one and still wins.
34
+ const OPERATOR = existsSync(IN_REPO) ? IN_REPO : VENDORED;
31
35
 
32
36
  function findUniverse() {
33
37
  for (let dir = process.cwd(); ; ) {
@@ -56,7 +60,7 @@ const UNIVERSE = findUniverse();
56
60
  const OPERATOR_COMMANDS = new Set([
57
61
  "start", "stop", "check", "logs", "deploy",
58
62
  // kept working, not advertised
59
- "ground", "dev", "build", "publish", "init", "doctor", "db-setup", "db-verify", "open",
63
+ "ground", "dev", "build", "publish", "init",
60
64
  ]);
61
65
 
62
66
  const [, , cmd, ...args] = process.argv;
package/lib/create.mjs CHANGED
@@ -256,7 +256,7 @@ export async function create(nameArg) {
256
256
  env: { ...process.env, UNOVERSE_DOCR_TOKEN: token },
257
257
  });
258
258
  if (r.status !== 0) {
259
- fail(`setup did not finish. Run 'unoverse init'${name === "." ? "" : ` in ./${name}`} to pick it up`);
259
+ fail(`setup did not finish. Run 'unoverse start'${name === "." ? "" : ` in ./${name}`} to pick it back up`);
260
260
  process.exit(r.status ?? 1);
261
261
  }
262
262
  console.log(`
@@ -20,7 +20,7 @@ check_first_run() {
20
20
  cmd_init
21
21
  else
22
22
  echo ""
23
- info "Run ${BOLD}unoverse init${NC} when you're ready."
23
+ info "Run ${BOLD}unoverse start${NC} when you're ready. It picks setup back up."
24
24
  echo ""
25
25
  fi
26
26
  exit 0
@@ -18,6 +18,8 @@ cmd_logs() {
18
18
  fi
19
19
  info "Tip: ${BOLD}unoverse logs <service>${NC} to stream one service in the terminal"
20
20
  echo ""
21
- cmd_open logs
21
+ if command -v open &>/dev/null; then open "http://localhost:8080"
22
+ elif command -v xdg-open &>/dev/null; then xdg-open "http://localhost:8080"
23
+ fi
22
24
  fi
23
25
  }
@@ -37,11 +37,9 @@ source "$GRAVITY_LIB/init.sh"
37
37
  source "$GRAVITY_LIB/start.sh"
38
38
  source "$GRAVITY_LIB/stop.sh"
39
39
  source "$GRAVITY_LIB/logs.sh"
40
- source "$GRAVITY_LIB/update.sh"
41
40
  source "$GRAVITY_LIB/doctor.sh"
42
41
  source "$GRAVITY_LIB/dev.sh"
43
42
  source "$GRAVITY_LIB/check.sh"
44
- source "$GRAVITY_LIB/dashboard.sh"
45
43
  source "$GRAVITY_LIB/help.sh"
46
44
  source "$GRAVITY_LIB/db-setup.sh"
47
45
  source "$GRAVITY_LIB/db-verify.sh"
@@ -56,27 +54,15 @@ source "$GRAVITY_LIB/ground.sh"
56
54
  # (The old "studio mode" gate is gone — 2026-07-28. This CLI has ONE job:
57
55
  # operate a universe. Studio is a separate app; authoring happens there.)
58
56
  case "${1:-}" in
59
- init) cmd_init ;;
57
+ init) cmd_init ;; # not advertised: create configures, start resumes
60
58
  start) shift; cmd_start "$@" ;;
61
59
  stop) cmd_stop ;;
62
- status) cmd_check ;; # alias — status merged into check 2026-07-28
63
60
  logs) cmd_logs "${2:-}" ;;
64
- update)
65
- if [ "${2:-}" = "nodes" ]; then
66
- cmd_update_nodes
67
- else
68
- cmd_update
69
- fi
70
- ;;
71
61
  # ONE question, one command. `check` runs the health check, then the schema check,
72
62
  # then the deeper environment diagnosis — the three things that used to be `check`,
73
63
  # `db-verify` and `doctor`, which nobody could pick between.
74
64
  check) cmd_check && cmd_db_verify && cmd_doctor ;;
75
- doctor|db-verify|db-setup)
76
- echo " '$1' folded into 'unoverse check' (db-setup runs inside create and deploy)"
77
- exit 1 ;;
78
65
  dev) cmd_dev ;;
79
- _db-setup) cmd_db_setup ;; # internal: called by create and deploy
80
66
  ground) shift; cmd_ground "$@" ;;
81
67
  deploy)
82
68
  # `deploy marketplace` is PLATFORM-OWNER only: it publishes the marketplace package
@@ -105,9 +91,8 @@ case "${1:-}" in
105
91
  fi
106
92
  ;;
107
93
  build) cmd_build "${2:-}" ;;
108
- open) echo " 'open' folded into 'unoverse where'"; exit 1 ;;
109
94
  help|--help|-h) cmd_help ;;
110
- "") cmd_dashboard ;;
95
+ "") cmd_help ;;
111
96
  *)
112
97
  echo "Unknown command: $1"
113
98
  echo "Run unoverse help for usage"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unoverse",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "The Unoverse front door — create a Studio project, a universe, or a client app, and launch Studio.",
5
5
  "license": "SEE LICENSE IN README.md",
6
6
  "type": "module",
@@ -1,85 +0,0 @@
1
- #!/usr/bin/env bash
2
- # unoverse dashboard (default command when no args)
3
-
4
- cmd_dashboard() {
5
- echo ""
6
- echo -e " ${BOLD}${CYAN}⬡ Unoverse Platform${NC} ${DIM}v${GRAVITY_VERSION}${NC}"
7
- echo -e " ${DIM}─────────────────────────────────${NC}"
8
-
9
- # Check if .env exists
10
- if [ ! -f "$ROOT/.env" ]; then
11
- echo ""
12
- echo -e " ${YELLOW}${BOLD}First time?${NC} Run ${GREEN}${BOLD}unoverse init${NC} to get started."
13
- echo ""
14
- return
15
- fi
16
-
17
- # Quick status — use -a to see Created containers too
18
- local ps
19
- ps=$(docker compose -f "$ROOT/docker-compose.yml" ps -a --format "{{.Name}}\t{{.Status}}" 2>/dev/null) || true
20
-
21
- if [ -n "$ps" ]; then
22
- local total running created
23
- total=$(echo "$ps" | wc -l | tr -d ' ')
24
- running=$(echo "$ps" | grep -ci "up" || echo "0")
25
- created=$(echo "$ps" | grep -ci "created" || echo "0")
26
-
27
- echo ""
28
- if [ "$running" -eq "$total" ] && [ "$total" -gt 0 ]; then
29
- echo -e " ${GREEN}●${NC} ${BOLD}Platform running${NC} ${DIM}($running services)${NC}"
30
- echo ""
31
- print_access_urls
32
- elif [ "$created" -gt 0 ] && [ "$running" -eq 0 ]; then
33
- echo -e " ${RED}●${NC} ${BOLD}$total containers stuck in Created state${NC}"
34
- echo ""
35
- echo -e " Containers were created but never started."
36
- echo -e " Run ${GREEN}${BOLD}unoverse check${NC} to diagnose"
37
- elif [ "$running" -gt 0 ]; then
38
- echo -e " ${YELLOW}●${NC} ${BOLD}$running/$total services up${NC}"
39
- echo ""
40
- print_access_urls
41
- else
42
- echo -e " ${RED}●${NC} ${BOLD}Platform not running${NC} ${DIM}($total containers stopped)${NC}"
43
- echo ""
44
- echo -e " Run ${GREEN}${BOLD}unoverse start${NC} to launch"
45
- fi
46
- else
47
- echo ""
48
- echo -e " ${DIM}●${NC} ${BOLD}Platform stopped${NC}"
49
- echo ""
50
- echo -e " Run ${GREEN}${BOLD}unoverse start${NC} to launch"
51
- fi
52
-
53
- echo ""
54
- echo -e " ${DIM}Run ${NC}${BOLD}unoverse help${NC}${DIM} for all commands${NC}"
55
- echo ""
56
- }
57
-
58
- # ── unoverse open — browser shortcut (merged from open.sh 2026-07-28) ──
59
-
60
- cmd_open() {
61
- local target="${1:-canvas}"
62
- local url
63
-
64
- case "$target" in
65
- canvas) url="http://localhost:3001" ;;
66
- api) url="http://localhost:4105" ;;
67
- logs|dozzle) url="http://localhost:8080" ;;
68
- *)
69
- fail "Unknown target: $target"
70
- info "Options: canvas, api, logs"
71
- return
72
- ;;
73
- esac
74
-
75
- ok "Opening $target → ${UNDERLINE}$url${NC}"
76
-
77
- # Cross-platform open
78
- if command -v open &>/dev/null; then
79
- open "$url"
80
- elif command -v xdg-open &>/dev/null; then
81
- xdg-open "$url"
82
- else
83
- info "Open in your browser: $url"
84
- fi
85
- }
@@ -1,357 +0,0 @@
1
- #!/usr/bin/env bash
2
- # unoverse update + gravity update nodes
3
-
4
- cmd_update() {
5
- echo ""
6
- echo -e " ${BOLD}Updating Unoverse Platform${NC}"
7
- echo ""
8
- timer_start
9
-
10
- # ── Refuse before doing half the job ───────────────────────────────────────────
11
- # This used to run against an unconfigured folder with Docker down: compose warned
12
- # about thirty blank variables, then failed on the daemon, after reporting that the
13
- # code had updated. Ask first.
14
- if [ ! -f "$ROOT/.env" ]; then
15
- fail "This universe is not configured yet"
16
- info "Run ${BOLD}unoverse init${NC} first"
17
- exit 1
18
- fi
19
- if ! docker info >/dev/null 2>&1; then
20
- fail "Docker is not running. Update pulls images, so it needs the daemon"
21
- info "Start Docker Desktop, then run ${BOLD}unoverse update${NC} again"
22
- exit 1
23
- fi
24
-
25
- # ── Developer work is SAFE, by design ──────────────────────────────────────────
26
- # `update` refreshes the PLATFORM (its own tracked files) and NEVER touches a developer's
27
- # own work. Custom nodes live as UNTRACKED files (apps/unoverse/nodes/<yours>) — `git reset
28
- # --hard` ignores untracked files, and we no longer `git clean` them (see Step 1). So a
29
- # developer can create as many nodes as they want and `update` leaves them completely alone —
30
- # no stashing, no ceremony. The ONLY thing update overwrites is tracked PLATFORM files, so we
31
- # guard just those (a dev who edited framework code shouldn't lose it silently).
32
- # UNOVERSE_UPDATE_FORCE=1 overrides even that.
33
- if [ "${UNOVERSE_UPDATE_FORCE:-}" != "1" ] && [ -n "$(git -C "$ROOT" status --porcelain --untracked-files=no 2>/dev/null)" ]; then
34
- fail "You have uncommitted edits to PLATFORM files that update would overwrite."
35
- echo ""
36
- echo -e " ${DIM}Tracked files with local edits (your own untracked nodes are NOT affected):${NC}"
37
- git -C "$ROOT" status --short --untracked-files=no | sed 's/^/ /' | head -30
38
- echo ""
39
- info "Commit them: git add -A && git commit -m 'wip'"
40
- info "Or overwrite on purpose: UNOVERSE_UPDATE_FORCE=1 unoverse update"
41
- echo ""
42
- exit 1
43
- fi
44
-
45
- # Step 1: Code — pull from customer's fork.
46
- #
47
- # NOT EVERY UNIVERSE IS A GIT CLONE. `unoverse create` extracts a tarball, so there is
48
- # no remote to pull from and never was. That is not a failure: the images carry the
49
- # platform, and the files here are yours. Skip the step and say which mode you are in,
50
- # rather than running git commands that all fail and then reporting "Code updated".
51
- if ! git -C "$ROOT" rev-parse --git-dir >/dev/null 2>&1; then
52
- info "Not a git checkout, so there is no code to pull. Updating images only"
53
- UNOVERSE_SKIP_GIT=1
54
- fi
55
-
56
- if [ "${UNOVERSE_SKIP_GIT:-}" != "1" ]; then
57
- printf " ${DIM}●${NC} Pulling latest code..."
58
- local git_ok=true
59
- local git_log
60
- git_log=$(mktemp)
61
- (
62
- cd "$ROOT"
63
- # Abort any stuck rebase/merge from a previous failed pull
64
- git rebase --abort >/dev/null 2>&1 || true
65
- git merge --abort >/dev/null 2>&1 || true
66
- # Reset generated files (e.g. marketplace templates from deploy.sh rebuild)
67
- # This is safe: .env, production.yml, node_modules, package-lock.json are gitignored
68
- git reset HEAD -- . >/dev/null 2>&1 || true
69
- git checkout -- . >/dev/null 2>&1 || true
70
- # Fetch latest from remote
71
- git fetch origin >/dev/null 2>&1 || true
72
- # Reset the PLATFORM's own tracked files to match remote exactly. This ignores UNTRACKED
73
- # files, so a developer's custom nodes (apps/unoverse/nodes/<yours>) are left alone.
74
- git reset --hard origin/$(git rev-parse --abbrev-ref HEAD) >/dev/null 2>&1 || true
75
- # NB: intentionally NO `git clean -fd` here. It DELETES untracked files, and a developer's
76
- # nodes are untracked — cleaning them was the "update deleted my node" bug. Marketplace
77
- # packages install into a gitignored dir, so clean never removed those anyway.
78
- ) || git_ok=false
79
- printf "\r\033[2K"
80
- if $git_ok; then
81
- ok "Code updated"
82
- rm -f "$git_log"
83
- else
84
- warn "Code update failed. Attempting recovery"
85
- local git_err
86
- git_err=$(cat "$git_log" 2>/dev/null | head -5)
87
- if [ -n "$git_err" ]; then
88
- echo -e " ${DIM}Reason: $git_err${NC}"
89
- fi
90
- rm -f "$git_log"
91
-
92
- # Recovery: download latest CLI script from GitHub, then force-sync
93
- printf " ${DIM}●${NC} Downloading latest update script..."
94
- local temp_update=$(mktemp)
95
- if curl -fsSL "https://raw.githubusercontent.com/unoverse-platform/starter/main/scripts/lib/update.sh" -o "$temp_update" 2>/dev/null; then
96
- cp "$temp_update" "$GRAVITY_LIB/update.sh"
97
- rm -f "$temp_update"
98
- printf "\r\033[2K"
99
- ok "Update script refreshed"
100
-
101
- # Now force-sync with the new script
102
- printf " ${DIM}●${NC} Forcing sync with remote..."
103
- (
104
- cd "$ROOT"
105
- git fetch origin >/dev/null 2>&1 || true
106
- local branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
107
- git reset --hard origin/$branch >/dev/null 2>&1 || true
108
- # No `git clean -fd` — never delete a developer's untracked nodes (see Step 1).
109
- )
110
- printf "\r\033[2K"
111
- ok "Forced sync completed"
112
- else
113
- printf "\r\033[2K"
114
- fail "Recovery failed. Run: cd $ROOT && git reset --hard origin/main"
115
- exit 1
116
- fi
117
- fi
118
- fi # UNOVERSE_SKIP_GIT
119
-
120
- # Step 2: Images — login to registry if DOCR_TOKEN is set
121
- local docr_token
122
- docr_token=$(grep "^DOCR_TOKEN=" "$ROOT/.env" 2>/dev/null | cut -d'=' -f2-)
123
- if [ -n "$docr_token" ]; then
124
- echo "$docr_token" | docker login registry.digitalocean.com -u "$docr_token" --password-stdin >/dev/null 2>&1 || true
125
- fi
126
-
127
- echo ""
128
- info "Pulling images..."
129
- echo ""
130
-
131
- local pull_start=$(date +%s)
132
- if docker compose -f "$ROOT/docker-compose.yml" pull; then
133
- local total_elapsed=$(( $(date +%s) - pull_start ))
134
- echo ""
135
- ok "Images pulled ${DIM}(${total_elapsed}s)${NC}"
136
- else
137
- echo ""
138
- fail "Image pull failed. Check network/registry and run ${BOLD}unoverse update${NC} again"
139
- exit 1
140
- fi
141
-
142
- # Step 3: Build packages (requires Node.js — installed by install.yml)
143
- local build_log
144
- build_log=$(mktemp)
145
-
146
- # Only run npm install if the dependency inputs changed or node_modules is
147
- # missing. Hash every package.json alongside the lockfile: a pulled range
148
- # bump (e.g. plugin-base ^1.2.0 → ^1.2.1) changes no lockfile — the starter
149
- # doesn't ship one — so hashing only package-lock.json would skip the install
150
- # that the bump exists to force.
151
- dep_inputs_hash() {
152
- cat "$ROOT/package-lock.json" "$ROOT"/package.json "$ROOT"/apps/unoverse/nodes/*/package.json 2>/dev/null \
153
- | { md5 -q 2>/dev/null || md5sum 2>/dev/null | cut -d' ' -f1; }
154
- }
155
- local need_install=false
156
- if [ ! -d "$ROOT/node_modules" ]; then
157
- need_install=true
158
- elif [ -f "$ROOT/.package-lock.hash" ]; then
159
- local old_hash=$(cat "$ROOT/.package-lock.hash" 2>/dev/null || echo "")
160
- local new_hash=$(dep_inputs_hash || echo "new")
161
- [ "$old_hash" != "$new_hash" ] && need_install=true
162
- else
163
- need_install=true
164
- fi
165
-
166
- (
167
- cd "$ROOT"
168
- if $need_install; then
169
- npm install --silent >/dev/null 2>&1 || true
170
- # Save hash for next run
171
- dep_inputs_hash > "$ROOT/.package-lock.hash" || true
172
- fi
173
- # Turbo handles build dependencies, no need to build plugin-base separately
174
- npm run build --workspaces --if-present >> "$build_log" 2>&1 || true
175
- # NOTE: component nodes are DEFINITION-BACKED (no generation): the universal
176
- # node package (nodes/components, shipped pre-built) synthesizes one node per
177
- # rx/components/* definition at boot. Component changes need only a restart —
178
- # a restart does that (start/dev also rebuild the package if dist is stale).
179
- ) &
180
- local build_pid=$!
181
- local build_start=$(date +%s)
182
- while kill -0 "$build_pid" 2>/dev/null || false; do
183
- local c="${spin:si%${#spin}:1}"
184
- si=$((si + 1))
185
- local elapsed=$(( $(date +%s) - build_start ))
186
- printf "\r ${CYAN}%s${NC} Building packages... ${DIM}(%ds)${NC} " "$c" "$elapsed"
187
- sleep 0.1
188
- done
189
- local build_exit=0
190
- wait "$build_pid" 2>/dev/null || build_exit=$?
191
- printf "\r\033[2K"
192
- if [ $build_exit -eq 0 ]; then
193
- ok "Packages built"
194
- else
195
- warn "Build completed with warnings. Check output:"
196
- cat "$build_log" | tail -20 | sed 's/^/ /'
197
- fi
198
- rm -f "$build_log"
199
-
200
- # Step 4: Restart — with spinner
201
- docker compose -f "$ROOT/docker-compose.yml" --env-file "$ROOT/.env" up -d --quiet-pull >/dev/null 2>&1 &
202
- local restart_pid=$!
203
- local restart_start=$(date +%s)
204
- while kill -0 "$restart_pid" 2>/dev/null || false; do
205
- local c="${spin:si%${#spin}:1}"
206
- si=$((si + 1))
207
- local elapsed=$(( $(date +%s) - restart_start ))
208
- printf "\r ${CYAN}%s${NC} Restarting services... ${DIM}(%ds)${NC} " "$c" "$elapsed"
209
- sleep 0.1
210
- done
211
- wait "$restart_pid" 2>/dev/null || true
212
- printf "\r\033[2K"
213
-
214
- # Verify services actually started (not stuck in Created)
215
- sleep 2
216
- local up_count=0 created_count=0 total_count=0
217
- # `grep -c` already PRINTS "0" (and exits 1) on no match, so `|| echo "0"` printed a
218
- # SECOND "0" → the var became "0\n0", which `[ -eq ]` rejects ("integer expression
219
- # expected"). Use `|| true` to swallow grep's exit code without emitting a stray line.
220
- total_count=$(docker compose -f "$ROOT/docker-compose.yml" ps -a --format "{{.Name}}" 2>/dev/null | grep -c . || true)
221
- up_count=$(docker compose -f "$ROOT/docker-compose.yml" ps -a --format "{{.Status}}" 2>/dev/null | grep -ci "up\|running" || true)
222
- created_count=$(docker compose -f "$ROOT/docker-compose.yml" ps -a --format "{{.Status}}" 2>/dev/null | grep -ci "created" || true)
223
-
224
- if [ "${up_count:-0}" -eq "${total_count:-0}" ] && [ "${total_count:-0}" -gt 0 ]; then
225
- ok "All $up_count services running"
226
- elif [ "${created_count:-0}" -gt 0 ]; then
227
- warn "$up_count/$total_count services running: $created_count stuck in Created state"
228
- info "Run ${BOLD}unoverse check${NC} to diagnose"
229
- elif [ "${up_count:-0}" -gt 0 ]; then
230
- warn "$up_count/$total_count services running"
231
- info "Run ${BOLD}unoverse status${NC} to check"
232
- else
233
- fail "No services running after restart"
234
- info "Run ${BOLD}unoverse check${NC} to diagnose"
235
- fi
236
-
237
- # Summary. Re-source the SHARED box helper first: `update` pulled fresh scripts a
238
- # moment ago, but the copy loaded at startup is stale — reload so the summary reflects
239
- # what we just pulled (fixes the "branded box / Studio line shows one run late" bug).
240
- source "$GRAVITY_LIB/common.sh" 2>/dev/null || true
241
- echo ""
242
- echo -e " ${GREEN}${BOLD}Done${NC} ${DIM}in $(timer_elapsed)${NC}"
243
- echo ""
244
- print_access_urls
245
- echo ""
246
- }
247
-
248
- cmd_update_nodes() {
249
- echo ""
250
- echo -e " ${BOLD}Updating Nodes${NC}"
251
- echo ""
252
- timer_start
253
-
254
- # Step 1: Dependencies
255
- printf " ${DIM}●${NC} Installing dependencies..."
256
- (cd "$ROOT" && npm install --silent >/dev/null 2>&1) || true
257
- printf "\r\033[2K"
258
- ok "Dependencies installed"
259
-
260
- # Step 2: Build
261
- printf " ${DIM}●${NC} Building packages..."
262
- (cd "$ROOT" && npm run build -w @unoverse-platform/plugin-base >/dev/null 2>&1) || true
263
- local build_output
264
- build_output=$(cd "$ROOT" && npm run build --workspaces --if-present 2>&1) || true
265
- local pkg_count
266
- pkg_count=$(echo "$build_output" | grep -c '> .* build' || echo "0")
267
- printf "\r\033[2K"
268
- ok "${pkg_count} packages built"
269
-
270
- # Step 3 (retired): component nodes are DEFINITION-BACKED — no generation step.
271
- # They synthesize from rx/components at boot; the universal package is built
272
- # in-container on start/dev (and by `unoverse deploy packages` on the server).
273
-
274
- # Step 4: Restart
275
- printf " ${DIM}●${NC} Restarting unoverse..."
276
- docker compose -f "$ROOT/docker-compose.yml" restart unoverse >/dev/null 2>&1 || true
277
- printf "\r\033[2K"
278
- ok "Node-service restarted"
279
-
280
- # Summary
281
- echo ""
282
- echo -e " ${GREEN}${BOLD}Done${NC} ${DIM}in $(timer_elapsed)${NC}"
283
- echo ""
284
- }
285
-
286
- # ── cmd_pull — INTERNAL helper (init + update use it). Not a user command since 2026-07-28. ──
287
-
288
- cmd_pull() {
289
- echo ""
290
- read -r -p " Pull platform images now? (~1.2GB first time) [Y/n] " REPLY
291
- echo ""
292
- if [[ "$REPLY" =~ ^[Nn]$ ]]; then
293
- info "Skipped. Run ${BOLD}unoverse start${NC} later."
294
- return
295
- fi
296
-
297
- local IMAGES=(
298
- "registry.digitalocean.com/gravity-repo/canvas:latest"
299
- "registry.digitalocean.com/gravity-repo/umap:latest"
300
- "registry.digitalocean.com/gravity-repo/unoverse:latest"
301
- "registry.digitalocean.com/gravity-repo/memory:latest"
302
- )
303
-
304
- local total=${#IMAGES[@]}
305
- local count=0
306
- local failed=0
307
-
308
- local spin='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
309
-
310
- echo ""
311
- for img in "${IMAGES[@]}"; do
312
- count=$((count + 1))
313
- local short="${img##*/}" # gravity-server:latest
314
- short="${short%%:*}" # gravity-server
315
-
316
- # Start pull in background
317
- docker pull "$img" &>/dev/null &
318
- local pid=$!
319
-
320
- # Animate spinner while pulling
321
- local i=0
322
- while kill -0 "$pid" 2>/dev/null || false; do
323
- local c="${spin:i%${#spin}:1}"
324
- i=$((i + 1))
325
- printf "\r ${DIM}[%d/%d]${NC} ${CYAN}%s${NC} Pulling ${BOLD}%s${NC} " "$count" "$total" "$c" "$short"
326
- sleep 0.1
327
- done
328
-
329
- # Check result
330
- local pull_exit=0
331
- wait "$pid" 2>/dev/null || pull_exit=$?
332
- if [ $pull_exit -eq 0 ]; then
333
- printf "\r ${DIM}[%d/%d]${NC} ${GREEN}✓${NC} Pulling ${BOLD}%s${NC} \n" "$count" "$total" "$short"
334
- else
335
- printf "\r ${DIM}[%d/%d]${NC} ${RED}✗${NC} Pulling ${BOLD}%s${NC} \n" "$count" "$total" "$short"
336
- failed=$((failed + 1))
337
- fi
338
- done
339
- echo ""
340
-
341
- # Count how many gravity images we have now
342
- local pulled
343
- pulled=$(docker images -a --format "{{.Repository}}" | grep -c "gravity-repo" || true)
344
- pulled=$(echo "$pulled" | tr -d '[:space:]')
345
- pulled=${pulled:-0}
346
-
347
- echo ""
348
- if [ "$pulled" -ge 5 ]; then
349
- ok "All $pulled images pulled"
350
- elif [ "$pulled" -gt 0 ]; then
351
- warn "$pulled images pulled (some may need retry)"
352
- info "Run ${BOLD}unoverse pull${NC} to retry"
353
- else
354
- fail "No images pulled. Check your DOCR token and network"
355
- exit 1
356
- fi
357
- }