@bcts/lifehash-cli 1.0.0-alpha.17

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 @@
1
+ {"version":3,"file":"index.mjs","names":["Version","makeFromUtf8"],"sources":["../src/png-writer.ts","../src/utils.ts","../src/main.ts","../src/index.ts"],"sourcesContent":["/**\n * PNG writer for LifeHash images.\n *\n * Ported from bc-lifehash-cli C++ implementation (png-writer.hpp).\n * Uses pngjs instead of libpng.\n *\n * @module\n */\n\nimport { writeFileSync } from \"fs\";\nimport { PNG } from \"pngjs\";\nimport type { Image } from \"@bcts/lifehash\";\n\n/**\n * Writes a LifeHash image to a PNG file.\n *\n * Port of `write_image()` from lifehash.cpp lines 174-186 and\n * PNGWriter class from png-writer.hpp.\n *\n * @param image - The LifeHash image to write\n * @param filename - The output filename\n * @category PNG Encoding\n *\n * @example\n * ```typescript\n * import { makeFromUtf8 } from \"@bcts/lifehash\";\n *\n * const image = makeFromUtf8(\"Hello\");\n * writeImage(image, \"Hello.png\");\n * ```\n */\nexport function writeImage(image: Image, filename: string): void {\n const png = new PNG({\n width: image.width,\n height: image.height,\n colorType: 2, // RGB\n bitDepth: 8,\n inputColorType: 2,\n inputHasAlpha: false,\n });\n\n // Copy RGB data from image to PNG\n // PNG expects RGBA, so we need to add alpha channel\n for (let y = 0; y < image.height; y++) {\n for (let x = 0; x < image.width; x++) {\n const srcOffset = (y * image.width + x) * 3;\n const dstOffset = (y * image.width + x) * 4;\n\n // Copy RGB from source\n png.data[dstOffset] = image.colors[srcOffset]; // R\n png.data[dstOffset + 1] = image.colors[srcOffset + 1]; // G\n png.data[dstOffset + 2] = image.colors[srcOffset + 2]; // B\n png.data[dstOffset + 3] = 255; // A (fully opaque)\n }\n }\n\n // Write to file\n const buffer = PNG.sync.write(png);\n writeFileSync(filename, buffer);\n}\n\n/**\n * Generates a PNG buffer from a LifeHash image without writing to disk.\n *\n * @param image - The LifeHash image to encode\n * @returns A Buffer containing the PNG data\n * @category PNG Encoding\n *\n * @example\n * ```typescript\n * import { makeFromUtf8 } from \"@bcts/lifehash\";\n *\n * const image = makeFromUtf8(\"Hello\");\n * const pngBuffer = generatePNG(image);\n * ```\n */\nexport function generatePNG(image: Image): Buffer {\n const png = new PNG({\n width: image.width,\n height: image.height,\n colorType: 2,\n bitDepth: 8,\n inputColorType: 2,\n inputHasAlpha: false,\n });\n\n // Copy RGB data from image to PNG\n for (let y = 0; y < image.height; y++) {\n for (let x = 0; x < image.width; x++) {\n const srcOffset = (y * image.width + x) * 3;\n const dstOffset = (y * image.width + x) * 4;\n\n png.data[dstOffset] = image.colors[srcOffset];\n png.data[dstOffset + 1] = image.colors[srcOffset + 1];\n png.data[dstOffset + 2] = image.colors[srcOffset + 2];\n png.data[dstOffset + 3] = 255;\n }\n }\n\n return PNG.sync.write(png);\n}\n","/**\n * Utility functions for the LifeHash CLI.\n *\n * Ported from bc-lifehash-cli C++ implementation.\n *\n * @module\n */\n\n/**\n * Appends a path component to a path, handling trailing slashes correctly.\n *\n * Port of `appending_path_component()` from lifehash.cpp lines 18-24.\n *\n * @param path - The base path\n * @param component - The component to append\n * @returns The combined path\n * @category Utilities\n *\n * @example\n * ```typescript\n * appendingPathComponent(\"\", \"file.png\") // => \"file.png\"\n * appendingPathComponent(\"/tmp/\", \"file.png\") // => \"/tmp/file.png\"\n * appendingPathComponent(\"/tmp\", \"file.png\") // => \"/tmp/file.png\"\n * ```\n */\nexport function appendingPathComponent(path: string, component: string): string {\n if (path === \"\") {\n return component;\n }\n if (path.endsWith(\"/\")) {\n return `${path}${component}`;\n }\n return `${path}/${component}`;\n}\n\n/**\n * Selects a random element from an array.\n *\n * Port of `random_element()` template from lifehash.cpp lines 38-50.\n *\n * @param array - The array to select from\n * @returns A random element from the array\n * @category Utilities\n *\n * @example\n * ```typescript\n * randomElement([\"A\", \"B\", \"C\"]) // => \"A\" or \"B\" or \"C\"\n * ```\n */\nexport function randomElement<T>(array: T[]): T {\n const index = Math.floor(Math.random() * array.length);\n return array[index];\n}\n\n/**\n * Generates a random input string in \"XXX-XXX\" format where X is a random uppercase letter.\n *\n * Port of `make_random_input()` from lifehash.cpp lines 52-57.\n *\n * @returns A random string in \"XXX-XXX\" format\n * @category Utilities\n *\n * @example\n * ```typescript\n * makeRandomInput() // => \"ABC-DEF\" (random letters)\n * ```\n */\nexport function makeRandomInput(): string {\n const letters = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\".split(\"\");\n\n const letter = (): string => randomElement(letters);\n const cluster = (): string => `${letter()}${letter()}${letter()}`;\n\n return `${cluster()}-${cluster()}`;\n}\n","/**\n * LifeHash CLI - Command line tool for generating LifeHash PNG images.\n *\n * Ported from bc-lifehash-cli C++ implementation (lifehash.cpp).\n *\n * @module\n */\n\nimport { Command } from \"commander\";\nimport { makeFromUtf8, Version } from \"@bcts/lifehash\";\nimport { writeImage } from \"./png-writer\";\nimport { appendingPathComponent, makeRandomInput } from \"./utils\";\n\n/**\n * CLI options interface.\n *\n * Port of Parameters struct from lifehash.cpp lines 86-91.\n *\n * @category CLI\n */\nexport interface CliOptions {\n /** LifeHash version to generate */\n version: string;\n /** Size of each module (\"pixel\") */\n module: string;\n /** Output directory path */\n path: string;\n}\n\n/**\n * Parses a version string to the Version enum.\n *\n * Port of version parsing logic from lifehash.cpp lines 130-145.\n *\n * @param versionString - The version string from CLI\n * @returns The corresponding Version enum value\n * @throws Error if the version string is invalid\n * @category CLI\n *\n * @example\n * ```typescript\n * parseVersion(\"version2\") // => Version.version2\n * parseVersion(\"detailed\") // => Version.detailed\n * parseVersion(\"invalid\") // throws Error\n * ```\n */\nexport function parseVersion(versionString: string): Version {\n switch (versionString) {\n case \"version1\":\n return Version.version1;\n case \"version2\":\n return Version.version2;\n case \"detailed\":\n return Version.detailed;\n case \"fiducial\":\n return Version.fiducial;\n case \"grayscaleFiducial\":\n return Version.grayscale_fiducial;\n default:\n throw new Error(\"Invalid version.\");\n }\n}\n\n/**\n * Main execution function that generates a LifeHash image.\n *\n * Port of `run()` from lifehash.cpp lines 188-192.\n *\n * @param input - Input string to hash (or empty for random)\n * @param options - CLI options\n * @category CLI\n *\n * @example\n * ```typescript\n * run(\"Hello\", { version: \"version2\", module: \"1\", path: \".\" });\n * // Generates Hello.png in current directory\n * ```\n */\nexport function run(input: string, options: CliOptions): void {\n // Parse parameters (matching Parameters constructor from C++)\n const version = parseVersion(options.version);\n const moduleSize = parseInt(options.module, 10);\n\n if (moduleSize < 1 || isNaN(moduleSize)) {\n throw new Error(\"Illegal value.\");\n }\n\n // Use random input if none provided\n const actualInput = input !== \"\" ? input : makeRandomInput();\n\n // Generate output filename\n const outputFilename = `${actualInput}.png`;\n const outputFile = appendingPathComponent(options.path, outputFilename);\n\n // Generate LifeHash image\n const image = makeFromUtf8(actualInput, version, moduleSize);\n\n // Write to PNG file\n writeImage(image, outputFile);\n}\n\n/**\n * CLI entry point.\n *\n * Port of `main()` from lifehash.cpp lines 194-206.\n *\n * @category CLI\n */\nexport function main(): void {\n const program = new Command();\n\n program\n .name(\"lifehash\")\n .description(\"Generate LifeHash PNG images from input strings\")\n .argument(\"[input]\", \"Input string to hash (default: random XXX-XXX)\")\n .option(\n \"-v, --version <version>\",\n \"LifeHash version: version1, version2, detailed, fiducial, grayscaleFiducial\",\n \"version2\",\n )\n .option(\"-m, --module <size>\", 'Size of each module (\"pixel\")', \"1\")\n .option(\"-p, --path <path>\", \"Output directory path\", \"\")\n .action((input: string | undefined, options: CliOptions) => {\n try {\n run(input ?? \"\", options);\n } catch (error) {\n if (error instanceof Error) {\n console.log(`\\u{1F928} ${error.message}`);\n console.log();\n program.help();\n }\n process.exit(1);\n }\n });\n\n program.parse();\n}\n\n// Run CLI when executed directly (not when imported as a module)\n// Check if this file is the main entry point\nconst isMainModule =\n typeof process !== \"undefined\" &&\n typeof process.argv[1] === \"string\" &&\n (process.argv[1].endsWith(\"/main.mjs\") ||\n process.argv[1].endsWith(\"/main.js\") ||\n process.argv[1].includes(\"lifehash-cli\"));\n\nif (isMainModule) {\n main();\n}\n","/**\n * @bcts/lifehash-cli - Command line tool for generating LifeHash PNG images.\n *\n * This package provides both a CLI tool and a programmatic API for generating\n * LifeHash visual hash images as PNG files.\n *\n * @packageDocumentation\n * @module @bcts/lifehash-cli\n *\n * @example CLI Usage\n * ```bash\n * # Generate a LifeHash from a string\n * lifehash Hello\n *\n * # Generate with specific version and module size\n * lifehash -v detailed -m 8 Hello\n *\n * # Generate with random input\n * lifehash\n * ```\n *\n * @example Programmatic Usage\n * ```typescript\n * import { generateLifeHash } from \"@bcts/lifehash-cli\";\n * import { writeFileSync } from \"fs\";\n *\n * // Generate a PNG buffer\n * const pngBuffer = generateLifeHash(\"Hello\", {\n * version: \"version2\",\n * moduleSize: 1,\n * });\n *\n * // Write to file\n * writeFileSync(\"Hello.png\", pngBuffer);\n * ```\n */\n\nimport { generatePNG, writeImage } from \"./png-writer\";\nimport { parseVersion, run, type CliOptions } from \"./main\";\nimport { makeFromUtf8, Version, type Image } from \"@bcts/lifehash\";\n\n// PNG encoding exports\nexport { writeImage, generatePNG };\n\n// Utility exports\nexport { appendingPathComponent, randomElement, makeRandomInput } from \"./utils\";\n\n// CLI exports\nexport { parseVersion, run, type CliOptions };\n\n// Re-export @bcts/lifehash types for convenience\nexport { Version, type Image };\n\n// Re-export makeFromUtf8 for programmatic usage\nexport { makeFromUtf8 };\n\n/**\n * Options for generating a LifeHash image.\n *\n * @category Image Generation\n */\nexport interface GenerateOptions {\n /**\n * LifeHash version to generate.\n * @default \"version2\"\n */\n version?: \"version1\" | \"version2\" | \"detailed\" | \"fiducial\" | \"grayscaleFiducial\";\n /**\n * Size of each module (\"pixel\").\n * @default 1\n */\n moduleSize?: number;\n}\n\n/**\n * Generates a LifeHash PNG buffer from an input string.\n *\n * This is the main programmatic API for generating LifeHash images.\n *\n * @param input - The input string to hash\n * @param options - Generation options\n * @returns A Buffer containing the PNG data\n * @category Image Generation\n *\n * @example\n * ```typescript\n * import { generateLifeHash } from \"@bcts/lifehash-cli\";\n * import { writeFileSync } from \"fs\";\n *\n * const pngBuffer = generateLifeHash(\"Hello\", {\n * version: \"version2\",\n * moduleSize: 1,\n * });\n *\n * writeFileSync(\"Hello.png\", pngBuffer);\n * ```\n */\nexport function generateLifeHash(input: string, options: GenerateOptions = {}): Buffer {\n const { version = \"version2\", moduleSize = 1 } = options;\n\n const versionEnum = parseVersion(version);\n const image = makeFromUtf8(input, versionEnum, moduleSize);\n return generatePNG(image);\n}\n"],"mappings":"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"}
package/dist/main.mjs ADDED
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { Version, makeFromUtf8 } from "@bcts/lifehash";
4
+ import { writeFileSync } from "fs";
5
+ import { PNG } from "pngjs";
6
+
7
+ //#region src/png-writer.ts
8
+ /**
9
+ * PNG writer for LifeHash images.
10
+ *
11
+ * Ported from bc-lifehash-cli C++ implementation (png-writer.hpp).
12
+ * Uses pngjs instead of libpng.
13
+ *
14
+ * @module
15
+ */
16
+ /**
17
+ * Writes a LifeHash image to a PNG file.
18
+ *
19
+ * Port of `write_image()` from lifehash.cpp lines 174-186 and
20
+ * PNGWriter class from png-writer.hpp.
21
+ *
22
+ * @param image - The LifeHash image to write
23
+ * @param filename - The output filename
24
+ * @category PNG Encoding
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * import { makeFromUtf8 } from "@bcts/lifehash";
29
+ *
30
+ * const image = makeFromUtf8("Hello");
31
+ * writeImage(image, "Hello.png");
32
+ * ```
33
+ */
34
+ function writeImage(image, filename) {
35
+ const png = new PNG({
36
+ width: image.width,
37
+ height: image.height,
38
+ colorType: 2,
39
+ bitDepth: 8,
40
+ inputColorType: 2,
41
+ inputHasAlpha: false
42
+ });
43
+ for (let y = 0; y < image.height; y++) for (let x = 0; x < image.width; x++) {
44
+ const srcOffset = (y * image.width + x) * 3;
45
+ const dstOffset = (y * image.width + x) * 4;
46
+ png.data[dstOffset] = image.colors[srcOffset];
47
+ png.data[dstOffset + 1] = image.colors[srcOffset + 1];
48
+ png.data[dstOffset + 2] = image.colors[srcOffset + 2];
49
+ png.data[dstOffset + 3] = 255;
50
+ }
51
+ writeFileSync(filename, PNG.sync.write(png));
52
+ }
53
+
54
+ //#endregion
55
+ //#region src/utils.ts
56
+ /**
57
+ * Utility functions for the LifeHash CLI.
58
+ *
59
+ * Ported from bc-lifehash-cli C++ implementation.
60
+ *
61
+ * @module
62
+ */
63
+ /**
64
+ * Appends a path component to a path, handling trailing slashes correctly.
65
+ *
66
+ * Port of `appending_path_component()` from lifehash.cpp lines 18-24.
67
+ *
68
+ * @param path - The base path
69
+ * @param component - The component to append
70
+ * @returns The combined path
71
+ * @category Utilities
72
+ *
73
+ * @example
74
+ * ```typescript
75
+ * appendingPathComponent("", "file.png") // => "file.png"
76
+ * appendingPathComponent("/tmp/", "file.png") // => "/tmp/file.png"
77
+ * appendingPathComponent("/tmp", "file.png") // => "/tmp/file.png"
78
+ * ```
79
+ */
80
+ function appendingPathComponent(path, component) {
81
+ if (path === "") return component;
82
+ if (path.endsWith("/")) return `${path}${component}`;
83
+ return `${path}/${component}`;
84
+ }
85
+ /**
86
+ * Selects a random element from an array.
87
+ *
88
+ * Port of `random_element()` template from lifehash.cpp lines 38-50.
89
+ *
90
+ * @param array - The array to select from
91
+ * @returns A random element from the array
92
+ * @category Utilities
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * randomElement(["A", "B", "C"]) // => "A" or "B" or "C"
97
+ * ```
98
+ */
99
+ function randomElement(array) {
100
+ return array[Math.floor(Math.random() * array.length)];
101
+ }
102
+ /**
103
+ * Generates a random input string in "XXX-XXX" format where X is a random uppercase letter.
104
+ *
105
+ * Port of `make_random_input()` from lifehash.cpp lines 52-57.
106
+ *
107
+ * @returns A random string in "XXX-XXX" format
108
+ * @category Utilities
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * makeRandomInput() // => "ABC-DEF" (random letters)
113
+ * ```
114
+ */
115
+ function makeRandomInput() {
116
+ const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
117
+ const letter = () => randomElement(letters);
118
+ const cluster = () => `${letter()}${letter()}${letter()}`;
119
+ return `${cluster()}-${cluster()}`;
120
+ }
121
+
122
+ //#endregion
123
+ //#region src/main.ts
124
+ /**
125
+ * LifeHash CLI - Command line tool for generating LifeHash PNG images.
126
+ *
127
+ * Ported from bc-lifehash-cli C++ implementation (lifehash.cpp).
128
+ *
129
+ * @module
130
+ */
131
+ /**
132
+ * Parses a version string to the Version enum.
133
+ *
134
+ * Port of version parsing logic from lifehash.cpp lines 130-145.
135
+ *
136
+ * @param versionString - The version string from CLI
137
+ * @returns The corresponding Version enum value
138
+ * @throws Error if the version string is invalid
139
+ * @category CLI
140
+ *
141
+ * @example
142
+ * ```typescript
143
+ * parseVersion("version2") // => Version.version2
144
+ * parseVersion("detailed") // => Version.detailed
145
+ * parseVersion("invalid") // throws Error
146
+ * ```
147
+ */
148
+ function parseVersion(versionString) {
149
+ switch (versionString) {
150
+ case "version1": return Version.version1;
151
+ case "version2": return Version.version2;
152
+ case "detailed": return Version.detailed;
153
+ case "fiducial": return Version.fiducial;
154
+ case "grayscaleFiducial": return Version.grayscale_fiducial;
155
+ default: throw new Error("Invalid version.");
156
+ }
157
+ }
158
+ /**
159
+ * Main execution function that generates a LifeHash image.
160
+ *
161
+ * Port of `run()` from lifehash.cpp lines 188-192.
162
+ *
163
+ * @param input - Input string to hash (or empty for random)
164
+ * @param options - CLI options
165
+ * @category CLI
166
+ *
167
+ * @example
168
+ * ```typescript
169
+ * run("Hello", { version: "version2", module: "1", path: "." });
170
+ * // Generates Hello.png in current directory
171
+ * ```
172
+ */
173
+ function run(input, options) {
174
+ const version = parseVersion(options.version);
175
+ const moduleSize = parseInt(options.module, 10);
176
+ if (moduleSize < 1 || isNaN(moduleSize)) throw new Error("Illegal value.");
177
+ const actualInput = input !== "" ? input : makeRandomInput();
178
+ const outputFilename = `${actualInput}.png`;
179
+ const outputFile = appendingPathComponent(options.path, outputFilename);
180
+ writeImage(makeFromUtf8(actualInput, version, moduleSize), outputFile);
181
+ }
182
+ /**
183
+ * CLI entry point.
184
+ *
185
+ * Port of `main()` from lifehash.cpp lines 194-206.
186
+ *
187
+ * @category CLI
188
+ */
189
+ function main() {
190
+ const program = new Command();
191
+ program.name("lifehash").description("Generate LifeHash PNG images from input strings").argument("[input]", "Input string to hash (default: random XXX-XXX)").option("-v, --version <version>", "LifeHash version: version1, version2, detailed, fiducial, grayscaleFiducial", "version2").option("-m, --module <size>", "Size of each module (\"pixel\")", "1").option("-p, --path <path>", "Output directory path", "").action((input, options) => {
192
+ try {
193
+ run(input ?? "", options);
194
+ } catch (error) {
195
+ if (error instanceof Error) {
196
+ console.log(`\u{1F928} ${error.message}`);
197
+ console.log();
198
+ program.help();
199
+ }
200
+ process.exit(1);
201
+ }
202
+ });
203
+ program.parse();
204
+ }
205
+ if (typeof process !== "undefined" && typeof process.argv[1] === "string" && (process.argv[1].endsWith("/main.mjs") || process.argv[1].endsWith("/main.js") || process.argv[1].includes("lifehash-cli"))) main();
206
+
207
+ //#endregion
208
+ export { main, parseVersion, run };
209
+ //# sourceMappingURL=main.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.mjs","names":[],"sources":["../src/png-writer.ts","../src/utils.ts","../src/main.ts"],"sourcesContent":["/**\n * PNG writer for LifeHash images.\n *\n * Ported from bc-lifehash-cli C++ implementation (png-writer.hpp).\n * Uses pngjs instead of libpng.\n *\n * @module\n */\n\nimport { writeFileSync } from \"fs\";\nimport { PNG } from \"pngjs\";\nimport type { Image } from \"@bcts/lifehash\";\n\n/**\n * Writes a LifeHash image to a PNG file.\n *\n * Port of `write_image()` from lifehash.cpp lines 174-186 and\n * PNGWriter class from png-writer.hpp.\n *\n * @param image - The LifeHash image to write\n * @param filename - The output filename\n * @category PNG Encoding\n *\n * @example\n * ```typescript\n * import { makeFromUtf8 } from \"@bcts/lifehash\";\n *\n * const image = makeFromUtf8(\"Hello\");\n * writeImage(image, \"Hello.png\");\n * ```\n */\nexport function writeImage(image: Image, filename: string): void {\n const png = new PNG({\n width: image.width,\n height: image.height,\n colorType: 2, // RGB\n bitDepth: 8,\n inputColorType: 2,\n inputHasAlpha: false,\n });\n\n // Copy RGB data from image to PNG\n // PNG expects RGBA, so we need to add alpha channel\n for (let y = 0; y < image.height; y++) {\n for (let x = 0; x < image.width; x++) {\n const srcOffset = (y * image.width + x) * 3;\n const dstOffset = (y * image.width + x) * 4;\n\n // Copy RGB from source\n png.data[dstOffset] = image.colors[srcOffset]; // R\n png.data[dstOffset + 1] = image.colors[srcOffset + 1]; // G\n png.data[dstOffset + 2] = image.colors[srcOffset + 2]; // B\n png.data[dstOffset + 3] = 255; // A (fully opaque)\n }\n }\n\n // Write to file\n const buffer = PNG.sync.write(png);\n writeFileSync(filename, buffer);\n}\n\n/**\n * Generates a PNG buffer from a LifeHash image without writing to disk.\n *\n * @param image - The LifeHash image to encode\n * @returns A Buffer containing the PNG data\n * @category PNG Encoding\n *\n * @example\n * ```typescript\n * import { makeFromUtf8 } from \"@bcts/lifehash\";\n *\n * const image = makeFromUtf8(\"Hello\");\n * const pngBuffer = generatePNG(image);\n * ```\n */\nexport function generatePNG(image: Image): Buffer {\n const png = new PNG({\n width: image.width,\n height: image.height,\n colorType: 2,\n bitDepth: 8,\n inputColorType: 2,\n inputHasAlpha: false,\n });\n\n // Copy RGB data from image to PNG\n for (let y = 0; y < image.height; y++) {\n for (let x = 0; x < image.width; x++) {\n const srcOffset = (y * image.width + x) * 3;\n const dstOffset = (y * image.width + x) * 4;\n\n png.data[dstOffset] = image.colors[srcOffset];\n png.data[dstOffset + 1] = image.colors[srcOffset + 1];\n png.data[dstOffset + 2] = image.colors[srcOffset + 2];\n png.data[dstOffset + 3] = 255;\n }\n }\n\n return PNG.sync.write(png);\n}\n","/**\n * Utility functions for the LifeHash CLI.\n *\n * Ported from bc-lifehash-cli C++ implementation.\n *\n * @module\n */\n\n/**\n * Appends a path component to a path, handling trailing slashes correctly.\n *\n * Port of `appending_path_component()` from lifehash.cpp lines 18-24.\n *\n * @param path - The base path\n * @param component - The component to append\n * @returns The combined path\n * @category Utilities\n *\n * @example\n * ```typescript\n * appendingPathComponent(\"\", \"file.png\") // => \"file.png\"\n * appendingPathComponent(\"/tmp/\", \"file.png\") // => \"/tmp/file.png\"\n * appendingPathComponent(\"/tmp\", \"file.png\") // => \"/tmp/file.png\"\n * ```\n */\nexport function appendingPathComponent(path: string, component: string): string {\n if (path === \"\") {\n return component;\n }\n if (path.endsWith(\"/\")) {\n return `${path}${component}`;\n }\n return `${path}/${component}`;\n}\n\n/**\n * Selects a random element from an array.\n *\n * Port of `random_element()` template from lifehash.cpp lines 38-50.\n *\n * @param array - The array to select from\n * @returns A random element from the array\n * @category Utilities\n *\n * @example\n * ```typescript\n * randomElement([\"A\", \"B\", \"C\"]) // => \"A\" or \"B\" or \"C\"\n * ```\n */\nexport function randomElement<T>(array: T[]): T {\n const index = Math.floor(Math.random() * array.length);\n return array[index];\n}\n\n/**\n * Generates a random input string in \"XXX-XXX\" format where X is a random uppercase letter.\n *\n * Port of `make_random_input()` from lifehash.cpp lines 52-57.\n *\n * @returns A random string in \"XXX-XXX\" format\n * @category Utilities\n *\n * @example\n * ```typescript\n * makeRandomInput() // => \"ABC-DEF\" (random letters)\n * ```\n */\nexport function makeRandomInput(): string {\n const letters = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\".split(\"\");\n\n const letter = (): string => randomElement(letters);\n const cluster = (): string => `${letter()}${letter()}${letter()}`;\n\n return `${cluster()}-${cluster()}`;\n}\n","/**\n * LifeHash CLI - Command line tool for generating LifeHash PNG images.\n *\n * Ported from bc-lifehash-cli C++ implementation (lifehash.cpp).\n *\n * @module\n */\n\nimport { Command } from \"commander\";\nimport { makeFromUtf8, Version } from \"@bcts/lifehash\";\nimport { writeImage } from \"./png-writer\";\nimport { appendingPathComponent, makeRandomInput } from \"./utils\";\n\n/**\n * CLI options interface.\n *\n * Port of Parameters struct from lifehash.cpp lines 86-91.\n *\n * @category CLI\n */\nexport interface CliOptions {\n /** LifeHash version to generate */\n version: string;\n /** Size of each module (\"pixel\") */\n module: string;\n /** Output directory path */\n path: string;\n}\n\n/**\n * Parses a version string to the Version enum.\n *\n * Port of version parsing logic from lifehash.cpp lines 130-145.\n *\n * @param versionString - The version string from CLI\n * @returns The corresponding Version enum value\n * @throws Error if the version string is invalid\n * @category CLI\n *\n * @example\n * ```typescript\n * parseVersion(\"version2\") // => Version.version2\n * parseVersion(\"detailed\") // => Version.detailed\n * parseVersion(\"invalid\") // throws Error\n * ```\n */\nexport function parseVersion(versionString: string): Version {\n switch (versionString) {\n case \"version1\":\n return Version.version1;\n case \"version2\":\n return Version.version2;\n case \"detailed\":\n return Version.detailed;\n case \"fiducial\":\n return Version.fiducial;\n case \"grayscaleFiducial\":\n return Version.grayscale_fiducial;\n default:\n throw new Error(\"Invalid version.\");\n }\n}\n\n/**\n * Main execution function that generates a LifeHash image.\n *\n * Port of `run()` from lifehash.cpp lines 188-192.\n *\n * @param input - Input string to hash (or empty for random)\n * @param options - CLI options\n * @category CLI\n *\n * @example\n * ```typescript\n * run(\"Hello\", { version: \"version2\", module: \"1\", path: \".\" });\n * // Generates Hello.png in current directory\n * ```\n */\nexport function run(input: string, options: CliOptions): void {\n // Parse parameters (matching Parameters constructor from C++)\n const version = parseVersion(options.version);\n const moduleSize = parseInt(options.module, 10);\n\n if (moduleSize < 1 || isNaN(moduleSize)) {\n throw new Error(\"Illegal value.\");\n }\n\n // Use random input if none provided\n const actualInput = input !== \"\" ? input : makeRandomInput();\n\n // Generate output filename\n const outputFilename = `${actualInput}.png`;\n const outputFile = appendingPathComponent(options.path, outputFilename);\n\n // Generate LifeHash image\n const image = makeFromUtf8(actualInput, version, moduleSize);\n\n // Write to PNG file\n writeImage(image, outputFile);\n}\n\n/**\n * CLI entry point.\n *\n * Port of `main()` from lifehash.cpp lines 194-206.\n *\n * @category CLI\n */\nexport function main(): void {\n const program = new Command();\n\n program\n .name(\"lifehash\")\n .description(\"Generate LifeHash PNG images from input strings\")\n .argument(\"[input]\", \"Input string to hash (default: random XXX-XXX)\")\n .option(\n \"-v, --version <version>\",\n \"LifeHash version: version1, version2, detailed, fiducial, grayscaleFiducial\",\n \"version2\",\n )\n .option(\"-m, --module <size>\", 'Size of each module (\"pixel\")', \"1\")\n .option(\"-p, --path <path>\", \"Output directory path\", \"\")\n .action((input: string | undefined, options: CliOptions) => {\n try {\n run(input ?? \"\", options);\n } catch (error) {\n if (error instanceof Error) {\n console.log(`\\u{1F928} ${error.message}`);\n console.log();\n program.help();\n }\n process.exit(1);\n }\n });\n\n program.parse();\n}\n\n// Run CLI when executed directly (not when imported as a module)\n// Check if this file is the main entry point\nconst isMainModule =\n typeof process !== \"undefined\" &&\n typeof process.argv[1] === \"string\" &&\n (process.argv[1].endsWith(\"/main.mjs\") ||\n process.argv[1].endsWith(\"/main.js\") ||\n process.argv[1].includes(\"lifehash-cli\"));\n\nif (isMainModule) {\n main();\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@bcts/lifehash-cli",
3
+ "version": "1.0.0-alpha.17",
4
+ "type": "module",
5
+ "description": "A command-line tool for generating LifeHash visual hash images as PNG files.",
6
+ "license": "BSD-2-Clause-Patent",
7
+ "contributors": [
8
+ {
9
+ "name": "Leonardo Custodio",
10
+ "email": "leonardo.custodio@parity.io",
11
+ "url": "https://github.com/leonardocustodio"
12
+ },
13
+ {
14
+ "name": "Karim Jedda",
15
+ "email": "karim@parity.io",
16
+ "url": "https://github.com/KarimJedda"
17
+ }
18
+ ],
19
+ "homepage": "https://bcts.dev",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/leonardocustodio/bcts.git",
23
+ "directory": "tools/lifehash-cli"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/leonardocustodio/bcts/issues"
27
+ },
28
+ "bin": {
29
+ "lifehash": "./dist/main.mjs"
30
+ },
31
+ "main": "dist/index.cjs",
32
+ "module": "dist/index.mjs",
33
+ "types": "dist/index.d.mts",
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/index.d.mts",
37
+ "import": "./dist/index.mjs",
38
+ "require": "./dist/index.cjs",
39
+ "default": "./dist/index.mjs"
40
+ }
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "src",
45
+ "README.md"
46
+ ],
47
+ "scripts": {
48
+ "build": "tsdown",
49
+ "dev": "tsdown --watch",
50
+ "test": "vitest run",
51
+ "test:watch": "vitest",
52
+ "lint": "eslint 'src/**/*.ts' 'tests/**/*.ts'",
53
+ "lint:fix": "eslint 'src/**/*.ts' 'tests/**/*.ts' --fix",
54
+ "typecheck": "tsc --noEmit",
55
+ "clean": "rm -rf dist",
56
+ "docs": "typedoc",
57
+ "prepublishOnly": "npm run clean && npm run build && npm test"
58
+ },
59
+ "keywords": [
60
+ "lifehash",
61
+ "visual-hash",
62
+ "hash-visualization",
63
+ "png",
64
+ "image",
65
+ "cli",
66
+ "blockchain-commons",
67
+ "game-of-life",
68
+ "cellular-automata",
69
+ "fiducial"
70
+ ],
71
+ "engines": {
72
+ "node": ">=18.0.0"
73
+ },
74
+ "dependencies": {
75
+ "@bcts/lifehash": "workspace:*",
76
+ "commander": "^14.0.0",
77
+ "pngjs": "^7.0.0"
78
+ },
79
+ "devDependencies": {
80
+ "@bcts/eslint": "workspace:*",
81
+ "@bcts/tsconfig": "workspace:*",
82
+ "@eslint/js": "^9.39.2",
83
+ "@types/node": "^25.0.10",
84
+ "@types/pngjs": "^6.0.5",
85
+ "eslint": "^9.39.2",
86
+ "ts-node": "^10.9.2",
87
+ "tsdown": "^0.20.1",
88
+ "typedoc": "^0.28.16",
89
+ "typescript": "^5.9.3",
90
+ "vitest": "^4.0.18"
91
+ }
92
+ }
package/src/index.ts ADDED
@@ -0,0 +1,104 @@
1
+ /**
2
+ * @bcts/lifehash-cli - Command line tool for generating LifeHash PNG images.
3
+ *
4
+ * This package provides both a CLI tool and a programmatic API for generating
5
+ * LifeHash visual hash images as PNG files.
6
+ *
7
+ * @packageDocumentation
8
+ * @module @bcts/lifehash-cli
9
+ *
10
+ * @example CLI Usage
11
+ * ```bash
12
+ * # Generate a LifeHash from a string
13
+ * lifehash Hello
14
+ *
15
+ * # Generate with specific version and module size
16
+ * lifehash -v detailed -m 8 Hello
17
+ *
18
+ * # Generate with random input
19
+ * lifehash
20
+ * ```
21
+ *
22
+ * @example Programmatic Usage
23
+ * ```typescript
24
+ * import { generateLifeHash } from "@bcts/lifehash-cli";
25
+ * import { writeFileSync } from "fs";
26
+ *
27
+ * // Generate a PNG buffer
28
+ * const pngBuffer = generateLifeHash("Hello", {
29
+ * version: "version2",
30
+ * moduleSize: 1,
31
+ * });
32
+ *
33
+ * // Write to file
34
+ * writeFileSync("Hello.png", pngBuffer);
35
+ * ```
36
+ */
37
+
38
+ import { generatePNG, writeImage } from "./png-writer";
39
+ import { parseVersion, run, type CliOptions } from "./main";
40
+ import { makeFromUtf8, Version, type Image } from "@bcts/lifehash";
41
+
42
+ // PNG encoding exports
43
+ export { writeImage, generatePNG };
44
+
45
+ // Utility exports
46
+ export { appendingPathComponent, randomElement, makeRandomInput } from "./utils";
47
+
48
+ // CLI exports
49
+ export { parseVersion, run, type CliOptions };
50
+
51
+ // Re-export @bcts/lifehash types for convenience
52
+ export { Version, type Image };
53
+
54
+ // Re-export makeFromUtf8 for programmatic usage
55
+ export { makeFromUtf8 };
56
+
57
+ /**
58
+ * Options for generating a LifeHash image.
59
+ *
60
+ * @category Image Generation
61
+ */
62
+ export interface GenerateOptions {
63
+ /**
64
+ * LifeHash version to generate.
65
+ * @default "version2"
66
+ */
67
+ version?: "version1" | "version2" | "detailed" | "fiducial" | "grayscaleFiducial";
68
+ /**
69
+ * Size of each module ("pixel").
70
+ * @default 1
71
+ */
72
+ moduleSize?: number;
73
+ }
74
+
75
+ /**
76
+ * Generates a LifeHash PNG buffer from an input string.
77
+ *
78
+ * This is the main programmatic API for generating LifeHash images.
79
+ *
80
+ * @param input - The input string to hash
81
+ * @param options - Generation options
82
+ * @returns A Buffer containing the PNG data
83
+ * @category Image Generation
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * import { generateLifeHash } from "@bcts/lifehash-cli";
88
+ * import { writeFileSync } from "fs";
89
+ *
90
+ * const pngBuffer = generateLifeHash("Hello", {
91
+ * version: "version2",
92
+ * moduleSize: 1,
93
+ * });
94
+ *
95
+ * writeFileSync("Hello.png", pngBuffer);
96
+ * ```
97
+ */
98
+ export function generateLifeHash(input: string, options: GenerateOptions = {}): Buffer {
99
+ const { version = "version2", moduleSize = 1 } = options;
100
+
101
+ const versionEnum = parseVersion(version);
102
+ const image = makeFromUtf8(input, versionEnum, moduleSize);
103
+ return generatePNG(image);
104
+ }
package/src/main.ts ADDED
@@ -0,0 +1,150 @@
1
+ /**
2
+ * LifeHash CLI - Command line tool for generating LifeHash PNG images.
3
+ *
4
+ * Ported from bc-lifehash-cli C++ implementation (lifehash.cpp).
5
+ *
6
+ * @module
7
+ */
8
+
9
+ import { Command } from "commander";
10
+ import { makeFromUtf8, Version } from "@bcts/lifehash";
11
+ import { writeImage } from "./png-writer";
12
+ import { appendingPathComponent, makeRandomInput } from "./utils";
13
+
14
+ /**
15
+ * CLI options interface.
16
+ *
17
+ * Port of Parameters struct from lifehash.cpp lines 86-91.
18
+ *
19
+ * @category CLI
20
+ */
21
+ export interface CliOptions {
22
+ /** LifeHash version to generate */
23
+ version: string;
24
+ /** Size of each module ("pixel") */
25
+ module: string;
26
+ /** Output directory path */
27
+ path: string;
28
+ }
29
+
30
+ /**
31
+ * Parses a version string to the Version enum.
32
+ *
33
+ * Port of version parsing logic from lifehash.cpp lines 130-145.
34
+ *
35
+ * @param versionString - The version string from CLI
36
+ * @returns The corresponding Version enum value
37
+ * @throws Error if the version string is invalid
38
+ * @category CLI
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * parseVersion("version2") // => Version.version2
43
+ * parseVersion("detailed") // => Version.detailed
44
+ * parseVersion("invalid") // throws Error
45
+ * ```
46
+ */
47
+ export function parseVersion(versionString: string): Version {
48
+ switch (versionString) {
49
+ case "version1":
50
+ return Version.version1;
51
+ case "version2":
52
+ return Version.version2;
53
+ case "detailed":
54
+ return Version.detailed;
55
+ case "fiducial":
56
+ return Version.fiducial;
57
+ case "grayscaleFiducial":
58
+ return Version.grayscale_fiducial;
59
+ default:
60
+ throw new Error("Invalid version.");
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Main execution function that generates a LifeHash image.
66
+ *
67
+ * Port of `run()` from lifehash.cpp lines 188-192.
68
+ *
69
+ * @param input - Input string to hash (or empty for random)
70
+ * @param options - CLI options
71
+ * @category CLI
72
+ *
73
+ * @example
74
+ * ```typescript
75
+ * run("Hello", { version: "version2", module: "1", path: "." });
76
+ * // Generates Hello.png in current directory
77
+ * ```
78
+ */
79
+ export function run(input: string, options: CliOptions): void {
80
+ // Parse parameters (matching Parameters constructor from C++)
81
+ const version = parseVersion(options.version);
82
+ const moduleSize = parseInt(options.module, 10);
83
+
84
+ if (moduleSize < 1 || isNaN(moduleSize)) {
85
+ throw new Error("Illegal value.");
86
+ }
87
+
88
+ // Use random input if none provided
89
+ const actualInput = input !== "" ? input : makeRandomInput();
90
+
91
+ // Generate output filename
92
+ const outputFilename = `${actualInput}.png`;
93
+ const outputFile = appendingPathComponent(options.path, outputFilename);
94
+
95
+ // Generate LifeHash image
96
+ const image = makeFromUtf8(actualInput, version, moduleSize);
97
+
98
+ // Write to PNG file
99
+ writeImage(image, outputFile);
100
+ }
101
+
102
+ /**
103
+ * CLI entry point.
104
+ *
105
+ * Port of `main()` from lifehash.cpp lines 194-206.
106
+ *
107
+ * @category CLI
108
+ */
109
+ export function main(): void {
110
+ const program = new Command();
111
+
112
+ program
113
+ .name("lifehash")
114
+ .description("Generate LifeHash PNG images from input strings")
115
+ .argument("[input]", "Input string to hash (default: random XXX-XXX)")
116
+ .option(
117
+ "-v, --version <version>",
118
+ "LifeHash version: version1, version2, detailed, fiducial, grayscaleFiducial",
119
+ "version2",
120
+ )
121
+ .option("-m, --module <size>", 'Size of each module ("pixel")', "1")
122
+ .option("-p, --path <path>", "Output directory path", "")
123
+ .action((input: string | undefined, options: CliOptions) => {
124
+ try {
125
+ run(input ?? "", options);
126
+ } catch (error) {
127
+ if (error instanceof Error) {
128
+ console.log(`\u{1F928} ${error.message}`);
129
+ console.log();
130
+ program.help();
131
+ }
132
+ process.exit(1);
133
+ }
134
+ });
135
+
136
+ program.parse();
137
+ }
138
+
139
+ // Run CLI when executed directly (not when imported as a module)
140
+ // Check if this file is the main entry point
141
+ const isMainModule =
142
+ typeof process !== "undefined" &&
143
+ typeof process.argv[1] === "string" &&
144
+ (process.argv[1].endsWith("/main.mjs") ||
145
+ process.argv[1].endsWith("/main.js") ||
146
+ process.argv[1].includes("lifehash-cli"));
147
+
148
+ if (isMainModule) {
149
+ main();
150
+ }