unoverse 0.1.12 → 0.1.14

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.
Files changed (2) hide show
  1. package/lib/create.mjs +100 -7
  2. package/package.json +1 -1
package/lib/create.mjs CHANGED
@@ -17,6 +17,7 @@ const STARTER_TARBALL =
17
17
 
18
18
  const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
19
19
  const dim = (s) => `\x1b[2m${s}\x1b[0m`;
20
+ const bold = (s) => `\x1b[1m${s}\x1b[0m`;
20
21
  const ok = (s) => console.log(` \x1b[32m✓\x1b[0m ${s}`);
21
22
  const fail = (s) => console.log(` \x1b[31m✗\x1b[0m ${s}`);
22
23
 
@@ -95,16 +96,108 @@ function download(dir, stripComponents = 1, filter = "") {
95
96
  if (r.status !== 0) throw new Error("download failed");
96
97
  }
97
98
 
99
+ const OPTIONS = [
100
+ ["Studio", "Components, agents and workflows", "Most people start here"],
101
+ ["Universe", "Run the platform yourself, on your own infrastructure"],
102
+ ["Client", "A client accelerator that talks to unoverse"],
103
+ ];
104
+ const NAME_W = Math.max(...OPTIONS.map(([n]) => n.length)) + 3;
105
+ // Lines the menu occupies, so a redraw knows how far up to go.
106
+ const MENU_LINES = OPTIONS.reduce((n, [, , note]) => n + (note ? 2 : 1), 0);
107
+
108
+ function renderMenu(active) {
109
+ OPTIONS.forEach(([name, desc, note], i) => {
110
+ const on = i === active;
111
+ process.stdout.write(
112
+ `\x1b[2K ${on ? cyan("❯") : " "} ${i + 1} ${on ? bold(name) : name}` +
113
+ `${" ".repeat(NAME_W - name.length)}${on ? desc : dim(desc)}\n`,
114
+ );
115
+ if (note) process.stdout.write(`\x1b[2K ${" ".repeat(NAME_W + 1)}${dim(note)}\n`);
116
+ });
117
+ }
118
+
119
+ /**
120
+ * Pick one, with the arrow keys.
121
+ *
122
+ * NO DEPENDENCY. Raw stdin is all a list picker needs, and `packages/cli` is deliberately
123
+ * zero-dependency (it is the first thing anyone installs). Up/down move, a digit jumps,
124
+ * Enter takes it, Ctrl-C leaves without doing anything.
125
+ *
126
+ * NOT A TTY means something is piping input, so fall back to reading a line. A picker
127
+ * that only works interactively would break every scripted install.
128
+ */
129
+ function select() {
130
+ if (!process.stdin.isTTY) {
131
+ renderMenu(0);
132
+ process.stdout.write("\n Choose 1, 2 or 3 (Enter for 1): ");
133
+ return new Promise((resolve) => {
134
+ let buf = "";
135
+ process.stdin.on("data", (d) => {
136
+ buf += d;
137
+ if (buf.includes("\n")) {
138
+ const n = parseInt(buf.trim(), 10);
139
+ resolve(Number.isInteger(n) && n >= 1 && n <= OPTIONS.length ? n - 1 : 0);
140
+ }
141
+ });
142
+ process.stdin.on("end", () => resolve(0));
143
+ });
144
+ }
145
+
146
+ return new Promise((resolve) => {
147
+ let active = 0;
148
+ process.stdout.write("\x1b[?25l"); // hide cursor while the list is live
149
+ renderMenu(active);
150
+ process.stdout.write(`\n ${dim("↑↓ to move, Enter to choose")}`);
151
+
152
+ const redraw = () => {
153
+ process.stdout.write(`\x1b[${MENU_LINES + 1}A\r`);
154
+ renderMenu(active);
155
+ process.stdout.write(`\n ${dim("↑↓ to move, Enter to choose")}`);
156
+ };
157
+
158
+ process.stdin.setRawMode(true);
159
+ process.stdin.resume();
160
+ // PARSE the chunk, do not compare it. Keystrokes batch: hold a key down, or paste,
161
+ // and "\x1b[B\x1b[B\r" arrives as ONE data event. Comparing the whole chunk to a
162
+ // single key silently ignores every one of them.
163
+ const onKey = (buf) => {
164
+ const k = buf.toString();
165
+ let moved = false;
166
+ for (let i = 0; i < k.length; i++) {
167
+ if (k[i] === "\u0003") { done(); process.stdout.write("\n"); process.exit(130); }
168
+ if (k[i] === "\x1b" && k[i + 1] === "[" && (k[i + 2] === "A" || k[i + 2] === "B")) {
169
+ const step = k[i + 2] === "A" ? -1 : 1;
170
+ active = (active + step + OPTIONS.length) % OPTIONS.length;
171
+ moved = true;
172
+ i += 2;
173
+ continue;
174
+ }
175
+ if (k[i] === "\r" || k[i] === "\n") {
176
+ if (moved) redraw();
177
+ done();
178
+ process.stdout.write("\n\n");
179
+ return resolve(active);
180
+ }
181
+ if (/[1-9]/.test(k[i]) && +k[i] <= OPTIONS.length) { active = +k[i] - 1; moved = true; }
182
+ }
183
+ if (moved) redraw();
184
+ };
185
+ const done = () => {
186
+ process.stdin.setRawMode(false);
187
+ process.stdin.pause();
188
+ process.stdin.off("data", onKey);
189
+ process.stdout.write("\x1b[?25h"); // cursor back
190
+ };
191
+ process.stdin.on("data", onKey);
192
+ });
193
+ }
194
+
98
195
  export async function create(nameArg) {
196
+ console.log(`\n ${cyan("⬡ What are you building?")}\n`);
197
+ const choice = String((await select()) + 1);
198
+
99
199
  const rl = createInterface({ input: process.stdin, output: process.stdout });
100
200
  try {
101
- console.log(`\n ${cyan("⬡ What are you building?")}\n`);
102
- console.log(` 1. ${"A Studio project".padEnd(20)}Components, nodes and agent skills`);
103
- console.log(` ${dim(" " + "".padEnd(20) + "Most people start here")}`);
104
- console.log(` 2. ${"A universe".padEnd(20)}Run the platform on your own infrastructure`);
105
- console.log(` 3. ${"A client app".padEnd(20)}A website that talks to a universe\n`);
106
-
107
- const choice = (await ask(rl, "Choose [1]: ")) || "1";
108
201
 
109
202
  if (choice === "1") {
110
203
  console.log(`\n ${dim("Launching Unoverse Studio. It creates and manages your projects.")}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unoverse",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
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",