aiui-d75814 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited aiui-d75814 (Agent Skill) and scored it 96/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 1 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
aiui exposes three MCP tools that render native dialogs on the user's Mac:
confirm — irreversible yes/noask — single- or multi-choice with descriptions and optional free-text fallbackform — composite window with typed fields and multiple action buttonsThe user installed aiui because they want the agent to use it. If you catch yourself about to write any of these in chat, stop and use aiui instead:
confirmaskformrollback, prod deploy) → confirm with destructive: true, even if the user already gave loose approval. The dialog makes the consequence explicit and ships the structured answer back, no chat parsing.
form with a password field, never paste in chat.
("which deploy strategy?", "which migration path?") → ask with per-option description.
form with asortable list field.
in a bounded interval** → form with the matching field.
Skip the dialog for content the user reads, doesn't answer:
in chat.
a dialog box anyway — just ask in chat.
| Intent | Tool |
|---|---|
| Yes/no, especially destructive | confirm |
| 2–6 options, possibly with per-option context | ask |
| Multi-field input, multi-action footer | form |
| Per-item verdict on a batch of images/videos ("approve/revise/skip each") | gallery |
| Single free-text answer | just ask in chat |
| More than 8 fields | split into multiple form calls; do not cram one dialog |
your age" / "What's your role?") reads as AI slop.
"enter value here".description/static_text only when the label alone is ambiguous —avoid redundancy.
"Create report" beats "OK".primary: true → blue, the main action.success: true → green, positive-outcome verbs ("Approve", "Publish").destructive: true → red, irreversible verbs ("Delete", "Rollback").Never style a save button red; never style a delete button green.
skip_validation: true) so required-field validationnever traps the user.
list field — one widget, four modesselectable | multi_select | sortable | Mode |
|---|---|---|---|
| – | – | – | Static info list |
| ✓ | – | – | Single-choice (radio) |
| ✓ | ✓ | – | Multi-choice (checkboxes) |
| – | – | ✓ | Ordering via drag handles |
| ✓ | ✓ | ✓ | Pick-and-order |
Result is always {selected: [values], order: [values]} — order reflects drag changes, selected reflects checkbox state. Items can carry a thumbnail (data: URL or path) — perfect for shotlists, mood boards, carousel slides where the visual anchor matters more than the label.
table field — column-aware row triageWhen you'd otherwise dump 30 branches / 50 search results / 20 stale files into chat, hand it as a table instead. Columns carry the context (date, size, owner) that list can't, rows are clickable for selection, and the agent gets back the picked rows by their value.
columns: [{key, label, align?: "left"|"right"|"center"}]
rows: [{value, values: {<key>: <string|number|null>}}]
multi_select?: true # checkbox-per-row
sortable_by_column?: true # click headers to sortResult: {selected: [values], order: [values], sort: {column, dir}}. The order field reflects user-driven sorts so you can preserve their view if you reopen the form.
markdown, image, static_textThese don't ask anything — they sit between input fields to give context for the inputs that follow.
markdown — rendered Markdown block (lists, code, links, tables). Usefor "here's the diff I generated, now decide" patterns. Not a standalone display tool — if you'd be tempted to open a window just to show the user a markdown blob, render it in chat instead.
image — read-only single image preview (src: data: URL or path,optional label, alt, max_height). Use when the agent generated a chart, screenshot, or diagram and needs visual sign-off before the next decision.
static_text — plain styled note with tone: "info"|"warn"|"muted".Lighter weight than markdown when no formatting is needed.
mermaidFor graph-shaped visualisations — flowcharts, sequence diagrams, state machines, gantt, ER, class diagrams, mind-maps — use the mermaid field instead of ASCII boxes-and-arrows. Spec: {kind: "mermaid", source: "<DSL>", label?, max_height?}. The source is a Mermaid-DSL string; aiui pipes it through mermaid.render(), sanitises the SVG, and embeds inline. Read-only, sits between input fields like markdown / image.
wireframeOrthogonal to mermaid — mermaid is for graphs, wireframe is for layouts. When you'd otherwise sketch a UI mockup with ASCII boxes-and-pipes (dashboard tiles, hardware-UI panels, login-screen sketch, app-surface layout), use wireframe instead. Real CSS-Grid panels, no monospace approximation.
Spec: {kind: "wireframe", panels: [{title?, content?, col_span?, row_span?, tone?}], columns?, gap?, label?, max_height?}.
Each panel has optional title (uppercase header), content (multi-line monospace body, escape \n for line-breaks), col_span / row_span (default 1), and tone ∈ {"default","muted","highlight"}.
{
"kind": "wireframe",
"label": "U-Boot-Funkbude",
"columns": 3,
"panels": [
{"title": "EMPFANG", "col_span": 2, "content": "14:32:07 [SCHWACH] …\n14:32:11 [STARK] WX MIDWAY"},
{"title": "STATUS", "col_span": 1, "content": "Tiefe: 18 m\nKurs: 270°"},
{"title": "AKTION", "col_span": 3, "content": "[T]auchen [A]uftauchen [K]urs", "tone": "highlight"}
]
}Read-only, sits between input fields. Anti-pattern: ASCII boxes-and-pipes for anything layout-shaped — that is exactly what this field replaces.
image_gridFor "pick one (or more) of these N generated images" — logo variants, thumbnail candidates, asset triage. Spec: images: [{value, src, label?}], multi_select?, columns? (default 3). Result: {selected: [values]}.
image_grid picks among candidates. For a separate verdict per item (approve this, revise that, skip the third, optional note each) use the gallery tool below instead.
galleryA standalone tool (not a form field) for reviewing a batch of images and/or videos and collecting one decision per item in a single window — instead of firing confirm once per asset.
Spec: items: [{value, src?, label?, detail?, max_height?}], actions? (per-item buttons, default Approve / Revise / Skip), comment? (free-text per item), columns?. Each item's value must be non-empty and unique — it keys the result. src follows the standard image rules; videos (data:video/ URL, http(s):// URL, or a local .mp4/.mov/.m4v/.webm path) render with native controls. Local videos of any size work — the bridge pushes them to aiui's media cache on the Mac and the dialog streams them back, so a remote clip plays without hosting it anywhere.
Result: {cancelled, decisions: {"<value>": {decision, comment?}}}. Only touched items appear — an untouched item means "no verdict", not a default.
sizeform and gallery take an optional size hint — "s", "m", "l" — and aiui picks good local defaults, clamped to the screen. (Explicit width/height in logical px override it; rarely needed.) The hint is a floor: the window opens at max(content-estimate, hint), so it never opens smaller than the content needs, but a sparse dialog can be told to start roomy. Windows are always resizable — but many users don't realise that, so opening at a comfortable size is what separates "polished" from "looks broken". Use "m"/"l" for forms with images/tables/wireframes/many fields, or galleries with a large batch; leave unset for short forms.
datetime fieldLückenfüller between date and date_range. Cron, scheduling, reminders — one field instead of splitting into two text fields with manual validation. Native <input type="datetime-local">, returns ISO YYYY-MM-DDTHH:MM.
Drop fields=… and pass tabs=[{label, fields: [...]}, ...] instead. One submit covers all tabs; validation jumps to the first invalid tab automatically. Tabs are display structure, not a wizard — no per-tab confirmation, no per-tab actions, all values land in one response.
Use when a single dialog naturally falls into 2-4 distinct topical groups (e.g. "Identity / Permissions / Notifications" on a user-create form). Don't reach for tabs to cram a 30-field form into 5 tabs — split into multiple form calls instead.
For short-lived secrets (one-off API tokens, test passwords), prefer form with a password field over asking in chat: the value is masked on screen while the user types, so it doesn't appear in screen recordings or to a shoulder-surfer.
Be honest with the user, though — the value still returns to you as plaintext in the tool response. For long-lived or high-value secrets, use the secret field with a target (below) so the value never enters the conversation.
secret field + target (#135)When a value must NOT pass through this conversation — a credential the user pastes that should land in a file, not your transcript — use a secret field with a target. Any input field may carry target; for a secret field the value is write-only (result: {written, target, bytes}, never the value).
{ "kind": "secret", "name": "pat", "label": "GitHub PAT für byte5ai",
"target": { "mode": "create", "path": "~/.github_tokens/byte5ai",
"perm": "0600", "overwrite": true } }mode:"create" — write raw value (needs overwrite:true to clobber).mode:"substitute" — replace a placeholder occurring exactly once inan existing file (YAML/TOML/INI/env); 0 or >1 → error (never misapplied). Pick a distinctive sentinel (__AIUI_SECRET_GITHUB_PAT__, not a common word) so the single match is unambiguous, not just lucky.
locally, bridge on a remote SSH host) writes it as a LOCAL file op, so create and substitute both work identically local and remote — no foreign host. The user sees the path and approves by submitting. Errors: {written:false, error}.
Replaces the fragile "guess a shell one-liner to stash a token" pattern. QoL + confused-deputy guard, not a hard guarantee.
| Slop | Clean |
|---|---|
confirm(title="Are you sure?") | confirm(title="Drop table 'orders'?", destructive=True, message="18,432 rows will be removed.") |
ask(question="Choose one", options=[{"label": "Option 1"}, …]) | ask(question="Which migration strategy?", options=[{"label":"In-place","description":"Fast, no rollback."}, …]) |
form with 15 text fields | Split into logical steps, or push back to chat entirely |
| Button labels "OK" / "Cancel" | "Deploy" / "Discard" — name what happens |
static_text echoing the title | static_text adds context the labels can't carry alone |
aiui.form(
title="New feature draft",
header="Discovery",
fields=[
{"kind": "text", "name": "job", "label": "User job",
"multiline": True, "required": True},
{"kind": "select", "name": "scope", "label": "Scope",
"options": [{"label": "Quick win", "value": "qw"},
{"label": "Feature", "value": "f"},
{"label": "Epic", "value": "e"}],
"default": "f"},
{"kind": "list", "name": "stakeholders", "label": "Stakeholders",
"items": [{"label": "Product", "value": "prod"},
{"label": "Design", "value": "design"},
{"label": "Engineering", "value": "eng"}],
"selectable": True, "multi_select": True,
"default_selected": ["prod", "eng"]},
{"kind": "date", "name": "deadline", "label": "Target date"},
],
actions=[
{"label": "Cancel", "value": "cancel", "skip_validation": True},
{"label": "Save draft", "value": "draft", "skip_validation": True},
{"label": "Create", "value": "commit", "primary": True},
],
)Response: {cancelled: false, action: "commit", values: {job: "…", scope: "f", stakeholders: {selected: [...], order: [...]}, deadline: "…"}}.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.