unoverse 0.1.6 → 0.1.8

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
@@ -9,23 +9,79 @@
9
9
  */
10
10
  import { existsSync } from "node:fs";
11
11
  import { spawnSync } from "node:child_process";
12
+ import { join, resolve } from "node:path";
12
13
  import { create } from "../lib/create.mjs";
13
14
 
15
+ /**
16
+ * ONE BINARY. There is no `./unoverse` any more.
17
+ *
18
+ * A universe is a folder holding the operator lib. Walk up for it, the same way git finds
19
+ * a repo, so the commands work from anywhere inside your universe rather than only at its
20
+ * root. Found means the operator commands exist; not found means they simply are not
21
+ * offered, instead of being advertised and then refusing.
22
+ */
23
+ function findUniverse() {
24
+ for (let dir = process.cwd(); ; ) {
25
+ if (existsSync(join(dir, "scripts/lib/common.sh"))) return dir;
26
+ const parent = resolve(dir, "..");
27
+ if (parent === dir) return null;
28
+ dir = parent;
29
+ }
30
+ }
31
+
32
+ /** Run an operator command in the universe we are standing in. */
33
+ function operator(root, args) {
34
+ const r = spawnSync("bash", [join(root, "scripts/operator.sh"), ...args], {
35
+ stdio: "inherit",
36
+ cwd: root,
37
+ });
38
+ process.exit(r.status ?? 0);
39
+ }
40
+
41
+ const UNIVERSE = findUniverse();
42
+
43
+ // Everything the operator half owns. Listed here so an unknown command can say so,
44
+ // rather than the two CLIs disagreeing about what exists.
45
+ const OPERATOR_COMMANDS = new Set([
46
+ "start", "stop", "check", "logs", "deploy",
47
+ // kept working, not advertised
48
+ "ground", "dev", "build", "publish", "init", "doctor", "db-setup", "db-verify", "open",
49
+ ]);
50
+
14
51
  const [, , cmd, ...args] = process.argv;
15
52
 
16
- const HELP = `
17
- unoverse
53
+ // Same visual language as the operator half and the boot readout
54
+ // (packages/base/src/boot.ts): dim structure, ONE accent, green for the words you type.
55
+ // NO_COLOR is honoured — a help screen is the first thing piped into a file.
56
+ const COLOR = !process.env.NO_COLOR && process.stdout.isTTY !== false;
57
+ const paint = (code) => (t) => (COLOR ? `\x1b[${code}m${t}\x1b[0m` : t);
58
+ const bold = paint("1");
59
+ const dim = paint("2");
60
+ const cyan = paint("36");
61
+ const green = paint("32");
18
62
 
19
- unoverse create [name] What are you building? Studio project · universe · client app
20
- No name scaffolds into the folder you are in; a name makes one
21
- unoverse studio Launch Unoverse Studio (authoring: components, nodes, skills)
22
- unoverse where Where your universe is — addresses probed live, not recited
23
- unoverse update Update this CLI to the latest version
24
- unoverse help This
63
+ const CMDS = [
64
+ ["create [name]", "What are you building? Studio project · universe · client app",
65
+ "No name scaffolds into the folder you are in; a name makes one"],
66
+ ["studio", "Author components, nodes, agent skills"],
67
+ ["where", "Your universe's addresses, probed live, not recited"],
68
+ ["update", "Update this CLI to the latest version"],
69
+ ["help", "This"],
70
+ ];
71
+ const PAD = Math.max(...CMDS.map(([c]) => c.length));
25
72
 
26
- Operating a running universe? That lives in your universe folder:
27
- cd <your-universe> && ./unoverse
28
- `;
73
+ const HELP = [
74
+ "",
75
+ ` ${cyan("⬡")} ${bold("unoverse")}`,
76
+ "",
77
+ ...CMDS.flatMap(([cmd, desc, note]) => [
78
+ ` ${green(cmd)}${" ".repeat(PAD - cmd.length)} ${desc}`,
79
+ ...(note ? [` ${" ".repeat(PAD)} ${dim(note)}`] : []),
80
+ ]),
81
+ "",
82
+ ` ${dim("In a universe folder, add:")} ${green("start")} ${dim("·")} ${green("stop")} ${dim("·")} ${green("check")} ${dim("·")} ${green("logs")} ${dim("·")} ${green("deploy")}`,
83
+ "",
84
+ ].join("\n");
29
85
 
30
86
  switch (cmd) {
31
87
  case "create":
@@ -33,12 +89,9 @@ switch (cmd) {
33
89
  break;
34
90
 
35
91
  case "update": {
36
- // The command people type when the CLI is stale. Inside a universe folder,
37
- // update means the OPERATOR kit defer to it. Otherwise update this package.
38
- if (existsSync("./unoverse")) {
39
- console.log(`\n This folder is a universe — its own CLI owns updates:\n\n ./unoverse update\n`);
40
- process.exit(1);
41
- }
92
+ // In a universe, "update" means THAT UNIVERSE (pull images, restart), not this CLI.
93
+ // Same word, different object, decided by where you are standing.
94
+ if (UNIVERSE) operator(UNIVERSE, ["update", ...args]);
42
95
  const r = spawnSync("npm", ["install", "-g", "unoverse@latest"], { stdio: "inherit" });
43
96
  if (r.status === 0) {
44
97
  const v = spawnSync("unoverse", ["-v"], { encoding: "utf8" }).stdout?.trim();
@@ -69,6 +122,8 @@ switch (cmd) {
69
122
  case "--help":
70
123
  case "-h":
71
124
  case undefined:
125
+ // In a universe, its own help prints — it knows whether this kit has the owner lane.
126
+ if (UNIVERSE) operator(UNIVERSE, ["help"]);
72
127
  console.log(HELP);
73
128
  break;
74
129
 
@@ -84,10 +139,10 @@ switch (cmd) {
84
139
  }
85
140
 
86
141
  default:
87
- // A universe folder has its own operator CLI point there rather than
88
- // half-implementing its commands here (v0.2 absorbs them properly).
89
- if (existsSync("./unoverse")) {
90
- console.log(`\n '${cmd}' is an operator command run it in your universe:\n\n ./unoverse ${cmd} ${args.join(" ")}\n`);
142
+ // The operator half, run right here. No second CLI to be sent to.
143
+ if (UNIVERSE && OPERATOR_COMMANDS.has(cmd)) operator(UNIVERSE, [cmd, ...args]);
144
+ if (OPERATOR_COMMANDS.has(cmd)) {
145
+ console.log(`\n '${cmd}' operates a universe, and this folder is not one.\n ${"cd into your universe, or run: unoverse create"}\n`);
91
146
  } else {
92
147
  console.log(`\n Unknown command: ${cmd}\n${HELP}`);
93
148
  }
package/lib/create.mjs CHANGED
@@ -133,11 +133,25 @@ export async function create(nameArg) {
133
133
  const name = resolveTarget(nameArg);
134
134
  download(name);
135
135
  ok(`universe scaffolded in ${label(name)}`);
136
+
137
+ // CREATE FINISHES THE JOB. It used to stop here and tell you to go and run
138
+ // `./unoverse init`, which asked for this same token a second time. The token is
139
+ // already in hand and already validated, so hand it over and configure now.
140
+ rl.close();
141
+ console.log("");
142
+ const r = spawnSync("bash", [`${name}/scripts/operator.sh`, "init"], {
143
+ stdio: "inherit",
144
+ cwd: name,
145
+ env: { ...process.env, UNOVERSE_DOCR_TOKEN: token },
146
+ });
147
+ if (r.status !== 0) {
148
+ fail("setup did not finish — run 'unoverse init' in that folder to pick it up");
149
+ process.exit(r.status ?? 1);
150
+ }
136
151
  console.log(`
137
152
  Next:${name === "." ? "" : `
138
153
  cd ${name}`}
139
- ./unoverse init ${dim("(setup wizard — have this token ready, it asks for it)")}
140
- ./unoverse start
154
+ unoverse start
141
155
 
142
156
  ${cyan("Build with Claude:")} open the folder in Claude Code — it ships an
143
157
  authoring skill (components, nodes, agent skills) and a builder MCP that
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unoverse",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
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",