unoverse 0.1.149 → 0.1.151

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/README.md CHANGED
@@ -27,9 +27,11 @@ unoverse stop
27
27
 
28
28
  ## Build with Claude
29
29
 
30
- Creating a universe also installs a Claude Code skill (`.claude/skills/unoverse-create`)
31
- and registers the `canvas` MCP (`.mcp.json`). Open the folder in Claude Code and ask for
32
- what you want: components, templates, custom nodes, agent skills, or whole workflows
33
- built live on your Canvas. `unoverse update` keeps the CLI and the skill current.
34
-
35
- Documentation: https://github.com/unoverse-platform/docs
30
+ Creating a universe registers the `canvas` MCP (`.mcp.json`): open the folder in
31
+ Claude Code and describe the workflow you want, built live on your Canvas. The
32
+ authoring skills for components, templates, themes, and custom nodes come with Studio
33
+ projects (`unoverse studio`). `unoverse update` keeps the CLI, a universe's images,
34
+ and any installed skills current; to add the skills to your own agent by hand:
35
+ `npx skills add unoverse-platform/skills`.
36
+
37
+ Documentation: https://docs.unoverse.ai
package/bin/unoverse.mjs CHANGED
@@ -125,13 +125,16 @@ switch (cmd) {
125
125
  }
126
126
 
127
127
  case "_postupdate": {
128
- // Runs AS the new version, in the developer's cwd. Outside a universe there is
129
- // nothing to refresh, and silence is the right answer.
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.
130
133
  if (UNIVERSE) {
131
- // The authoring skill is Studio's, not the universe's; update touches only what
132
- // a universe actually has: this CLI and the platform images.
133
134
  operator(UNIVERSE, ["refresh-images"]);
134
135
  }
136
+ const { refreshSkills } = await import("../lib/skills.mjs");
137
+ refreshSkills(process.cwd());
135
138
  process.exit(0);
136
139
  }
137
140
 
@@ -154,6 +157,37 @@ switch (cmd) {
154
157
  // new cache key, so every studio release installs clean and fully resolved, and
155
158
  // relaunching the same version stays instant from cache. Offline (npm view
156
159
  // fails), fall back to the cached latest — stale beats broken with no network.
160
+ // RECLAIM THE PORT FROM A STALE STUDIO. A studio left over from an earlier session
161
+ // keeps :4108, so the new launch either fails to bind or — worse — the browser
162
+ // quietly keeps talking to last week's build. If the listener IS a studio, it is
163
+ // ours to replace: kill it and wait for the port. Anything else on the port is
164
+ // NOT ours: name it and stop, never kill a stranger's process.
165
+ const STUDIO_PORT = 4108;
166
+ const lsof = spawnSync("lsof", ["-nP", `-iTCP:${STUDIO_PORT}`, "-sTCP:LISTEN", "-Fpc"], { encoding: "utf8" });
167
+ const holder = lsof.status === 0 ? (lsof.stdout.match(/^p(\d+)$/m) || [])[1] : undefined;
168
+ if (holder) {
169
+ const cmd = spawnSync("ps", ["-p", holder, "-o", "command="], { encoding: "utf8" }).stdout?.trim() ?? "";
170
+ if (/unoverse-studio|@unoverse-platform\/studio/.test(cmd)) {
171
+ try {
172
+ process.kill(Number(holder));
173
+ } catch {
174
+ /* already gone */
175
+ }
176
+ // Wait (up to ~5s) for the socket to actually free — kill returns before close.
177
+ for (let i = 0; i < 25; i++) {
178
+ const still = spawnSync("lsof", ["-nP", `-iTCP:${STUDIO_PORT}`, "-sTCP:LISTEN"], { encoding: "utf8" });
179
+ if (!still.stdout?.trim()) break;
180
+ spawnSync("sleep", ["0.2"]);
181
+ }
182
+ console.log(` Reclaimed :${STUDIO_PORT} from a previous studio (pid ${holder})`);
183
+ } else {
184
+ console.error(
185
+ ` :${STUDIO_PORT} is held by something that is not a studio (pid ${holder}: ${cmd || "unknown"}).\n` +
186
+ ` Stop that process first — unoverse won't kill what it didn't start.`,
187
+ );
188
+ process.exit(1);
189
+ }
190
+ }
157
191
  const view = spawnSync("npm", ["view", "@unoverse-platform/studio", "version"], { encoding: "utf8" });
158
192
  const exact = view.status === 0 ? view.stdout.trim() : "";
159
193
  const spec = exact ? `@unoverse-platform/studio@${exact}` : "@unoverse-platform/studio@latest";
package/lib/skills.mjs ADDED
@@ -0,0 +1,41 @@
1
+ // Refresh the Claude Code authoring skills installed in a folder.
2
+ //
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";
10
+ import { join } from "node:path";
11
+ import { tmpdir } from "node:os";
12
+ import { spawnSync } from "node:child_process";
13
+
14
+ const TARBALL = "https://codeload.github.com/unoverse-platform/skills/tar.gz/refs/heads/main";
15
+
16
+ export function refreshSkills(dir) {
17
+ const target = join(dir, ".claude", "skills");
18
+ if (!existsSync(target)) return;
19
+ const tmp = mkdtempSync(join(tmpdir(), "unoverse-skills-"));
20
+ try {
21
+ const dl = spawnSync("bash", ["-c", `curl -fsSL "${TARBALL}" | tar -xz -C "${tmp}" --strip-components=1`], {
22
+ stdio: "pipe",
23
+ });
24
+ if (dl.status !== 0) {
25
+ console.log(" Skills refresh skipped (github.com unreachable). They refresh on the next update.");
26
+ return;
27
+ }
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.
30
+ const names = readdirSync(tmp, { withFileTypes: true })
31
+ .filter((e) => e.isDirectory() && existsSync(join(tmp, e.name, "SKILL.md")))
32
+ .map((e) => e.name);
33
+ for (const name of names) {
34
+ rmSync(join(target, name), { recursive: true, force: true });
35
+ cpSync(join(tmp, name), join(target, name), { recursive: true });
36
+ }
37
+ if (names.length) console.log(` ✓ authoring skills refreshed (${names.join(", ")})`);
38
+ } finally {
39
+ rmSync(tmp, { recursive: true, force: true });
40
+ }
41
+ }
@@ -6,7 +6,7 @@
6
6
  # - marketplace nodes CONVERGE at boot (keep-latest: the shared installed_plugins
7
7
  # row is "the newest version anyone chose"; boot reinstalls anything missing
8
8
  # or older — see runtime/plugins/startup.ts)
9
- # - component nodes synthesize from rx/ definitions at boot (no build, ever)
9
+ # - component nodes synthesize from design/ definitions at boot (no build, ever)
10
10
  #
11
11
  # The CLI's `unoverse deploy` runs this first, then deploy-packages.yml (the
12
12
  # local-source half). Use directly only when images alone changed.
@@ -122,5 +122,5 @@
122
122
  {{ status_result.stdout }}
123
123
  Marketplace nodes converge to the recorded
124
124
  versions at boot; component nodes synthesize
125
- from rx/ definitions.
125
+ from design/ definitions.
126
126
  ============================================
@@ -8,7 +8,7 @@
8
8
  #
9
9
  # LAYER 2: PLATFORM-DEV MOUNTS (monorepo only — sync-starter strips them)
10
10
  # - apps/unoverse/nodes — node manifests
11
- # - apps/unoverse/rx — design (components, templates, styles)
11
+ # - apps/unoverse/design — design (components, templates, styles)
12
12
  # - apps/unoverse/prompts — behavior (skills + blocks)
13
13
  # In a STARTER/customer universe these folders do not exist: authoring is
14
14
  # Studio's (publish over the API) and content is DB-driven (marketplace).
@@ -255,7 +255,8 @@ services:
255
255
  - unoverse_plugins:/app/plugins
256
256
  # ── Platform-dev mounts (monorepo only; sync-starter strips these three
257
257
  # lines from the starter's copy — a customer universe serves what the DB
258
- # says). Local edits are picked up on restart; rx/prompts are data, no build.
258
+ # says). Local edits are picked up on restart; design/prompts are data, no build.
259
+ - ./apps/unoverse/design:/app/apps/unoverse/design # design (components, templates, styles)
259
260
  - ./node_modules:/app/host_node_modules # Hoisted deps for locally-built nodes
260
261
  healthcheck:
261
262
  # Ready = internal runtime port up (nodes are loaded into memory BEFORE it binds).
@@ -35,7 +35,8 @@ cmd_db_verify() {
35
35
  workflows: [
36
36
  "id", "name", "description", "nodes", "edges", "active",
37
37
  "execution_mode", "test_inputs", "umap_settings", "viewport",
38
- "mcp_schema", "memory_config", "content_taxonomy", "created_at", "updated_at"
38
+ "mcp_schema", "memory_config", "content_taxonomy", "created_at", "updated_at",
39
+ "workflow_document", "layout_document"
39
40
  ],
40
41
  workflow_executions: [
41
42
  "execution_id", "workflow_id", "status", "start_time", "end_time",
@@ -14,6 +14,7 @@
14
14
  * publish-assets.mjs <project> --to <universe> --token <jwt> [--dry-run] [--yes]
15
15
  */
16
16
  import { resolve, dirname, join } from "node:path";
17
+ import { existsSync } from "node:fs";
17
18
  import { fileURLToPath } from "node:url";
18
19
  import { createInterface } from "node:readline/promises";
19
20
 
@@ -31,7 +32,7 @@ const flag = (name) => process.argv.includes(`--${name}`);
31
32
  const project = process.argv[2] && !process.argv[2].startsWith("--") ? process.argv[2] : undefined;
32
33
  const universe = arg("to");
33
34
  const token = arg("token") ?? process.env.UNOVERSE_TOKEN;
34
- const rxHome = resolve(arg("rx") ?? "apps/unoverse/rx");
35
+ const designRoot = resolve(arg("design") ?? "apps/unoverse/design");
35
36
 
36
37
  const c = { dim: "\x1b[2m", red: "\x1b[31m", green: "\x1b[32m", yellow: "\x1b[33m", bold: "\x1b[1m", off: "\x1b[0m" };
37
38
  const die = (msg) => {
@@ -40,7 +41,7 @@ const die = (msg) => {
40
41
  };
41
42
 
42
43
  if (!project) {
43
- const projects = listProjects(rxHome);
44
+ const projects = listProjects(designRoot);
44
45
  die(`which project?\n\n unoverse publish <project> --to <universe>\n\n Found: ${projects.join(", ") || "none"}`);
45
46
  }
46
47
  if (!universe) die("no universe. Pass --to https://your-universe.com");
@@ -49,7 +50,7 @@ if (!token) die("no credential. Pass --token, or set UNOVERSE_TOKEN");
49
50
  // ── 1. lint. Nothing is sent if this fails ────────────────────────────────────
50
51
  console.log(`\n ${c.bold}publish ${project}${c.off} ${c.dim}→ ${universe}${c.off}\n`);
51
52
  process.stdout.write(` ${c.dim}checking…${c.off}`);
52
- const { problems, errors } = await lintForPublish(rxHome, project);
53
+ const { problems, errors } = await lintForPublish(designRoot, project);
53
54
  process.stdout.write("\r \r");
54
55
 
55
56
  if (errors.length) {
@@ -63,7 +64,7 @@ const warnings = problems.filter((p) => p.level === "warn").length;
63
64
  console.log(` ${c.green}✓${c.off} checks passed${warnings ? ` ${c.dim}(${warnings} warning(s))${c.off}` : ""}`);
64
65
 
65
66
  // ── 2. compare against what the universe already holds ────────────────────────
66
- const items = collectProject(rxHome, project);
67
+ const items = collectProject(designRoot, project);
67
68
  if (!items.length) die(`nothing to publish in ${project}`);
68
69
 
69
70
  let plan;
@@ -202,7 +202,7 @@ cmd_start() {
202
202
  echo ""
203
203
  echo -e " ${GREEN}${BOLD}All $running services running${NC} ${DIM}($(timer_elapsed))${NC}"
204
204
  echo ""
205
- # Auto-compile component (design) nodes from rx/ — same step prod's deploy runs.
205
+ # Auto-compile component (design) nodes from the design tree — same step prod's deploy runs.
206
206
  # Generates + tsc-builds nodes/components, then reloads unoverse so they load.
207
207
  # Non-fatal: build_component_nodes returns (doesn't exit) if it can't run, so start still finishes.
208
208
  build_component_nodes
@@ -274,7 +274,7 @@ cmd_build() {
274
274
  # Internal helper (called by start/dev — not a user-facing command): builds the
275
275
  # universal component-node package LOCALLY (never in the container — deploys and
276
276
  # dev alike ship/serve locally built dists) and restarts unoverse. Design
277
- # DEFINITION changes never need this — they synthesize from rx/ at boot; a plain
277
+ # DEFINITION changes never need this — they synthesize from design/ at boot; a plain
278
278
  # restart suffices. This exists for the PACKAGE-source case.
279
279
  build_component_nodes() {
280
280
  # MONOREPO ONLY. packages/marketplace exists in the platform checkout and nowhere else, so
@@ -298,7 +298,7 @@ build_component_nodes() {
298
298
  return
299
299
  fi
300
300
 
301
- echo " Restarting unoverse (definitions re-synthesize from rx/components)..."
301
+ echo " Restarting unoverse (definitions re-synthesize from design/components)..."
302
302
  docker compose -f "$ROOT/docker-compose.yml" restart unoverse 2>/dev/null || true
303
303
  ok "Restarted. Component changes are live"
304
304
  echo ""
@@ -194,7 +194,7 @@ case "${1:-}" in
194
194
  shift; cmd_publish "$@"
195
195
  elif _in_platform_monorepo; then
196
196
  # THE INSTALLED CLI CANNOT RELEASE THE PLATFORM, and inside this repo the fallthrough
197
- # below is never what anyone meant: it would start publishing rx assets to a universe
197
+ # below is never what anyone meant: it would start publishing design assets to a universe
198
198
  # while the operator believes they are shipping the platform. It used to do exactly
199
199
  # that, silently, which is the whole reason this branch exists. Refuse and say where
200
200
  # to go. A starter kit never reaches here (no `unoverse-platform` package above it),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unoverse",
3
- "version": "0.1.149",
3
+ "version": "0.1.151",
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",