unoverse 0.1.31 → 0.1.33

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
@@ -103,26 +103,36 @@ switch (cmd) {
103
103
  break;
104
104
 
105
105
  case "update": {
106
- // Refresh the repo-local authoring skill alongside the CLI, when we are in a
107
- // universe. It is a copy by necessity (Claude Code discovers .claude/skills off
108
- // disk), so the CLI's own update is what keeps it current.
106
+ // ONE update: the CLI from npm, then everything the CLI is responsible for keeping
107
+ // current the repo-local authoring skill and, in a universe, the platform images.
108
+ // Those two steps run via `_postupdate` in the NEWLY installed binary, not this
109
+ // process: this file is being replaced mid-command, and copying the skill from the
110
+ // old install would pin it one release behind forever.
111
+ const r = spawnSync("npm", ["install", "-g", "unoverse@latest"], { stdio: "inherit" });
112
+ if (r.status === 0) {
113
+ const v = spawnSync("unoverse", ["-v"], { encoding: "utf8" }).stdout?.trim();
114
+ console.log(`\n ✓ unoverse is now ${v || "up to date"}`);
115
+ spawnSync("unoverse", ["_postupdate"], { stdio: "inherit" });
116
+ } else {
117
+ console.log(`\n ✗ global install failed (permissions?). Try:\n\n sudo npm install -g unoverse@latest\n npx unoverse@latest # or skip the global install entirely\n`);
118
+ }
119
+ process.exit(r.status ?? 0);
120
+ }
121
+
122
+ case "_postupdate": {
123
+ // Runs AS the new version, in the developer's cwd. Outside a universe there is
124
+ // nothing to refresh, and silence is the right answer.
109
125
  if (UNIVERSE) {
110
126
  const skillSrc = join(dirname(fileURLToPath(import.meta.url)), "../operator/skills/unoverse-create");
111
127
  if (existsSync(skillSrc)) {
112
128
  const { cpSync, mkdirSync } = await import("node:fs");
113
129
  mkdirSync(join(UNIVERSE.root, ".claude/skills"), { recursive: true });
114
130
  cpSync(skillSrc, join(UNIVERSE.root, ".claude/skills/unoverse-create"), { recursive: true });
115
- console.log(" ✓ authoring skill refreshed (.claude/skills/unoverse-create)");
131
+ console.log(" ✓ authoring skill refreshed");
116
132
  }
133
+ operator(UNIVERSE, ["refresh-images"]);
117
134
  }
118
- const r = spawnSync("npm", ["install", "-g", "unoverse@latest"], { stdio: "inherit" });
119
- if (r.status === 0) {
120
- const v = spawnSync("unoverse", ["-v"], { encoding: "utf8" }).stdout?.trim();
121
- console.log(`\n ✓ unoverse is now ${v || "up to date"}\n`);
122
- } else {
123
- console.log(`\n ✗ global install failed (permissions?). Try:\n\n sudo npm install -g unoverse@latest\n npx unoverse@latest # or skip the global install entirely\n`);
124
- }
125
- process.exit(r.status ?? 0);
135
+ process.exit(0);
126
136
  }
127
137
 
128
138
  // `urls` stays as an alias: it shipped in 0.1.4/0.1.5 and is in people's fingers.
@@ -256,6 +256,24 @@ cmd_init() {
256
256
  [ -z "$OPENAI_API_KEY" ] && warn "No OpenAI key: the universe starts, but agents, embeddings and memory will not work until one is in .env"
257
257
  fi
258
258
 
259
+ # Node vendor keys, OPTIONAL: only the matching nodes need them, nothing platform-level
260
+ # does. Asked so compose stops warning about them and so they land in .env with names.
261
+ local cur_hb cur_sa
262
+ cur_hb=$(_env_cur HYPERBROWSER_API_KEY)
263
+ if [ -n "$cur_hb" ]; then
264
+ read -p " HYPERBROWSER_API_KEY [keep current]: " HYPERBROWSER_API_KEY
265
+ HYPERBROWSER_API_KEY="${HYPERBROWSER_API_KEY:-$cur_hb}"
266
+ else
267
+ read -p " HYPERBROWSER_API_KEY (browser nodes, blank to skip): " HYPERBROWSER_API_KEY
268
+ fi
269
+ cur_sa=$(_env_cur SEARCHAPI_KEY)
270
+ if [ -n "$cur_sa" ]; then
271
+ read -p " SEARCHAPI_KEY [keep current]: " SEARCHAPI_KEY
272
+ SEARCHAPI_KEY="${SEARCHAPI_KEY:-$cur_sa}"
273
+ else
274
+ read -p " SEARCHAPI_KEY (web-search nodes, blank to skip): " SEARCHAPI_KEY
275
+ fi
276
+
259
277
  # Write .env
260
278
  cat > "$ROOT/.env" << ENVEOF
261
279
  # Generated by unoverse init
@@ -272,6 +290,8 @@ AUTH_CLIENT_ID=${AUTH_CLIENT_ID}
272
290
  AUTH_AUDIENCE=${AUTH_AUDIENCE}
273
291
  API_URL=${API_URL}
274
292
  OPENAI_API_KEY=${OPENAI_API_KEY}
293
+ HYPERBROWSER_API_KEY=${HYPERBROWSER_API_KEY}
294
+ SEARCHAPI_KEY=${SEARCHAPI_KEY}
275
295
  DOMAIN=
276
296
  ENVEOF
277
297
 
@@ -288,7 +308,7 @@ ENVEOF
288
308
  fail "DOCR login failed. Check your token"
289
309
  exit 1
290
310
  fi
291
- cmd_pull
311
+ pull_missing_images
292
312
  else
293
313
  echo ""
294
314
  info "Skipped the registry login and image pull. ${BOLD}unoverse start${NC} does both once Docker is up"
@@ -306,11 +326,14 @@ ENVEOF
306
326
  # A database that is not reachable yet is not a failed setup — .env is written and
307
327
  # correct, so say so and move on rather than unwinding everything.
308
328
  echo ""
329
+ # SUBSHELL, deliberately: cmd_db_setup exits on "services not up yet", and an exit in a
330
+ # sourced function would take all of init with it — setup then reported failure for a
331
+ # situation that just means "migrations run on start". Which they now do (start.sh).
309
332
  if grep -q '^DATABASE_URL=' "$ROOT/.env" 2>/dev/null; then
310
- if cmd_db_setup; then
333
+ if (cmd_db_setup); then
311
334
  ok "Database schema is up to date"
312
335
  else
313
- warn "Could not reach the database yet. Run ${BOLD}unoverse check${NC} once it is up"
336
+ info "Migrations wait for the platform: ${BOLD}unoverse start${NC} applies them once services are up"
314
337
  fi
315
338
  fi
316
339
 
@@ -326,5 +349,6 @@ ENVEOF
326
349
  echo -e " ${GREEN}unoverse where${NC} Its addresses, once it is up"
327
350
  echo ""
328
351
  info "Run ${BOLD}unoverse check${NC} anytime to see if it is healthy"
352
+ info "Change a setting any time: edit ${BOLD}.env${NC} directly, or re-run ${BOLD}unoverse create${NC} (Enter keeps each current value)"
329
353
  echo ""
330
354
  }
@@ -33,6 +33,24 @@ check_docker_file_sharing() {
33
33
  return 0
34
34
  }
35
35
 
36
+ # Pull whatever images are missing, VISIBLY. docker's own per-layer progress bars are the
37
+ # loading indication — a developer staring at a silent screen while gigabytes download
38
+ # assumes the tool is hung. Exists because init used to call cmd_pull (deleted with
39
+ # update.sh), so nothing pulled and nothing printed, and start buried compose's pull
40
+ # inside a log file.
41
+ pull_missing_images() {
42
+ local img missing=""
43
+ for img in $(docker compose -f "$ROOT/docker-compose.yml" config --images 2>/dev/null | sort -u); do
44
+ docker image inspect "$img" >/dev/null 2>&1 || missing="$missing $img"
45
+ done
46
+ [ -n "$missing" ] || return 0
47
+ echo ""
48
+ info "Pulling platform images (first run — a few minutes, progress below)..."
49
+ echo ""
50
+ docker compose -f "$ROOT/docker-compose.yml" --env-file "$ROOT/.env" pull || { fail "Image pull failed. Check the registry token (re-run unoverse create to change it)"; exit 1; }
51
+ echo ""
52
+ }
53
+
36
54
  cmd_start() {
37
55
  # Login to registry if DOCR_TOKEN is set
38
56
  local docr_token
@@ -44,6 +62,8 @@ cmd_start() {
44
62
  # `start --pull` refreshes the images first. This is where refreshing a LOCAL universe
45
63
  # lives now: `update` is the CLI's word and means only the CLI, wherever you type it.
46
64
  # A server is refreshed by `unoverse deploy` from your machine, not on the box.
65
+ pull_missing_images
66
+
47
67
  if [ "${1:-}" = "--pull" ]; then
48
68
  info "Pulling the latest images..."
49
69
  docker compose -f "$ROOT/docker-compose.yml" pull || { fail "Image pull failed"; exit 1; }
@@ -137,6 +157,13 @@ cmd_start() {
137
157
  # Generates + tsc-builds nodes/components, then reloads unoverse so they load.
138
158
  # Non-fatal: build_component_nodes returns (doesn't exit) if it can't run, so start still finishes.
139
159
  build_component_nodes
160
+ # Migrations, now that the unoverse service is up (they run from inside its
161
+ # container). This is what makes start the resume point after any interrupted
162
+ # setup: pull, boot, migrate, no third command to know. Subshell for the same
163
+ # reason init uses one: cmd_db_setup exits rather than returns on failure.
164
+ if ! (cmd_db_setup) >/dev/null 2>&1; then
165
+ warn "Migrations did not apply. Run ${BOLD}unoverse check${NC} to see why"
166
+ fi
140
167
  print_access_urls
141
168
  echo ""
142
169
  info "Run ${BOLD}unoverse open${NC} to open Canvas in your browser"
@@ -64,6 +64,22 @@ case "${1:-}" in
64
64
  check) cmd_check && cmd_db_verify && cmd_doctor ;;
65
65
  dev) cmd_dev ;;
66
66
  ground) shift; cmd_ground "$@" ;;
67
+ refresh-images)
68
+ # internal: `unoverse update` runs this after updating the CLI. Pull newer images
69
+ # if the registry has them, and recreate only what is already running — an update
70
+ # never boots a stopped universe.
71
+ if [ ! -f "$ROOT/.env" ]; then
72
+ : # unconfigured: nothing to refresh
73
+ elif ! docker info >/dev/null 2>&1; then
74
+ info "Docker is not running. Images refresh on the next unoverse start"
75
+ else
76
+ info "Checking for newer platform images..."
77
+ docker compose -f "$ROOT/docker-compose.yml" --env-file "$ROOT/.env" pull || warn "image check failed (network/registry?)"
78
+ if [ -n "$(docker compose -f "$ROOT/docker-compose.yml" ps -q 2>/dev/null)" ]; then
79
+ docker compose -f "$ROOT/docker-compose.yml" --env-file "$ROOT/.env" up -d --remove-orphans >/dev/null 2>&1 \
80
+ && ok "Running services moved to the new images"
81
+ fi
82
+ fi ;;
67
83
  deploy)
68
84
  # `deploy marketplace` is PLATFORM-OWNER only: it publishes the marketplace package
69
85
  # to npm, which a starter developer never does (they INSTALL from the marketplace).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unoverse",
3
- "version": "0.1.31",
3
+ "version": "0.1.33",
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",