28 apps told the user to paste an `hk-` key β input placeholders, settings help text, READMEs, the error thrown when the field is empty β and five of them validated that paste against /^hk-/ before sending it as a bearer. Nothing mints that prefix any more: cloud accepts pk- and sk-, IAM mints sk-. So the guard admitted a credential that authenticates nothing, and the panel failed later at the gateway with nothing on screen to explain it. The three leak detectors keep hk- and gain sk-. Salesforce's source scan and the two Zendesk tests exist to catch a key committed to source or echoed back through a request; retired keys are still in circulation and leak exactly as badly, while a detector that only knew hk- would have gone blind to every key minted from here on. `sk-` used to be the example of the foreign key the guard screened out β an OpenAI key. It cannot mean that now, and shape alone cannot tell one vendor's sk- from another's, so those comments say what the check really proves and point at the /v1/models call that proves the rest. The three tests that pinned "sk- is rejected" now pin "hk- is rejected", which is the invariant that survived. Verified: 2029 tests pass across the 28 packages; typecheck clean on 25 of them (hubspot, jupyter and teams fail identically at HEAD on JSX and ESM type conflicts in files this does not touch); salesforce validate-source green. Co-authored-by: Hanzo Dev <[email protected]>
5.8 KiB
Hanzo AI for Sketch
@hanzo/sketch β a native Sketch plugin that puts Hanzo AI on your macOS design
canvas. Critique a selection, rewrite / translate / content-fill text layers, and
ask about your design, without leaving Sketch.
It is a thin adapter over the shared Hanzo design-AI action catalog β the same
prompt builders, context assembly, and response parsing that back
@hanzo/figma and @hanzo/canva, expressed once as the PURE
design-core.ts. Model calls route through the
api.hanzo.ai gateway via the published @hanzo/ai
headless client. No /api/ prefix, ever β always /v1/....
Design actions
| Command | What it does | Write-back |
|---|---|---|
| Summarize / critique selection | UX/copy/hierarchy critique of the selection, leading with a one-line take. | Inserts a note layer |
| Rewrite selected text | Rewrites each text layer's copy (optionally per an instruction). | layer.text = β¦ |
| Translate text layers | Translates each text layer into a target language. | layer.text = β¦ |
| Content fill | Replaces each text layer with realistic, role-fitting placeholder copy. | layer.text = β¦ |
| Ask Hanzo⦠| Free-form question about the selection. | Inserts a note layer |
rewrite, translate, and contentfill are per-layer: the model returns a
JSON array positionally aligned to the selected layers, parsed by
parseLayerEdits into { id, name, text } edits the panel previews before you
Apply to layers. critique and ask return prose you can Add note to
canvas as a new Text layer next to the selection.
Selection read β write-back flow
Sketch selection
β command.js β sketch-bridge.readSelection() (sketch/dom)
βΌ
Selection { layers: TextLayer[], totalCount }
β seeded into the WebView (window.__hanzoSeed)
βΌ
design-core.runAction() β @hanzo/ai createAiClient().chat.completions.create()
β (the model call runs in the WebView β it has fetch)
βΌ
raw text
ββ per-layer β parseLayerEdits(raw, layers) β LayerEdit[]
β window.postMessage('applyEdits') β sketch-bridge.applyLayerEdits()
β layer.text = edit.text
ββ prose β resultNote(id, raw) β { heading, body }
window.postMessage('insertNote') β sketch-bridge.insertNote()
new sketch.Text(...)
sketch-bridge.js is the ONLY file that touches sketch/dom; design-core.ts
is pure and fully unit-tested.
Your Hanzo key
The plugin never bundles or commits a key. Open the panel, expand Hanzo key &
model, paste an sk-β¦ Hanzo API key (or an IAM access token from
@hanzo/iam), and Save. It is
stored per-plugin with sketch/settings. Optionally set a model (default
zen5).
Build
pnpm install # from the monorepo root
pnpm --filter @hanzo/sketch build # β dist/Hanzo AI.sketchplugin
pnpm --filter @hanzo/sketch watch # rebuild on change
pnpm --filter @hanzo/sketch test # vitest
pnpm --filter @hanzo/sketch typecheck # tsc --noEmit
build.js assembles the .sketchplugin bundle in two esbuild passes:
command.js β Contents/Sketch/ as CommonJS for Sketch's CocoaScript runtime
(sketch/* external), and the WebView app.ts (with @hanzo/ai bundled) β
Contents/Resources/app.js as a browser IIFE, alongside webview.html, the
manifest.json, and the icon.
Install
- Local: run the build, then double-click
dist/Hanzo AI.sketchplugin. Sketch installs it into~/Library/Application Support/com.bohemiancoding.sketch3/Plugins/. - The commands appear under Plugins β Hanzo AI with the shortcuts declared in
manifest.json(ββ§K critique, ββ§R rewrite, ββ§T translate, ββ§F content-fill, ββ§A ask).
Publish to the Sketch plugin directory
The build produces a standards-compliant .sketchplugin bundle with a
manifest.json (identifier: ai.hanzo.sketch, appcast, bundleVersion).
To publish to the Sketch plugin directory:
- Host the built
.sketchpluginand a Sparkleappcast.xml(theappcastURL in the manifest) so Sketch can auto-update installs. - Tag a release in the plugin's GitHub repo containing the built bundle.
- Submit the repo via the Sketch extensions submission form; Sketch reviews and
lists it. Bump
version/bundleVersioninmanifest.jsonon each release β patch bumps only.
Layout
src/
manifest.json Sketch plugin manifest β 5 commands + menu
command.cjs plugin entry (CommonJS, CocoaScript runtime): opens the WebView, bridges messages
sketch-bridge.cjs sketch/dom read (readSelection) + write-back (applyLayerEdits, insertNote)
design-core.ts PURE: action catalog, context assembly/truncation, @hanzo/ai shaping, parsing
webview/
index.html the assistant panel
app.ts panel logic over design-core + @hanzo/ai (built to Resources/app.js)
build.js assembles the .sketchplugin bundle
tests/ vitest β the pure design-core + bridge helpers
command.cjs and sketch-bridge.cjs are CommonJS (.cjs) because they run on
Sketch's JavaScriptCore/CocoaScript runtime, not Node ESM. build.js emits them
as Contents/Sketch/command.js for the manifest to reference.