formatferry 0.1.2 → 0.1.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.
Files changed (3) hide show
  1. package/README.md +4 -0
  2. package/dist/index.js +70 -27
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -214,6 +214,10 @@ formatferry -i page.html -f commonmark
214
214
  curl -s https://example.com | formatferry -f reddit
215
215
  ```
216
216
 
217
+ ## Tips
218
+
219
+ - For HTML documents over ~100 KB, prefer `-i <file>` over piping through stdin to avoid OS shell buffer limitations.
220
+
217
221
  ## Development
218
222
 
219
223
  ```bash
package/dist/index.js CHANGED
@@ -21981,9 +21981,11 @@ var CachedEntitlementProvider = class {
21981
21981
  // src/entitlement.ts
21982
21982
  var LICENSE_SERVER_URL = "https://formatferry.vibingfun.com";
21983
21983
  var VALIDATE_ENDPOINT = `${LICENSE_SERVER_URL}/v1/licenses/validate`;
21984
- var STARTUP_TIMEOUT_MS = 2e3;
21984
+ var DEFAULT_TIMEOUT_MS = 2e3;
21985
21985
  var STALE_CACHE_DAYS = 7;
21986
- async function resolveEntitlement() {
21986
+ var FRESH_CACHE_HOURS = 24;
21987
+ async function resolveEntitlement(opts = {}) {
21988
+ const { offline = false, timeoutMs = DEFAULT_TIMEOUT_MS } = opts;
21987
21989
  let config;
21988
21990
  try {
21989
21991
  config = readConfig();
@@ -22007,11 +22009,19 @@ async function resolveEntitlement() {
22007
22009
  return new FreeEntitlementProvider();
22008
22010
  }
22009
22011
  const { licenseKey, lastValidated } = config;
22012
+ if (offline) {
22013
+ return buildCachedProvider(config, lastValidated);
22014
+ }
22015
+ const cacheAgeMs = lastValidated ? Date.now() - new Date(lastValidated).getTime() : Infinity;
22016
+ const freshCacheMs = FRESH_CACHE_HOURS * 60 * 60 * 1e3;
22017
+ if (cacheAgeMs < freshCacheMs) {
22018
+ return buildCachedProvider(config, lastValidated);
22019
+ }
22010
22020
  let data = null;
22011
22021
  let networkError = false;
22012
22022
  try {
22013
22023
  const controller = new AbortController();
22014
- const timer = setTimeout(() => controller.abort(), STARTUP_TIMEOUT_MS);
22024
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
22015
22025
  const response = await fetch(VALIDATE_ENDPOINT, {
22016
22026
  method: "POST",
22017
22027
  headers: { "Content-Type": "application/json" },
@@ -22046,22 +22056,24 @@ async function resolveEntitlement() {
22046
22056
  });
22047
22057
  return new CachedEntitlementProvider(freshState);
22048
22058
  }
22049
- const cacheAgeMs = lastValidated ? Date.now() - new Date(lastValidated).getTime() : Infinity;
22050
22059
  const staleCacheMs = STALE_CACHE_DAYS * 24 * 60 * 60 * 1e3;
22051
22060
  if (cacheAgeMs < staleCacheMs) {
22052
- const cachedState = {
22053
- tier: config.tier ?? "free",
22054
- features: config.features ?? [],
22055
- expiresAt: config.expiresAt ?? null,
22056
- lastValidated: lastValidated ?? ""
22057
- };
22058
- return new CachedEntitlementProvider(cachedState);
22061
+ return buildCachedProvider(config, lastValidated);
22059
22062
  }
22060
22063
  process.stderr.write(
22061
22064
  "Warning: Could not reach license server and cached license is older than 7 days. Operating in free tier. Please reconnect to validate your license.\n"
22062
22065
  );
22063
22066
  return new FreeEntitlementProvider();
22064
22067
  }
22068
+ function buildCachedProvider(config, lastValidated) {
22069
+ const cachedState = {
22070
+ tier: config.tier ?? "free",
22071
+ features: config.features ?? [],
22072
+ expiresAt: config.expiresAt ?? null,
22073
+ lastValidated: lastValidated ?? ""
22074
+ };
22075
+ return new CachedEntitlementProvider(cachedState);
22076
+ }
22065
22077
 
22066
22078
  // ../../src/shared/readability-processor.ts
22067
22079
  var import_readability = __toESM(require_readability(), 1);
@@ -22486,6 +22498,7 @@ var ToonifyService = class {
22486
22498
  };
22487
22499
 
22488
22500
  // src/toonify-helpers.ts
22501
+ var TOONIFY_MAX_INPUT_BYTES = 10 * 1024 * 1024;
22489
22502
  function formatSingleStats(result) {
22490
22503
  return `Toonify: ${result.originalTokenEstimate} \u2192 ${result.compressedTokenEstimate} tokens (${result.reductionPercent}% reduction)
22491
22504
  `;
@@ -22651,6 +22664,13 @@ async function urlToMarkdown(url, apiKey, format, proxyBaseUrl = process.env.FOR
22651
22664
  return result.markdown;
22652
22665
  }
22653
22666
  function applyToonify(markdown) {
22667
+ if (Buffer.byteLength(markdown, "utf8") > TOONIFY_MAX_INPUT_BYTES) {
22668
+ process.stderr.write(
22669
+ `Error: Input too large for toonify (>${TOONIFY_MAX_INPUT_BYTES / 1024 / 1024} MB). Convert without --toonify or split the input.
22670
+ `
22671
+ );
22672
+ process.exit(1);
22673
+ }
22654
22674
  try {
22655
22675
  const result = ToonifyService.compress(markdown);
22656
22676
  process.stderr.write(formatSingleStats(result));
@@ -22663,8 +22683,17 @@ function applyToonify(markdown) {
22663
22683
  }
22664
22684
  }
22665
22685
  async function convertCommand(options) {
22686
+ const parsedTimeout = options.timeout ? parseInt(options.timeout, 10) : void 0;
22687
+ if (parsedTimeout !== void 0 && (!Number.isFinite(parsedTimeout) || parsedTimeout <= 0)) {
22688
+ process.stderr.write("Error: --timeout must be a positive integer (milliseconds).\n");
22689
+ process.exit(1);
22690
+ }
22691
+ const entitlementOpts = {
22692
+ offline: options.offline,
22693
+ timeoutMs: parsedTimeout
22694
+ };
22666
22695
  if (options.toonify) {
22667
- const provider = await resolveEntitlement();
22696
+ const provider = await resolveEntitlement(entitlementOpts);
22668
22697
  if (!provider.hasFeature("token-optimization")) {
22669
22698
  process.stderr.write(
22670
22699
  "Error: Token optimization requires a Premium license.\nRun `formatferry auth --license-key <KEY>` to activate your license.\n"
@@ -22716,7 +22745,7 @@ async function convertCommand(options) {
22716
22745
  const html = fileBuffer.toString("utf8");
22717
22746
  let applyAtlassian = false;
22718
22747
  if (isAtlassianHTML(html)) {
22719
- const provider = await resolveEntitlement();
22748
+ const provider = await resolveEntitlement(entitlementOpts);
22720
22749
  if (!provider.hasFeature("atlassian")) {
22721
22750
  process.stderr.write(
22722
22751
  "Error: Atlassian HTML detected. A Premium license is required to process Atlassian content.\nRun `formatferry auth --license-key <KEY>` to activate your license.\n"
@@ -22733,7 +22762,7 @@ async function convertCommand(options) {
22733
22762
  const html = await readStdin();
22734
22763
  let applyAtlassian = false;
22735
22764
  if (isAtlassianHTML(html)) {
22736
- const provider = await resolveEntitlement();
22765
+ const provider = await resolveEntitlement(entitlementOpts);
22737
22766
  if (!provider.hasFeature("atlassian")) {
22738
22767
  process.stderr.write(
22739
22768
  "Error: Atlassian HTML detected. A Premium license is required to process Atlassian content.\nRun `formatferry auth --license-key <KEY>` to activate your license.\n"
@@ -22799,7 +22828,16 @@ async function convertFile(filePath, format) {
22799
22828
  return c.markdown;
22800
22829
  }
22801
22830
  async function batchCommand(options) {
22802
- const provider = await resolveEntitlement();
22831
+ const parsedTimeout = options.timeout ? parseInt(options.timeout, 10) : void 0;
22832
+ if (parsedTimeout !== void 0 && (!Number.isFinite(parsedTimeout) || parsedTimeout <= 0)) {
22833
+ process.stderr.write("Error: --timeout must be a positive integer (milliseconds).\n");
22834
+ process.exit(1);
22835
+ }
22836
+ const entitlementOpts = {
22837
+ offline: options.offline,
22838
+ timeoutMs: parsedTimeout
22839
+ };
22840
+ const provider = await resolveEntitlement(entitlementOpts);
22803
22841
  if (!provider.hasFeature("batch")) {
22804
22842
  process.stderr.write(
22805
22843
  "Error: Batch processing requires a Premium license.\nRun `formatferry auth --license-key <KEY>` to activate your license.\n"
@@ -22834,18 +22872,23 @@ async function batchCommand(options) {
22834
22872
  let markdown = await convertFile(filePath, format);
22835
22873
  if (options.toonify) {
22836
22874
  if (markdown.trim()) {
22837
- try {
22838
- const result = ToonifyService.compress(markdown);
22839
- process.stderr.write(formatFileStats(basename(filePath), result));
22840
- markdown = result.compressed;
22841
- totalOriginal += result.originalTokenEstimate;
22842
- totalCompressed += result.compressedTokenEstimate;
22843
- toonifiedCount++;
22844
- } catch (err) {
22845
- process.stderr.write(`Error toonifying ${filePath}: ${err instanceof Error ? err.message : String(err)}
22875
+ if (Buffer.byteLength(markdown, "utf8") > TOONIFY_MAX_INPUT_BYTES) {
22876
+ process.stderr.write(`Skipping toonify for ${basename(filePath)}: input exceeds ${TOONIFY_MAX_INPUT_BYTES / 1024 / 1024} MB
22846
22877
  `);
22847
- failed++;
22848
- continue;
22878
+ } else {
22879
+ try {
22880
+ const result = ToonifyService.compress(markdown);
22881
+ process.stderr.write(formatFileStats(basename(filePath), result));
22882
+ markdown = result.compressed;
22883
+ totalOriginal += result.originalTokenEstimate;
22884
+ totalCompressed += result.compressedTokenEstimate;
22885
+ toonifiedCount++;
22886
+ } catch (err) {
22887
+ process.stderr.write(`Error toonifying ${filePath}: ${err instanceof Error ? err.message : String(err)}
22888
+ `);
22889
+ failed++;
22890
+ continue;
22891
+ }
22849
22892
  }
22850
22893
  } else {
22851
22894
  markdown = "";
@@ -23059,7 +23102,7 @@ function handleLogout() {
23059
23102
 
23060
23103
  // src/index.ts
23061
23104
  var VERSION = "0.1.0";
23062
- program.name("formatferry").version(VERSION).description("Convert HTML, DOCX, PDF, XLSX, CSV to Markdown").option("-i, --input <path>", "Input file path").option("-o, --output <path>", "Output file path").option("-f, --format <format>", "Markdown format (github, commonmark, slack, discord, reddit, confluence, custom, rmarkdown)", "github").option("--url <url>", "Fetch article from URL").option("--batch <glob>", "Batch convert files (Premium)").option("--output-dir <dir>", "Output directory for batch").option("--toonify", "Compress output to TOON format (Premium)").action(async (options) => {
23105
+ program.name("formatferry").version(VERSION).description("Convert HTML, DOCX, PDF, XLSX, CSV to Markdown").option("-i, --input <path>", "Input file path").option("-o, --output <path>", "Output file path").option("-f, --format <format>", "Markdown format (github, commonmark, slack, discord, reddit, confluence, custom, rmarkdown)", "github").option("--url <url>", "Fetch article from URL").option("--batch <glob>", "Batch convert files (Premium)").option("--output-dir <dir>", "Output directory for batch").option("--toonify", "Compress output to TOON format (Premium)").option("--offline", "Skip entitlement check (use cached or free tier)").option("--timeout <ms>", "License server timeout in milliseconds (default: 2000)", "2000").action(async (options) => {
23063
23106
  if (options.batch) {
23064
23107
  await batchCommand(options);
23065
23108
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "formatferry",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "FormatFerry CLI — Convert HTML, DOCX, PDF, XLSX, CSV to Markdown",
5
5
  "keywords": [
6
6
  "markdown",