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 +8 -6
- package/bin/unoverse.mjs +38 -4
- package/lib/skills.mjs +41 -0
- package/operator/ansible/playbooks/deploy-images.yml +2 -2
- package/operator/docker-compose.yml +3 -2
- package/operator/lib/db-verify.sh +2 -1
- package/operator/lib/publish-assets.mjs +5 -4
- package/operator/lib/start.sh +3 -3
- package/operator/operator.sh +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,9 +27,11 @@ unoverse stop
|
|
|
27
27
|
|
|
28
28
|
## Build with Claude
|
|
29
29
|
|
|
30
|
-
Creating a universe
|
|
31
|
-
and
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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.
|
|
129
|
-
//
|
|
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
|
|
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
|
|
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/
|
|
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;
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
67
|
+
const items = collectProject(designRoot, project);
|
|
67
68
|
if (!items.length) die(`nothing to publish in ${project}`);
|
|
68
69
|
|
|
69
70
|
let plan;
|
package/operator/lib/start.sh
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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 ""
|
package/operator/operator.sh
CHANGED
|
@@ -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
|
|
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