Calculate SHA-256, SHA-384, or SHA-512 for UTF-8 text.

Calculate SHA-256, SHA-384, or SHA-512 for UTF-8 text.
Use it when: Compare a checksum for a short string.

Screenshot of this mod's render tree in the fixture preview, with sample data. This is not a capture of the Claude Code application. How previews are made.
claude plugin marketplace add whyashthakker/awesome-claude-code-mods
claude plugin install hash-lab@awesome-claude-code-mods
Run /reload-plugins in an existing session, then /hash-lab. Use Tab and Enter to operate controls; Esc closes the pane. Terminal and Desktop Code tab supported; pane UI is unavailable in claude -p and the VS Code chat panel.
Try the source without installing:
claude --plugin-dir ./mods/hash-lab
Works on the text you enter, in memory. Uses the clipboard on request. No files, processes, network, persistence, or model calls.
Module variables reset on hot reload. All display output is bounded; viewers may truncate long content to 9,000 characters. Relative file paths and Git commands use the session working directory.
Tested with Claude Code 2.1.288. Minimum documented mods version: 2.1.287. The API can change; validate against your installed version.
claude plugin validate mods/hash-lab --strict
claude plugin test mods/hash-lab
Tests cover plugin commands, pane isolation, both UI surfaces, and the behavior described in tests/register.test.ts. Drawing tests validate element trees; they do not verify pixels painted by the native apps. Validation details.
MIT licensed. No runtime packages or build step required.
hooks/register.js 47 lines1import { clean, text, code, row, frame, bar, pretty, bounded, entries, safePath } from './ui.js'
2const ID = "hash-lab"
3let algorithm="SHA-256"
4let source="hello"
5let output=""
6
7
8async function transform(){
9 if(!['SHA-256','SHA-384','SHA-512'].includes(algorithm))throw new Error('Use SHA-256, SHA-384, or SHA-512.')
10 const digest=await crypto.subtle.digest(algorithm,new TextEncoder().encode(bounded(source)))
11 return algorithm+'\n'+Array.from(new Uint8Array(digest),x=>x.toString(16).padStart(2,'0')).join('')
12}
13
14async function calculate($) {
15 try { output=await transform() } catch(error){output='Invalid input: '+error.message}
16 $.ui.invalidate('ui.render')
17}
18
19export function register(on) {
20 on('session.start', async ($, e, next) => {
21
22 await $.command.register({name:ID, description:"Calculate SHA-256, SHA-384, or SHA-512 for UTF-8 text.", immediate:true, argumentHint:""})
23 return next(e)
24 })
25 on('command.run', {command:"hash-lab"}, async ($, e) => {
26 await calculate($)
27 await $.ui.open({id:ID,title:"Hash Lab",focus:true,closeOnEscape:true})
28 return {}
29 })
30
31 on('ui.render', {component:'Pane'}, async ($, e, next) => {
32 if(e.requestId !== ID) return next(e)
33 const E = $.ui.resolve(e)
34 try {
35 const body = await draw($, E)
36 return frame(E,"Hash Lab","Calculate SHA-256, SHA-384, or SHA-512 for UTF-8 text.",body)
37 } catch(error) {
38 return frame(E,"Hash Lab",'Unable to inspect this data.',[text(E,error.message,{color:'yellow'}),E.Button({key:'refresh',label:'Refresh',onPress:()=>$.ui.invalidate('ui.render')})])
39 }
40 })
41}
42async function draw($, E) {
43 const refresh = E.Button({key:'refresh',label:'Refresh',hotkey:'r',plain:true,onPress:()=>$.ui.invalidate('ui.render')})
44return [E.Input({key:"algorithm",label:"Algorithm",value:clean(algorithm),submitLabel:'apply',onSubmit:async(v)=>{algorithm=v;await calculate($)}}),
45E.Input({key:"source",label:"Text",value:clean(source),submitLabel:'apply',onSubmit:async(v)=>{source=v;await calculate($)}}),code(E,output),E.Button({key:'copy',label:'Copy result',onPress:async()=>{const r=await $.ui.copy({text:output});if(!r.isCopied)$.ui.toast('Clipboard unavailable.')}})]
46}
47hooks/ui.js 36 lines1// Pure UI helpers. No engine access; each plugin contains its own copy.
2export function clean(value, limit = 9000) {
3 return String(value ?? '').replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g, '').slice(0, limit)
4}
5export function text(E, value, props = {}) {
6 return E.Text({ ...props, children: [clean(value)] })
7}
8export function code(E, value, language = 'text') {
9 return E.Code({ source: clean(value), language })
10}
11export function row(E, children) {
12 return E.Box({ flexDirection: 'row', columnGap: 2, flexWrap: 'wrap', children })
13}
14export function frame(E, title, subtitle, children) {
15 return E.Box({ flexDirection: 'column', gap: 1, children: [
16 text(E, title, { bold: true, color: 'cyan' }),
17 text(E, subtitle, { dimColor: true }), ...children,
18 ] })
19}
20export function bar(percent, width = 24) {
21 const p = Math.max(0, Math.min(100, Number(percent) || 0))
22 const n = Math.round(p / 100 * width)
23 return '[' + '#'.repeat(n) + '-'.repeat(width - n) + '] ' + p.toFixed(1) + '%'
24}
25export function pretty(value) { return JSON.stringify(value, null, 2) }
26export function bounded(value, max = 9000) {
27 if (value.length > max) throw new Error('Input exceeds ' + max + ' characters. Use a smaller selection.')
28 return value
29}
30export function entries(value) { return value && typeof value === 'object' ? Object.entries(value) : [] }
31export function safePath(value) {
32 const path = value.trim()
33 if (!path || path.startsWith('-') || /[\x00-\x1f]/.test(path)) throw new Error('Enter a path without control characters or leading flags.')
34 return path
35}
36