hashname-cli 0.1.0__py3-none-any.whl

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,73 @@
1
+ Metadata-Version: 2.4
2
+ Name: hashname-cli
3
+ Version: 0.1.0
4
+ Summary: Generate deterministic human-readable names from any input
5
+ Author-email: Marcus <marcus.builds.things@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/marcusbuildsthings-droid/hashname
8
+ Keywords: cli,hash,name,deterministic,human-readable
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Utilities
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: click>=8.0
17
+ Dynamic: license-file
18
+
19
+ # hashname-cli
20
+
21
+ Generate deterministic human-readable names from any input — hashes, UUIDs, IPs, commit SHAs, anything.
22
+
23
+ Same input always produces the same name. Like Docker container names, but for everything.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install hashname-cli
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ```bash
34
+ # Basic naming
35
+ hashname name "abc123" # → keen-spark
36
+ hashname name "192.168.1.1" # → golden-reef
37
+
38
+ # More words for uniqueness
39
+ hashname name "abc123" -w 3 # → keen-cosmic-spark
40
+
41
+ # Capitalize
42
+ hashname name "abc123" -c # → Keen-Spark
43
+
44
+ # Custom separator
45
+ hashname name "abc123" -s "_" # → keen_spark
46
+
47
+ # Multiple names from one input
48
+ hashname name "server" -n 5
49
+
50
+ # Batch processing
51
+ hashname batch id1 id2 id3
52
+ echo -e "hash1\nhash2" | hashname batch
53
+
54
+ # JSON output
55
+ hashname name "abc123" --json
56
+
57
+ # Verify determinism
58
+ hashname verify "abc123"
59
+
60
+ # Word list stats
61
+ hashname stats
62
+ ```
63
+
64
+ ## Why?
65
+
66
+ - **Log readability**: Replace `a4995e18227e62fa` with `bold-ember`
67
+ - **Quick identification**: Same hash = same name, every time
68
+ - **No collisions needed**: It's a label, not a UUID
69
+ - **Agent-friendly**: `--json` output, predictable CLI
70
+
71
+ ## For AI Agents
72
+
73
+ See [SKILL.md](SKILL.md) for agent-optimized documentation.
@@ -0,0 +1,7 @@
1
+ hashname_cli.py,sha256=2T5OSH12h_5fXm-xU2tb7euKFLA_HjwZqLG9NJlaNzc,6101
2
+ hashname_cli-0.1.0.dist-info/licenses/LICENSE,sha256=9tNBpWq8KGbuJqmeComp40OiNnbvpvsKn1YP26PUtck,1063
3
+ hashname_cli-0.1.0.dist-info/METADATA,sha256=mfvm869g0qnv2oM0qXZWnECryAJE989qKntgEe-3KJA,1857
4
+ hashname_cli-0.1.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
5
+ hashname_cli-0.1.0.dist-info/entry_points.txt,sha256=vU-4LuEUfhMkZNTqA1-O6vELhAohrv-gqia-gRA932g,47
6
+ hashname_cli-0.1.0.dist-info/top_level.txt,sha256=cOpDkxysuDx639m7OLOBpGjp0iYQ9pf9ub-f_9E8d_0,13
7
+ hashname_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ hashname = hashname_cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marcus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ hashname_cli
hashname_cli.py ADDED
@@ -0,0 +1,157 @@
1
+ """hashname-cli - Generate deterministic human-readable names from any input."""
2
+
3
+ import hashlib
4
+ import json
5
+ import sys
6
+
7
+ import click
8
+
9
+ __version__ = "0.1.0"
10
+
11
+ ADJECTIVES = [
12
+ "ancient", "bold", "calm", "dark", "eager", "fierce", "gentle", "happy",
13
+ "icy", "jolly", "keen", "lively", "mellow", "noble", "odd", "proud",
14
+ "quiet", "rapid", "sharp", "tall", "unique", "vivid", "warm", "young",
15
+ "zesty", "bright", "clever", "daring", "elegant", "fancy", "golden",
16
+ "hollow", "iron", "jade", "kind", "lucky", "mighty", "neat", "orange",
17
+ "pale", "quick", "red", "silver", "tidy", "ultra", "vast", "wild",
18
+ "xenial", "yellow", "zen", "arctic", "blazing", "cosmic", "deep",
19
+ "electric", "frozen", "grand", "hidden", "infinite", "jeweled", "kinetic",
20
+ "lunar", "misty", "neon", "opaque", "phantom", "quartz", "rustic",
21
+ "sonic", "turbo", "umbral", "velvet", "wired", "crystal", "digital",
22
+ "ember", "fading", "glowing", "hazy", "ivory", "jumbo", "krypton",
23
+ "liquid", "marble", "nimble", "obsidian", "pixel", "quantum", "royal",
24
+ "steel", "toxic", "urban", "violet", "woven", "xerox", "yonder", "zero",
25
+ "atomic", "binary", "chrome", "dusty", "epic", "flint", "granite",
26
+ "hyper", "indigo", "jungle", "karma", "lemon", "molten", "nova",
27
+ "onyx", "prism", "quill", "rogue", "scarlet", "terra", "ultra",
28
+ "vortex", "whisper", "xenon", "yew", "zinc",
29
+ ]
30
+
31
+ NOUNS = [
32
+ "alpha", "bear", "cloud", "dawn", "echo", "flame", "ghost", "hawk",
33
+ "iris", "jade", "kite", "lion", "moon", "nest", "oak", "pine",
34
+ "quail", "river", "stone", "tiger", "unity", "vine", "wolf", "xenon",
35
+ "yarn", "zephyr", "arrow", "blade", "comet", "dusk", "ember", "frost",
36
+ "grove", "haze", "isle", "jewel", "knot", "leaf", "mist", "north",
37
+ "orbit", "pearl", "quest", "reef", "spark", "trail", "umber", "vale",
38
+ "wave", "pixel", "yak", "zen", "anchor", "brook", "crest", "delta",
39
+ "eagle", "flare", "glyph", "horn", "icon", "jazz", "kelp", "lark",
40
+ "marsh", "nova", "opal", "plume", "quartz", "raven", "sage", "thorn",
41
+ "urchin", "vault", "wren", "axis", "byte", "crane", "drift", "fern",
42
+ "grain", "helm", "igloo", "jinx", "karma", "lotus", "mesa", "nexus",
43
+ "ore", "pulse", "ridge", "shard", "torch", "union", "vigor", "wisp",
44
+ "flux", "yew", "zone", "apex", "blaze", "cliff", "dome", "edge",
45
+ "forge", "gale", "husk", "ink", "jet", "keel", "lance", "maze",
46
+ "null", "oxide", "pike", "quad", "rust", "scope", "tide", "urge",
47
+ "vibe", "warp", "xray", "yoke", "zip",
48
+ ]
49
+
50
+ def _hash_bytes(text: str) -> bytes:
51
+ return hashlib.sha256(text.encode("utf-8")).digest()
52
+
53
+
54
+ def generate_name(text: str, words: int = 2, sep: str = "-", capitalize: bool = False) -> str:
55
+ h = _hash_bytes(text)
56
+ parts = []
57
+ for i in range(words - 1):
58
+ idx = int.from_bytes(h[i*2:i*2+2], "big") % len(ADJECTIVES)
59
+ parts.append(ADJECTIVES[idx])
60
+ noun_idx = int.from_bytes(h[(words-1)*2:(words-1)*2+2], "big") % len(NOUNS)
61
+ parts.append(NOUNS[noun_idx])
62
+ if capitalize:
63
+ parts = [p.capitalize() for p in parts]
64
+ return sep.join(parts)
65
+
66
+
67
+ def _output(data: dict, as_json: bool):
68
+ if as_json:
69
+ click.echo(json.dumps(data))
70
+ else:
71
+ click.echo(data.get("name", data.get("output", "")))
72
+
73
+
74
+ @click.group()
75
+ @click.version_option(__version__)
76
+ def cli():
77
+ """Generate deterministic human-readable names from any input."""
78
+ pass
79
+
80
+
81
+ @cli.command()
82
+ @click.argument("text")
83
+ @click.option("-w", "--words", default=2, help="Number of words (default: 2)")
84
+ @click.option("-s", "--sep", default="-", help="Separator (default: -)")
85
+ @click.option("-c", "--capitalize", is_flag=True, help="Capitalize words")
86
+ @click.option("-n", "--count", default=1, help="Generate N names (varies suffix)")
87
+ @click.option("--json", "as_json", is_flag=True, help="JSON output")
88
+ def name(text, words, sep, capitalize, count, as_json):
89
+ """Generate a name from input text."""
90
+ if count == 1:
91
+ n = generate_name(text, words, sep, capitalize)
92
+ _output({"input": text, "name": n}, as_json)
93
+ else:
94
+ names = []
95
+ for i in range(count):
96
+ input_val = f"{text}:{i}" if i > 0 else text
97
+ names.append(generate_name(input_val, words, sep, capitalize))
98
+ if as_json:
99
+ click.echo(json.dumps({"input": text, "count": count, "names": names}))
100
+ else:
101
+ for n in names:
102
+ click.echo(n)
103
+
104
+
105
+ @cli.command()
106
+ @click.argument("texts", nargs=-1)
107
+ @click.option("-w", "--words", default=2, help="Number of words")
108
+ @click.option("-s", "--sep", default="-", help="Separator")
109
+ @click.option("-c", "--capitalize", is_flag=True, help="Capitalize words")
110
+ @click.option("--json", "as_json", is_flag=True, help="JSON output")
111
+ def batch(texts, words, sep, capitalize, as_json):
112
+ """Generate names for multiple inputs."""
113
+ if not texts:
114
+ # Read from stdin
115
+ texts = [line.strip() for line in sys.stdin if line.strip()]
116
+ results = []
117
+ for t in texts:
118
+ n = generate_name(t, words, sep, capitalize)
119
+ results.append({"input": t, "name": n})
120
+ if as_json:
121
+ click.echo(json.dumps(results))
122
+ else:
123
+ for r in results:
124
+ click.echo(f"{r['input']}\t{r['name']}")
125
+
126
+
127
+ @cli.command()
128
+ @click.argument("text")
129
+ @click.option("-w", "--words", default=2, help="Number of words")
130
+ @click.option("-s", "--sep", default="-", help="Separator")
131
+ def verify(text, words, sep):
132
+ """Verify determinism — run twice, same result."""
133
+ a = generate_name(text, words, sep)
134
+ b = generate_name(text, words, sep)
135
+ if a == b:
136
+ click.echo(f"✓ Deterministic: {a}")
137
+ else:
138
+ click.echo(f"✗ MISMATCH: {a} != {b}", err=True)
139
+ sys.exit(1)
140
+
141
+
142
+ @cli.command()
143
+ def stats():
144
+ """Show word list sizes and possible combinations."""
145
+ adj = len(ADJECTIVES)
146
+ noun = len(NOUNS)
147
+ click.echo(f"Adjectives: {adj}")
148
+ click.echo(f"Nouns: {noun}")
149
+ click.echo(f"2-word combos: {adj * noun:,}")
150
+ click.echo(f"3-word combos: {adj * adj * noun:,}")
151
+
152
+
153
+ def main():
154
+ cli()
155
+
156
+ if __name__ == "__main__":
157
+ main()