formatferry 0.1.2 → 0.1.4
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 +4 -0
- package/dist/index.js +93 -31
- 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
|
|
21984
|
+
var DEFAULT_TIMEOUT_MS = 2e3;
|
|
21985
21985
|
var STALE_CACHE_DAYS = 7;
|
|
21986
|
-
|
|
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(),
|
|
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
|
-
|
|
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
|
`;
|
|
@@ -22544,13 +22557,13 @@ Supported formats: ${VALID_FORMATS.join(", ")}
|
|
|
22544
22557
|
process.exit(1);
|
|
22545
22558
|
}
|
|
22546
22559
|
function readStdin() {
|
|
22547
|
-
return new Promise((
|
|
22560
|
+
return new Promise((resolve2, reject) => {
|
|
22548
22561
|
let data = "";
|
|
22549
22562
|
process.stdin.setEncoding("utf8");
|
|
22550
22563
|
process.stdin.on("data", (chunk) => {
|
|
22551
22564
|
data += chunk;
|
|
22552
22565
|
});
|
|
22553
|
-
process.stdin.on("end", () =>
|
|
22566
|
+
process.stdin.on("end", () => resolve2(data));
|
|
22554
22567
|
process.stdin.on("error", reject);
|
|
22555
22568
|
});
|
|
22556
22569
|
}
|
|
@@ -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
|
|
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
|
-
|
|
22838
|
-
|
|
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
|
-
|
|
22848
|
-
|
|
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 = "";
|
|
@@ -23058,15 +23101,34 @@ function handleLogout() {
|
|
|
23058
23101
|
}
|
|
23059
23102
|
|
|
23060
23103
|
// src/index.ts
|
|
23061
|
-
|
|
23062
|
-
|
|
23104
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
23105
|
+
import { resolve, dirname as dirname3 } from "path";
|
|
23106
|
+
import { fileURLToPath } from "url";
|
|
23107
|
+
var getVersion = () => {
|
|
23108
|
+
if ("0.1.4") {
|
|
23109
|
+
return "0.1.4";
|
|
23110
|
+
}
|
|
23111
|
+
try {
|
|
23112
|
+
const __dirname = dirname3(fileURLToPath(import.meta.url));
|
|
23113
|
+
const packageJson = JSON.parse(
|
|
23114
|
+
readFileSync4(resolve(__dirname, "..", "package.json"), "utf-8")
|
|
23115
|
+
);
|
|
23116
|
+
return packageJson.version || "0.0.0";
|
|
23117
|
+
} catch {
|
|
23118
|
+
return "0.0.0";
|
|
23119
|
+
}
|
|
23120
|
+
};
|
|
23121
|
+
var VERSION = getVersion();
|
|
23122
|
+
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
23123
|
if (options.batch) {
|
|
23064
23124
|
await batchCommand(options);
|
|
23065
23125
|
} else {
|
|
23066
23126
|
await convertCommand(options);
|
|
23067
23127
|
}
|
|
23068
23128
|
});
|
|
23069
|
-
program.command("auth").description("Manage license key and API key authentication").option("--license-key <key>", "Activate a license key").option("--api-key <key>", "Activate an API key").option("--status", "Show current license status").option("--logout", "Remove stored license key").action(
|
|
23129
|
+
program.command("auth").description("Manage license key and API key authentication").option("--license-key <key>", "Activate a license key").option("--api-key <key>", "Activate an API key").option("--status", "Show current license status").option("--logout", "Remove stored license key").action(async (options) => {
|
|
23130
|
+
await authCommand(options);
|
|
23131
|
+
});
|
|
23070
23132
|
program.parse();
|
|
23071
23133
|
/*! Bundled license information:
|
|
23072
23134
|
|