subwrench-cli 0.0.0-stage → 0.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/bin/subwrench.js +112 -0
- package/package.json +15 -4
- package/README.md +0 -3
package/bin/subwrench.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from "commander";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import * as core from "subwrench-core";
|
|
6
|
+
|
|
7
|
+
program.name("subkit").description("Swiss-army CLI for files & text").version(core.VERSION);
|
|
8
|
+
|
|
9
|
+
const readInput = (file) => (file === "-" ? fs.readFileSync(0, "utf8") : fs.readFileSync(file, "utf8"));
|
|
10
|
+
const writeOut = (out, str) => {
|
|
11
|
+
if (out) fs.writeFileSync(out, str);
|
|
12
|
+
else process.stdout.write(str);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const subtitle = program.command("subtitle").description("Subtitle tools (srt/vtt/sbv/ass)");
|
|
16
|
+
subtitle
|
|
17
|
+
.command("convert <file>")
|
|
18
|
+
.description("convert subtitle file between srt/vtt/sbv/ass")
|
|
19
|
+
.requiredOption("--to <fmt>", "target format: srt|vtt|sbv|ass")
|
|
20
|
+
.option("--from <fmt>", "source format (auto-detected if omitted)")
|
|
21
|
+
.option("-o, --out <file>", "write to file instead of stdout")
|
|
22
|
+
.action((file, opts) => writeOut(opts.out, core.subtitles.convert(readInput(file), opts.from, opts.to)));
|
|
23
|
+
subtitle
|
|
24
|
+
.command("shift <file> <ms>")
|
|
25
|
+
.description("shift all cues by milliseconds (negative to move earlier)")
|
|
26
|
+
.option("-o, --out <file>", "write to file instead of stdout")
|
|
27
|
+
.action((file, ms, opts) => writeOut(opts.out, core.subtitles.shift(readInput(file), Number(ms))));
|
|
28
|
+
subtitle
|
|
29
|
+
.command("merge <fileA> <fileB>")
|
|
30
|
+
.description("merge two subtitle files, sorted by time")
|
|
31
|
+
.option("--out-format <fmt>", "output format (default: same as fileA)")
|
|
32
|
+
.option("-o, --out <file>", "write to file instead of stdout")
|
|
33
|
+
.action((a, b, opts) => writeOut(opts.out, core.subtitles.merge(readInput(a), readInput(b), null, null, opts.outFormat)));
|
|
34
|
+
subtitle
|
|
35
|
+
.command("stats <file>")
|
|
36
|
+
.description("show cue count, duration, char count")
|
|
37
|
+
.action((file) => console.log(JSON.stringify(core.subtitles.stats(readInput(file)), null, 2)));
|
|
38
|
+
|
|
39
|
+
const text = program.command("text").description("Text tools");
|
|
40
|
+
text
|
|
41
|
+
.command("count [text...]")
|
|
42
|
+
.description("word/char/sentence stats (multilingual). Reads stdin with -")
|
|
43
|
+
.action((parts) => {
|
|
44
|
+
const input = parts.length ? parts.join(" ") : fs.readFileSync(0, "utf8");
|
|
45
|
+
console.log(JSON.stringify(core.text.countStats(input), null, 2));
|
|
46
|
+
});
|
|
47
|
+
text
|
|
48
|
+
.command("tokens [text...]")
|
|
49
|
+
.description("estimate LLM tokens. Reads stdin with -")
|
|
50
|
+
.option("--model <m>", "gpt|claude|gemini", "gpt")
|
|
51
|
+
.action((parts, opts) => {
|
|
52
|
+
const input = parts.length ? parts.join(" ") : fs.readFileSync(0, "utf8");
|
|
53
|
+
console.log(core.text.estimateTokens(input, opts.model));
|
|
54
|
+
});
|
|
55
|
+
for (const to of ["upper", "lower", "title", "sentence", "camel", "pascal", "snake", "kebab", "constant"]) {
|
|
56
|
+
text
|
|
57
|
+
.command(`${to} [text...]`)
|
|
58
|
+
.description(`transform case to ${to}. Reads stdin with -`)
|
|
59
|
+
.action((parts) => {
|
|
60
|
+
const input = parts.length ? parts.join(" ") : fs.readFileSync(0, "utf8");
|
|
61
|
+
process.stdout.write(core.text.caseTransform(input, to) + "\n");
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const json = program.command("json").description("JSON tools");
|
|
66
|
+
json.command("validate <file>").description("validate JSON, show error position").action((file) => {
|
|
67
|
+
const r = core.json.validate(readInput(file));
|
|
68
|
+
console.log(r.valid ? "valid" : `INVALID: ${r.error}${r.context ? `\nnear: ...${r.context}...` : ""}`);
|
|
69
|
+
if (!r.valid) process.exitCode = 1;
|
|
70
|
+
});
|
|
71
|
+
json.command("format <file>").option("-i, --indent <n>", "indent spaces", "2").option("-o, --out <file>").description("pretty-print JSON").action((file, opts) => writeOut(opts.out, core.json.format(readInput(file), +opts.indent)));
|
|
72
|
+
json.command("minify <file>").option("-o, --out <file>").description("minify JSON").action((file, opts) => writeOut(opts.out, core.json.minify(readInput(file))));
|
|
73
|
+
json.command("sort <file>").option("--desc").description("sort all object keys recursively").action((file, opts) => process.stdout.write(core.json.sortKeys(readInput(file), opts.desc ? "desc" : "asc")));
|
|
74
|
+
json.command("get <file> <path>").description("extract value by dot path, e.g. a.b[1].c").action((file, p) => {
|
|
75
|
+
const v = core.json.get(readInput(file), p);
|
|
76
|
+
console.log(v === null ? "null" : typeof v === "object" ? JSON.stringify(v, null, 2) : v);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const hash = program.command("hash").description("Hash & id tools");
|
|
80
|
+
hash.command("uuid").option("-n <count>", "how many", "1").description("generate UUID v4").action((opts) => console.log(core.hash.uuid(+opts.n).join("\n")));
|
|
81
|
+
hash.command("sha256 <input>").option("-f, --file <file>", "hash file content instead of arg").description("SHA-256 hex digest").action((input, opts) => console.log(core.hash.sha256(opts.file ? fs.readFileSync(opts.file) : input)));
|
|
82
|
+
hash.command("md5 <input>").option("-f, --file <file>", "hash file content instead of arg").description("MD5 hex digest").action((input, opts) => console.log(core.hash.md5(opts.file ? fs.readFileSync(opts.file) : input)));
|
|
83
|
+
hash.command("b64e <input>").description("base64 encode").action((input) => console.log(core.hash.base64Encode(input)));
|
|
84
|
+
hash.command("b64d <input>").description("base64 decode").action((input) => process.stdout.write(core.hash.base64Decode(input) + "\n"));
|
|
85
|
+
|
|
86
|
+
const image = program.command("image").description("Image tools (requires optional sharp)");
|
|
87
|
+
image
|
|
88
|
+
.command("convert <file>")
|
|
89
|
+
.requiredOption("--format <fmt>", "jpg|png|webp|avif|tiff")
|
|
90
|
+
.option("--quality <n>", "1-100", "80")
|
|
91
|
+
.option("--width <px>")
|
|
92
|
+
.option("--height <px>")
|
|
93
|
+
.option("-o, --out <file>", "output file (default: same name with new extension)")
|
|
94
|
+
.description("convert / compress / resize an image")
|
|
95
|
+
.action(async (file, opts) => {
|
|
96
|
+
const buf = fs.readFileSync(file);
|
|
97
|
+
const r = await core.image.convertImage(buf, {
|
|
98
|
+
format: opts.format,
|
|
99
|
+
quality: +opts.quality,
|
|
100
|
+
width: opts.width ? +opts.width : undefined,
|
|
101
|
+
height: opts.height ? +opts.height : undefined,
|
|
102
|
+
});
|
|
103
|
+
const out = opts.out || file.replace(path.extname(file), "") + "." + (opts.format === "jpeg" ? "jpg" : opts.format);
|
|
104
|
+
fs.writeFileSync(out, r.data);
|
|
105
|
+
console.log(`${out}: ${r.format} ${r.width}x${r.height} ${(r.size / 1024).toFixed(1)}KB (saved ${r.savedPct}%)`);
|
|
106
|
+
});
|
|
107
|
+
image.command("info <file>").description("image metadata").action(async (file) => {
|
|
108
|
+
const meta = await core.image.imageInfo(fs.readFileSync(file));
|
|
109
|
+
console.log(JSON.stringify({ format: meta.format, width: meta.width, height: meta.height, size: meta.size }, null, 2));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
program.parseAsync();
|
package/package.json
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "subwrench-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Swiss-army CLI for files & text: subtitle convert/shift/merge, text stats & tokens, JSON tools, hashes, image compress",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"subkit": "./bin/subkit.js"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"subwrench-core": "0.1.0",
|
|
11
|
+
"commander": "^12.1.0"
|
|
12
|
+
},
|
|
13
|
+
"optionalDependencies": {
|
|
14
|
+
"sharp": "^0.33.5"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT"
|
|
17
|
+
}
|
package/README.md
DELETED