fylzap 1.1.0
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 +66 -0
- package/dist/commands/convert.d.ts +10 -0
- package/dist/commands/convert.js +155 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +128 -0
- package/dist/utils/config.d.ts +5 -0
- package/dist/utils/config.js +32 -0
- package/dist/utils/formatter.d.ts +6 -0
- package/dist/utils/formatter.js +41 -0
- package/dist/utils/progress.d.ts +5 -0
- package/dist/utils/progress.js +21 -0
- package/package.json +39 -0
- package/src/commands/convert.ts +184 -0
- package/src/index.ts +137 -0
- package/src/utils/config.ts +33 -0
- package/src/utils/formatter.ts +38 -0
- package/src/utils/progress.ts +20 -0
- package/tsconfig.json +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# fylzap
|
|
2
|
+
|
|
3
|
+
Convert files from your terminal — PDF, DOCX, images and more.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
```bash
|
|
7
|
+
npm install -g fylzap
|
|
8
|
+
# or run without installing:
|
|
9
|
+
npx fylzap document.pdf
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Setup
|
|
13
|
+
|
|
14
|
+
Get your API key from [yourapp.com/dashboard](https://yourapp.com/dashboard), then:
|
|
15
|
+
```bash
|
|
16
|
+
fylzap config --key fc_live_your_key_here
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
```bash
|
|
21
|
+
# Convert PDF to Markdown
|
|
22
|
+
fylzap document.pdf
|
|
23
|
+
|
|
24
|
+
# Specify output format
|
|
25
|
+
fylzap document.pdf --output txt
|
|
26
|
+
|
|
27
|
+
# Convert image with OCR
|
|
28
|
+
fylzap scan.png --output txt --language eng
|
|
29
|
+
|
|
30
|
+
# Save to specific directory
|
|
31
|
+
fylzap document.pdf --out-dir ./output
|
|
32
|
+
|
|
33
|
+
# Use AI mode for better PDF quality
|
|
34
|
+
fylzap document.pdf --ai
|
|
35
|
+
|
|
36
|
+
# Multiple files
|
|
37
|
+
fylzap *.pdf --output md --out-dir ./converted
|
|
38
|
+
|
|
39
|
+
# One-time key (without saving)
|
|
40
|
+
fylzap document.pdf --key fc_live_xxx
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Supported Formats
|
|
44
|
+
|
|
45
|
+
| Input | Output |
|
|
46
|
+
|-------|--------|
|
|
47
|
+
| PDF | Markdown, Text |
|
|
48
|
+
| Word (DOCX) | Markdown, Text |
|
|
49
|
+
| CSV | JSON |
|
|
50
|
+
| JSON | CSV |
|
|
51
|
+
| Images (JPG, PNG, WEBP, etc.) | Text (OCR) |
|
|
52
|
+
|
|
53
|
+
## Commands
|
|
54
|
+
|
|
55
|
+
| Command | Description |
|
|
56
|
+
|---------|-------------|
|
|
57
|
+
| `fylzap <file>` | Convert a file |
|
|
58
|
+
| `fylzap config --key <key>` | Save API key |
|
|
59
|
+
| `fylzap config --show` | Show current config |
|
|
60
|
+
| `fylzap formats` | List supported formats |
|
|
61
|
+
|
|
62
|
+
## Environment Variables
|
|
63
|
+
```bash
|
|
64
|
+
FYLZAP_API_KEY=fc_live_xxx # API key
|
|
65
|
+
FYLZAP_API_URL=https://... # Custom API URL
|
|
66
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface ConvertOptions {
|
|
2
|
+
output: string;
|
|
3
|
+
outDir?: string;
|
|
4
|
+
language: string;
|
|
5
|
+
ai: boolean;
|
|
6
|
+
key?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function convertFile(filePath: string, options: ConvertOptions): Promise<void>;
|
|
9
|
+
export declare function convertMultiple(files: string[], options: ConvertOptions): Promise<void>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.convertFile = convertFile;
|
|
7
|
+
exports.convertMultiple = convertMultiple;
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
const form_data_1 = __importDefault(require("form-data"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
13
|
+
const progress_1 = require("../utils/progress");
|
|
14
|
+
const formatter_1 = require("../utils/formatter");
|
|
15
|
+
const config_1 = require("../utils/config");
|
|
16
|
+
const SUPPORTED_INPUTS = [
|
|
17
|
+
"pdf", "docx",
|
|
18
|
+
"md",
|
|
19
|
+
"csv", "json",
|
|
20
|
+
"yaml", "yml",
|
|
21
|
+
"jpg", "jpeg", "png", "gif", "bmp", "tiff", "webp",
|
|
22
|
+
];
|
|
23
|
+
const SUPPORTED_OUTPUTS = {
|
|
24
|
+
pdf: ["md", "txt", "json"],
|
|
25
|
+
docx: ["md", "txt"],
|
|
26
|
+
md: ["pdf", "epub"],
|
|
27
|
+
csv: ["json"],
|
|
28
|
+
json: ["csv"],
|
|
29
|
+
yaml: ["md"],
|
|
30
|
+
yml: ["md"],
|
|
31
|
+
jpg: ["txt"], jpeg: ["txt"], png: ["txt"],
|
|
32
|
+
gif: ["txt"], bmp: ["txt"], tiff: ["txt"], webp: ["txt"],
|
|
33
|
+
};
|
|
34
|
+
async function convertFile(filePath, options) {
|
|
35
|
+
const absPath = path_1.default.resolve(filePath);
|
|
36
|
+
if (!fs_1.default.existsSync(absPath)) {
|
|
37
|
+
(0, formatter_1.printError)(`File not found: ${filePath}`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const ext = path_1.default.extname(absPath).slice(1).toLowerCase();
|
|
41
|
+
if (!SUPPORTED_INPUTS.includes(ext)) {
|
|
42
|
+
(0, formatter_1.printError)(`Unsupported file type: .${ext}`);
|
|
43
|
+
(0, formatter_1.printInfo)(`Supported inputs: ${SUPPORTED_INPUTS.join(", ")}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
// Validate output format for input type
|
|
47
|
+
const validOutputs = SUPPORTED_OUTPUTS[ext] ?? [];
|
|
48
|
+
if (!validOutputs.includes(options.output)) {
|
|
49
|
+
(0, formatter_1.printError)(`Cannot convert .${ext} to .${options.output}`);
|
|
50
|
+
(0, formatter_1.printInfo)(`Valid outputs for .${ext}: ${validOutputs.join(", ")}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
const apiKey = options.key ?? (0, config_1.getApiKey)();
|
|
54
|
+
if (!apiKey) {
|
|
55
|
+
(0, formatter_1.printError)("No API key found.");
|
|
56
|
+
(0, formatter_1.printInfo)("Set your key with: fylzap config --key fc_live_xxx");
|
|
57
|
+
(0, formatter_1.printInfo)("Or set env var: FYLZAP_API_KEY=fc_live_xxx");
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
const apiUrl = (0, config_1.getApiUrl)();
|
|
61
|
+
const outputExt = options.output;
|
|
62
|
+
const filename = path_1.default.basename(absPath);
|
|
63
|
+
const saveDir = (0, formatter_1.getSaveDir)(options.outDir);
|
|
64
|
+
const baseName = path_1.default.basename(absPath, path_1.default.extname(absPath));
|
|
65
|
+
(0, formatter_1.ensureDir)(saveDir);
|
|
66
|
+
const progress = (0, progress_1.createProgressBar)(filename);
|
|
67
|
+
progress.start();
|
|
68
|
+
const startTime = Date.now();
|
|
69
|
+
try {
|
|
70
|
+
progress.update(10, `Uploading ${filename}...`);
|
|
71
|
+
const form = new form_data_1.default();
|
|
72
|
+
form.append("files", fs_1.default.createReadStream(absPath), filename);
|
|
73
|
+
form.append("outputType", outputExt);
|
|
74
|
+
form.append("splitMode", "whole");
|
|
75
|
+
form.append("useAI", options.ai.toString());
|
|
76
|
+
const imageExts = ["jpg", "jpeg", "png", "gif", "bmp", "tiff", "webp"];
|
|
77
|
+
if (imageExts.includes(ext) && outputExt === "txt") {
|
|
78
|
+
form.append("ocrLanguage", options.language);
|
|
79
|
+
}
|
|
80
|
+
progress.update(30, "Converting...");
|
|
81
|
+
const response = await axios_1.default.post(`${apiUrl}/api/converter`, form, {
|
|
82
|
+
headers: {
|
|
83
|
+
...form.getHeaders(),
|
|
84
|
+
Authorization: `Bearer ${apiKey}`,
|
|
85
|
+
},
|
|
86
|
+
responseType: "arraybuffer",
|
|
87
|
+
maxContentLength: Infinity,
|
|
88
|
+
maxBodyLength: Infinity,
|
|
89
|
+
onUploadProgress: (e) => {
|
|
90
|
+
if (e.total) {
|
|
91
|
+
const pct = Math.round((e.loaded / e.total) * 30) + 10;
|
|
92
|
+
progress.update(pct, "Uploading...");
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
progress.update(80, "Saving output...");
|
|
97
|
+
const contentType = response.headers["content-type"] ?? "";
|
|
98
|
+
const isZip = contentType.includes("application/zip");
|
|
99
|
+
const outExt = isZip ? "zip" : outputExt;
|
|
100
|
+
const outFilename = `${baseName}.${outExt}`;
|
|
101
|
+
const outPath = path_1.default.join(saveDir, outFilename);
|
|
102
|
+
fs_1.default.writeFileSync(outPath, Buffer.from(response.data));
|
|
103
|
+
progress.update(100, "Done!");
|
|
104
|
+
progress.stop();
|
|
105
|
+
const elapsed = Date.now() - startTime;
|
|
106
|
+
(0, formatter_1.printSuccess)(absPath, outPath, elapsed);
|
|
107
|
+
if (isZip) {
|
|
108
|
+
(0, formatter_1.printInfo)(`Output is a ZIP (contains markdown + images) → ${outPath}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
progress.stop();
|
|
113
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
114
|
+
if (error.response?.status === 401) {
|
|
115
|
+
(0, formatter_1.printError)("Invalid or expired API key.");
|
|
116
|
+
(0, formatter_1.printInfo)("Get a new key at: https://fylzap.com/dashboard");
|
|
117
|
+
}
|
|
118
|
+
else if (error.response?.status === 429) {
|
|
119
|
+
(0, formatter_1.printError)("Rate limit reached. Upgrade your plan for more conversions.");
|
|
120
|
+
(0, formatter_1.printInfo)("Upgrade at: https://fylzap.com/pricing");
|
|
121
|
+
}
|
|
122
|
+
else if (error.response?.status === 413) {
|
|
123
|
+
(0, formatter_1.printError)("File too large for your current plan.");
|
|
124
|
+
(0, formatter_1.printInfo)("Upgrade at: https://fylzap.com/pricing");
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
const msg = error.response?.data
|
|
128
|
+
? Buffer.from(error.response.data).toString()
|
|
129
|
+
: error.message;
|
|
130
|
+
(0, formatter_1.printError)(`Conversion failed: ${msg}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
(0, formatter_1.printError)(`Unexpected error: ${error.message}`);
|
|
135
|
+
}
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function convertMultiple(files, options) {
|
|
140
|
+
const results = { success: 0, failed: 0 };
|
|
141
|
+
for (const file of files) {
|
|
142
|
+
try {
|
|
143
|
+
await convertFile(file, options);
|
|
144
|
+
results.success++;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
results.failed++;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (files.length > 1) {
|
|
151
|
+
console.log(chalk_1.default.gray(`\n Summary: `) +
|
|
152
|
+
chalk_1.default.green(`${results.success} converted`) +
|
|
153
|
+
(results.failed > 0 ? chalk_1.default.red(` · ${results.failed} failed`) : ""));
|
|
154
|
+
}
|
|
155
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const convert_1 = require("./commands/convert");
|
|
10
|
+
const config_1 = require("./utils/config");
|
|
11
|
+
const formatter_1 = require("./utils/formatter");
|
|
12
|
+
const glob_1 = require("glob");
|
|
13
|
+
const program = new commander_1.Command();
|
|
14
|
+
program
|
|
15
|
+
.name("fylzap")
|
|
16
|
+
.description("Convert files from your terminal — PDF, DOCX, Markdown, images and more")
|
|
17
|
+
.version("1.1.0");
|
|
18
|
+
// ── Convert command (default) ─────────────────────────────────────────────────
|
|
19
|
+
program
|
|
20
|
+
.command("convert <files...>", { isDefault: true })
|
|
21
|
+
.description("Convert one or more files")
|
|
22
|
+
.option("-o, --output <type>", "Output format: md, txt, json, csv, pdf, epub", "md")
|
|
23
|
+
.option("-d, --out-dir <dir>", "Output directory (default: current directory)")
|
|
24
|
+
.option("-l, --language <code>", "OCR language code (for image→txt)", "eng")
|
|
25
|
+
.option("--ai", "Use AI mode for PDF→MD (better quality, slower)")
|
|
26
|
+
.option("-k, --key <apikey>", "API key (overrides saved key)")
|
|
27
|
+
.action(async (files, options) => {
|
|
28
|
+
(0, formatter_1.printBanner)();
|
|
29
|
+
// Expand globs on Windows (Unix handles this in shell)
|
|
30
|
+
const expanded = [];
|
|
31
|
+
for (const pattern of files) {
|
|
32
|
+
if (pattern.includes("*")) {
|
|
33
|
+
const matches = (0, glob_1.globSync)(pattern);
|
|
34
|
+
if (matches.length === 0) {
|
|
35
|
+
(0, formatter_1.printError)(`No files matched: ${pattern}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
expanded.push(...matches);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
expanded.push(pattern);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (expanded.length === 1) {
|
|
45
|
+
await (0, convert_1.convertFile)(expanded[0], {
|
|
46
|
+
output: options.output,
|
|
47
|
+
outDir: options.outDir,
|
|
48
|
+
language: options.language,
|
|
49
|
+
ai: !!options.ai,
|
|
50
|
+
key: options.key,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
(0, formatter_1.printInfo)(`Converting ${expanded.length} files...\n`);
|
|
55
|
+
await (0, convert_1.convertMultiple)(expanded, {
|
|
56
|
+
output: options.output,
|
|
57
|
+
outDir: options.outDir,
|
|
58
|
+
language: options.language,
|
|
59
|
+
ai: !!options.ai,
|
|
60
|
+
key: options.key,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
// ── Config command ────────────────────────────────────────────────────────────
|
|
65
|
+
program
|
|
66
|
+
.command("config")
|
|
67
|
+
.description("Configure fylzap settings")
|
|
68
|
+
.option("-k, --key <apikey>", "Save your API key")
|
|
69
|
+
.option("-u, --url <url>", "Set custom API URL (for self-hosted)")
|
|
70
|
+
.option("--show", "Show current config")
|
|
71
|
+
.option("--clear", "Clear all saved config")
|
|
72
|
+
.action((options) => {
|
|
73
|
+
if (options.key) {
|
|
74
|
+
if (!options.key.startsWith("fc_live_")) {
|
|
75
|
+
(0, formatter_1.printError)("Invalid API key format. Keys must start with fc_live_");
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
(0, config_1.setApiKey)(options.key);
|
|
79
|
+
console.log(chalk_1.default.green(" ✓ API key saved"));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (options.url) {
|
|
83
|
+
(0, config_1.setApiUrl)(options.url);
|
|
84
|
+
console.log(chalk_1.default.green(` ✓ API URL set to: ${options.url}`));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (options.clear) {
|
|
88
|
+
(0, config_1.clearConfig)();
|
|
89
|
+
console.log(chalk_1.default.green(" ✓ Config cleared"));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (options.show) {
|
|
93
|
+
const key = (0, config_1.getApiKey)();
|
|
94
|
+
const url = (0, config_1.getApiUrl)();
|
|
95
|
+
console.log(chalk_1.default.gray(" API Key: ") + (key ? chalk_1.default.white(key.substring(0, 20) + "...") : chalk_1.default.red("not set")));
|
|
96
|
+
console.log(chalk_1.default.gray(" API URL: ") + chalk_1.default.white(url));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
program.commands.find(c => c.name() === "config")?.help();
|
|
100
|
+
});
|
|
101
|
+
// ── Formats command ───────────────────────────────────────────────────────────
|
|
102
|
+
program
|
|
103
|
+
.command("formats")
|
|
104
|
+
.description("List supported input and output formats")
|
|
105
|
+
.action(() => {
|
|
106
|
+
(0, formatter_1.printBanner)();
|
|
107
|
+
console.log(chalk_1.default.bold(" Supported conversions:\n"));
|
|
108
|
+
const formats = [
|
|
109
|
+
["PDF (.pdf)", "Markdown (.md), Text (.txt), JSON (.json)"],
|
|
110
|
+
["Word (.docx)", "Markdown (.md), Text (.txt)"],
|
|
111
|
+
["Markdown (.md)", "PDF (.pdf), EPUB (.epub)"],
|
|
112
|
+
["CSV (.csv)", "JSON (.json)"],
|
|
113
|
+
["JSON (.json)", "CSV (.csv)"],
|
|
114
|
+
["OpenAPI/YAML (.yaml)", "Markdown (.md) — API docs"],
|
|
115
|
+
["Image (.jpg, .png, etc)", "Text via OCR (.txt)"],
|
|
116
|
+
];
|
|
117
|
+
formats.forEach(([input, output]) => {
|
|
118
|
+
console.log(chalk_1.default.cyan(` ${input.padEnd(35)}`) +
|
|
119
|
+
chalk_1.default.gray("→ ") +
|
|
120
|
+
chalk_1.default.white(output));
|
|
121
|
+
});
|
|
122
|
+
console.log();
|
|
123
|
+
console.log(chalk_1.default.gray(" Flags:"));
|
|
124
|
+
console.log(chalk_1.default.gray(" --ai ") + chalk_1.default.white("AI-powered PDF→MD (Pro/Enterprise)"));
|
|
125
|
+
console.log(chalk_1.default.gray(" --language ") + chalk_1.default.white("OCR language: eng, ara, urd, fra, ger, spa..."));
|
|
126
|
+
console.log();
|
|
127
|
+
});
|
|
128
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getApiKey = getApiKey;
|
|
7
|
+
exports.setApiKey = setApiKey;
|
|
8
|
+
exports.getApiUrl = getApiUrl;
|
|
9
|
+
exports.setApiUrl = setApiUrl;
|
|
10
|
+
exports.clearConfig = clearConfig;
|
|
11
|
+
const conf_1 = __importDefault(require("conf"));
|
|
12
|
+
const config = new conf_1.default({
|
|
13
|
+
projectName: "fylzap",
|
|
14
|
+
defaults: {
|
|
15
|
+
apiUrl: "https://yourapp.com", // ← replace with your actual domain
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
function getApiKey() {
|
|
19
|
+
return process.env.FYLZAP_API_KEY ?? config.get("apiKey");
|
|
20
|
+
}
|
|
21
|
+
function setApiKey(key) {
|
|
22
|
+
config.set("apiKey", key);
|
|
23
|
+
}
|
|
24
|
+
function getApiUrl() {
|
|
25
|
+
return process.env.FYLZAP_API_URL ?? config.get("apiUrl") ?? "https://yourapp.com";
|
|
26
|
+
}
|
|
27
|
+
function setApiUrl(url) {
|
|
28
|
+
config.set("apiUrl", url);
|
|
29
|
+
}
|
|
30
|
+
function clearConfig() {
|
|
31
|
+
config.clear();
|
|
32
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function printBanner(): void;
|
|
2
|
+
export declare function printSuccess(inputFile: string, outputFile: string, ms: number): void;
|
|
3
|
+
export declare function printError(message: string): void;
|
|
4
|
+
export declare function printInfo(message: string): void;
|
|
5
|
+
export declare function getSaveDir(outputDir?: string): string;
|
|
6
|
+
export declare function ensureDir(dir: string): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.printBanner = printBanner;
|
|
7
|
+
exports.printSuccess = printSuccess;
|
|
8
|
+
exports.printError = printError;
|
|
9
|
+
exports.printInfo = printInfo;
|
|
10
|
+
exports.getSaveDir = getSaveDir;
|
|
11
|
+
exports.ensureDir = ensureDir;
|
|
12
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const fs_1 = __importDefault(require("fs"));
|
|
15
|
+
function printBanner() {
|
|
16
|
+
console.log(chalk_1.default.bold.blue("\n fylzap") + chalk_1.default.gray(" — file converter CLI"));
|
|
17
|
+
console.log(chalk_1.default.gray(" ─────────────────────────────\n"));
|
|
18
|
+
}
|
|
19
|
+
function printSuccess(inputFile, outputFile, ms) {
|
|
20
|
+
console.log(chalk_1.default.green(" ✓") +
|
|
21
|
+
chalk_1.default.white(` ${path_1.default.basename(inputFile)}`) +
|
|
22
|
+
chalk_1.default.gray(` → `) +
|
|
23
|
+
chalk_1.default.white(path_1.default.basename(outputFile)) +
|
|
24
|
+
chalk_1.default.gray(` (${(ms / 1000).toFixed(1)}s)`));
|
|
25
|
+
}
|
|
26
|
+
function printError(message) {
|
|
27
|
+
console.log(chalk_1.default.red(" ✗ ") + chalk_1.default.white(message));
|
|
28
|
+
}
|
|
29
|
+
function printInfo(message) {
|
|
30
|
+
console.log(chalk_1.default.gray(" ℹ ") + chalk_1.default.white(message));
|
|
31
|
+
}
|
|
32
|
+
function getSaveDir(outputDir) {
|
|
33
|
+
return outputDir
|
|
34
|
+
? path_1.default.resolve(outputDir)
|
|
35
|
+
: process.cwd();
|
|
36
|
+
}
|
|
37
|
+
function ensureDir(dir) {
|
|
38
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
39
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createProgressBar = createProgressBar;
|
|
7
|
+
const cli_progress_1 = __importDefault(require("cli-progress"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
function createProgressBar(filename) {
|
|
10
|
+
const bar = new cli_progress_1.default.SingleBar({
|
|
11
|
+
format: chalk_1.default.cyan("{bar}") + " {percentage}% | {status}",
|
|
12
|
+
barCompleteChar: "█",
|
|
13
|
+
barIncompleteChar: "░",
|
|
14
|
+
hideCursor: true,
|
|
15
|
+
}, cli_progress_1.default.Presets.shades_classic);
|
|
16
|
+
return {
|
|
17
|
+
start: () => bar.start(100, 0, { status: `Converting ${filename}...` }),
|
|
18
|
+
update: (pct, status) => bar.update(pct, { status }),
|
|
19
|
+
stop: () => bar.stop(),
|
|
20
|
+
};
|
|
21
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fylzap",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Convert files from your terminal — PDF, DOCX, images and more",
|
|
5
|
+
"keywords": ["converter", "pdf", "markdown", "docx", "ocr", "cli"],
|
|
6
|
+
"author": "Muhammad Ali Raza",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://fylzap.com",
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"bin": {
|
|
11
|
+
"fylzap": "dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "ts-node src/index.ts",
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"axios": "^1.6.0",
|
|
21
|
+
"chalk": "^4.1.2",
|
|
22
|
+
"cli-progress": "^3.12.0",
|
|
23
|
+
"commander": "^11.0.0",
|
|
24
|
+
"conf": "^10.2.0",
|
|
25
|
+
"form-data": "^4.0.0",
|
|
26
|
+
"glob": "^13.0.6",
|
|
27
|
+
"ora": "^5.4.1"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/cli-progress": "^3.11.0",
|
|
31
|
+
"@types/glob": "^8.1.0",
|
|
32
|
+
"@types/node": "^20.0.0",
|
|
33
|
+
"ts-node": "^10.9.0",
|
|
34
|
+
"typescript": "^5.0.0"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=16.0.0"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import FormData from "form-data";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { createProgressBar } from "../utils/progress";
|
|
7
|
+
import { printSuccess, printError, printInfo, getSaveDir, ensureDir } from "../utils/formatter";
|
|
8
|
+
import { getApiKey, getApiUrl } from "../utils/config";
|
|
9
|
+
|
|
10
|
+
const SUPPORTED_INPUTS = [
|
|
11
|
+
"pdf", "docx",
|
|
12
|
+
"md",
|
|
13
|
+
"csv", "json",
|
|
14
|
+
"yaml", "yml",
|
|
15
|
+
"jpg", "jpeg", "png", "gif", "bmp", "tiff", "webp",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const SUPPORTED_OUTPUTS: Record<string, string[]> = {
|
|
19
|
+
pdf: ["md", "txt", "json"],
|
|
20
|
+
docx: ["md", "txt"],
|
|
21
|
+
md: ["pdf", "epub"],
|
|
22
|
+
csv: ["json"],
|
|
23
|
+
json: ["csv"],
|
|
24
|
+
yaml: ["md"],
|
|
25
|
+
yml: ["md"],
|
|
26
|
+
jpg: ["txt"], jpeg: ["txt"], png: ["txt"],
|
|
27
|
+
gif: ["txt"], bmp: ["txt"], tiff: ["txt"], webp: ["txt"],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
interface ConvertOptions {
|
|
31
|
+
output: string;
|
|
32
|
+
outDir?: string;
|
|
33
|
+
language: string;
|
|
34
|
+
ai: boolean;
|
|
35
|
+
key?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function convertFile(
|
|
39
|
+
filePath: string,
|
|
40
|
+
options: ConvertOptions
|
|
41
|
+
): Promise<void> {
|
|
42
|
+
const absPath = path.resolve(filePath);
|
|
43
|
+
if (!fs.existsSync(absPath)) {
|
|
44
|
+
printError(`File not found: ${filePath}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const ext = path.extname(absPath).slice(1).toLowerCase();
|
|
49
|
+
if (!SUPPORTED_INPUTS.includes(ext)) {
|
|
50
|
+
printError(`Unsupported file type: .${ext}`);
|
|
51
|
+
printInfo(`Supported inputs: ${SUPPORTED_INPUTS.join(", ")}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Validate output format for input type
|
|
56
|
+
const validOutputs = SUPPORTED_OUTPUTS[ext] ?? [];
|
|
57
|
+
if (!validOutputs.includes(options.output)) {
|
|
58
|
+
printError(`Cannot convert .${ext} to .${options.output}`);
|
|
59
|
+
printInfo(`Valid outputs for .${ext}: ${validOutputs.join(", ")}`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const apiKey = options.key ?? getApiKey();
|
|
64
|
+
if (!apiKey) {
|
|
65
|
+
printError("No API key found.");
|
|
66
|
+
printInfo("Set your key with: fylzap config --key fc_live_xxx");
|
|
67
|
+
printInfo("Or set env var: FYLZAP_API_KEY=fc_live_xxx");
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const apiUrl = getApiUrl();
|
|
72
|
+
const outputExt = options.output;
|
|
73
|
+
const filename = path.basename(absPath);
|
|
74
|
+
const saveDir = getSaveDir(options.outDir);
|
|
75
|
+
const baseName = path.basename(absPath, path.extname(absPath));
|
|
76
|
+
|
|
77
|
+
ensureDir(saveDir);
|
|
78
|
+
|
|
79
|
+
const progress = createProgressBar(filename);
|
|
80
|
+
progress.start();
|
|
81
|
+
const startTime = Date.now();
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
progress.update(10, `Uploading ${filename}...`);
|
|
85
|
+
|
|
86
|
+
const form = new FormData();
|
|
87
|
+
form.append("files", fs.createReadStream(absPath), filename);
|
|
88
|
+
form.append("outputType", outputExt);
|
|
89
|
+
form.append("splitMode", "whole");
|
|
90
|
+
form.append("useAI", options.ai.toString());
|
|
91
|
+
|
|
92
|
+
const imageExts = ["jpg","jpeg","png","gif","bmp","tiff","webp"];
|
|
93
|
+
if (imageExts.includes(ext) && outputExt === "txt") {
|
|
94
|
+
form.append("ocrLanguage", options.language);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
progress.update(30, "Converting...");
|
|
98
|
+
|
|
99
|
+
const response = await axios.post(`${apiUrl}/api/converter`, form, {
|
|
100
|
+
headers: {
|
|
101
|
+
...form.getHeaders(),
|
|
102
|
+
Authorization: `Bearer ${apiKey}`,
|
|
103
|
+
},
|
|
104
|
+
responseType: "arraybuffer",
|
|
105
|
+
maxContentLength: Infinity,
|
|
106
|
+
maxBodyLength: Infinity,
|
|
107
|
+
onUploadProgress: (e) => {
|
|
108
|
+
if (e.total) {
|
|
109
|
+
const pct = Math.round((e.loaded / e.total) * 30) + 10;
|
|
110
|
+
progress.update(pct, "Uploading...");
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
progress.update(80, "Saving output...");
|
|
116
|
+
|
|
117
|
+
const contentType = response.headers["content-type"] ?? "";
|
|
118
|
+
const isZip = contentType.includes("application/zip");
|
|
119
|
+
const outExt = isZip ? "zip" : outputExt;
|
|
120
|
+
const outFilename = `${baseName}.${outExt}`;
|
|
121
|
+
const outPath = path.join(saveDir, outFilename);
|
|
122
|
+
|
|
123
|
+
fs.writeFileSync(outPath, Buffer.from(response.data));
|
|
124
|
+
|
|
125
|
+
progress.update(100, "Done!");
|
|
126
|
+
progress.stop();
|
|
127
|
+
|
|
128
|
+
const elapsed = Date.now() - startTime;
|
|
129
|
+
printSuccess(absPath, outPath, elapsed);
|
|
130
|
+
|
|
131
|
+
if (isZip) {
|
|
132
|
+
printInfo(`Output is a ZIP (contains markdown + images) → ${outPath}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
} catch (error: any) {
|
|
136
|
+
progress.stop();
|
|
137
|
+
|
|
138
|
+
if (axios.isAxiosError(error)) {
|
|
139
|
+
if (error.response?.status === 401) {
|
|
140
|
+
printError("Invalid or expired API key.");
|
|
141
|
+
printInfo("Get a new key at: https://fylzap.com/dashboard");
|
|
142
|
+
} else if (error.response?.status === 429) {
|
|
143
|
+
printError("Rate limit reached. Upgrade your plan for more conversions.");
|
|
144
|
+
printInfo("Upgrade at: https://fylzap.com/pricing");
|
|
145
|
+
} else if (error.response?.status === 413) {
|
|
146
|
+
printError("File too large for your current plan.");
|
|
147
|
+
printInfo("Upgrade at: https://fylzap.com/pricing");
|
|
148
|
+
} else {
|
|
149
|
+
const msg = error.response?.data
|
|
150
|
+
? Buffer.from(error.response.data).toString()
|
|
151
|
+
: error.message;
|
|
152
|
+
printError(`Conversion failed: ${msg}`);
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
printError(`Unexpected error: ${error.message}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function convertMultiple(
|
|
163
|
+
files: string[],
|
|
164
|
+
options: ConvertOptions
|
|
165
|
+
): Promise<void> {
|
|
166
|
+
const results = { success: 0, failed: 0 };
|
|
167
|
+
|
|
168
|
+
for (const file of files) {
|
|
169
|
+
try {
|
|
170
|
+
await convertFile(file, options);
|
|
171
|
+
results.success++;
|
|
172
|
+
} catch {
|
|
173
|
+
results.failed++;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (files.length > 1) {
|
|
178
|
+
console.log(
|
|
179
|
+
chalk.gray(`\n Summary: `) +
|
|
180
|
+
chalk.green(`${results.success} converted`) +
|
|
181
|
+
(results.failed > 0 ? chalk.red(` · ${results.failed} failed`) : "")
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { convertFile, convertMultiple } from "./commands/convert";
|
|
6
|
+
import { setApiKey, setApiUrl, getApiKey, getApiUrl, clearConfig } from "./utils/config";
|
|
7
|
+
import { printBanner, printInfo, printError } from "./utils/formatter";
|
|
8
|
+
import { globSync } from "glob";
|
|
9
|
+
import path from "path";
|
|
10
|
+
|
|
11
|
+
const program = new Command();
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.name("fylzap")
|
|
15
|
+
.description("Convert files from your terminal — PDF, DOCX, Markdown, images and more")
|
|
16
|
+
.version("1.1.0");
|
|
17
|
+
|
|
18
|
+
// ── Convert command (default) ─────────────────────────────────────────────────
|
|
19
|
+
program
|
|
20
|
+
.command("convert <files...>", { isDefault: true })
|
|
21
|
+
.description("Convert one or more files")
|
|
22
|
+
.option("-o, --output <type>", "Output format: md, txt, json, csv, pdf, epub", "md")
|
|
23
|
+
.option("-d, --out-dir <dir>", "Output directory (default: current directory)")
|
|
24
|
+
.option("-l, --language <code>", "OCR language code (for image→txt)", "eng")
|
|
25
|
+
.option("--ai", "Use AI mode for PDF→MD (better quality, slower)")
|
|
26
|
+
.option("-k, --key <apikey>", "API key (overrides saved key)")
|
|
27
|
+
.action(async (files: string[], options) => {
|
|
28
|
+
printBanner();
|
|
29
|
+
|
|
30
|
+
// Expand globs on Windows (Unix handles this in shell)
|
|
31
|
+
const expanded: string[] = [];
|
|
32
|
+
for (const pattern of files) {
|
|
33
|
+
if (pattern.includes("*")) {
|
|
34
|
+
const matches = globSync(pattern);
|
|
35
|
+
if (matches.length === 0) {
|
|
36
|
+
printError(`No files matched: ${pattern}`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
expanded.push(...matches);
|
|
40
|
+
} else {
|
|
41
|
+
expanded.push(pattern);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (expanded.length === 1) {
|
|
46
|
+
await convertFile(expanded[0], {
|
|
47
|
+
output: options.output,
|
|
48
|
+
outDir: options.outDir,
|
|
49
|
+
language: options.language,
|
|
50
|
+
ai: !!options.ai,
|
|
51
|
+
key: options.key,
|
|
52
|
+
});
|
|
53
|
+
} else {
|
|
54
|
+
printInfo(`Converting ${expanded.length} files...\n`);
|
|
55
|
+
await convertMultiple(expanded, {
|
|
56
|
+
output: options.output,
|
|
57
|
+
outDir: options.outDir,
|
|
58
|
+
language: options.language,
|
|
59
|
+
ai: !!options.ai,
|
|
60
|
+
key: options.key,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// ── Config command ────────────────────────────────────────────────────────────
|
|
66
|
+
program
|
|
67
|
+
.command("config")
|
|
68
|
+
.description("Configure fylzap settings")
|
|
69
|
+
.option("-k, --key <apikey>", "Save your API key")
|
|
70
|
+
.option("-u, --url <url>", "Set custom API URL (for self-hosted)")
|
|
71
|
+
.option("--show", "Show current config")
|
|
72
|
+
.option("--clear", "Clear all saved config")
|
|
73
|
+
.action((options) => {
|
|
74
|
+
if (options.key) {
|
|
75
|
+
if (!options.key.startsWith("fc_live_")) {
|
|
76
|
+
printError("Invalid API key format. Keys must start with fc_live_");
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
setApiKey(options.key);
|
|
80
|
+
console.log(chalk.green(" ✓ API key saved"));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (options.url) {
|
|
85
|
+
setApiUrl(options.url);
|
|
86
|
+
console.log(chalk.green(` ✓ API URL set to: ${options.url}`));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (options.clear) {
|
|
91
|
+
clearConfig();
|
|
92
|
+
console.log(chalk.green(" ✓ Config cleared"));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (options.show) {
|
|
97
|
+
const key = getApiKey();
|
|
98
|
+
const url = getApiUrl();
|
|
99
|
+
console.log(chalk.gray(" API Key: ") + (key ? chalk.white(key.substring(0, 20) + "...") : chalk.red("not set")));
|
|
100
|
+
console.log(chalk.gray(" API URL: ") + chalk.white(url));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
program.commands.find(c => c.name() === "config")?.help();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// ── Formats command ───────────────────────────────────────────────────────────
|
|
108
|
+
program
|
|
109
|
+
.command("formats")
|
|
110
|
+
.description("List supported input and output formats")
|
|
111
|
+
.action(() => {
|
|
112
|
+
printBanner();
|
|
113
|
+
console.log(chalk.bold(" Supported conversions:\n"));
|
|
114
|
+
const formats = [
|
|
115
|
+
["PDF (.pdf)", "Markdown (.md), Text (.txt), JSON (.json)"],
|
|
116
|
+
["Word (.docx)", "Markdown (.md), Text (.txt)"],
|
|
117
|
+
["Markdown (.md)", "PDF (.pdf), EPUB (.epub)"],
|
|
118
|
+
["CSV (.csv)", "JSON (.json)"],
|
|
119
|
+
["JSON (.json)", "CSV (.csv)"],
|
|
120
|
+
["OpenAPI/YAML (.yaml)", "Markdown (.md) — API docs"],
|
|
121
|
+
["Image (.jpg, .png, etc)", "Text via OCR (.txt)"],
|
|
122
|
+
];
|
|
123
|
+
formats.forEach(([input, output]) => {
|
|
124
|
+
console.log(
|
|
125
|
+
chalk.cyan(` ${input.padEnd(35)}`) +
|
|
126
|
+
chalk.gray("→ ") +
|
|
127
|
+
chalk.white(output)
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
console.log();
|
|
131
|
+
console.log(chalk.gray(" Flags:"));
|
|
132
|
+
console.log(chalk.gray(" --ai ") + chalk.white("AI-powered PDF→MD (Pro/Enterprise)"));
|
|
133
|
+
console.log(chalk.gray(" --language ") + chalk.white("OCR language: eng, ara, urd, fra, ger, spa..."));
|
|
134
|
+
console.log();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import Conf from "conf";
|
|
2
|
+
|
|
3
|
+
interface FylzapConfig {
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
apiUrl?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const config = new Conf<FylzapConfig>({
|
|
9
|
+
projectName: "fylzap",
|
|
10
|
+
defaults: {
|
|
11
|
+
apiUrl: "https://yourapp.com", // ← replace with your actual domain
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export function getApiKey(): string | undefined {
|
|
16
|
+
return process.env.FYLZAP_API_KEY ?? config.get("apiKey");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function setApiKey(key: string): void {
|
|
20
|
+
config.set("apiKey", key);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getApiUrl(): string {
|
|
24
|
+
return process.env.FYLZAP_API_URL ?? config.get("apiUrl") ?? "https://yourapp.com";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function setApiUrl(url: string): void {
|
|
28
|
+
config.set("apiUrl", url);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function clearConfig(): void {
|
|
32
|
+
config.clear();
|
|
33
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
|
|
5
|
+
export function printBanner() {
|
|
6
|
+
console.log(chalk.bold.blue("\n fylzap") + chalk.gray(" — file converter CLI"));
|
|
7
|
+
console.log(chalk.gray(" ─────────────────────────────\n"));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function printSuccess(inputFile: string, outputFile: string, ms: number) {
|
|
11
|
+
console.log(
|
|
12
|
+
chalk.green(" ✓") +
|
|
13
|
+
chalk.white(` ${path.basename(inputFile)}`) +
|
|
14
|
+
chalk.gray(` → `) +
|
|
15
|
+
chalk.white(path.basename(outputFile)) +
|
|
16
|
+
chalk.gray(` (${(ms / 1000).toFixed(1)}s)`)
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function printError(message: string) {
|
|
21
|
+
console.log(chalk.red(" ✗ ") + chalk.white(message));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function printInfo(message: string) {
|
|
25
|
+
console.log(chalk.gray(" ℹ ") + chalk.white(message));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getSaveDir(outputDir?: string): string {
|
|
29
|
+
return outputDir
|
|
30
|
+
? path.resolve(outputDir)
|
|
31
|
+
: process.cwd();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function ensureDir(dir: string): void {
|
|
35
|
+
if (!fs.existsSync(dir)) {
|
|
36
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import cliProgress from "cli-progress";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
|
|
4
|
+
export function createProgressBar(filename: string) {
|
|
5
|
+
const bar = new cliProgress.SingleBar(
|
|
6
|
+
{
|
|
7
|
+
format: chalk.cyan("{bar}") + " {percentage}% | {status}",
|
|
8
|
+
barCompleteChar: "█",
|
|
9
|
+
barIncompleteChar: "░",
|
|
10
|
+
hideCursor: true,
|
|
11
|
+
},
|
|
12
|
+
cliProgress.Presets.shades_classic
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
start: () => bar.start(100, 0, { status: `Converting ${filename}...` }),
|
|
17
|
+
update: (pct: number, status: string) => bar.update(pct, { status }),
|
|
18
|
+
stop: () => bar.stop(),
|
|
19
|
+
};
|
|
20
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"declaration": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|