nconv-cli 1.0.0 → 1.0.3

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/index.js CHANGED
@@ -4,20 +4,108 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/config.ts
7
- import { config as dotenvConfig } from "dotenv";
8
- import { resolve } from "path";
9
- dotenvConfig();
7
+ import { existsSync, readFileSync } from "fs";
8
+ import { resolve, join } from "path";
9
+ import os from "os";
10
+
11
+ // src/utils/logger.ts
12
+ import chalk from "chalk";
13
+ import ora from "ora";
14
+ function success(message) {
15
+ console.log(chalk.green("\u2713"), message);
16
+ }
17
+ function error(message) {
18
+ console.error(chalk.red("\u2717"), message);
19
+ }
20
+ function info(message) {
21
+ console.log(chalk.blue("\u2139"), message);
22
+ }
23
+ function warn(message) {
24
+ console.log(chalk.yellow("\u26A0"), message);
25
+ }
26
+ function spinner(text) {
27
+ return ora(text).start();
28
+ }
29
+
30
+ // src/config.ts
31
+ process.env.DOTENV_CONFIG_SILENT = "true";
32
+ function getConfigPath() {
33
+ return join(os.homedir(), ".nconv", ".env");
34
+ }
35
+ function loadEnv() {
36
+ const configPath = getConfigPath();
37
+ if (!existsSync(configPath)) {
38
+ return;
39
+ }
40
+ try {
41
+ const content = readFileSync(configPath, "utf-8");
42
+ let loadedCount = 0;
43
+ content.split("\n").forEach((line) => {
44
+ const trimmed = line.trim();
45
+ if (!trimmed || trimmed.startsWith("#")) {
46
+ return;
47
+ }
48
+ const equalIndex = trimmed.indexOf("=");
49
+ if (equalIndex === -1) {
50
+ return;
51
+ }
52
+ const key = trimmed.substring(0, equalIndex).trim();
53
+ const value = trimmed.substring(equalIndex + 1).trim();
54
+ if (key && value) {
55
+ process.env[key] = value;
56
+ loadedCount++;
57
+ }
58
+ });
59
+ if (process.env.NCONV_VERBOSE) {
60
+ info(`\u2713 Loaded ${loadedCount} environment variable(s) from ${configPath}`);
61
+ }
62
+ } catch (error2) {
63
+ error(`Failed to load configuration from: ${configPath}`);
64
+ if (error2 instanceof Error) {
65
+ error(error2.message);
66
+ }
67
+ }
68
+ }
69
+ function checkEnv() {
70
+ const configPath = getConfigPath();
71
+ if (!process.env.TOKEN_V2 || !process.env.FILE_TOKEN) {
72
+ error("Notion tokens are not set.");
73
+ if (!existsSync(configPath)) {
74
+ error("Configuration file not found.");
75
+ error('Please run "nconv init" to create a configuration file.');
76
+ } else {
77
+ error(`Please set TOKEN_V2 and FILE_TOKEN in: ${configPath}`);
78
+ }
79
+ process.exit(1);
80
+ }
81
+ }
82
+ function validateConfig() {
83
+ loadEnv();
84
+ const configPath = getConfigPath();
85
+ if (!process.env.TOKEN_V2 || !process.env.FILE_TOKEN) {
86
+ if (!existsSync(configPath)) {
87
+ return {
88
+ valid: false,
89
+ message: "Configuration file not found. Please run /init to set up your Notion tokens."
90
+ };
91
+ } else {
92
+ return {
93
+ valid: false,
94
+ message: `Notion tokens are not set. Please run /init or /config to set up your tokens.
95
+ Config file: ${configPath}`
96
+ };
97
+ }
98
+ }
99
+ return { valid: true };
100
+ }
10
101
  function getNotionConfig() {
11
102
  const tokenV2 = process.env.TOKEN_V2 || "";
12
103
  const fileToken = process.env.FILE_TOKEN || "";
13
- if (!tokenV2 || !fileToken) {
14
- throw new Error(
15
- "Notion \uD1A0\uD070\uC774 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n.env \uD30C\uC77C\uC5D0 TOKEN_V2\uC640 FILE_TOKEN\uC744 \uC124\uC815\uD574\uC8FC\uC138\uC694.\n\uC790\uC138\uD55C \uB0B4\uC6A9\uC740 .env.example\uC744 \uCC38\uACE0\uD558\uC138\uC694."
16
- );
17
- }
18
104
  return { tokenV2, fileToken };
19
105
  }
20
106
  function createConfig(options) {
107
+ loadEnv();
108
+ checkEnv();
21
109
  const notionConfig = getNotionConfig();
22
110
  const outputDir = resolve(process.cwd(), options.output);
23
111
  return {
@@ -31,9 +119,12 @@ function createConfig(options) {
31
119
  import { NotionExporter } from "notion-exporter";
32
120
  import { promises as fs } from "fs";
33
121
  import path from "path";
122
+ import { mdToPdf } from "md-to-pdf";
34
123
  var NotionMarkdownExporter = class {
35
- constructor(config) {
36
- this.exporter = new NotionExporter(config.tokenV2, config.fileToken);
124
+ constructor(config, exportType = "markdown") {
125
+ this.exporter = new NotionExporter(config.tokenV2, config.fileToken, {
126
+ exportType
127
+ });
37
128
  }
38
129
  /**
39
130
  * Notion URL에서 마크다운과 이미지 파일 가져오기
@@ -73,6 +164,115 @@ var NotionMarkdownExporter = class {
73
164
  throw new Error("Failed to fetch Notion page.");
74
165
  }
75
166
  }
167
+ /**
168
+ * Notion URL에서 HTML과 이미지 파일 가져오기
169
+ */
170
+ async exportHTML(notionUrl, tempDir) {
171
+ try {
172
+ await fs.mkdir(tempDir, { recursive: true });
173
+ await this.exporter.getMdFiles(notionUrl, tempDir);
174
+ const files = await fs.readdir(tempDir, { withFileTypes: true });
175
+ const htmlFile = files.find((f) => f.isFile() && f.name.endsWith(".html"));
176
+ if (!htmlFile) {
177
+ throw new Error("HTML file not found.");
178
+ }
179
+ const htmlPath = path.join(tempDir, htmlFile.name);
180
+ const html = await fs.readFile(htmlPath, "utf-8");
181
+ const imageFiles = files.filter((f) => f.isFile() && !f.name.endsWith(".html")).map((f) => ({
182
+ filename: f.name,
183
+ sourcePath: path.join(tempDir, f.name)
184
+ }));
185
+ const dirs = files.filter((f) => f.isDirectory());
186
+ for (const dir of dirs) {
187
+ const subFiles = await fs.readdir(path.join(tempDir, dir.name), { withFileTypes: true });
188
+ for (const subFile of subFiles) {
189
+ if (subFile.isFile() && !subFile.name.endsWith(".html")) {
190
+ imageFiles.push({
191
+ filename: path.join(dir.name, subFile.name),
192
+ sourcePath: path.join(tempDir, dir.name, subFile.name)
193
+ });
194
+ }
195
+ }
196
+ }
197
+ return { html, imageFiles };
198
+ } catch (error2) {
199
+ if (error2 instanceof Error) {
200
+ throw new Error(`Failed to fetch Notion page as HTML: ${error2.message}`);
201
+ }
202
+ throw new Error("Failed to fetch Notion page as HTML.");
203
+ }
204
+ }
205
+ /**
206
+ * Markdown을 PDF로 변환 (md-to-pdf 사용)
207
+ * GitHub 스타일의 깔끔한 PDF 생성
208
+ */
209
+ async exportMarkdownToPDF(markdownContent, outputPath, options = {}) {
210
+ try {
211
+ const pdfOptions = {
212
+ content: markdownContent,
213
+ stylesheet: options.stylesheet || [
214
+ "https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"
215
+ ],
216
+ body_class: "markdown-body",
217
+ css: `
218
+ .markdown-body {
219
+ box-sizing: border-box;
220
+ min-width: 200px;
221
+ max-width: 980px;
222
+ margin: 0 auto;
223
+ padding: 45px;
224
+ }
225
+
226
+ @media (max-width: 767px) {
227
+ .markdown-body {
228
+ padding: 15px;
229
+ }
230
+ }
231
+
232
+ /* \uCF54\uB4DC \uBE14\uB85D \uC2A4\uD0C0\uC77C \uAC1C\uC120 */
233
+ .markdown-body pre {
234
+ background-color: #f6f8fa;
235
+ border-radius: 6px;
236
+ padding: 16px;
237
+ overflow: auto;
238
+ }
239
+
240
+ .markdown-body code {
241
+ background-color: rgba(175, 184, 193, 0.2);
242
+ border-radius: 6px;
243
+ padding: 0.2em 0.4em;
244
+ }
245
+
246
+ /* \uC774\uBBF8\uC9C0 \uC2A4\uD0C0\uC77C */
247
+ .markdown-body img {
248
+ max-width: 100%;
249
+ height: auto;
250
+ }
251
+ `,
252
+ pdf_options: {
253
+ format: options.format || "A4",
254
+ margin: options.margin || {
255
+ top: "20mm",
256
+ right: "20mm",
257
+ bottom: "20mm",
258
+ left: "20mm"
259
+ },
260
+ printBackground: true
261
+ }
262
+ };
263
+ const result = await mdToPdf(pdfOptions);
264
+ if (result.content) {
265
+ await fs.writeFile(outputPath, result.content);
266
+ } else {
267
+ throw new Error("PDF content is empty");
268
+ }
269
+ } catch (error2) {
270
+ if (error2 instanceof Error) {
271
+ throw new Error(`Failed to convert Markdown to PDF: ${error2.message}`);
272
+ }
273
+ throw new Error("Failed to convert Markdown to PDF.");
274
+ }
275
+ }
76
276
  };
77
277
 
78
278
  // src/utils/file.ts
@@ -115,28 +315,12 @@ async function saveMarkdownFile(outputDir, filename, content) {
115
315
  return filePath;
116
316
  }
117
317
 
118
- // src/utils/logger.ts
119
- import chalk from "chalk";
120
- import ora from "ora";
121
- function success(message) {
122
- console.log(chalk.green("\u2713"), message);
123
- }
124
- function error(message) {
125
- console.error(chalk.red("\u2717"), message);
126
- }
127
- function info(message) {
128
- console.log(chalk.blue("\u2139"), message);
129
- }
130
- function spinner(text) {
131
- return ora(text).start();
132
- }
133
-
134
318
  // src/commands/md.ts
135
319
  import path3 from "path";
136
320
  import { promises as fs3 } from "fs";
137
- import os from "os";
321
+ import os2 from "os";
138
322
  async function mdCommand(notionUrl, options) {
139
- const tempDir = path3.join(os.tmpdir(), `nconv-cli-${Date.now()}`);
323
+ const tempDir = path3.join(os2.tmpdir(), `nconv-cli-${Date.now()}`);
140
324
  try {
141
325
  const config = createConfig(options);
142
326
  if (config.verbose) {
@@ -166,10 +350,6 @@ async function mdCommand(notionUrl, options) {
166
350
  }
167
351
  const pageDir = path3.join(config.output, baseFilename);
168
352
  await fs3.mkdir(pageDir, { recursive: true });
169
- if (config.verbose) {
170
- console.log(`\u{1F4C1} \uCD9C\uB825 \uD3F4\uB354: ${path3.relative(process.cwd(), pageDir)}
171
- `);
172
- }
173
353
  const imageOutputDir = path3.join(pageDir, config.imageDir);
174
354
  await fs3.mkdir(imageOutputDir, { recursive: true });
175
355
  if (config.verbose && result.imageFiles.length > 0) {
@@ -225,10 +405,206 @@ async function mdCommand(notionUrl, options) {
225
405
  }
226
406
  }
227
407
 
408
+ // src/commands/html.ts
409
+ import path4 from "path";
410
+ import { promises as fs4 } from "fs";
411
+ import os3 from "os";
412
+ async function htmlCommand(notionUrl, options) {
413
+ const tempDir = path4.join(os3.tmpdir(), `nconv-cli-${Date.now()}`);
414
+ try {
415
+ const config = createConfig(options);
416
+ if (config.verbose) {
417
+ info("Configuration loaded successfully");
418
+ console.log(` Output directory: ${config.output}
419
+ `);
420
+ }
421
+ const spinner2 = spinner("Fetching Notion page as HTML...");
422
+ const exporter = new NotionMarkdownExporter({
423
+ tokenV2: config.tokenV2,
424
+ fileToken: config.fileToken
425
+ }, "html");
426
+ let result;
427
+ try {
428
+ result = await exporter.exportHTML(notionUrl, tempDir);
429
+ spinner2.succeed(`Notion page fetched (${result.imageFiles.length} images)`);
430
+ } catch (error2) {
431
+ spinner2.fail("Failed to fetch Notion page");
432
+ throw error2;
433
+ }
434
+ let baseFilename;
435
+ if (config.filename) {
436
+ baseFilename = config.filename.replace(/\.html?$/, "");
437
+ } else {
438
+ const title = extractTitleFromUrl(notionUrl);
439
+ baseFilename = generateSafeFilename(title, "");
440
+ }
441
+ const pageDir = path4.join(config.output, baseFilename);
442
+ await fs4.mkdir(pageDir, { recursive: true });
443
+ const imageOutputDir = path4.join(pageDir, config.imageDir);
444
+ await fs4.mkdir(imageOutputDir, { recursive: true });
445
+ if (config.verbose && result.imageFiles.length > 0) {
446
+ console.log(`Processing image files...
447
+ `);
448
+ }
449
+ let processedHtml = result.html;
450
+ for (const imageFile of result.imageFiles) {
451
+ try {
452
+ const originalFileName = path4.basename(imageFile.filename);
453
+ const safeFileName = originalFileName.replace(/\s+/g, "-");
454
+ const targetPath = path4.join(imageOutputDir, safeFileName);
455
+ await fs4.copyFile(imageFile.sourcePath, targetPath);
456
+ if (config.verbose) {
457
+ console.log(`\u2713 ${safeFileName}`);
458
+ }
459
+ const originalPath = imageFile.filename;
460
+ const relativePath = `./${config.imageDir}/${safeFileName}`;
461
+ const pathParts = originalPath.split("/");
462
+ const encodedPath = pathParts.map((part) => encodeURIComponent(part)).join("/");
463
+ const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
464
+ processedHtml = processedHtml.replace(new RegExp(`(src|href)="${escapeRegex(originalPath)}"`, "g"), `$1="${relativePath}"`).replace(new RegExp(`(src|href)="${escapeRegex(encodedPath)}"`, "g"), `$1="${relativePath}"`);
465
+ } catch (error2) {
466
+ if (config.verbose) {
467
+ const errorMsg = error2 instanceof Error ? error2.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958";
468
+ console.error(`\u2717 ${imageFile.filename}: ${errorMsg}`);
469
+ }
470
+ }
471
+ }
472
+ const filename = `${baseFilename}.html`;
473
+ const filePath = path4.join(pageDir, filename);
474
+ await fs4.writeFile(filePath, processedHtml, "utf-8");
475
+ console.log("");
476
+ success("Conversion complete!");
477
+ console.log("");
478
+ console.log(`\u{1F4C1} Folder: ${path4.relative(process.cwd(), pageDir)}`);
479
+ console.log(`\u{1F4C4} HTML: ${filename}`);
480
+ if (result.imageFiles.length > 0) {
481
+ console.log(`\u{1F5BC}\uFE0F Images: ${config.imageDir}/ (${result.imageFiles.length} files)`);
482
+ }
483
+ console.log("");
484
+ } catch (error2) {
485
+ if (error2 instanceof Error) {
486
+ error(error2.message);
487
+ } else {
488
+ error("An unknown error occurred.");
489
+ }
490
+ process.exit(1);
491
+ } finally {
492
+ try {
493
+ await fs4.rm(tempDir, { recursive: true, force: true });
494
+ } catch {
495
+ }
496
+ }
497
+ }
498
+
499
+ // src/commands/pdf.ts
500
+ import path5 from "path";
501
+ import { promises as fs5 } from "fs";
502
+ import os4 from "os";
503
+ async function pdfCommand(notionUrl, options) {
504
+ const tempDir = path5.join(os4.tmpdir(), `nconv-cli-${Date.now()}`);
505
+ try {
506
+ const config = createConfig(options);
507
+ if (config.verbose) {
508
+ info("Configuration loaded successfully");
509
+ console.log(` Output directory: ${config.output}
510
+ `);
511
+ }
512
+ const spinner2 = spinner("Fetching Notion page as Markdown...");
513
+ const exporter = new NotionMarkdownExporter({
514
+ tokenV2: config.tokenV2,
515
+ fileToken: config.fileToken
516
+ });
517
+ let result;
518
+ try {
519
+ result = await exporter.exportWithImages(notionUrl, tempDir);
520
+ spinner2.succeed(`Notion page fetched (${result.imageFiles.length} images)`);
521
+ } catch (error2) {
522
+ spinner2.fail("Failed to fetch Notion page");
523
+ throw error2;
524
+ }
525
+ let baseFilename;
526
+ if (config.filename) {
527
+ baseFilename = config.filename.replace(/\.pdf$/, "");
528
+ } else {
529
+ const title = extractTitleFromUrl(notionUrl);
530
+ baseFilename = generateSafeFilename(title, "");
531
+ }
532
+ const pageDir = path5.join(config.output, baseFilename);
533
+ await fs5.mkdir(pageDir, { recursive: true });
534
+ const imageOutputDir = path5.join(pageDir, config.imageDir);
535
+ await fs5.mkdir(imageOutputDir, { recursive: true });
536
+ if (config.verbose && result.imageFiles.length > 0) {
537
+ console.log(`Processing image files...
538
+ `);
539
+ }
540
+ let processedMarkdown = result.markdown;
541
+ for (const imageFile of result.imageFiles) {
542
+ try {
543
+ const originalFileName = path5.basename(imageFile.filename);
544
+ const safeFileName = originalFileName.replace(/\s+/g, "-");
545
+ const targetPath = path5.join(imageOutputDir, safeFileName);
546
+ await fs5.copyFile(imageFile.sourcePath, targetPath);
547
+ if (config.verbose) {
548
+ console.log(`\u2713 ${safeFileName}`);
549
+ }
550
+ const imageBuffer = await fs5.readFile(targetPath);
551
+ const base64 = imageBuffer.toString("base64");
552
+ const ext = path5.extname(safeFileName).slice(1).toLowerCase();
553
+ const mimeType = ext === "jpg" ? "jpeg" : ext;
554
+ const dataUrl = `data:image/${mimeType};base64,${base64}`;
555
+ const originalPath = imageFile.filename;
556
+ const relativePath = `./${config.imageDir}/${safeFileName}`;
557
+ const pathParts = originalPath.split("/");
558
+ const encodedPath = pathParts.map((part) => encodeURIComponent(part)).join("/");
559
+ const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
560
+ processedMarkdown = processedMarkdown.replace(new RegExp(`\\(${escapeRegex(originalPath)}\\)`, "g"), `(${dataUrl})`).replace(new RegExp(`\\(${escapeRegex(encodedPath)}\\)`, "g"), `(${dataUrl})`);
561
+ } catch (error2) {
562
+ if (config.verbose) {
563
+ const errorMsg = error2 instanceof Error ? error2.message : "Unknown error";
564
+ console.error(`\u2717 ${imageFile.filename}: ${errorMsg}`);
565
+ }
566
+ }
567
+ }
568
+ const pdfSpinner = spinner("Converting Markdown to PDF...");
569
+ const filename = `${baseFilename}.pdf`;
570
+ const pdfPath = path5.join(pageDir, filename);
571
+ try {
572
+ await exporter.exportMarkdownToPDF(processedMarkdown, pdfPath, {
573
+ format: "A4"
574
+ });
575
+ pdfSpinner.succeed("PDF generated successfully");
576
+ } catch (error2) {
577
+ pdfSpinner.fail("Failed to generate PDF");
578
+ throw error2;
579
+ }
580
+ console.log("");
581
+ success("PDF export complete!");
582
+ console.log("");
583
+ console.log(`\u{1F4C1} Folder: ${path5.relative(process.cwd(), pageDir)}`);
584
+ console.log(`\u{1F4C4} PDF: ${filename}`);
585
+ if (result.imageFiles.length > 0) {
586
+ console.log(`\u{1F5BC}\uFE0F Images: ${config.imageDir}/ (${result.imageFiles.length} files)`);
587
+ }
588
+ console.log("");
589
+ } catch (error2) {
590
+ if (error2 instanceof Error) {
591
+ error(error2.message);
592
+ } else {
593
+ error("An unknown error occurred.");
594
+ }
595
+ process.exit(1);
596
+ } finally {
597
+ try {
598
+ await fs5.rm(tempDir, { recursive: true, force: true });
599
+ } catch {
600
+ }
601
+ }
602
+ }
603
+
228
604
  // src/commands/debug.ts
229
- import * as fs4 from "fs/promises";
230
- import * as os2 from "os";
231
- import * as path4 from "path";
605
+ import * as fs6 from "fs/promises";
606
+ import * as os5 from "os";
607
+ import * as path6 from "path";
232
608
  async function debugCommand(notionUrl, options) {
233
609
  let tempDir;
234
610
  try {
@@ -237,7 +613,7 @@ async function debugCommand(notionUrl, options) {
237
613
  tokenV2: config.tokenV2,
238
614
  fileToken: config.fileToken
239
615
  });
240
- tempDir = path4.join(os2.tmpdir(), `notion-debug-${Date.now()}`);
616
+ tempDir = path6.join(os5.tmpdir(), `notion-debug-${Date.now()}`);
241
617
  console.log("Fetching Notion page...\n");
242
618
  const { markdown, imageFiles } = await exporter.exportWithImages(notionUrl, tempDir);
243
619
  console.log("=== Raw Markdown ===\n");
@@ -253,17 +629,614 @@ async function debugCommand(notionUrl, options) {
253
629
  console.error("Error:", error2);
254
630
  } finally {
255
631
  if (tempDir) {
256
- await fs4.rm(tempDir, { recursive: true, force: true }).catch((err) => {
632
+ await fs6.rm(tempDir, { recursive: true, force: true }).catch((err) => {
257
633
  console.warn(`Error cleaning up temporary directory: ${err.message}`);
258
634
  });
259
635
  }
260
636
  }
261
637
  }
262
638
 
639
+ // src/commands/init.ts
640
+ import fs7 from "fs";
641
+ import path7 from "path";
642
+ import os6 from "os";
643
+ var handler = async (argv) => {
644
+ const configDir = path7.join(os6.homedir(), ".nconv");
645
+ const configFile = path7.join(configDir, ".env");
646
+ info(`Checking for config file at: ${configFile}`);
647
+ if (fs7.existsSync(configFile)) {
648
+ warn("Configuration file already exists.");
649
+ warn(`If you want to re-initialize, please delete the file first: ${configFile}`);
650
+ return;
651
+ }
652
+ const envContent = `
653
+ # Please provide your Notion access tokens.
654
+ # These are required to fetch content from your Notion pages.
655
+ # You can find these tokens in your browser's cookies when you are logged into Notion.
656
+ TOKEN_V2=
657
+ FILE_TOKEN=
658
+ `.trim();
659
+ try {
660
+ info(`Creating directory at: ${configDir}`);
661
+ if (!fs7.existsSync(configDir)) {
662
+ fs7.mkdirSync(configDir, { recursive: true });
663
+ }
664
+ fs7.writeFileSync(configFile, envContent);
665
+ info("\u2705 Successfully created configuration file.");
666
+ info(`Please edit the file to set your environment variables: ${configFile}`);
667
+ } catch (error2) {
668
+ error("Failed to create configuration file.");
669
+ if (error2 instanceof Error) {
670
+ error(error2.message);
671
+ }
672
+ }
673
+ };
674
+
675
+ // src/repl/index.ts
676
+ import prompts from "prompts";
677
+ import chalk2 from "chalk";
678
+
679
+ // src/repl/commands.ts
680
+ import { input as input2, confirm as confirm2 } from "@inquirer/prompts";
681
+
682
+ // src/repl/prompts.ts
683
+ import { input } from "@inquirer/prompts";
684
+ import fs8 from "fs";
685
+ import path8 from "path";
686
+ import os7 from "os";
687
+ function getConfigPath2() {
688
+ return path8.join(os7.homedir(), ".nconv", ".env");
689
+ }
690
+ function getConfigDir() {
691
+ return path8.join(os7.homedir(), ".nconv");
692
+ }
693
+ function loadConfig() {
694
+ const configPath = getConfigPath2();
695
+ if (!fs8.existsSync(configPath)) {
696
+ return null;
697
+ }
698
+ const content = fs8.readFileSync(configPath, "utf-8");
699
+ const config = { TOKEN_V2: "", FILE_TOKEN: "" };
700
+ content.split("\n").forEach((line) => {
701
+ const trimmed = line.trim();
702
+ if (trimmed.startsWith("#") || !trimmed) return;
703
+ const [key, ...valueParts] = trimmed.split("=");
704
+ const value = valueParts.join("=").trim();
705
+ if (key === "TOKEN_V2") config.TOKEN_V2 = value;
706
+ if (key === "FILE_TOKEN") config.FILE_TOKEN = value;
707
+ });
708
+ return config;
709
+ }
710
+ function saveConfig(config) {
711
+ const configDir = getConfigDir();
712
+ const configPath = getConfigPath2();
713
+ const envContent = `# Notion Access Tokens
714
+ # These tokens are required to fetch content from Notion.
715
+ # You can find them in your browser's cookies when logged into Notion.
716
+
717
+ TOKEN_V2=${config.TOKEN_V2}
718
+ FILE_TOKEN=${config.FILE_TOKEN}
719
+ `;
720
+ try {
721
+ if (!fs8.existsSync(configDir)) {
722
+ fs8.mkdirSync(configDir, { recursive: true });
723
+ }
724
+ fs8.writeFileSync(configPath, envContent);
725
+ info("\u2705 Configuration saved successfully.");
726
+ } catch (error2) {
727
+ error("Failed to save configuration.");
728
+ if (error2 instanceof Error) {
729
+ error(error2.message);
730
+ }
731
+ throw error2;
732
+ }
733
+ }
734
+ async function promptInitConfig() {
735
+ info("Please enter your Notion access tokens.");
736
+ info("You can find these in your browser cookies when logged into Notion.\n");
737
+ const TOKEN_V2 = await input({
738
+ message: "TOKEN_V2:",
739
+ required: true,
740
+ validate: (value) => {
741
+ if (!value.trim()) return "TOKEN_V2 is required";
742
+ return true;
743
+ }
744
+ });
745
+ const FILE_TOKEN = await input({
746
+ message: "FILE_TOKEN:",
747
+ required: true,
748
+ validate: (value) => {
749
+ if (!value.trim()) return "FILE_TOKEN is required";
750
+ return true;
751
+ }
752
+ });
753
+ return { TOKEN_V2, FILE_TOKEN };
754
+ }
755
+ async function promptEditConfig(existing) {
756
+ info("Current configuration:\n");
757
+ const TOKEN_V2 = await input({
758
+ message: "TOKEN_V2:",
759
+ default: existing.TOKEN_V2,
760
+ required: true
761
+ });
762
+ const FILE_TOKEN = await input({
763
+ message: "FILE_TOKEN:",
764
+ default: existing.FILE_TOKEN,
765
+ required: true
766
+ });
767
+ return { TOKEN_V2, FILE_TOKEN };
768
+ }
769
+
770
+ // src/repl/commands.ts
771
+ async function handleInit() {
772
+ const existing = loadConfig();
773
+ const isFirstTime = !existing || !existing.TOKEN_V2 && !existing.FILE_TOKEN;
774
+ if (!isFirstTime) {
775
+ warn("Configuration already exists.");
776
+ try {
777
+ const overwrite = await confirm2({
778
+ message: "Do you want to overwrite the existing configuration?",
779
+ default: false
780
+ });
781
+ if (!overwrite) {
782
+ info("Configuration unchanged. Use /config to view or edit your settings.");
783
+ return;
784
+ }
785
+ } catch (error2) {
786
+ if (error2 instanceof Error && error2.message === "User force closed the prompt") {
787
+ warn("\nCancelled.");
788
+ }
789
+ return;
790
+ }
791
+ }
792
+ info("\n\u{1F4DD} How to find your Notion tokens\n");
793
+ info("1. Log in to https://notion.so in your browser");
794
+ info("2. Open browser developer tools (press F12)");
795
+ info('3. Go to the "Application" tab');
796
+ info('4. Find "Cookies" section and select https://www.notion.so');
797
+ info('5. Copy the value of "token_v2" cookie \u2192 TOKEN_V2');
798
+ info('6. Copy the value of "file_token" cookie \u2192 FILE_TOKEN\n');
799
+ try {
800
+ const config = await promptInitConfig();
801
+ saveConfig(config);
802
+ } catch (error2) {
803
+ if (error2 instanceof Error && error2.message === "User force closed the prompt") {
804
+ warn("\nConfiguration cancelled.");
805
+ } else {
806
+ throw error2;
807
+ }
808
+ }
809
+ }
810
+ async function handleConfig() {
811
+ const existing = loadConfig();
812
+ if (!existing || !existing.TOKEN_V2 && !existing.FILE_TOKEN) {
813
+ warn("No configuration found.");
814
+ info("Run /init to create initial configuration.");
815
+ return;
816
+ }
817
+ info("Current configuration:");
818
+ info(`TOKEN_V2: ${existing.TOKEN_V2 ? "***" + existing.TOKEN_V2.slice(-8) : "(not set)"}`);
819
+ info(`FILE_TOKEN: ${existing.FILE_TOKEN ? "***" + existing.FILE_TOKEN.slice(-8) : "(not set)"}
820
+ `);
821
+ try {
822
+ const edit = await input2({
823
+ message: "Edit configuration? (y/n)",
824
+ default: "n"
825
+ });
826
+ if (edit.toLowerCase() === "y" || edit.toLowerCase() === "yes") {
827
+ const newConfig = await promptEditConfig(existing);
828
+ saveConfig(newConfig);
829
+ }
830
+ } catch (error2) {
831
+ if (error2 instanceof Error && error2.message === "User force closed the prompt") {
832
+ warn("\nEdit cancelled.");
833
+ } else {
834
+ throw error2;
835
+ }
836
+ }
837
+ }
838
+ async function handleMd(args) {
839
+ let url = "";
840
+ const options = {
841
+ output: "./nconv-output",
842
+ imageDir: "images",
843
+ verbose: false
844
+ };
845
+ if (args.length === 0) {
846
+ try {
847
+ url = await input2({
848
+ message: "Notion URL",
849
+ validate: (value) => {
850
+ if (!value.trim()) return "URL is required";
851
+ if (!value.includes("notion.so") && !value.includes("notion.site")) {
852
+ return "Please enter a valid Notion URL";
853
+ }
854
+ return true;
855
+ }
856
+ });
857
+ const outputDir = await input2({
858
+ message: "Output directory [default: ./nconv-output]",
859
+ default: "./nconv-output"
860
+ });
861
+ options.output = outputDir;
862
+ const filename = await input2({
863
+ message: "Filename [leave empty for auto-generated]",
864
+ default: ""
865
+ });
866
+ if (filename.trim()) {
867
+ options.filename = filename;
868
+ }
869
+ options.verbose = await confirm2({
870
+ message: "Enable verbose logging?",
871
+ default: false
872
+ });
873
+ } catch (error2) {
874
+ if (error2 instanceof Error && error2.message === "User force closed the prompt") {
875
+ warn("\nConversion cancelled.");
876
+ }
877
+ return;
878
+ }
879
+ } else {
880
+ url = args[0];
881
+ const additionalArgs = args.slice(1);
882
+ for (let i = 0; i < additionalArgs.length; i++) {
883
+ const arg = additionalArgs[i];
884
+ if ((arg === "-o" || arg === "--output") && i + 1 < additionalArgs.length) {
885
+ options.output = additionalArgs[++i];
886
+ } else if ((arg === "-i" || arg === "--image-dir") && i + 1 < additionalArgs.length) {
887
+ options.imageDir = additionalArgs[++i];
888
+ } else if ((arg === "-f" || arg === "--filename") && i + 1 < additionalArgs.length) {
889
+ options.filename = additionalArgs[++i];
890
+ } else if (arg === "-v" || arg === "--verbose") {
891
+ options.verbose = true;
892
+ }
893
+ }
894
+ }
895
+ const configCheck = validateConfig();
896
+ if (!configCheck.valid) {
897
+ error("Cannot convert Notion page:");
898
+ error(configCheck.message || "Configuration is invalid.");
899
+ return;
900
+ }
901
+ await mdCommand(url, options);
902
+ }
903
+ async function handleHtml(args) {
904
+ let url = "";
905
+ const options = {
906
+ output: "./nconv-output",
907
+ imageDir: "images",
908
+ verbose: false
909
+ };
910
+ if (args.length === 0) {
911
+ try {
912
+ url = await input2({
913
+ message: "Notion URL",
914
+ validate: (value) => {
915
+ if (!value.trim()) return "URL is required";
916
+ if (!value.includes("notion.so") && !value.includes("notion.site")) {
917
+ return "Please enter a valid Notion URL";
918
+ }
919
+ return true;
920
+ }
921
+ });
922
+ const outputDir = await input2({
923
+ message: "Output directory [default: ./nconv-output]",
924
+ default: "./nconv-output"
925
+ });
926
+ options.output = outputDir;
927
+ const filename = await input2({
928
+ message: "Filename [leave empty for auto-generated]",
929
+ default: ""
930
+ });
931
+ if (filename.trim()) {
932
+ options.filename = filename;
933
+ }
934
+ options.verbose = await confirm2({
935
+ message: "Enable verbose logging?",
936
+ default: false
937
+ });
938
+ } catch (error2) {
939
+ if (error2 instanceof Error && error2.message === "User force closed the prompt") {
940
+ warn("\nConversion cancelled.");
941
+ }
942
+ return;
943
+ }
944
+ } else {
945
+ url = args[0];
946
+ const additionalArgs = args.slice(1);
947
+ for (let i = 0; i < additionalArgs.length; i++) {
948
+ const arg = additionalArgs[i];
949
+ if ((arg === "-o" || arg === "--output") && i + 1 < additionalArgs.length) {
950
+ options.output = additionalArgs[++i];
951
+ } else if ((arg === "-i" || arg === "--image-dir") && i + 1 < additionalArgs.length) {
952
+ options.imageDir = additionalArgs[++i];
953
+ } else if ((arg === "-f" || arg === "--filename") && i + 1 < additionalArgs.length) {
954
+ options.filename = additionalArgs[++i];
955
+ } else if (arg === "-v" || arg === "--verbose") {
956
+ options.verbose = true;
957
+ }
958
+ }
959
+ }
960
+ const configCheck = validateConfig();
961
+ if (!configCheck.valid) {
962
+ error("Cannot convert Notion page:");
963
+ error(configCheck.message || "Configuration is invalid.");
964
+ return;
965
+ }
966
+ await htmlCommand(url, options);
967
+ }
968
+ async function handlePdf(args) {
969
+ let url = "";
970
+ const options = {
971
+ output: "./nconv-output",
972
+ imageDir: "images",
973
+ verbose: false
974
+ };
975
+ if (args.length === 0) {
976
+ try {
977
+ url = await input2({
978
+ message: "Notion URL",
979
+ validate: (value) => {
980
+ if (!value.trim()) return "URL is required";
981
+ if (!value.includes("notion.so") && !value.includes("notion.site")) {
982
+ return "Please enter a valid Notion URL";
983
+ }
984
+ return true;
985
+ }
986
+ });
987
+ const outputDir = await input2({
988
+ message: "Output directory [default: ./nconv-output]",
989
+ default: "./nconv-output"
990
+ });
991
+ options.output = outputDir;
992
+ const filename = await input2({
993
+ message: "Filename [leave empty for auto-generated]",
994
+ default: ""
995
+ });
996
+ if (filename.trim()) {
997
+ options.filename = filename;
998
+ }
999
+ options.verbose = await confirm2({
1000
+ message: "Enable verbose logging?",
1001
+ default: false
1002
+ });
1003
+ } catch (error2) {
1004
+ if (error2 instanceof Error && error2.message === "User force closed the prompt") {
1005
+ warn("\nConversion cancelled.");
1006
+ }
1007
+ return;
1008
+ }
1009
+ } else {
1010
+ url = args[0];
1011
+ const additionalArgs = args.slice(1);
1012
+ for (let i = 0; i < additionalArgs.length; i++) {
1013
+ const arg = additionalArgs[i];
1014
+ if ((arg === "-o" || arg === "--output") && i + 1 < additionalArgs.length) {
1015
+ options.output = additionalArgs[++i];
1016
+ } else if ((arg === "-f" || arg === "--filename") && i + 1 < additionalArgs.length) {
1017
+ options.filename = additionalArgs[++i];
1018
+ } else if (arg === "-v" || arg === "--verbose") {
1019
+ options.verbose = true;
1020
+ }
1021
+ }
1022
+ }
1023
+ const configCheck = validateConfig();
1024
+ if (!configCheck.valid) {
1025
+ error("Cannot convert Notion page:");
1026
+ error(configCheck.message || "Configuration is invalid.");
1027
+ return;
1028
+ }
1029
+ await pdfCommand(url, options);
1030
+ }
1031
+ var AVAILABLE_COMMANDS = [
1032
+ {
1033
+ name: "init",
1034
+ description: "Initialize configuration (set Notion tokens)",
1035
+ examples: ["/init"]
1036
+ },
1037
+ {
1038
+ name: "config",
1039
+ description: "View and edit current configuration",
1040
+ examples: ["/config"]
1041
+ },
1042
+ {
1043
+ name: "md",
1044
+ description: "Convert Notion page to markdown",
1045
+ examples: [
1046
+ "/md https://notion.so/page-id",
1047
+ "/md https://notion.so/page-id -o ./blog",
1048
+ "/md https://notion.so/page-id -o ./blog -f my-post -v"
1049
+ ]
1050
+ },
1051
+ {
1052
+ name: "html",
1053
+ description: "Convert Notion page to HTML",
1054
+ examples: [
1055
+ "/html https://notion.so/page-id",
1056
+ "/html https://notion.so/page-id -o ./blog",
1057
+ "/html https://notion.so/page-id -o ./blog -f my-post -v"
1058
+ ]
1059
+ },
1060
+ {
1061
+ name: "pdf",
1062
+ description: "Convert Notion page to PDF (renders actual page)",
1063
+ examples: [
1064
+ "/pdf https://notion.so/page-id",
1065
+ "/pdf https://notion.so/page-id -o ./blog",
1066
+ "/pdf https://notion.so/page-id -o ./blog -f my-post -v"
1067
+ ]
1068
+ },
1069
+ {
1070
+ name: "help",
1071
+ description: "Show this help message",
1072
+ examples: ["/help"]
1073
+ },
1074
+ {
1075
+ name: "exit",
1076
+ description: "Exit the REPL",
1077
+ examples: ["/exit"]
1078
+ }
1079
+ ];
1080
+ function getCommandSuggestions(input3) {
1081
+ const cleanInput = input3.toLowerCase().replace(/^\//, "");
1082
+ return AVAILABLE_COMMANDS.filter((cmd) => cmd.name.startsWith(cleanInput)).map((cmd) => `/${cmd.name}`);
1083
+ }
1084
+ function getCommandChoices() {
1085
+ return AVAILABLE_COMMANDS.map((cmd) => ({
1086
+ title: `/${cmd.name}`,
1087
+ value: `/${cmd.name}`,
1088
+ description: cmd.description
1089
+ }));
1090
+ }
1091
+ function findSimilarCommand(input3) {
1092
+ const cleanInput = input3.toLowerCase();
1093
+ for (const cmd of AVAILABLE_COMMANDS) {
1094
+ if (cmd.name.includes(cleanInput) || cleanInput.includes(cmd.name)) {
1095
+ return cmd.name;
1096
+ }
1097
+ }
1098
+ return null;
1099
+ }
1100
+ function handleHelp() {
1101
+ info("Available commands:\n");
1102
+ AVAILABLE_COMMANDS.forEach((cmd) => {
1103
+ info(` /${cmd.name.padEnd(20)} ${cmd.description}`);
1104
+ });
1105
+ console.log("");
1106
+ info("Conversion options (for /md, /html, and /pdf):");
1107
+ info(" -o, --output <dir> Output directory (default: ./nconv-output)");
1108
+ info(" -i, --image-dir <dir> Image folder name (default: images) [md/html only]");
1109
+ info(" -f, --filename <name> Output filename");
1110
+ info(" -v, --verbose Enable verbose logging\n");
1111
+ info("Examples:");
1112
+ AVAILABLE_COMMANDS.forEach((cmd) => {
1113
+ cmd.examples.forEach((example) => {
1114
+ info(` ${example}`);
1115
+ });
1116
+ });
1117
+ console.log("");
1118
+ }
1119
+ async function executeCommand(input3) {
1120
+ const trimmed = input3.trim();
1121
+ if (!trimmed) {
1122
+ return false;
1123
+ }
1124
+ if (!trimmed.startsWith("/")) {
1125
+ error("Commands must start with /");
1126
+ info("Type /help for available commands");
1127
+ info("Example: /init, /md <url>, /config\n");
1128
+ return false;
1129
+ }
1130
+ const parts = trimmed.slice(1).split(/\s+/);
1131
+ const command = parts[0].toLowerCase();
1132
+ const args = parts.slice(1);
1133
+ switch (command) {
1134
+ case "init":
1135
+ await handleInit();
1136
+ break;
1137
+ case "config":
1138
+ await handleConfig();
1139
+ break;
1140
+ case "md":
1141
+ await handleMd(args);
1142
+ break;
1143
+ case "html":
1144
+ await handleHtml(args);
1145
+ break;
1146
+ case "pdf":
1147
+ await handlePdf(args);
1148
+ break;
1149
+ case "help":
1150
+ case "h":
1151
+ handleHelp();
1152
+ break;
1153
+ case "exit":
1154
+ case "quit":
1155
+ case "q":
1156
+ return true;
1157
+ default:
1158
+ error(`Unknown command: /${command}`);
1159
+ const similar = findSimilarCommand(command);
1160
+ if (similar) {
1161
+ info(`Did you mean /${similar}?`);
1162
+ }
1163
+ info("Type /help to see all available commands\n");
1164
+ const suggestions = getCommandSuggestions(command);
1165
+ if (suggestions.length > 0 && suggestions.length < 5) {
1166
+ info("Available commands:");
1167
+ suggestions.forEach((cmd) => info(` ${cmd}`));
1168
+ console.log("");
1169
+ }
1170
+ }
1171
+ return false;
1172
+ }
1173
+
1174
+ // src/repl/index.ts
1175
+ function showBanner() {
1176
+ const title = "NCONV CLI (Notion Converter CLI)";
1177
+ const padding = 2;
1178
+ const totalWidth = title.length + padding * 2;
1179
+ const topBorder = "\u2554" + "\u2550".repeat(totalWidth) + "\u2557";
1180
+ const bottomBorder = "\u255A" + "\u2550".repeat(totalWidth) + "\u255D";
1181
+ console.log(chalk2.cyan("\n" + topBorder));
1182
+ console.log(chalk2.cyan("\u2551") + chalk2.bold(" ".repeat(padding) + title + " ".repeat(padding)) + chalk2.cyan("\u2551"));
1183
+ console.log(chalk2.cyan(bottomBorder + "\n"));
1184
+ info("Welcome to nconv interactive mode!");
1185
+ info("Type /help to see available commands");
1186
+ info("Type /exit to quit\n");
1187
+ console.log(chalk2.dim("Quick examples:"));
1188
+ console.log(chalk2.dim(" /init - Set up Notion tokens"));
1189
+ console.log(chalk2.dim(" /md <url> - Convert Notion page"));
1190
+ console.log(chalk2.dim(" /md <url> -o ./blog -f my-post - Convert with options\n"));
1191
+ }
1192
+ async function startRepl() {
1193
+ showBanner();
1194
+ let shouldExit = false;
1195
+ while (!shouldExit) {
1196
+ try {
1197
+ const response = await prompts({
1198
+ type: "autocomplete",
1199
+ name: "command",
1200
+ message: chalk2.cyan("nconv"),
1201
+ choices: getCommandChoices(),
1202
+ suggest: async (input3, choices) => {
1203
+ const searchInput = input3.startsWith("/") ? input3 : `/${input3}`;
1204
+ const filtered = choices.filter(
1205
+ (choice) => choice.title.toLowerCase().startsWith(searchInput.toLowerCase())
1206
+ );
1207
+ if (filtered.length === 0 && input3.trim()) {
1208
+ return Promise.resolve([{ title: input3, value: input3 }]);
1209
+ }
1210
+ return Promise.resolve(filtered);
1211
+ },
1212
+ limit: 5
1213
+ });
1214
+ if (response.command === void 0) {
1215
+ console.log("\n");
1216
+ info("Goodbye! \u{1F44B}");
1217
+ break;
1218
+ }
1219
+ shouldExit = await executeCommand(response.command);
1220
+ if (!shouldExit) {
1221
+ console.log();
1222
+ }
1223
+ } catch (error2) {
1224
+ if (error2 instanceof Error) {
1225
+ error(`Error: ${error2.message}`);
1226
+ console.log();
1227
+ }
1228
+ }
1229
+ }
1230
+ info("Exiting nconv...");
1231
+ }
1232
+
263
1233
  // src/index.ts
264
1234
  var program = new Command();
265
1235
  program.name("nconv").description("CLI tool for converting Notion pages to blog-ready markdown").version("1.0.0");
266
- program.command("md <url>").description("Convert a Notion page to markdown").option("-o, --output <dir>", "Output directory", "./output").option("-i, --image-dir <dir>", "Image folder name (relative to output)", "images").option("-f, --filename <name>", "\uCD9C\uB825 \uD30C\uC77C\uBA85 (\uD655\uC7A5\uC790 \uC81C\uC678 \uB610\uB294 \uD3EC\uD568)").option("-v, --verbose", "Enable verbose logging", false).action(async (url, options) => {
1236
+ program.command("init").description("Create a default .env configuration file").action(async () => {
1237
+ await handler({});
1238
+ });
1239
+ program.command("md <url>").description("Convert a Notion page to markdown").option("-o, --output <dir>", "Output directory", "./nconv-output").option("-i, --image-dir <dir>", "Image folder name (relative to output)", "images").option("-f, --filename <name>", "\uCD9C\uB825 \uD30C\uC77C\uBA85 (\uD655\uC7A5\uC790 \uC81C\uC678 \uB610\uB294 \uD3EC\uD568)").option("-v, --verbose", "Enable verbose logging", false).action(async (url, options) => {
267
1240
  await mdCommand(url, {
268
1241
  output: options.output,
269
1242
  imageDir: options.imageDir,
@@ -271,8 +1244,25 @@ program.command("md <url>").description("Convert a Notion page to markdown").opt
271
1244
  verbose: options.verbose
272
1245
  });
273
1246
  });
1247
+ program.command("html <url>").description("Convert a Notion page to HTML").option("-o, --output <dir>", "Output directory", "./nconv-output").option("-i, --image-dir <dir>", "Image folder name (relative to output)", "images").option("-f, --filename <name>", "Output filename (without extension or with)").option("-v, --verbose", "Enable verbose logging", false).action(async (url, options) => {
1248
+ await htmlCommand(url, {
1249
+ output: options.output,
1250
+ imageDir: options.imageDir,
1251
+ filename: options.filename,
1252
+ verbose: options.verbose
1253
+ });
1254
+ });
1255
+ program.command("pdf <url>").description("Convert a Notion page to PDF (renders actual Notion page)").option("-o, --output <dir>", "Output directory", "./nconv-output").option("-f, --filename <name>", "Output filename (without extension or with)").option("-v, --verbose", "Enable verbose logging", false).action(async (url, options) => {
1256
+ await pdfCommand(url, {
1257
+ output: options.output,
1258
+ imageDir: "images",
1259
+ // Not used for PDF, but required by interface
1260
+ filename: options.filename,
1261
+ verbose: options.verbose
1262
+ });
1263
+ });
274
1264
  if (process.env.NODE_ENV !== "production") {
275
- program.command("debug <url>").description("Debug: Output raw markdown and image URLs").option("-o, --output <dir>", "\uCD9C\uB825 \uB514\uB809\uD1A0\uB9AC", "./output").option("-i, --image-dir <dir>", "Image folder name", "images").option("-v, --verbose", "Enable verbose logging", false).action(async (url, options) => {
1265
+ program.command("debug <url>").description("Debug: Output raw markdown and image URLs").option("-o, --output <dir>", "\uCD9C\uB825 \uB514\uB809\uD1A0\uB9AC", "./nconv-output").option("-i, --image-dir <dir>", "Image folder name", "images").option("-v, --verbose", "Enable verbose logging", false).action(async (url, options) => {
276
1266
  await debugCommand(url, {
277
1267
  output: options.output,
278
1268
  imageDir: options.imageDir,
@@ -281,5 +1271,11 @@ if (process.env.NODE_ENV !== "production") {
281
1271
  });
282
1272
  });
283
1273
  }
284
- program.parse();
1274
+ (async () => {
1275
+ if (process.argv.length === 2) {
1276
+ await startRepl();
1277
+ } else {
1278
+ program.parse();
1279
+ }
1280
+ })();
285
1281
  //# sourceMappingURL=index.js.map