@satori-sh/cli 0.0.13 → 0.0.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.
package/README.md CHANGED
@@ -37,6 +37,13 @@ Add new memories:
37
37
  satori add "I like pizza"
38
38
  ```
39
39
 
40
+ ### Subscribe
41
+
42
+ Open checkout in the browser:
43
+ ```bash
44
+ npx @satori-sh sub
45
+ ```
46
+
40
47
  **Options & Memory:**
41
48
  - `--memory-id <id>` (scopes conversations)
42
49
 
@@ -53,8 +60,16 @@ satori chat "Follow up question"
53
60
 
54
61
  **Optional:**
55
62
  - `SATORI_BASE_URL` (default: http://localhost:8000)
63
+ - `SATORI_CHECKOUT_URL` - Stripe checkout link for `npx @satori-sh sub`
56
64
  - `SATORI_MEMORY_ID` - Session scoping
57
65
  - `SATORI_MOCK` - Enable mock mode
66
+ - `OPENAI_API_KEY` - Pass-through OpenAI key for `/ask` (sent as `X-OpenAI-Key`)
67
+ - `NODE_ENV` - Set to `development` to default to `http://localhost:8000`
68
+
69
+ Example:
70
+ ```bash
71
+ NODE_ENV=development bun run src/index.ts
72
+ ```
58
73
 
59
74
  ## Troubleshooting
60
75
 
package/dist/index.js CHANGED
@@ -3,8 +3,9 @@
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
5
  import { readFileSync, realpathSync } from "fs";
6
- import { dirname, join as join2 } from "path";
7
- import { fileURLToPath } from "url";
6
+ import { execFile } from "child_process";
7
+ import { dirname as dirname2, join as join3 } from "path";
8
+ import { fileURLToPath as fileURLToPath2 } from "url";
8
9
  import chalk from "chalk";
9
10
 
10
11
  // src/config.ts
@@ -73,6 +74,7 @@ async function getConfig() {
73
74
  }
74
75
  let apiKey = null;
75
76
  let memoryId = void 0;
77
+ const openaiApiKey = process.env.OPENAI_API_KEY || null;
76
78
  try {
77
79
  const data = await loadConfigFile();
78
80
  if (typeof data.api_key === "string") {
@@ -86,12 +88,21 @@ async function getConfig() {
86
88
  if (!apiKey) {
87
89
  apiKey = process.env.SATORI_API_KEY || null;
88
90
  }
89
- const baseUrl = process.env.SATORI_BASE_URL || "https://api.satori.sh";
91
+ const nodeEnv = process.env.NODE_ENV;
92
+ const baseUrl = process.env.SATORI_BASE_URL || (nodeEnv !== "production" ? "http://localhost:8000" : "https://api.satori.sh");
93
+ const checkoutUrl = process.env.SATORI_CHECKOUT_URL || (nodeEnv === "production" ? "https://buy.stripe.com/aFabJ03GIez6bH53Ckew800" : "https://buy.stripe.com/test_14AeVc5TZc2GapagZp0gw00");
90
94
  try {
91
95
  new URL(baseUrl);
92
96
  } catch {
93
97
  throw new Error("Invalid SATORI_BASE_URL format");
94
98
  }
99
+ if (checkoutUrl) {
100
+ try {
101
+ new URL(checkoutUrl);
102
+ } catch {
103
+ throw new Error("Invalid SATORI_CHECKOUT_URL format");
104
+ }
105
+ }
95
106
  if (!apiKey) {
96
107
  try {
97
108
  const response = await fetch(`${baseUrl}/orgs`, {
@@ -114,7 +125,7 @@ async function getConfig() {
114
125
  if (!memoryId) {
115
126
  memoryId = process.env.SATORI_MEMORY_ID;
116
127
  }
117
- return { apiKey, baseUrl, memoryId };
128
+ return { apiKey, openaiApiKey, baseUrl, checkoutUrl, memoryId };
118
129
  }
119
130
 
120
131
  // src/search.ts
@@ -133,6 +144,10 @@ async function searchMemories(query) {
133
144
  },
134
145
  body: JSON.stringify({ query })
135
146
  });
147
+ if (response.status === 402) {
148
+ console.error("Free tier exceeded. Run `npx @satori-sh sub` to subscribe");
149
+ return;
150
+ }
136
151
  if (!response.ok) {
137
152
  console.error(`HTTP error: ${response.status} ${response.statusText}`);
138
153
  return;
@@ -159,6 +174,10 @@ async function addMemories(text, options = {}) {
159
174
  },
160
175
  body: JSON.stringify({ messages: [{ role: "user", content: text }], ...options.memoryId && { memory_id: options.memoryId } })
161
176
  });
177
+ if (response.status === 402) {
178
+ console.error("Free tier exceeded. Run `npx @satori-sh sub` to subscribe");
179
+ return;
180
+ }
162
181
  if (!response.ok) {
163
182
  console.error(`HTTP error: ${response.status} ${response.statusText}`);
164
183
  return;
@@ -208,10 +227,260 @@ ${memoryText}`
208
227
  return [systemMessage, ...messages];
209
228
  }
210
229
 
230
+ // src/ui.tsx
231
+ import { render, useRenderer, useTerminalDimensions } from "@opentui/solid";
232
+ import { For, Show, createSignal, onMount, onCleanup } from "solid-js";
233
+ import cliSpinners from "cli-spinners";
234
+
235
+ // src/logo.ts
236
+ import { dirname, join as join2 } from "path";
237
+ import { fileURLToPath } from "url";
238
+ async function loadLogo() {
239
+ const { default: fs } = await import("fs");
240
+ const __dirname = dirname(fileURLToPath(import.meta.url));
241
+ const logoPath = join2(__dirname, "..", "logos", "satori.ans");
242
+ return fs.readFileSync(logoPath, "utf8");
243
+ }
244
+
245
+ // src/ui.tsx
246
+ async function runInteractiveApp({
247
+ initialPrompt,
248
+ options,
249
+ processUserInput,
250
+ infoLine,
251
+ infoDisplay
252
+ }) {
253
+ const logo = await loadLogo();
254
+ console.log(` ${logo}`);
255
+ const rows = process.stdout.rows ?? 24;
256
+ const logoHeight = logo.endsWith("\n") ? logo.slice(0, -1).split("\n").length : logo.split("\n").length;
257
+ const splitHeight = Math.max(1, rows - logoHeight - 1);
258
+ render(
259
+ () => /* @__PURE__ */ React.createElement(
260
+ App,
261
+ {
262
+ initialPrompt,
263
+ options,
264
+ processUserInput,
265
+ infoLine,
266
+ infoDisplay
267
+ }
268
+ ),
269
+ {
270
+ useAlternateScreen: false,
271
+ exitOnCtrlC: true,
272
+ useMouse: true,
273
+ enableMouseMovement: true,
274
+ experimental_splitHeight: splitHeight
275
+ }
276
+ );
277
+ }
278
+ function App({ initialPrompt, options, processUserInput, infoLine, infoDisplay }) {
279
+ const renderer = useRenderer();
280
+ const dimensions = useTerminalDimensions();
281
+ const [messages, setMessages] = createSignal([]);
282
+ const [inputValue, setInputValue] = createSignal("");
283
+ const [showIntro, setShowIntro] = createSignal(true);
284
+ const [isFullScreen, setIsFullScreen] = createSignal(false);
285
+ const [spinnerFrame, setSpinnerFrame] = createSignal(0);
286
+ const [isLoading, setIsLoading] = createSignal(false);
287
+ const promptFg = "#00ffff";
288
+ const responseFg = "#ffffff";
289
+ const promptBg = "#2b2b2b";
290
+ let inputRef;
291
+ let currentMemoryId = options.memoryId;
292
+ let messageId = 0;
293
+ const usageText = infoDisplay?.usageLine ?? infoLine ?? "";
294
+ const versionText = infoDisplay?.versionLine ?? "";
295
+ const modelText = infoDisplay?.modelLine ?? "";
296
+ const appendMessage = (role, text) => {
297
+ setMessages((prev) => [...prev, { id: messageId++, role, text }]);
298
+ };
299
+ const exitApp = () => {
300
+ renderer.destroy();
301
+ process.exit(0);
302
+ };
303
+ const submitPrompt = async (raw) => {
304
+ const trimmed = raw.trim();
305
+ if (!trimmed) return;
306
+ if (trimmed.toLowerCase() === "exit" || trimmed.toLowerCase() === "quit") {
307
+ exitApp();
308
+ return;
309
+ }
310
+ if (showIntro()) {
311
+ setShowIntro(false);
312
+ }
313
+ if (!isFullScreen()) {
314
+ setIsFullScreen(true);
315
+ }
316
+ setInputValue("");
317
+ if (inputRef) {
318
+ inputRef.value = "";
319
+ }
320
+ appendMessage("prompt", trimmed);
321
+ try {
322
+ setIsLoading(true);
323
+ const result = await processUserInput(trimmed, { ...options, memoryId: currentMemoryId }, "tui");
324
+ currentMemoryId = result.memoryId;
325
+ appendMessage("response", result.response);
326
+ if (result.instruction) {
327
+ appendMessage("response", result.instruction);
328
+ }
329
+ } catch (error) {
330
+ const message = error instanceof Error ? error.message : String(error);
331
+ appendMessage("response", `Error: ${message}`);
332
+ } finally {
333
+ setIsLoading(false);
334
+ }
335
+ };
336
+ onMount(async () => {
337
+ const spinner = cliSpinners.dots;
338
+ const timer = setInterval(() => {
339
+ if (isLoading()) {
340
+ setSpinnerFrame((prev) => (prev + 1) % spinner.frames.length);
341
+ }
342
+ }, spinner.interval);
343
+ onCleanup(() => clearInterval(timer));
344
+ if (initialPrompt) {
345
+ await submitPrompt(initialPrompt);
346
+ }
347
+ if (inputRef) {
348
+ inputRef.focus();
349
+ }
350
+ });
351
+ const inputBoxWidth = () => Math.max(1, Math.round(dimensions().width * 0.6));
352
+ const inputBoxLeft = () => Math.max(0, Math.round(dimensions().width * 0.15));
353
+ const inputBoxHeight = () => isFullScreen() ? 7 : 14;
354
+ const inputBoxTop = () => isFullScreen() ? Math.max(1, dimensions().height - inputBoxHeight() - 2) : Math.max(1, Math.round(dimensions().height * 0.666));
355
+ const messagesTop = () => 1;
356
+ const messagesHeight = () => Math.max(1, inputBoxTop() - messagesTop() - 1);
357
+ const messagesWidth = () => Math.min(dimensions().width - 2, inputBoxWidth() + 10);
358
+ const messagesLeft = () => Math.max(1, inputBoxLeft() - 5);
359
+ return /* @__PURE__ */ React.createElement("box", { width: "100%", height: "100%", flexDirection: "column" }, /* @__PURE__ */ React.createElement(
360
+ "scrollbox",
361
+ {
362
+ id: "messages",
363
+ width: messagesWidth(),
364
+ height: messagesHeight(),
365
+ position: "absolute",
366
+ left: messagesLeft(),
367
+ top: messagesTop(),
368
+ paddingLeft: 1,
369
+ paddingRight: 1,
370
+ focused: true,
371
+ stickyScroll: true,
372
+ stickyStart: "bottom"
373
+ },
374
+ /* @__PURE__ */ React.createElement("box", { width: "100%", flexDirection: "column" }, /* @__PURE__ */ React.createElement(For, { each: messages() }, (message) => /* @__PURE__ */ React.createElement(
375
+ "box",
376
+ {
377
+ width: "100%",
378
+ flexDirection: "row",
379
+ justifyContent: message.role === "prompt" ? "flex-start" : "flex-end",
380
+ marginBottom: 1
381
+ },
382
+ /* @__PURE__ */ React.createElement(
383
+ "box",
384
+ {
385
+ paddingLeft: 1,
386
+ paddingRight: 1,
387
+ paddingTop: 1,
388
+ paddingBottom: 1,
389
+ backgroundColor: message.role === "prompt" ? promptBg : void 0
390
+ },
391
+ /* @__PURE__ */ React.createElement(
392
+ "text",
393
+ {
394
+ fg: message.role === "prompt" ? promptFg : responseFg,
395
+ width: "100%",
396
+ wrapMode: "word",
397
+ selectable: false
398
+ },
399
+ message.text
400
+ )
401
+ )
402
+ )))
403
+ ), /* @__PURE__ */ React.createElement(
404
+ "box",
405
+ {
406
+ id: "input-box",
407
+ width: inputBoxWidth(),
408
+ height: inputBoxHeight(),
409
+ position: "absolute",
410
+ left: inputBoxLeft(),
411
+ top: inputBoxTop(),
412
+ paddingLeft: 1,
413
+ paddingRight: 1,
414
+ paddingTop: 1,
415
+ flexDirection: "column"
416
+ },
417
+ /* @__PURE__ */ React.createElement(For, { each: !isFullScreen() && showIntro() ? [
418
+ "Use Satori just like you would use ChatGPT.",
419
+ "Except, it stores your conversations in a long term memory.",
420
+ "The memories you store here can be accessed through the SDK."
421
+ ] : [] }, (line) => /* @__PURE__ */ React.createElement("text", { fg: "cyan" }, line)),
422
+ /* @__PURE__ */ React.createElement(
423
+ "box",
424
+ {
425
+ id: "input-box",
426
+ width: inputBoxWidth(),
427
+ height: 5,
428
+ backgroundColor: "#1a1a1a",
429
+ flexDirection: "column",
430
+ justifyContent: "center"
431
+ },
432
+ /* @__PURE__ */ React.createElement(
433
+ "input",
434
+ {
435
+ id: "input",
436
+ width: "100%",
437
+ height: 1,
438
+ placeholder: "Type a message and press Enter...",
439
+ focusedBackgroundColor: "#1a1a1a",
440
+ onInput: (value) => setInputValue(value),
441
+ onSubmit: () => submitPrompt(inputValue()),
442
+ ref: (r) => {
443
+ inputRef = r;
444
+ }
445
+ }
446
+ ),
447
+ /* @__PURE__ */ React.createElement("box", { flexDirection: "row", flexShrink: 0, paddingTop: 1 }, /* @__PURE__ */ React.createElement("text", { fg: "#ffffff" }, modelText))
448
+ )
449
+ ), /* @__PURE__ */ React.createElement(Show, { when: isLoading() }, /* @__PURE__ */ React.createElement(
450
+ "box",
451
+ {
452
+ id: "spinner",
453
+ position: "absolute",
454
+ left: inputBoxLeft(),
455
+ top: inputBoxTop() + inputBoxHeight(),
456
+ paddingLeft: 1
457
+ },
458
+ /* @__PURE__ */ React.createElement("text", { fg: "#00ffff" }, cliSpinners.dots.frames[spinnerFrame()])
459
+ )), /* @__PURE__ */ React.createElement(
460
+ "box",
461
+ {
462
+ id: "footer",
463
+ width: dimensions().width,
464
+ height: 1,
465
+ position: "absolute",
466
+ bottom: 0,
467
+ left: 0,
468
+ backgroundColor: "#000000",
469
+ paddingLeft: 1,
470
+ paddingRight: 1,
471
+ flexDirection: "row",
472
+ justifyContent: "space-between",
473
+ alignItems: "center"
474
+ },
475
+ /* @__PURE__ */ React.createElement("text", { fg: "#00ffff", wrapMode: "none", width: "100%" }, usageText),
476
+ /* @__PURE__ */ React.createElement("box", { flexShrink: 0, paddingLeft: 1 }, /* @__PURE__ */ React.createElement("text", { fg: "#00ffff" }, versionText))
477
+ ));
478
+ }
479
+
211
480
  // src/index.ts
212
481
  async function main() {
213
482
  const argv = process.argv.slice(2);
214
- const isGetApiKey = argv[0] === "get" && argv[1] === "apikey";
483
+ const isGetApiKey = argv[0] === "get" && argv[1] === "api-key";
215
484
  if (!isGetApiKey) {
216
485
  try {
217
486
  await getConfig();
@@ -220,44 +489,83 @@ async function main() {
220
489
  process.exit(1);
221
490
  }
222
491
  }
223
- const __dirname = dirname(fileURLToPath(import.meta.url));
224
- const logoPath = join2(__dirname, "..", "logo.txt");
225
- console.log(chalk.cyan(readFileSync(logoPath, "utf8")));
226
492
  const program = new Command();
227
493
  program.name("satori").description("CLI tool for Satori memory server").version("0.0.1");
228
494
  program.option("--memory-id <id>", "Memory ID for scoping");
229
495
  const DEFAULT_LLM_MODEL = "gpt-4o";
496
+ const getCliVersion = () => {
497
+ const __dirname = dirname2(fileURLToPath2(import.meta.url));
498
+ const packagePath = join3(__dirname, "..", "package.json");
499
+ const raw = readFileSync(packagePath, "utf8");
500
+ const data = JSON.parse(raw);
501
+ return data.version ?? "unknown";
502
+ };
503
+ const fetchOrgUsage = async () => {
504
+ try {
505
+ const config = await getConfig();
506
+ const response = await fetch(`${config.baseUrl}/org/usage`, {
507
+ method: "GET",
508
+ headers: {
509
+ "Authorization": `Bearer ${config.apiKey}`
510
+ }
511
+ });
512
+ if (!response.ok) {
513
+ return null;
514
+ }
515
+ return await response.json();
516
+ } catch {
517
+ return null;
518
+ }
519
+ };
520
+ const getInfoDisplay = async () => {
521
+ const usage = await fetchOrgUsage();
522
+ const tokens = usage ? usage.total_tokens.toLocaleString() : "unknown";
523
+ const memoryId = usage?.memory_id ?? "unknown";
524
+ const version = getCliVersion();
525
+ return {
526
+ usageLine: `Tokens Used ${tokens} | Memory Layer: ${memoryId}`,
527
+ versionLine: `${version}`,
528
+ modelLine: `Model: ${DEFAULT_LLM_MODEL}`,
529
+ fullLine: `Model: ${DEFAULT_LLM_MODEL} | Tokens: ${tokens} | Memory ID: ${memoryId} | CLI: ${version}`
530
+ };
531
+ };
230
532
  const callAskAPI = async (prompt, memoryId) => {
231
533
  const config = await getConfig();
534
+ const headers = {
535
+ "Content-Type": "application/json",
536
+ "Authorization": `Bearer ${config.apiKey}`
537
+ };
538
+ if (config.openaiApiKey) {
539
+ headers["X-OpenAI-Key"] = config.openaiApiKey;
540
+ }
232
541
  const response = await fetch(`${config.baseUrl}/ask`, {
233
542
  method: "POST",
234
- headers: {
235
- "Content-Type": "application/json",
236
- "Authorization": `Bearer ${config.apiKey}`
237
- },
543
+ headers,
238
544
  body: JSON.stringify({
239
545
  prompt,
240
546
  memory_id: memoryId,
241
547
  llm_model: DEFAULT_LLM_MODEL
242
548
  })
243
549
  });
550
+ if (response.status === 402) {
551
+ console.error("Free tier exceeded. Run `npx @satori-sh sub` to subscribe");
552
+ process.exit(1);
553
+ }
244
554
  if (!response.ok) {
245
555
  throw new Error(`HTTP error: ${response.status} ${response.statusText}`);
246
556
  }
247
557
  const data = await response.json();
248
558
  return data.response;
249
559
  };
250
- const processUserInput = async (input, options, isInteractive = false) => {
560
+ const processUserInput = async (input, options, outputMode) => {
251
561
  const { memoryId, instruction } = await resolveMemoryId({ memoryId: options.memoryId });
252
562
  const response = await callAskAPI(input, memoryId);
253
- if (isInteractive) {
254
- console.log(`Assistant: ${response}`);
255
- } else {
563
+ if (outputMode === "cli") {
256
564
  console.log(response);
257
- }
258
- if (instruction) {
259
- console.log(`
565
+ if (instruction) {
566
+ console.log(`
260
567
  ${instruction}`);
568
+ }
261
569
  }
262
570
  addMemories(input, { memoryId }).catch((err) => {
263
571
  console.error("Failed to save memory:", err);
@@ -266,54 +574,26 @@ ${instruction}`);
266
574
  };
267
575
  program.argument("[prompt]", "initial prompt for chat session (optional)").action(async (initialPrompt, options) => {
268
576
  try {
269
- let memoryId = options.memoryId;
270
- if (!process.stdin.isTTY) {
577
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
271
578
  if (initialPrompt) {
272
- await processUserInput(initialPrompt, options, false);
579
+ const info2 = await getInfoDisplay();
580
+ console.log(info2.fullLine);
581
+ await processUserInput(initialPrompt, options, "cli");
273
582
  }
274
583
  return;
275
584
  }
276
- if (initialPrompt) {
277
- const result = await processUserInput(initialPrompt, options, true);
278
- memoryId = result.memoryId;
279
- }
280
- const { createInterface } = await import("readline");
281
- const rl = createInterface({
282
- input: process.stdin,
283
- output: process.stdout
284
- });
285
- const getPrompt = async () => {
286
- if (!memoryId) {
287
- const resolved = await resolveMemoryId({ memoryId: options.memoryId });
288
- memoryId = resolved.memoryId;
585
+ const info = await getInfoDisplay();
586
+ await runInteractiveApp({
587
+ initialPrompt,
588
+ options,
589
+ processUserInput,
590
+ infoLine: info.fullLine,
591
+ infoDisplay: {
592
+ usageLine: info.usageLine,
593
+ versionLine: info.versionLine,
594
+ modelLine: info.modelLine
289
595
  }
290
- return chalk.cyan(`[${memoryId}] > `);
291
- };
292
- const chatLoop = async () => {
293
- const prompt = await getPrompt();
294
- rl.question(prompt, async (input) => {
295
- if (input.toLowerCase() === "exit" || input.toLowerCase() === "quit") {
296
- console.log("Goodbye!");
297
- rl.close();
298
- return;
299
- }
300
- if (!input.trim()) {
301
- chatLoop();
302
- return;
303
- }
304
- try {
305
- const result = await processUserInput(input, { ...options, memoryId }, true);
306
- memoryId = result.memoryId;
307
- } catch (error) {
308
- console.error("Chat error:", error instanceof Error ? error.message : error);
309
- }
310
- chatLoop();
311
- });
312
- };
313
- console.log(chalk.magenta("\nSatori is memory for AI. It remembers your sessions forever. Think of it like 'infinite context' for AI.\n"));
314
- console.log(chalk.cyan("You're in interactive mode. Use interactive mode just like you would use ChatGPT."));
315
- console.log(chalk.cyan('Type "exit" or "quit" to end the session.\n'));
316
- chatLoop();
596
+ });
317
597
  } catch (error) {
318
598
  console.error("Chat error:", error instanceof Error ? error.message : error);
319
599
  process.exit(1);
@@ -325,8 +605,35 @@ ${instruction}`);
325
605
  program.command("search").description("search memories").argument("<query>", "search query for memories").action(async (query) => {
326
606
  await searchMemories(query);
327
607
  });
328
- program.command("get").description("get config value").argument("<key>", "key to get").action(async (key) => {
329
- if (key !== "apikey") {
608
+ program.command("sub").description("open the subscription checkout").action(async () => {
609
+ const config = await getConfig();
610
+ const checkoutUrlRaw = config.checkoutUrl;
611
+ if (!checkoutUrlRaw) {
612
+ console.error("SATORI_CHECKOUT_URL is not set");
613
+ process.exit(1);
614
+ }
615
+ let checkoutUrl;
616
+ try {
617
+ checkoutUrl = new URL(checkoutUrlRaw).toString();
618
+ } catch {
619
+ console.error("SATORI_CHECKOUT_URL is not a valid URL");
620
+ process.exit(1);
621
+ }
622
+ if (process.platform !== "darwin") {
623
+ console.log(checkoutUrl);
624
+ return;
625
+ }
626
+ await new Promise((resolve) => {
627
+ execFile("open", [checkoutUrl], (error) => {
628
+ if (error) {
629
+ console.log(checkoutUrl);
630
+ }
631
+ resolve();
632
+ });
633
+ });
634
+ });
635
+ program.command("get").description("get config value (e.g. `get api-key`)").argument("<key>", "key to get").action(async (key) => {
636
+ if (key !== "api-key") {
330
637
  console.error("Unknown key");
331
638
  process.exit(1);
332
639
  }
@@ -342,7 +649,7 @@ ${instruction}`);
342
649
  program.parse();
343
650
  }
344
651
  var entryPath = process.argv[1] ? realpathSync(process.argv[1]) : "";
345
- var modulePath = realpathSync(fileURLToPath(import.meta.url));
652
+ var modulePath = realpathSync(fileURLToPath2(import.meta.url));
346
653
  if (entryPath === modulePath) {
347
654
  main();
348
655
  }