unoverse 0.1.152 → 0.1.154

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
@@ -126,15 +126,19 @@ switch (cmd) {
126
126
 
127
127
  case "_postupdate": {
128
128
  // Runs AS the new version, in the developer's cwd. A universe refreshes its
129
- // platform images; ANY folder that has authoring skills installed (a Studio
130
- // project) gets them refreshed from the public mirror, so `unoverse update` is
131
- // the one command that brings everything current. A folder with neither is
132
- // left alone, and silence is the right answer.
129
+ // platform images; the authoring skills install to ~/.claude/skills from the public
130
+ // mirror, so `unoverse update` is the one command that brings everything current.
131
+ //
132
+ // THE SKILLS ARE NOT CWD-SCOPED. This used to refresh `<cwd>/.claude/skills` and only
133
+ // when that folder already existed, which meant it fired on whichever folder update
134
+ // happened to be run in — a universe, almost never a design workspace — and never
135
+ // installed anything at all. They are per developer, so they go to the home directory
136
+ // and this runs unconditionally.
133
137
  if (UNIVERSE) {
134
138
  operator(UNIVERSE, ["refresh-images"]);
135
139
  }
136
- const { refreshSkills } = await import("../lib/skills.mjs");
137
- refreshSkills(process.cwd());
140
+ const { installSkills } = await import("../lib/skills.mjs");
141
+ installSkills();
138
142
  process.exit(0);
139
143
  }
140
144
 
package/lib/skills.mjs CHANGED
@@ -1,40 +1,57 @@
1
- // Refresh the Claude Code authoring skills installed in a folder.
1
+ // Install the Claude Code authoring skills for this developer.
2
2
  //
3
3
  // The public mirror github.com/unoverse-platform/skills is the one place installed
4
- // skills come from (publish syncs it from the platform repo's .claude/skills). This
5
- // runs on `unoverse update` (_postupdate) in the developer's cwd: a folder that HAS
6
- // .claude/skills gets every skill the mirror carries, replaced whole; a folder that
7
- // never had them is left alone installing skills is Studio's job at project
8
- // creation, not update's.
9
- import { mkdtempSync, existsSync, readdirSync, rmSync, cpSync } from "node:fs";
4
+ // skills come from (publish syncs it from the platform repo's .claude/skills).
5
+ //
6
+ // PER DEVELOPER, NOT PER PROJECT (2026-08-21). These install to ~/.claude/skills, so
7
+ // Claude Code picks them up in every folder the developer opens and nothing lands
8
+ // inside a workspace where it could be committed. They used to be copied into each
9
+ // Studio workspace's own .claude/skills by Studio's scaffold, out of a snapshot
10
+ // vendored in the Studio npm package: that reproduced the platform repo's .claude
11
+ // folder on every remote developer's disk, pinned to whichever Studio version they
12
+ // installed. Studio no longer writes them (packages/studio/local/scaffold.mjs) and
13
+ // the mirror is the only channel.
14
+ //
15
+ // INSTALLS, NOT JUST REFRESHES. This used to return early when the folder was absent,
16
+ // because seeding was Studio's job. Nothing seeds them now, so an absent folder is the
17
+ // FIRST install and creating it is the whole point.
18
+ //
19
+ // Runs on `unoverse update` (_postupdate), which is the one command that brings a
20
+ // developer's tooling current.
21
+ import { mkdtempSync, existsSync, mkdirSync, readdirSync, rmSync, cpSync } from "node:fs";
10
22
  import { join } from "node:path";
11
- import { tmpdir } from "node:os";
23
+ import { tmpdir, homedir } from "node:os";
12
24
  import { spawnSync } from "node:child_process";
13
25
 
14
26
  const TARBALL = "https://codeload.github.com/unoverse-platform/skills/tar.gz/refs/heads/main";
15
27
 
16
- export function refreshSkills(dir) {
17
- const target = join(dir, ".claude", "skills");
18
- if (!existsSync(target)) return;
28
+ export function installSkills() {
29
+ const target = join(homedir(), ".claude", "skills");
19
30
  const tmp = mkdtempSync(join(tmpdir(), "unoverse-skills-"));
20
31
  try {
21
32
  const dl = spawnSync("bash", ["-c", `curl -fsSL "${TARBALL}" | tar -xz -C "${tmp}" --strip-components=1`], {
22
33
  stdio: "pipe",
23
34
  });
24
35
  if (dl.status !== 0) {
25
- console.log(" Skills refresh skipped (github.com unreachable). They refresh on the next update.");
36
+ console.log(" Skills install skipped (github.com unreachable). They install on the next update.");
26
37
  return;
27
38
  }
28
- // Same filter as Studio's scaffold: a skill is a directory holding a SKILL.md.
29
- // The mirror's README and dotfiles never install.
39
+ // A skill is a directory holding a SKILL.md. The mirror's README and dotfiles never
40
+ // install.
30
41
  const names = readdirSync(tmp, { withFileTypes: true })
31
42
  .filter((e) => e.isDirectory() && existsSync(join(tmp, e.name, "SKILL.md")))
32
43
  .map((e) => e.name);
44
+ if (!names.length) return;
45
+ mkdirSync(target, { recursive: true });
46
+ // REPLACED WHOLE, ours only. Each skill folder the mirror carries is removed and
47
+ // re-copied, so a deleted file upstream really goes. Anything else in the
48
+ // developer's ~/.claude/skills is THEIRS and is never touched: this folder is
49
+ // shared with every skill they have installed from anywhere else.
33
50
  for (const name of names) {
34
51
  rmSync(join(target, name), { recursive: true, force: true });
35
52
  cpSync(join(tmp, name), join(target, name), { recursive: true });
36
53
  }
37
- if (names.length) console.log(` ✓ authoring skills refreshed (${names.join(", ")})`);
54
+ console.log(` ✓ authoring skills installed to ~/.claude/skills (${names.join(", ")})`);
38
55
  } finally {
39
56
  rmSync(tmp, { recursive: true, force: true });
40
57
  }
@@ -157,6 +157,17 @@ case "${1:-}" in
157
157
  if [ -n "$(docker compose -f "$ROOT/docker-compose.yml" ps -q 2>/dev/null)" ]; then
158
158
  docker compose -f "$ROOT/docker-compose.yml" --env-file "$ROOT/.env" up -d --remove-orphans >/dev/null 2>&1 \
159
159
  && ok "Running services moved to the new images"
160
+ # MIGRATIONS RUN ON UPDATE, same as deploy and start. An update is exactly when a
161
+ # new migration arrives (it ships inside the image just pulled), and this was the
162
+ # one path that skipped db-setup: a universe could run new code against a schema
163
+ # missing the columns that code expects, silently, until something read them.
164
+ # Additive-by-rule until Retire and idempotent, so re-running costs nothing.
165
+ # Subshell because cmd_db_setup exits rather than returns on failure.
166
+ if ! (cmd_db_setup) >/dev/null 2>&1; then
167
+ warn "Migrations did not apply. Run ${BOLD}unoverse check${NC} to see why"
168
+ else
169
+ ok "Database migrations are current"
170
+ fi
160
171
  echo ""
161
172
  echo -e " ${GREEN}unoverse where${NC} Links to your Canvas and API"
162
173
  echo ""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unoverse",
3
- "version": "0.1.152",
3
+ "version": "0.1.154",
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",