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.
@@ -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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.