@kohryan/moodui 0.0.11 → 0.0.13

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/dist/cli.js DELETED
@@ -1,1298 +0,0 @@
1
- #!/usr/bin/env node
2
- #!/usr/bin/env node
3
- "use strict";
4
- var __create = Object.create;
5
- var __defProp = Object.defineProperty;
6
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
- var __getOwnPropNames = Object.getOwnPropertyNames;
8
- var __getProtoOf = Object.getPrototypeOf;
9
- var __hasOwnProp = Object.prototype.hasOwnProperty;
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
- // If the importer is in node compatibility mode or this is not an ESM
20
- // file that has been converted to a CommonJS file using a Babel-
21
- // compatible transform (i.e. "__esModule" has not been set), then set
22
- // "default" to the CommonJS "module.exports" for node compatibility.
23
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
- mod
25
- ));
26
-
27
- // src/cli.ts
28
- var import_promises3 = __toESM(require("fs/promises"));
29
- var import_node_path2 = __toESM(require("path"));
30
- var import_node_url2 = require("url");
31
- var import_node_http = __toESM(require("http"));
32
-
33
- // node_modules/open/index.js
34
- var import_node_process6 = __toESM(require("process"), 1);
35
- var import_node_buffer = require("buffer");
36
- var import_node_path = __toESM(require("path"), 1);
37
- var import_node_url = require("url");
38
- var import_node_util5 = require("util");
39
- var import_node_child_process5 = __toESM(require("child_process"), 1);
40
- var import_promises2 = __toESM(require("fs/promises"), 1);
41
-
42
- // node_modules/wsl-utils/index.js
43
- var import_node_process2 = __toESM(require("process"), 1);
44
- var import_promises = __toESM(require("fs/promises"), 1);
45
-
46
- // node_modules/is-wsl/index.js
47
- var import_node_process = __toESM(require("process"), 1);
48
- var import_node_os = __toESM(require("os"), 1);
49
- var import_node_fs3 = __toESM(require("fs"), 1);
50
-
51
- // node_modules/is-inside-container/index.js
52
- var import_node_fs2 = __toESM(require("fs"), 1);
53
-
54
- // node_modules/is-docker/index.js
55
- var import_node_fs = __toESM(require("fs"), 1);
56
- var isDockerCached;
57
- function hasDockerEnv() {
58
- try {
59
- import_node_fs.default.statSync("/.dockerenv");
60
- return true;
61
- } catch {
62
- return false;
63
- }
64
- }
65
- function hasDockerCGroup() {
66
- try {
67
- return import_node_fs.default.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
68
- } catch {
69
- return false;
70
- }
71
- }
72
- function isDocker() {
73
- if (isDockerCached === void 0) {
74
- isDockerCached = hasDockerEnv() || hasDockerCGroup();
75
- }
76
- return isDockerCached;
77
- }
78
-
79
- // node_modules/is-inside-container/index.js
80
- var cachedResult;
81
- var hasContainerEnv = () => {
82
- try {
83
- import_node_fs2.default.statSync("/run/.containerenv");
84
- return true;
85
- } catch {
86
- return false;
87
- }
88
- };
89
- function isInsideContainer() {
90
- if (cachedResult === void 0) {
91
- cachedResult = hasContainerEnv() || isDocker();
92
- }
93
- return cachedResult;
94
- }
95
-
96
- // node_modules/is-wsl/index.js
97
- var isWsl = () => {
98
- if (import_node_process.default.platform !== "linux") {
99
- return false;
100
- }
101
- if (import_node_os.default.release().toLowerCase().includes("microsoft")) {
102
- if (isInsideContainer()) {
103
- return false;
104
- }
105
- return true;
106
- }
107
- try {
108
- if (import_node_fs3.default.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft")) {
109
- return !isInsideContainer();
110
- }
111
- } catch {
112
- }
113
- if (import_node_fs3.default.existsSync("/proc/sys/fs/binfmt_misc/WSLInterop") || import_node_fs3.default.existsSync("/run/WSL")) {
114
- return !isInsideContainer();
115
- }
116
- return false;
117
- };
118
- var is_wsl_default = import_node_process.default.env.__IS_WSL_TEST__ ? isWsl : isWsl();
119
-
120
- // node_modules/wsl-utils/index.js
121
- var wslDrivesMountPoint = /* @__PURE__ */ (() => {
122
- const defaultMountPoint = "/mnt/";
123
- let mountPoint;
124
- return async function() {
125
- if (mountPoint) {
126
- return mountPoint;
127
- }
128
- const configFilePath = "/etc/wsl.conf";
129
- let isConfigFileExists = false;
130
- try {
131
- await import_promises.default.access(configFilePath, import_promises.constants.F_OK);
132
- isConfigFileExists = true;
133
- } catch {
134
- }
135
- if (!isConfigFileExists) {
136
- return defaultMountPoint;
137
- }
138
- const configContent = await import_promises.default.readFile(configFilePath, { encoding: "utf8" });
139
- const configMountPoint = /(?<!#.*)root\s*=\s*(?<mountPoint>.*)/g.exec(configContent);
140
- if (!configMountPoint) {
141
- return defaultMountPoint;
142
- }
143
- mountPoint = configMountPoint.groups.mountPoint.trim();
144
- mountPoint = mountPoint.endsWith("/") ? mountPoint : `${mountPoint}/`;
145
- return mountPoint;
146
- };
147
- })();
148
- var powerShellPathFromWsl = async () => {
149
- const mountPoint = await wslDrivesMountPoint();
150
- return `${mountPoint}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`;
151
- };
152
- var powerShellPath = async () => {
153
- if (is_wsl_default) {
154
- return powerShellPathFromWsl();
155
- }
156
- return `${import_node_process2.default.env.SYSTEMROOT || import_node_process2.default.env.windir || String.raw`C:\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
157
- };
158
-
159
- // node_modules/define-lazy-prop/index.js
160
- function defineLazyProperty(object, propertyName, valueGetter) {
161
- const define = (value) => Object.defineProperty(object, propertyName, { value, enumerable: true, writable: true });
162
- Object.defineProperty(object, propertyName, {
163
- configurable: true,
164
- enumerable: true,
165
- get() {
166
- const result = valueGetter();
167
- define(result);
168
- return result;
169
- },
170
- set(value) {
171
- define(value);
172
- }
173
- });
174
- return object;
175
- }
176
-
177
- // node_modules/default-browser/index.js
178
- var import_node_util4 = require("util");
179
- var import_node_process5 = __toESM(require("process"), 1);
180
- var import_node_child_process4 = require("child_process");
181
-
182
- // node_modules/default-browser-id/index.js
183
- var import_node_util = require("util");
184
- var import_node_process3 = __toESM(require("process"), 1);
185
- var import_node_child_process = require("child_process");
186
- var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
187
- async function defaultBrowserId() {
188
- if (import_node_process3.default.platform !== "darwin") {
189
- throw new Error("macOS only");
190
- }
191
- const { stdout } = await execFileAsync("defaults", ["read", "com.apple.LaunchServices/com.apple.launchservices.secure", "LSHandlers"]);
192
- const match = /LSHandlerRoleAll = "(?!-)(?<id>[^"]+?)";\s+?LSHandlerURLScheme = (?:http|https);/.exec(stdout);
193
- const browserId = match?.groups.id ?? "com.apple.Safari";
194
- if (browserId === "com.apple.safari") {
195
- return "com.apple.Safari";
196
- }
197
- return browserId;
198
- }
199
-
200
- // node_modules/run-applescript/index.js
201
- var import_node_process4 = __toESM(require("process"), 1);
202
- var import_node_util2 = require("util");
203
- var import_node_child_process2 = require("child_process");
204
- var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
205
- async function runAppleScript(script, { humanReadableOutput = true, signal } = {}) {
206
- if (import_node_process4.default.platform !== "darwin") {
207
- throw new Error("macOS only");
208
- }
209
- const outputArguments = humanReadableOutput ? [] : ["-ss"];
210
- const execOptions = {};
211
- if (signal) {
212
- execOptions.signal = signal;
213
- }
214
- const { stdout } = await execFileAsync2("osascript", ["-e", script, outputArguments], execOptions);
215
- return stdout.trim();
216
- }
217
-
218
- // node_modules/bundle-name/index.js
219
- async function bundleName(bundleId) {
220
- return runAppleScript(`tell application "Finder" to set app_path to application file id "${bundleId}" as string
221
- tell application "System Events" to get value of property list item "CFBundleName" of property list file (app_path & ":Contents:Info.plist")`);
222
- }
223
-
224
- // node_modules/default-browser/windows.js
225
- var import_node_util3 = require("util");
226
- var import_node_child_process3 = require("child_process");
227
- var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process3.execFile);
228
- var windowsBrowserProgIds = {
229
- MSEdgeHTM: { name: "Edge", id: "com.microsoft.edge" },
230
- // The missing `L` is correct.
231
- MSEdgeBHTML: { name: "Edge Beta", id: "com.microsoft.edge.beta" },
232
- MSEdgeDHTML: { name: "Edge Dev", id: "com.microsoft.edge.dev" },
233
- AppXq0fevzme2pys62n3e0fbqa7peapykr8v: { name: "Edge", id: "com.microsoft.edge.old" },
234
- ChromeHTML: { name: "Chrome", id: "com.google.chrome" },
235
- ChromeBHTML: { name: "Chrome Beta", id: "com.google.chrome.beta" },
236
- ChromeDHTML: { name: "Chrome Dev", id: "com.google.chrome.dev" },
237
- ChromiumHTM: { name: "Chromium", id: "org.chromium.Chromium" },
238
- BraveHTML: { name: "Brave", id: "com.brave.Browser" },
239
- BraveBHTML: { name: "Brave Beta", id: "com.brave.Browser.beta" },
240
- BraveDHTML: { name: "Brave Dev", id: "com.brave.Browser.dev" },
241
- BraveSSHTM: { name: "Brave Nightly", id: "com.brave.Browser.nightly" },
242
- FirefoxURL: { name: "Firefox", id: "org.mozilla.firefox" },
243
- OperaStable: { name: "Opera", id: "com.operasoftware.Opera" },
244
- VivaldiHTM: { name: "Vivaldi", id: "com.vivaldi.Vivaldi" },
245
- "IE.HTTP": { name: "Internet Explorer", id: "com.microsoft.ie" }
246
- };
247
- var _windowsBrowserProgIdMap = new Map(Object.entries(windowsBrowserProgIds));
248
- var UnknownBrowserError = class extends Error {
249
- };
250
- async function defaultBrowser(_execFileAsync = execFileAsync3) {
251
- const { stdout } = await _execFileAsync("reg", [
252
- "QUERY",
253
- " HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice",
254
- "/v",
255
- "ProgId"
256
- ]);
257
- const match = /ProgId\s*REG_SZ\s*(?<id>\S+)/.exec(stdout);
258
- if (!match) {
259
- throw new UnknownBrowserError(`Cannot find Windows browser in stdout: ${JSON.stringify(stdout)}`);
260
- }
261
- const { id } = match.groups;
262
- const dotIndex = id.lastIndexOf(".");
263
- const hyphenIndex = id.lastIndexOf("-");
264
- const baseIdByDot = dotIndex === -1 ? void 0 : id.slice(0, dotIndex);
265
- const baseIdByHyphen = hyphenIndex === -1 ? void 0 : id.slice(0, hyphenIndex);
266
- return windowsBrowserProgIds[id] ?? windowsBrowserProgIds[baseIdByDot] ?? windowsBrowserProgIds[baseIdByHyphen] ?? { name: id, id };
267
- }
268
-
269
- // node_modules/default-browser/index.js
270
- var execFileAsync4 = (0, import_node_util4.promisify)(import_node_child_process4.execFile);
271
- var titleize = (string) => string.toLowerCase().replaceAll(/(?:^|\s|-)\S/g, (x) => x.toUpperCase());
272
- async function defaultBrowser2() {
273
- if (import_node_process5.default.platform === "darwin") {
274
- const id = await defaultBrowserId();
275
- const name = await bundleName(id);
276
- return { name, id };
277
- }
278
- if (import_node_process5.default.platform === "linux") {
279
- const { stdout } = await execFileAsync4("xdg-mime", ["query", "default", "x-scheme-handler/http"]);
280
- const id = stdout.trim();
281
- const name = titleize(id.replace(/.desktop$/, "").replace("-", " "));
282
- return { name, id };
283
- }
284
- if (import_node_process5.default.platform === "win32") {
285
- return defaultBrowser();
286
- }
287
- throw new Error("Only macOS, Linux, and Windows are supported");
288
- }
289
-
290
- // node_modules/open/index.js
291
- var import_meta = {};
292
- var execFile5 = (0, import_node_util5.promisify)(import_node_child_process5.default.execFile);
293
- var __dirname = import_node_path.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
294
- var localXdgOpenPath = import_node_path.default.join(__dirname, "xdg-open");
295
- var { platform, arch } = import_node_process6.default;
296
- async function getWindowsDefaultBrowserFromWsl() {
297
- const powershellPath = await powerShellPath();
298
- const rawCommand = String.raw`(Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice").ProgId`;
299
- const encodedCommand = import_node_buffer.Buffer.from(rawCommand, "utf16le").toString("base64");
300
- const { stdout } = await execFile5(
301
- powershellPath,
302
- [
303
- "-NoProfile",
304
- "-NonInteractive",
305
- "-ExecutionPolicy",
306
- "Bypass",
307
- "-EncodedCommand",
308
- encodedCommand
309
- ],
310
- { encoding: "utf8" }
311
- );
312
- const progId = stdout.trim();
313
- const browserMap = {
314
- ChromeHTML: "com.google.chrome",
315
- BraveHTML: "com.brave.Browser",
316
- MSEdgeHTM: "com.microsoft.edge",
317
- FirefoxURL: "org.mozilla.firefox"
318
- };
319
- return browserMap[progId] ? { id: browserMap[progId] } : {};
320
- }
321
- var pTryEach = async (array, mapper) => {
322
- let latestError;
323
- for (const item of array) {
324
- try {
325
- return await mapper(item);
326
- } catch (error) {
327
- latestError = error;
328
- }
329
- }
330
- throw latestError;
331
- };
332
- var baseOpen = async (options) => {
333
- options = {
334
- wait: false,
335
- background: false,
336
- newInstance: false,
337
- allowNonzeroExitCode: false,
338
- ...options
339
- };
340
- if (Array.isArray(options.app)) {
341
- return pTryEach(options.app, (singleApp) => baseOpen({
342
- ...options,
343
- app: singleApp
344
- }));
345
- }
346
- let { name: app, arguments: appArguments = [] } = options.app ?? {};
347
- appArguments = [...appArguments];
348
- if (Array.isArray(app)) {
349
- return pTryEach(app, (appName) => baseOpen({
350
- ...options,
351
- app: {
352
- name: appName,
353
- arguments: appArguments
354
- }
355
- }));
356
- }
357
- if (app === "browser" || app === "browserPrivate") {
358
- const ids = {
359
- "com.google.chrome": "chrome",
360
- "google-chrome.desktop": "chrome",
361
- "com.brave.Browser": "brave",
362
- "org.mozilla.firefox": "firefox",
363
- "firefox.desktop": "firefox",
364
- "com.microsoft.msedge": "edge",
365
- "com.microsoft.edge": "edge",
366
- "com.microsoft.edgemac": "edge",
367
- "microsoft-edge.desktop": "edge"
368
- };
369
- const flags = {
370
- chrome: "--incognito",
371
- brave: "--incognito",
372
- firefox: "--private-window",
373
- edge: "--inPrivate"
374
- };
375
- const browser = is_wsl_default ? await getWindowsDefaultBrowserFromWsl() : await defaultBrowser2();
376
- if (browser.id in ids) {
377
- const browserName = ids[browser.id];
378
- if (app === "browserPrivate") {
379
- appArguments.push(flags[browserName]);
380
- }
381
- return baseOpen({
382
- ...options,
383
- app: {
384
- name: apps[browserName],
385
- arguments: appArguments
386
- }
387
- });
388
- }
389
- throw new Error(`${browser.name} is not supported as a default browser`);
390
- }
391
- let command;
392
- const cliArguments = [];
393
- const childProcessOptions = {};
394
- if (platform === "darwin") {
395
- command = "open";
396
- if (options.wait) {
397
- cliArguments.push("--wait-apps");
398
- }
399
- if (options.background) {
400
- cliArguments.push("--background");
401
- }
402
- if (options.newInstance) {
403
- cliArguments.push("--new");
404
- }
405
- if (app) {
406
- cliArguments.push("-a", app);
407
- }
408
- } else if (platform === "win32" || is_wsl_default && !isInsideContainer() && !app) {
409
- command = await powerShellPath();
410
- cliArguments.push(
411
- "-NoProfile",
412
- "-NonInteractive",
413
- "-ExecutionPolicy",
414
- "Bypass",
415
- "-EncodedCommand"
416
- );
417
- if (!is_wsl_default) {
418
- childProcessOptions.windowsVerbatimArguments = true;
419
- }
420
- const encodedArguments = ["Start"];
421
- if (options.wait) {
422
- encodedArguments.push("-Wait");
423
- }
424
- if (app) {
425
- encodedArguments.push(`"\`"${app}\`""`);
426
- if (options.target) {
427
- appArguments.push(options.target);
428
- }
429
- } else if (options.target) {
430
- encodedArguments.push(`"${options.target}"`);
431
- }
432
- if (appArguments.length > 0) {
433
- appArguments = appArguments.map((argument) => `"\`"${argument}\`""`);
434
- encodedArguments.push("-ArgumentList", appArguments.join(","));
435
- }
436
- options.target = import_node_buffer.Buffer.from(encodedArguments.join(" "), "utf16le").toString("base64");
437
- } else {
438
- if (app) {
439
- command = app;
440
- } else {
441
- const isBundled = !__dirname || __dirname === "/";
442
- let exeLocalXdgOpen = false;
443
- try {
444
- await import_promises2.default.access(localXdgOpenPath, import_promises2.constants.X_OK);
445
- exeLocalXdgOpen = true;
446
- } catch {
447
- }
448
- const useSystemXdgOpen = import_node_process6.default.versions.electron ?? (platform === "android" || isBundled || !exeLocalXdgOpen);
449
- command = useSystemXdgOpen ? "xdg-open" : localXdgOpenPath;
450
- }
451
- if (appArguments.length > 0) {
452
- cliArguments.push(...appArguments);
453
- }
454
- if (!options.wait) {
455
- childProcessOptions.stdio = "ignore";
456
- childProcessOptions.detached = true;
457
- }
458
- }
459
- if (platform === "darwin" && appArguments.length > 0) {
460
- cliArguments.push("--args", ...appArguments);
461
- }
462
- if (options.target) {
463
- cliArguments.push(options.target);
464
- }
465
- const subprocess = import_node_child_process5.default.spawn(command, cliArguments, childProcessOptions);
466
- if (options.wait) {
467
- return new Promise((resolve, reject) => {
468
- subprocess.once("error", reject);
469
- subprocess.once("close", (exitCode) => {
470
- if (!options.allowNonzeroExitCode && exitCode > 0) {
471
- reject(new Error(`Exited with code ${exitCode}`));
472
- return;
473
- }
474
- resolve(subprocess);
475
- });
476
- });
477
- }
478
- subprocess.unref();
479
- return subprocess;
480
- };
481
- var open = (target, options) => {
482
- if (typeof target !== "string") {
483
- throw new TypeError("Expected a `target`");
484
- }
485
- return baseOpen({
486
- ...options,
487
- target
488
- });
489
- };
490
- function detectArchBinary(binary) {
491
- if (typeof binary === "string" || Array.isArray(binary)) {
492
- return binary;
493
- }
494
- const { [arch]: archBinary } = binary;
495
- if (!archBinary) {
496
- throw new Error(`${arch} is not supported`);
497
- }
498
- return archBinary;
499
- }
500
- function detectPlatformBinary({ [platform]: platformBinary }, { wsl }) {
501
- if (wsl && is_wsl_default) {
502
- return detectArchBinary(wsl);
503
- }
504
- if (!platformBinary) {
505
- throw new Error(`${platform} is not supported`);
506
- }
507
- return detectArchBinary(platformBinary);
508
- }
509
- var apps = {};
510
- defineLazyProperty(apps, "chrome", () => detectPlatformBinary({
511
- darwin: "google chrome",
512
- win32: "chrome",
513
- linux: ["google-chrome", "google-chrome-stable", "chromium"]
514
- }, {
515
- wsl: {
516
- ia32: "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
517
- x64: ["/mnt/c/Program Files/Google/Chrome/Application/chrome.exe", "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe"]
518
- }
519
- }));
520
- defineLazyProperty(apps, "brave", () => detectPlatformBinary({
521
- darwin: "brave browser",
522
- win32: "brave",
523
- linux: ["brave-browser", "brave"]
524
- }, {
525
- wsl: {
526
- ia32: "/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe",
527
- x64: ["/mnt/c/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe", "/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe"]
528
- }
529
- }));
530
- defineLazyProperty(apps, "firefox", () => detectPlatformBinary({
531
- darwin: "firefox",
532
- win32: String.raw`C:\Program Files\Mozilla Firefox\firefox.exe`,
533
- linux: "firefox"
534
- }, {
535
- wsl: "/mnt/c/Program Files/Mozilla Firefox/firefox.exe"
536
- }));
537
- defineLazyProperty(apps, "edge", () => detectPlatformBinary({
538
- darwin: "microsoft edge",
539
- win32: "msedge",
540
- linux: ["microsoft-edge", "microsoft-edge-dev"]
541
- }, {
542
- wsl: "/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
543
- }));
544
- defineLazyProperty(apps, "browser", () => "browser");
545
- defineLazyProperty(apps, "browserPrivate", () => "browserPrivate");
546
- var open_default = open;
547
-
548
- // src/validate.ts
549
- function validateMoodUISpec(input) {
550
- const errors = [];
551
- if (!isObject(input)) {
552
- return { ok: false, errors: ["spec must be an object"] };
553
- }
554
- const version = input.version;
555
- if (version !== 1) errors.push("spec.version must be 1");
556
- const root = input.root;
557
- const rootResult = validateNode(root, "spec.root");
558
- if (!rootResult.ok) errors.push(...rootResult.errors);
559
- if (errors.length > 0) return { ok: false, errors };
560
- return { ok: true, value: input };
561
- }
562
- function assertMoodUISpec(input) {
563
- const result = validateMoodUISpec(input);
564
- if (!result.ok) {
565
- const message = ["Invalid MoodUI spec:", ...result.errors.map((e) => `- ${e}`)].join("\n");
566
- throw new Error(message);
567
- }
568
- return result.value;
569
- }
570
- function validateNode(input, path3) {
571
- const errors = [];
572
- if (!isObject(input)) return { ok: false, errors: [`${path3} must be an object`] };
573
- const type = input.type;
574
- if (!isString(type)) return { ok: false, errors: [`${path3}.type must be a string`] };
575
- switch (type) {
576
- case "box": {
577
- const children = input.children;
578
- if (children != null) {
579
- if (!Array.isArray(children)) {
580
- errors.push(`${path3}.children must be an array`);
581
- } else {
582
- for (let i = 0; i < children.length; i += 1) {
583
- const child = children[i];
584
- const childResult = validateNode(child, `${path3}.children[${i}]`);
585
- if (!childResult.ok) errors.push(...childResult.errors);
586
- }
587
- }
588
- }
589
- break;
590
- }
591
- case "text": {
592
- const props = input.props;
593
- if (!isObject(props)) errors.push(`${path3}.props must be an object`);
594
- else if (!isString(props.value)) errors.push(`${path3}.props.value must be a string`);
595
- break;
596
- }
597
- case "button": {
598
- const props = input.props;
599
- if (!isObject(props)) errors.push(`${path3}.props must be an object`);
600
- else if (!isString(props.label)) errors.push(`${path3}.props.label must be a string`);
601
- break;
602
- }
603
- case "input": {
604
- const props = input.props;
605
- if (props != null && !isObject(props)) errors.push(`${path3}.props must be an object if provided`);
606
- break;
607
- }
608
- case "image": {
609
- const props = input.props;
610
- if (!isObject(props)) errors.push(`${path3}.props must be an object`);
611
- else if (!isString(props.src)) errors.push(`${path3}.props.src must be a string`);
612
- break;
613
- }
614
- case "spacer": {
615
- const props = input.props;
616
- if (props != null && !isObject(props)) errors.push(`${path3}.props must be an object if provided`);
617
- break;
618
- }
619
- default:
620
- errors.push(`${path3}.type must be one of: box | text | button | input | image | spacer`);
621
- }
622
- if (errors.length > 0) return { ok: false, errors };
623
- return { ok: true, value: input };
624
- }
625
- function isObject(value) {
626
- return typeof value === "object" && value !== null && !Array.isArray(value);
627
- }
628
- function isString(value) {
629
- return typeof value === "string";
630
- }
631
-
632
- // src/ai/generateSpec.ts
633
- async function generateMoodUISpec(llm, options) {
634
- const maxAttempts = options.maxAttempts ?? 2;
635
- if (maxAttempts < 1) throw new Error("maxAttempts must be >= 1");
636
- const system = buildSystemPrompt();
637
- const user = buildUserPrompt(options.prompt);
638
- let lastRaw = "";
639
- let lastError = void 0;
640
- for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
641
- const messages = [
642
- { role: "system", content: system },
643
- { role: "user", content: user }
644
- ];
645
- if (attempt > 1) {
646
- messages.push({
647
- role: "user",
648
- content: "Perbaiki output kamu supaya valid JSON dan valid MoodUISpec. Output hanya JSON."
649
- });
650
- }
651
- const raw = await llm.chat({
652
- model: options.model,
653
- temperature: options.temperature,
654
- messages
655
- });
656
- lastRaw = raw;
657
- try {
658
- const json = parseFirstJsonObject(raw);
659
- const spec = assertMoodUISpec(json);
660
- return { spec, raw };
661
- } catch (e) {
662
- lastError = e;
663
- }
664
- }
665
- const message = lastError instanceof Error ? lastError.message : String(lastError);
666
- throw new Error(`Failed to generate valid MoodUISpec. Last error: ${message}
667
-
668
- Raw:
669
- ${lastRaw}`);
670
- }
671
- function buildSystemPrompt() {
672
- return [
673
- "Kamu adalah generator JSON untuk MoodUI.",
674
- "TUGAS: keluarkan 1 objek JSON yang valid, sesuai schema MoodUISpec versi 1.",
675
- "JANGAN keluarkan markdown, JANGAN ada penjelasan, JANGAN pakai backticks.",
676
- "",
677
- "MoodUISpec shape:",
678
- "{",
679
- ' "version": 1,',
680
- ' "root": MoodUINode',
681
- "}",
682
- "",
683
- "MoodUINode union:",
684
- "- box: { type:'box', props?: { direction?, gap?, align?, justify?, wrap?, ...common }, children?: MoodUINode[] }",
685
- "- text: { type:'text', props: { value: string, as?, color?, fontSize?, fontWeight?, textAlign?, ...common } }",
686
- "- button: { type:'button', props: { label: string, variant?, actionId?, disabled?, ...common } }",
687
- "- input: { type:'input', props?: { name?, placeholder?, defaultValue?, ...common } }",
688
- "- image: { type:'image', props: { src: string, alt?, fit?, ...common } }",
689
- "- spacer: { type:'spacer', props?: { size? } }",
690
- "",
691
- "common props:",
692
- "- id, testId, className, style(object), padding, margin, background, borderRadius, width, height",
693
- "",
694
- "Rules:",
695
- "- root wajib ada",
696
- "- minimal pakai box sebagai container utama",
697
- "- semua string pakai double quotes (JSON standard)",
698
- "- jangan pakai function / JS expression apa pun"
699
- ].join("\n");
700
- }
701
- function buildUserPrompt(prompt) {
702
- return ["Buat UI dari request ini:", prompt].join("\n");
703
- }
704
- function parseFirstJsonObject(text) {
705
- const start = text.indexOf("{");
706
- if (start < 0) throw new Error("No JSON object found");
707
- let depth = 0;
708
- let inString = false;
709
- let escaped = false;
710
- for (let i = start; i < text.length; i += 1) {
711
- const ch = text[i];
712
- if (inString) {
713
- if (escaped) {
714
- escaped = false;
715
- } else if (ch === "\\") {
716
- escaped = true;
717
- } else if (ch === '"') {
718
- inString = false;
719
- }
720
- continue;
721
- }
722
- if (ch === '"') {
723
- inString = true;
724
- continue;
725
- }
726
- if (ch === "{") depth += 1;
727
- else if (ch === "}") depth -= 1;
728
- if (depth === 0) {
729
- const candidate = text.slice(start, i + 1);
730
- return JSON.parse(candidate);
731
- }
732
- }
733
- throw new Error("Unterminated JSON object");
734
- }
735
-
736
- // src/react/renderReact.ts
737
- function renderReact(specInput, options = {}) {
738
- const spec = assertMoodUISpec(specInput);
739
- const componentName = options.componentName ?? "MoodUIScreen";
740
- const jsx = renderNode(spec.root, { indent: 2, onActionProp: "onAction" });
741
- return [
742
- 'import * as React from "react";',
743
- "",
744
- "export type MoodUIScreenActionHandler = (actionId: string) => void;",
745
- "",
746
- "export type MoodUIScreenProps = {",
747
- " onAction?: MoodUIScreenActionHandler;",
748
- "};",
749
- "",
750
- `export function ${componentName}(props: MoodUIScreenProps) {`,
751
- " const { onAction } = props;",
752
- " return (",
753
- jsx,
754
- " );",
755
- "}",
756
- ""
757
- ].join("\n");
758
- }
759
- function renderNode(node, ctx) {
760
- switch (node.type) {
761
- case "box":
762
- return renderElement("div", node, ctx, () => {
763
- const children = node.children ?? [];
764
- if (children.length === 0) return [];
765
- return children.map((child) => renderNode(child, { ...ctx, indent: ctx.indent + 2 }));
766
- });
767
- case "text": {
768
- const as = node.props?.as ?? "p";
769
- const text = escapeText(node.props?.value ?? "");
770
- return renderElement(as, node, ctx, () => [indent(ctx.indent + 2) + text]);
771
- }
772
- case "button": {
773
- const label = escapeText(node.props?.label ?? "");
774
- const actionId = node.props?.actionId;
775
- return renderElement(
776
- "button",
777
- node,
778
- ctx,
779
- () => [indent(ctx.indent + 2) + label],
780
- actionId ? { onClick: `() => ${ctx.onActionProp}?.(${serializeJsValue(actionId)})` } : void 0
781
- );
782
- }
783
- case "input":
784
- return renderSelfClosingElement("input", node, ctx);
785
- case "image":
786
- return renderSelfClosingElement("img", node, ctx);
787
- case "spacer": {
788
- const size = node.props?.size ?? 8;
789
- const style = { width: normalizeCssValue(size), height: normalizeCssValue(size) };
790
- return renderElement(
791
- "div",
792
- { type: "box", props: { style }, children: [] },
793
- ctx,
794
- () => []
795
- );
796
- }
797
- }
798
- }
799
- function renderElement(tag, node, ctx, renderChildren, extraProps) {
800
- const children = renderChildren();
801
- const propParts = buildProps(tag, node, extraProps);
802
- const open2 = `<${tag}${propParts.length ? " " + propParts.join(" ") : ""}>`;
803
- const close = `</${tag}>`;
804
- if (children.length === 0) return indent(ctx.indent) + open2 + close;
805
- return [
806
- indent(ctx.indent) + open2,
807
- ...children,
808
- indent(ctx.indent) + close
809
- ].join("\n");
810
- }
811
- function renderSelfClosingElement(tag, node, ctx) {
812
- const propParts = buildProps(tag, node);
813
- return indent(ctx.indent) + `<${tag}${propParts.length ? " " + propParts.join(" ") : ""} />`;
814
- }
815
- function buildProps(tag, node, extraProps) {
816
- const props = node.props ?? {};
817
- const out = [];
818
- if (props.id) out.push(`id=${serializeJsxAttrValue(props.id)}`);
819
- if (props.testId) out.push(`data-testid=${serializeJsxAttrValue(props.testId)}`);
820
- if (props.className) out.push(`className=${serializeJsxAttrValue(props.className)}`);
821
- const computedStyle = computeStyle(tag, node);
822
- const mergedStyle = mergeStyle(computedStyle, props.style) ?? {};
823
- if (tag === "input") {
824
- if (props.name) out.push(`name=${serializeJsxAttrValue(props.name)}`);
825
- if (props.placeholder) out.push(`placeholder=${serializeJsxAttrValue(props.placeholder)}`);
826
- if (props.defaultValue) out.push(`defaultValue=${serializeJsxAttrValue(props.defaultValue)}`);
827
- }
828
- if (tag === "img") {
829
- if (props.src) out.push(`src=${serializeJsxAttrValue(props.src)}`);
830
- if (props.alt) out.push(`alt=${serializeJsxAttrValue(props.alt)}`);
831
- if (props.fit) mergedStyle.objectFit = props.fit;
832
- }
833
- if (Object.keys(mergedStyle).length > 0) {
834
- out.push(`style={(${serializeJsValue(mergedStyle)} as React.CSSProperties)}`);
835
- }
836
- if (extraProps) {
837
- for (const [k, v] of Object.entries(extraProps)) out.push(`${k}={${v}}`);
838
- }
839
- return out;
840
- }
841
- function computeStyle(tag, node) {
842
- const props = node.props ?? {};
843
- const style = {};
844
- if (props.width != null) style.width = normalizeCssValue(props.width);
845
- if (props.height != null) style.height = normalizeCssValue(props.height);
846
- if (props.background != null) style.background = props.background;
847
- if (props.borderRadius != null) style.borderRadius = normalizeCssValue(props.borderRadius);
848
- applySpacing(style, "margin", props.margin);
849
- applySpacing(style, "padding", props.padding);
850
- if (node.type === "box") {
851
- style.display = "flex";
852
- style.flexDirection = normalizeFlexDirection(props.direction) ?? "column";
853
- if (props.gap != null) style.gap = normalizeCssValue(props.gap);
854
- if (props.align != null) style.alignItems = props.align;
855
- if (props.justify != null) style.justifyContent = props.justify;
856
- if (props.wrap != null) style.flexWrap = props.wrap;
857
- }
858
- if (node.type === "text") {
859
- if (props.color != null) style.color = props.color;
860
- if (props.fontSize != null) style.fontSize = normalizeCssValue(props.fontSize);
861
- if (props.fontWeight != null) style.fontWeight = props.fontWeight;
862
- if (props.textAlign != null) style.textAlign = props.textAlign;
863
- if (tag.startsWith("h")) style.margin = 0;
864
- }
865
- if (node.type === "button") {
866
- style.cursor = "pointer";
867
- style.border = "none";
868
- style.padding = "10px 14px";
869
- style.borderRadius = 10;
870
- const variant = props.variant ?? "primary";
871
- if (variant === "primary") {
872
- style.background = "#111827";
873
- style.color = "#ffffff";
874
- } else if (variant === "secondary") {
875
- style.background = "#e5e7eb";
876
- style.color = "#111827";
877
- } else {
878
- style.background = "transparent";
879
- style.color = "#111827";
880
- }
881
- if (props.disabled) {
882
- style.opacity = 0.6;
883
- style.cursor = "not-allowed";
884
- }
885
- }
886
- if (node.type === "input") {
887
- style.padding = "10px 12px";
888
- style.borderRadius = 10;
889
- style.border = "1px solid #d1d5db";
890
- style.outline = "none";
891
- }
892
- if (node.type === "image") {
893
- if (props.fit != null) style.objectFit = props.fit;
894
- style.maxWidth = "100%";
895
- }
896
- return style;
897
- }
898
- function applySpacing(style, kind, value) {
899
- if (value == null) return;
900
- if (typeof value === "number" || typeof value === "string") {
901
- style[kind] = normalizeCssValue(value);
902
- return;
903
- }
904
- if (typeof value !== "object" || Array.isArray(value)) return;
905
- const v = value;
906
- const all = v.all;
907
- const x = v.x;
908
- const y = v.y;
909
- if (all != null) style[kind] = normalizeCssValue(all);
910
- if (x != null) {
911
- style[`${kind}Left`] = normalizeCssValue(x);
912
- style[`${kind}Right`] = normalizeCssValue(x);
913
- }
914
- if (y != null) {
915
- style[`${kind}Top`] = normalizeCssValue(y);
916
- style[`${kind}Bottom`] = normalizeCssValue(y);
917
- }
918
- if (v.top != null) style[`${kind}Top`] = normalizeCssValue(v.top);
919
- if (v.right != null) style[`${kind}Right`] = normalizeCssValue(v.right);
920
- if (v.bottom != null) style[`${kind}Bottom`] = normalizeCssValue(v.bottom);
921
- if (v.left != null) style[`${kind}Left`] = normalizeCssValue(v.left);
922
- }
923
- function mergeStyle(a, b) {
924
- if (!a && !b) return void 0;
925
- if (!a) return b;
926
- if (!b) return a;
927
- return { ...a, ...b };
928
- }
929
- function normalizeCssValue(value) {
930
- if (typeof value === "number") return value;
931
- if (typeof value === "string") return value;
932
- return value;
933
- }
934
- function normalizeFlexDirection(value) {
935
- if (value === "row" || value === "column") return value;
936
- if (value === "horizontal") return "row";
937
- if (value === "vertical") return "column";
938
- return void 0;
939
- }
940
- function serializeJsxAttrValue(value) {
941
- return `{${serializeJsValue(value)}}`;
942
- }
943
- function serializeJsValue(value) {
944
- if (value === null) return "null";
945
- if (value === void 0) return "undefined";
946
- if (typeof value === "string") return JSON.stringify(value);
947
- if (typeof value === "number" || typeof value === "boolean") return String(value);
948
- if (Array.isArray(value)) return `[${value.map(serializeJsValue).join(", ")}]`;
949
- if (typeof value === "object") {
950
- const entries = Object.entries(value).filter(([, v]) => v !== void 0).map(([k, v]) => `${safeObjectKey(k)}: ${serializeJsValue(v)}`);
951
- return `{ ${entries.join(", ")} }`;
952
- }
953
- return "undefined";
954
- }
955
- function safeObjectKey(key) {
956
- if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) return key;
957
- return JSON.stringify(key);
958
- }
959
- function escapeText(value) {
960
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
961
- }
962
- function indent(spaces) {
963
- return " ".repeat(spaces);
964
- }
965
-
966
- // src/llm/gemini.ts
967
- function createGeminiClient(options) {
968
- const baseUrl = (options.baseUrl ?? "https://generativelanguage.googleapis.com").replace(/\/+$/, "");
969
- const fetchFn = options.fetchFn ?? fetch;
970
- const apiKey = options.apiKey;
971
- return {
972
- async chat(req) {
973
- const { systemInstruction, contents } = toGeminiContents(req.messages);
974
- const url = `${baseUrl}/v1beta/models/${encodeURIComponent(req.model)}:generateContent?key=${encodeURIComponent(apiKey)}`;
975
- const res = await fetchFn(url, {
976
- method: "POST",
977
- headers: { "content-type": "application/json" },
978
- body: JSON.stringify({
979
- ...systemInstruction ? { systemInstruction } : {},
980
- contents,
981
- generationConfig: req.temperature == null ? void 0 : { temperature: req.temperature }
982
- })
983
- });
984
- if (!res.ok) {
985
- const text2 = await safeReadText(res);
986
- throw new Error(`Gemini generateContent failed (${res.status}): ${text2}`);
987
- }
988
- const json = await res.json();
989
- const parts = json?.candidates?.[0]?.content?.parts;
990
- const text = Array.isArray(parts) ? parts.map((p) => typeof p?.text === "string" ? p.text : "").join("") : void 0;
991
- if (typeof text !== "string" || text.length === 0) throw new Error("Gemini response missing candidates[0].content.parts[].text");
992
- return text;
993
- }
994
- };
995
- }
996
- function toGeminiContents(messages) {
997
- const systemTexts = messages.filter((m) => m.role === "system").map((m) => m.content).filter((t) => t.trim().length > 0);
998
- const systemInstruction = systemTexts.length > 0 ? { role: "system", parts: [{ text: systemTexts.join("\n") }] } : void 0;
999
- const contents = messages.filter((m) => m.role !== "system").map((m) => ({
1000
- role: m.role === "assistant" ? "model" : "user",
1001
- parts: [{ text: m.content }]
1002
- }));
1003
- if (contents.length === 0) {
1004
- contents.push({ role: "user", parts: [{ text: "" }] });
1005
- }
1006
- return { systemInstruction, contents };
1007
- }
1008
- async function safeReadText(res) {
1009
- try {
1010
- return await res.text();
1011
- } catch {
1012
- return "";
1013
- }
1014
- }
1015
-
1016
- // src/llm/ollama.ts
1017
- function createOllamaClient(options = {}) {
1018
- const baseUrl = options.baseUrl ?? "http://localhost:11434";
1019
- const fetchFn = options.fetchFn ?? fetch;
1020
- return {
1021
- async chat(req) {
1022
- const res = await fetchFn(`${baseUrl}/api/chat`, {
1023
- method: "POST",
1024
- headers: { "content-type": "application/json" },
1025
- body: JSON.stringify({
1026
- model: req.model,
1027
- messages: req.messages,
1028
- stream: false,
1029
- options: req.temperature == null ? void 0 : { temperature: req.temperature }
1030
- })
1031
- });
1032
- if (!res.ok) {
1033
- const text = await safeReadText2(res);
1034
- throw new Error(`Ollama chat failed (${res.status}): ${text}`);
1035
- }
1036
- const json = await res.json();
1037
- const content = json?.message?.content;
1038
- if (typeof content !== "string") throw new Error("Ollama response missing message.content");
1039
- return content;
1040
- }
1041
- };
1042
- }
1043
- async function safeReadText2(res) {
1044
- try {
1045
- return await res.text();
1046
- } catch {
1047
- return "";
1048
- }
1049
- }
1050
-
1051
- // src/llm/openaiCompatible.ts
1052
- function createOpenAICompatibleClient(options) {
1053
- const fetchFn = options.fetchFn ?? fetch;
1054
- const baseUrl = options.baseUrl.replace(/\/+$/, "");
1055
- const apiKey = options.apiKey;
1056
- const defaultHeaders = options.defaultHeaders ?? {};
1057
- return {
1058
- async chat(req) {
1059
- const res = await fetchFn(`${baseUrl}/v1/chat/completions`, {
1060
- method: "POST",
1061
- headers: {
1062
- "content-type": "application/json",
1063
- authorization: `Bearer ${apiKey}`,
1064
- ...defaultHeaders
1065
- },
1066
- body: JSON.stringify({
1067
- model: req.model,
1068
- messages: req.messages,
1069
- temperature: req.temperature
1070
- })
1071
- });
1072
- if (!res.ok) {
1073
- const text = await safeReadText3(res);
1074
- throw new Error(`Chat completion failed (${res.status}): ${text}`);
1075
- }
1076
- const json = await res.json();
1077
- const content = json?.choices?.[0]?.message?.content;
1078
- if (typeof content !== "string") throw new Error("Response missing choices[0].message.content");
1079
- return content;
1080
- }
1081
- };
1082
- }
1083
- async function safeReadText3(res) {
1084
- try {
1085
- return await res.text();
1086
- } catch {
1087
- return "";
1088
- }
1089
- }
1090
-
1091
- // src/ai/generateReactFromPrompt.ts
1092
- async function generateReactFromPrompt(options) {
1093
- const llm = options.provider === "gemini" ? createGeminiClient({ apiKey: options.apiKey, baseUrl: options.baseUrl }) : options.provider === "ollama" ? createOllamaClient({ baseUrl: options.baseUrl }) : createOpenAICompatibleClient({ apiKey: options.apiKey, baseUrl: options.baseUrl });
1094
- const { spec, raw } = await generateMoodUISpec(llm, {
1095
- model: options.model,
1096
- prompt: options.prompt,
1097
- temperature: options.temperature,
1098
- maxAttempts: options.maxAttempts
1099
- });
1100
- const code = renderReact(spec, { componentName: options.componentName });
1101
- return { spec, raw, code };
1102
- }
1103
-
1104
- // src/cli.ts
1105
- var import_meta2 = {};
1106
- var __dirname2 = import_node_path2.default.dirname((0, import_node_url2.fileURLToPath)(import_meta2.url));
1107
- async function main() {
1108
- const argv = process.argv.slice(2);
1109
- const command = argv[0] ?? "help";
1110
- if (command === "help" || command === "--help" || command === "-h") {
1111
- printHelp();
1112
- return;
1113
- }
1114
- if (command === "ui") {
1115
- await runUI();
1116
- return;
1117
- }
1118
- if (command !== "generate") {
1119
- console.error(`Unknown command: ${command}`);
1120
- printHelp();
1121
- process.exitCode = 1;
1122
- return;
1123
- }
1124
- const { args, positionals } = parseArgs(argv.slice(1));
1125
- const provider = args.provider || "gemini";
1126
- const model = args.model || (provider === "gemini" ? "gemini-3-flash-preview" : "");
1127
- const outFile = args.out || "src/generated/MoodScreen.tsx";
1128
- const componentName = args.component || "MoodScreen";
1129
- const prompt = args.prompt || positionals.join(" ");
1130
- if (!model) {
1131
- console.error("Missing --model");
1132
- process.exitCode = 1;
1133
- return;
1134
- }
1135
- if (!prompt) {
1136
- console.error('Missing prompt. Use --prompt "..." or pass it as arguments.');
1137
- process.exitCode = 1;
1138
- return;
1139
- }
1140
- const temperature = args.temperature != null ? Number(args.temperature) : void 0;
1141
- const maxAttempts = args.maxAttempts != null ? Number(args.maxAttempts) : void 0;
1142
- const baseUrl = typeof args.baseUrl === "string" ? args.baseUrl : void 0;
1143
- if (provider === "gemini" && !process.env.GEMINI_API_KEY) {
1144
- console.error("Missing GEMINI_API_KEY env var");
1145
- process.exitCode = 1;
1146
- return;
1147
- }
1148
- if (provider === "openai-compatible" && !process.env.OPENAI_COMPATIBLE_API_KEY) {
1149
- console.error("Missing OPENAI_COMPATIBLE_API_KEY env var");
1150
- process.exitCode = 1;
1151
- return;
1152
- }
1153
- const result = provider === "ollama" ? await generateReactFromPrompt({
1154
- provider: "ollama",
1155
- model,
1156
- baseUrl,
1157
- prompt,
1158
- componentName,
1159
- temperature,
1160
- maxAttempts
1161
- }) : provider === "openai-compatible" ? await generateReactFromPrompt({
1162
- provider: "openai-compatible",
1163
- model,
1164
- baseUrl: baseUrl || process.env.OPENAI_COMPATIBLE_BASE_URL || "",
1165
- apiKey: process.env.OPENAI_COMPATIBLE_API_KEY || "",
1166
- prompt,
1167
- componentName,
1168
- temperature,
1169
- maxAttempts
1170
- }) : await generateReactFromPrompt({
1171
- provider: "gemini",
1172
- model,
1173
- baseUrl,
1174
- apiKey: process.env.GEMINI_API_KEY || "",
1175
- prompt,
1176
- componentName,
1177
- temperature,
1178
- maxAttempts
1179
- });
1180
- const resolvedOut = import_node_path2.default.resolve(process.cwd(), outFile);
1181
- await import_promises3.default.mkdir(import_node_path2.default.dirname(resolvedOut), { recursive: true });
1182
- await import_promises3.default.writeFile(resolvedOut, result.code, "utf8");
1183
- console.log(`OK: wrote ${resolvedOut}`);
1184
- }
1185
- async function runUI() {
1186
- let uiDir = import_node_path2.default.join(__dirname2, "ui");
1187
- try {
1188
- await import_promises3.default.access(uiDir);
1189
- } catch {
1190
- uiDir = import_node_path2.default.join(__dirname2, "..", "dist", "ui");
1191
- }
1192
- const PORT = 3e3;
1193
- console.log(`Starting MoodUI UI...`);
1194
- const server = import_node_http.default.createServer(async (req, res) => {
1195
- let filePath = import_node_path2.default.join(uiDir, req.url === "/" ? "index.html" : req.url);
1196
- const extname = String(import_node_path2.default.extname(filePath)).toLowerCase();
1197
- const mimeTypes = {
1198
- ".html": "text/html",
1199
- ".js": "text/javascript",
1200
- ".css": "text/css",
1201
- ".json": "application/json",
1202
- ".png": "image/png",
1203
- ".jpg": "image/jpg",
1204
- ".gif": "image/gif",
1205
- ".svg": "image/svg+xml",
1206
- ".ico": "image/x-icon"
1207
- };
1208
- const contentType = mimeTypes[extname] || "application/octet-stream";
1209
- try {
1210
- const content = await import_promises3.default.readFile(filePath);
1211
- res.writeHead(200, { "Content-Type": contentType });
1212
- res.end(content, "utf-8");
1213
- } catch (err) {
1214
- res.writeHead(404);
1215
- res.end("Not Found", "utf-8");
1216
- }
1217
- });
1218
- server.listen(PORT, () => {
1219
- const url = `http://localhost:${PORT}`;
1220
- console.log(`MoodUI UI running at ${url}`);
1221
- open_default(url);
1222
- });
1223
- process.on("SIGINT", () => {
1224
- server.close();
1225
- process.exit(0);
1226
- });
1227
- }
1228
- function printHelp() {
1229
- console.log(
1230
- [
1231
- "moodui <command>",
1232
- "",
1233
- "Commands:",
1234
- " generate [options] <prompt...> Generate component via CLI",
1235
- " ui Launch Web UI for generating components",
1236
- " help, --help, -h Show this help message",
1237
- "",
1238
- "Generate Options:",
1239
- " --provider gemini|ollama|openai-compatible default: gemini",
1240
- " --model <name> required (default for gemini: gemini-3-flash-preview)",
1241
- " --out <path> path lengkap ke file output (termasuk direktori), default: src/generated/MoodScreen.tsx",
1242
- " --component <name> nama component, default: MoodScreen",
1243
- " --prompt <text> optional (or pass as positional args)",
1244
- " --temperature <number>",
1245
- " --maxAttempts <number>",
1246
- " --baseUrl <url>",
1247
- "",
1248
- "Env:",
1249
- " GEMINI_API_KEY",
1250
- " OPENAI_COMPATIBLE_BASE_URL",
1251
- " OPENAI_COMPATIBLE_API_KEY",
1252
- "",
1253
- "Examples:",
1254
- " # Launch Web UI",
1255
- " npx @kohryan/moodui ui",
1256
- "",
1257
- " # Generate ke src/generated/MoodScreen.tsx (default)",
1258
- ' GEMINI_API_KEY=... npx @kohryan/moodui generate --model gemini-3-flash-preview "Buat UI login sederhana"',
1259
- "",
1260
- " # Generate ke src/components/LoginPage.tsx",
1261
- ' GEMINI_API_KEY=... npx @kohryan/moodui generate --model gemini-3-flash-preview --out src/components/LoginPage.tsx --component LoginPage "Buat UI login"',
1262
- "",
1263
- " # Generate ke src/pages/Home.tsx",
1264
- ' GEMINI_API_KEY=... npx @kohryan/moodui generate --model gemini-3-flash-preview --out src/pages/Home.tsx --component Home "Buat homepage"'
1265
- ].join("\n")
1266
- );
1267
- }
1268
- function parseArgs(argv) {
1269
- const args = {};
1270
- const positionals = [];
1271
- for (let i = 0; i < argv.length; i += 1) {
1272
- const token = argv[i];
1273
- if (!token.startsWith("--")) {
1274
- positionals.push(token);
1275
- continue;
1276
- }
1277
- const eq = token.indexOf("=");
1278
- const key = (eq >= 0 ? token.slice(2, eq) : token.slice(2)).trim();
1279
- const inlineValue = eq >= 0 ? token.slice(eq + 1) : void 0;
1280
- if (inlineValue != null) {
1281
- args[key] = inlineValue;
1282
- continue;
1283
- }
1284
- const next = argv[i + 1];
1285
- if (next == null || next.startsWith("--")) {
1286
- args[key] = true;
1287
- continue;
1288
- }
1289
- args[key] = next;
1290
- i += 1;
1291
- }
1292
- return { args, positionals };
1293
- }
1294
- main().catch((e) => {
1295
- const msg = e instanceof Error ? e.message : String(e);
1296
- console.error(msg);
1297
- process.exitCode = 1;
1298
- });