Install this skill

Paste this into Claude Code (VS Code panel, Adom editor, or terminal) to install:

Search the Adom Wiki for the skill "Tool Publisher" (slug: tool-publisher) at https://wiki-ufypy5dpx93o.adom.cloud/wiki/skills/tool-publisher and install it into my local ~/.claude/skills/tool-publisher/ directory. Fetch the skill_source content from the wiki page and save it as SKILL.md. Then confirm it's installed by showing the first 5 lines.
?
What is a skill? Skills are instructions that teach AI assistants like Claude Code how to perform specific tasks. The description below is loaded into the AI as context when you invoke this skill. Well-written skills make the AI significantly more effective. Like Wikipedia, anyone can improve a skill by clicking Edit AI Skill — or have your AI submit an edit on your behalf.

Description

Edit AI Skill

Publishing Tools to the Adom Wiki

This skill covers the full lifecycle for making a CLI tool or skill discoverable and installable by any Adom user — without hardcoding it into gallia/install.mjs, and without requiring the installing user to have GitHub access to adom-inc/*.

Public vs Private: The Line

Every wiki-published tool has a public side and a private side, and the demarcation matters for onboarding 3rd-party collaborators:

  • Public (wiki-hosted): the compiled binary, the usage docs, the SKILL.md, screenshots, install hint. Anyone on the Adom platform — including 3rd-party collaborators with zero GitHub access — can see and install it by hitting https://wiki-ufypy5dpx93o.adom.cloud/static/apps/<slug>/<binary>. No auth, no GitHub token.
  • Private (GitHub, optional): the source code, build system, issue tracker, internal design docs. Stays on adom-inc/<repo> with whatever read permissions you want. The wiki page MAY carry a repo link in metadata for team members who have GitHub access, but the link is not required and is not used by the installer.

What this means when you publish:

  • Build the binary yourself (cargo build --release).
  • Upload it to the wiki as a docker_binary asset via adom-wiki asset upload. Latest wins — the server auto-deletes any previous docker_binary on the same page before inserting the new one.
  • Set metadata.releases.adom_docker.install_hint to a curl command that hits /static/apps/<slug>/<asset_name>, not gh release download.
  • Bump the top-level version: field in the publish body (→ pub_version column, semver-validated on the wiki side).
  • Do not tell users to clone the private repo. The install flow must work end-to-end from the wiki alone, no GitHub credentials.

What lives where — quick-reference table

ArtifactLives inWhy
Source code (*.rs, Cargo.toml, tests, CI config)Private GitHub repo (optional)Internal dev, CI secrets, issue tracking. Choose whether to use GitHub at all.
Compiled CLI binaryPublic wiki at /static/apps/<slug>/<slug> (uploaded as docker_binary asset)Paste-prompt curl must work for any user — no repo access needed.
SKILL.md contentPublic wiki page (skill_source field) AND embedded in the binary via include_str!Users browse it on the wiki; <tool> install drops it to ~/.claude/skills/<slug>/.
Install prompt (rendered)Public — auto-generated on the wiki page from metadataUsers paste it into Claude Code to trigger the install.
Discovery triggers & pitchPublic wiki page metadataAggregated by /discover into the gallia auto-discover snippet.
Screenshots, README, examples, demosPublic wiki page content + assetsBrowsable by anyone; drives adoption.
Internal design notes, WIP features, roadmapPrivate GitHub repoNot ready for distribution.
Secrets, API keys, signing keysNeither — env vars / 1Password / CI secretsNever committed to either.

Rule of thumb: if a user on a fresh container needs it to install or run your tool, it goes on the wiki. Everything else stays in the private repo (if you use one at all).

Architecture: 2-Layer Distribution

Layer 1: Source Code (private GitHub repo, optional link only)
    ↓  cargo build --release   +   adom-wiki asset upload
Layer 2: Adom Wiki (public: binary asset + discovery metadata + install prompt)
    ↓  gallia/hooks/refresh-wiki-catalog.mjs audits installed versions every 30 min
    ↓  stale tool? Claude asks user "Shall I upgrade?"
User Container (binary upgraded from wiki)

Key principle: gallia/install.mjs is for core infrastructure only (adom-cli, adom-wiki, viewer, MCP servers). New tools use wiki auto-discovery instead — they get discovered and installed on-demand, and the 30-min refresh hook keeps users' installed versions in sync with the wiki's pub_version.

Gallia's only role for new tools is carrying the auto-discover snippet. Not source, not binary, not SKILL.md, not docs — none of it lives in gallia. The snippet is regenerated from wiki metadata on every container setup and on every 30-min hook tick. Ship updates by re-publishing the wiki page, not by opening a gallia PR.

When to Use Each Layer

DistributionWhenExample
gallia/install.mjsCore infra every container needsadom-cli, adom-wiki, viewer
Wiki auto-discoveryTools users install on-demandadom-molecule, shotlog, adom-tsci
gallia/skills/Cross-cutting reference skills not tied to one appbeing phased out — prefer wiki

Prerequisites

Before publishing, you need:

  1. A working CLI (see adom-cli-design skill for Rust CLI conventions)
  2. A SKILL.md embedded in the binary (see skill-creator skill)
  3. A built target/release/<tool> binary — where you build it is up to you. A private adom-inc/<tool> GitHub repo is the usual home for source, but you do NOT need a GitHub release — the binary goes to the wiki.
  4. The adom-wiki CLI installed
  5. A <tool> --version command that prints a semver line (defaults to --version; override via metadata.releases.adom_docker.version_command if your CLI uses a different flag)

Step 1: Build the CLI

Follow the adom-cli-design skill. Key requirements:

  • Rust with clap, ureq, serde_json
  • OK: / ERROR: output format
  • install subcommand that deploys SKILL.md via include_str!
  • health subcommand if it talks to a server
  • .gitignore with target/
# Cargo.toml
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
ureq = { version = "2", features = ["json"] }
clap = { version = "4", features = ["derive", "env"] }

[profile.release]
strip = true
lto = "thin"

Build:

cargo build --release
sudo cp target/release/my-tool /usr/local/bin/
my-tool install  # deploys SKILL.md to ~/.claude/skills/my-tool/

Step 2: Put source on GitHub (optional) and upload binary to the wiki

2a. Private source repo (optional)

Source code stays private. You choose whether to host it on GitHub at all; it's fine to just keep the source in your local checkout. If you do use GitHub:

gh repo create adom-inc/my-tool --private --description "One-line description"
cd /path/to/my-tool
git init && git remote add origin https://github.com/adom-inc/my-tool.git
echo "target/" > .gitignore
git add -A && git commit -m "Initial release: my-tool v0.1.0"
git branch -M main && git push -u origin main

Do NOT gh release create. The compiled binary does not belong in a GitHub release anymore — it goes to the wiki, where it's actually installable by people without GitHub access.

2b. Upload the binary to the wiki

# Publish a minimal page first (Step 3 fills in the full metadata).
# If you're doing this as part of an update (page already exists), skip the
# publish and go straight to the asset upload.
adom-wiki asset upload apps/my-tool \
  --asset-type docker_binary \
  --file target/release/my-tool \
  --caption "v0.1.0 build for adom docker"

The wiki auto-deletes any previous docker_binary on this page before storing the new file, so this command is idempotent — run it again on every release.

The canonical public download URL is:

https://wiki-ufypy5dpx93o.adom.cloud/static/apps/my-tool/my-tool

Verify it's reachable without any credentials:

curl -I https://wiki-ufypy5dpx93o.adom.cloud/static/apps/my-tool/my-tool
# expect HTTP/1.1 200 OK, content-type: application/octet-stream

Step 3: Publish Wiki Page

Publish as page type app with full metadata including discovery triggers.

3a. Write the wiki content

Create a markdown file (/tmp/my-tool-wiki.md) with:

  • What it does (1-2 paragraphs)
  • What the curl-based install prompt does for them (they paste it, Claude runs it)
  • Quick start examples
  • Link to the repo (optional — informational only)

3b. Publish with metadata

Critical: Include metadata in the initial publish. The v1 publish endpoint accepts a monotonically-increasing version: field and overwrites content, brief, and skill_source on re-publish. Metadata updates, however, are best done via the dedicated endpoint (see 3c below) or by delete+recreate if the direct metadata edit doesn't stick — this gap is being closed; see the "Wiki-side work" section at the bottom of this page.

WIKI_URL="https://wiki-ufypy5dpx93o.adom.cloud"

curl -s -X POST "$WIKI_URL/api/v1/pages" \
  -H "Authorization: Bearer adom-wiki-dev-2025" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "app",
    "slug": "my-tool",
    "title": "My Tool Name",
    "brief": "One-line description for search results and page header.",
    "content": "... markdown content ...",
    "skill_source": "... contents of SKILL.md ...",
    "metadata": {
      "repo": "adom-inc/my-tool",
      "repo_visibility": "private",
      "releases": {
        "adom_docker": {
          "asset_name": "my-tool",
          "install_hint": "curl -fsSL https://wiki-ufypy5dpx93o.adom.cloud/static/apps/my-tool/my-tool -o /usr/local/bin/my-tool && chmod +x /usr/local/bin/my-tool && my-tool install"
        }
      },
      "discovery_triggers": [
        "trigger phrase 1",
        "trigger phrase 2",
        "trigger phrase 3"
      ],
      "discovery_pitch": "One sentence explaining what this tool does and why you want it."
    },
    "version": "0.1.0",
    "changelog": "Initial release"
  }'

Notes on the fields:

  • Top-level versionpub_version column on the page. Semver-validated on the wiki side. This is the single source of truth for the tool's version. Gallia's 30-min refresh hook reads pub_version directly; it ignores metadata.version. Don't bother setting metadata.version.
  • install_hint must be self-contained (no GitHub access, no tokens). Use curl -fsSL <wiki static URL> — never gh release download.
  • repo is purely informational. Team members with GitHub access may follow it to the source; 3rd-party collaborators will just ignore it.
  • If your tool's --version flag isn't the default, also set metadata.releases.adom_docker.version_command to the exact shell command gallia should run (e.g. "my-tool version --short").

3c. Verify

# Check the page exists and has metadata
adom-wiki page get apps/my-tool | python3 -c "
import sys, json
d = json.load(sys.stdin)
meta = json.loads(d['page']['metadata'])
print('Triggers:', meta.get('discovery_triggers'))
print('Pitch:', meta.get('discovery_pitch'))
print('Install:', meta.get('releases',{}).get('adom_docker',{}).get('install_hint'))
"

# Check the discover page includes your tool
# Visit: https://wiki-ufypy5dpx93o.adom.cloud/discover

Metadata Fields Reference

Required for Auto-Discovery

FieldTypePurpose
discovery_triggersstring[]Phrases users might say. Claude matches these to suggest the tool.
discovery_pitchstringOne-sentence hook shown when Claude suggests the tool.

Required for Auto-Install

FieldTypePurpose
releases.adom_docker.asset_namestringBinary filename on the wiki asset (must match adom-wiki asset upload --file's filename). Also used as the command -v check and default --version bin.
releases.adom_docker.install_hintstringFull install command. Must be self-contained (no GitHub auth). Use curl -fsSL https://wiki-.../static/apps/<slug>/<asset>.

Optional

FieldTypePurpose
releases.adom_docker.version_commandstringShell command to check the installed version (default: <asset_name> --version). Output must contain a semver like 1.2.3.
repostringGitHub repo (owner/name) — informational, not used by installer
repo_visibilitystring"private" or "public"
releases.windowsobjectWindows installer info (for desktop apps)

NOT used anymore

  • metadata.version — ignored. Use the top-level version: field, which writes to the pub_version column.

How Auto-Discovery + Continuous Sync Works

  1. The wiki's /discover page aggregates all pages with discovery_triggers metadata.
  2. gallia/hooks/refresh-wiki-catalog.mjs fetches /discover and writes the concatenated SKILL.md snippet to ~/.claude/skills/adom-wiki-discover/SKILL.md.
  3. The same script also fetches /api/v1/pages, walks every page with a releases.adom_docker entry, HEAD-checks the binary's public download URL, runs the tool's version command, and compares installed to pub_version.
  4. gallia/hooks/check-updates.sh runs the refresh script every ~30 min (via the UserPromptSubmit hook). On success, it updates ~/.adom/last-wiki-check.
  5. If any installed tool is older than its wiki pub_version, the hook injects a <system-reminder> telling Claude to ASK the user "Shall I upgrade from ?". On confirmation, Claude runs the install_hint (which curls the wiki asset). On decline, Claude drops it for the session.
  6. install.mjs also calls the refresh script on container setup, so a fresh container starts with the current catalog instead of waiting 30 min for the first hook tick.

Gallia never downloads tools from GitHub anymore. Every install / upgrade path is rooted in the wiki — which means any 3rd-party user with access to the Adom container gets the same install experience as a team member.

How the Install Prompt Works

The wiki renders an "Install this app" block on every app page. The prompt text is auto-generated by renderInstallPrompt() in wiki/lib/templates.js based on the page type and metadata.

For app pages, it uses releases.adom_docker.install_hint to build a prompt like:

I want to install the "My Tool" app from the Adom Wiki. Run this: curl -fsSL https://wiki-.../static/apps/my-tool/my-tool -o /usr/local/bin/my-tool && chmod +x /usr/local/bin/my-tool && my-tool install Then verify the install works.

Users copy this prompt and paste it into Claude Code, which executes the install.

Writing Good Discovery Triggers

Think about what a user would say when they need your tool:

Good triggers (specific, action-oriented):

["create molecule", "import kicad files", "upload fusion files",
 "molecule from kicad", "optimize molecule 3d"]

Bad triggers (too generic, would match everything):

["help", "tool", "create", "upload"]

Include 8-15 triggers covering:

  • The primary action ("create molecule")
  • Input formats ("import kicad files", "upload fusion files")
  • Alternate phrasings ("molecule from kicad", "kicad to molecule")
  • Specific features ("optimize molecule", "blender optimization")

Writing a Good Discovery Pitch

One sentence that answers: "Why would I want this?"

Good: "Import KiCad, Fusion 360, or EasyEDA design files into Adom molecules with one command. Handles file upload, 3D optimization, and molecule management."

Bad: "A CLI tool for molecules."

The install subcommand: what it actually needs to do

The paste-prompt curl ... && my-tool install is the entire onboarding surface for a new user. Don't under-spec this subcommand — users won't forgive a half-broken install that leaves them to read a --help and figure it out. Required responsibilities, in order:

  1. Check system prereqs. If your tool needs bun, node, ffmpeg, python3.11+, or anything else the base container doesn't already have, check for it up front and print a single ERROR: line with the exact install command the user should run. Never proceed past a missing prereq — the user's first experience must not be a cryptic runtime crash.

    if which::which("bun").is_err() {
        eprintln!("ERROR: `bun` is required but not found.");
        eprintln!("Install with: curl -fsSL https://bun.sh/install | bash");
        std::process::exit(1);
    }
    
  2. Deploy the binary to ~/.local/bin/<tool> (or /usr/local/bin/ if the curl in the install hint put it there — your install subcommand should handle the case where the binary is re-run from either location).

  3. Deploy every bundled SKILL.md to ~/.claude/skills/<skill-name>/SKILL.md. Use include_str! at compile time so each skill lives alongside the source in the private repo. Multi-skill apps (next section) drop multiple files here.

  4. Install shell completions for the user's current shell. Silent best-effort — if the shell's completion dir doesn't exist, skip. Completions significantly improve discoverability of subcommands for non-programmer users who don't reflexively run --help.

    my-tool completions bash > ~/.local/share/bash-completion/completions/my-tool 2>/dev/null || true
    my-tool completions zsh  > ~/.zsh/completions/_my-tool 2>/dev/null || true
    
  5. Write an OK: line per action taken and a final summary. Mirror the adom-cli-design output format. Users and Claude both read install output to confirm success.

  6. Be idempotent. Re-running install should update everything to match the current binary (newer SKILL.md, newer completions) and print OK: for each step, not error out because files exist.

Minimum viable output:

OK: prereqs checked (bun 1.1.30, gh 2.55.0)
OK: binary at /usr/local/bin/my-tool
OK: deployed 2 skill(s) to ~/.claude/skills/
OK: bash completions installed
OK: my-tool ready. Try `my-tool --help`.

Multi-skill apps: bundling more than one SKILL.md

Some apps ship a reference skill (how to drive the app's own commands) and a recipe skill (higher-level workflow that uses the app). The archetype is adom-tsci, which ships both:

  • adom-tsci — reference skill for the binary's CLI (start, reload, open, …)
  • adom-tscircuit — recipe skill for designing Adom Molecules in tscircuit using the adom-tsci preview workflow

Both belong in the same repo (adom-inc/adom-tsci) and both get deployed by the same adom-tsci install subcommand. Layout:

adom-inc/adom-tsci/
├── Cargo.toml
├── src/
│   └── main.rs              // include_str! both SKILL.mds
├── skills/
│   ├── adom-tsci/
│   │   └── SKILL.md         // reference skill
│   └── adom-tscircuit/
│       └── SKILL.md         // recipe skill
└── README.md

In main.rs:

const SKILL_REFERENCE: &str = include_str!("../skills/adom-tsci/SKILL.md");
const SKILL_RECIPE:    &str = include_str!("../skills/adom-tscircuit/SKILL.md");

fn install() {
    write_skill("adom-tsci",      SKILL_REFERENCE);
    write_skill("adom-tscircuit", SKILL_RECIPE);
    // ... completions, prereqs, etc
}

Rules:

  • One wiki page per app, not per skill. The wiki page's skill_source field carries the primary (reference) skill; secondary skills are documented in the page content and shipped in the binary.
  • Each SKILL.md has its own discovery triggers in its frontmatter so Claude loads the right one for the right trigger phrase. The wiki page metadata's discovery_triggers covers the install trigger ("install adom-tsci", "preview tscircuit in webview", etc.).
  • Keep the recipe skill tightly scoped. If a recipe skill grows features that aren't specific to this app, split it out into its own wiki page.

Example: shotlog (reference implementation for the wiki-hosted model)

  • Source (private): adom-inc/shotlog — source stays here, no public release.
  • Wiki page: apps/shotlog
  • Wiki asset: docker_binary uploaded via adom-wiki asset upload, public URL https://wiki-ufypy5dpx93o.adom.cloud/static/apps/shotlog/shotlog
  • Discovery triggers: 14 phrases (screenshot log, visual debug loop, ...)
  • Install: curl -fsSL <wiki URL>/static/apps/shotlog/shotlog -o /usr/local/bin/shotlog && chmod +x /usr/local/bin/shotlog && shotlog install
  • NOT in install.mjs — discovered and installed on-demand via the wiki
  • Auto-upgrade: gallia's 30-min refresh hook compares installed shotlog --version against the wiki pub_version and offers an upgrade when they diverge

Updating a Published Tool

To release a new version:

# 1. Build the new binary
cargo build --release

# 2. Push the source change to the private repo (optional — if you use one)
git commit -am "..." && git push origin main

# 3. Upload the new binary to the wiki. The previous docker_binary asset on
#    this page is auto-deleted before the new one is stored.
adom-wiki asset upload apps/my-tool \
  --asset-type docker_binary \
  --file target/release/my-tool \
  --caption "v0.2.0 build for adom docker"

# 4. Bump the page's pub_version (and install_hint if it changed).
#    The publish endpoint accepts a monotonically-greater version and
#    overwrites content / skill_source / brief / title. If you also need
#    to change metadata and the field-edit path is flaky, fall back to
#    DELETE + recreate. See "Wiki-side work" below.
adom-wiki page publish apps/my-tool \
  --title "My Tool Name" \
  --brief "..." \
  --body-md /tmp/my-tool-wiki.md \
  --skill-source /path/to/SKILL.md \
  --version 0.2.0 \
  --changelog "..."

# 5. Update metadata (discovery triggers, install hint, etc.)
adom-wiki page edit apps/my-tool \
  --field metadata \
  --body-md /tmp/metadata.json

After republishing, the next time any Adom container's 30-min hook ticks, the refresh script sees the new pub_version and (if the user has the old binary installed) injects a system-reminder offering an upgrade. Propagation is automatic from this point — you don't need to do anything else.

Checklist

Before publishing:

  • CLI builds clean with cargo build --release
  • my-tool --version prints a semver (or you set version_command in the wiki metadata)
  • my-tool health works (if applicable)
  • my-tool install deploys SKILL.md correctly, checks prereqs, installs completions
  • my-tool install is idempotent (re-running just re-syncs everything)
  • Binary uploaded to the wiki as docker_binary
  • curl -I https://wiki-.../static/apps/<slug>/<bin> returns 200 with no auth
  • Wiki page has top-level version: "x.y.z" (→ pub_version)
  • Wiki page has discovery_triggers and discovery_pitch in metadata
  • Wiki page has releases.adom_docker.install_hint starting with curl -fsSL https://wiki-not gh release download
  • Install prompt on wiki page renders the new curl-based command
  • Tool appears on /discover page
  • End-to-end install tested in a zero-credential environment — a fresh container with no GitHub token must be able to run the install_hint and get a working binary
  • NOT added to install.mjs (unless it's core infrastructure)

Wiki-side work (tracked separately)

The wiki service is still closing a couple of gaps. Both affect smoothness of the publish → update → distribute loop; neither blocks shipping a new tool today. Workarounds below apply until they land.

  1. Metadata-only updates on an existing page. The adom-wiki page edit --field metadata path is the intended update endpoint, but its propagation to /discover isn't fully deterministic yet. Workaround: bump pub_version on a full re-publish, or DELETE + re-POST if metadata doesn't stick.

  2. adom-wiki asset upload --asset-type docker_binary is the documented path, but if your adom-wiki CLI build doesn't surface that value in its --asset-type enum, fall back to the raw curl upload against /api/v1/pages/<slug>/assets until the CLI catches up.

These are tracked on the wiki repo. The pattern described in this skill is the forward-compatible one — don't code around either gap in ways that would break once they're fixed.

AI Hints: Making CLIs Smart for AI Callers

When AI (Claude) calls your CLI, it needs contextual guidance on what to do next. Add _hint fields to JSON responses that tell the AI:

  • What went wrong and how to fix it
  • What to do after a successful command
  • What might go wrong next

This is the single most important pattern for making a CLI work well with AI. Without hints, the AI guesses. With hints, it acts correctly on the first try.

Pattern 1: Success hints — tell AI the next step

{
  "success": true,
  "output": "Exported gerbers to C:/tmp/gerbers.zip",
  "_hint": "Files saved on Windows. Use pull_file to get them to Docker: pull_file '{\"filePaths\":[\"C:/tmp/gerbers.zip\"],\"saveTo\":\"/tmp/mfg\"}'"
}

The AI reads _hint and knows to call pull_file — without this, it would just report "exported" and stop.

Pattern 2: Warning hints — tell AI what can go wrong

{
  "success": true,
  "output": "Opened BQ25792 from cloud",
  "_hint": "WARNING: May trigger 'Select Electronics Design File' dialog when switching to electronics workspace. If next command fails with 'add-in not responding', check fusion_window_info for blocking dialogs. Try fusion_send_key escape to dismiss."
}

The AI is now primed to handle the dialog if it appears, instead of getting stuck.

Pattern 3: Verification hints — tell AI to check its work

{
  "success": true,
  "output": "Launched schematic editor",
  "_hint": "WARNING: KiCad may show modal dialogs (save prompts, file warnings) that block further commands. ALWAYS check kicad_window_info for hasModalDialogs after this command. If true, use kicad_screenshot_all to capture all windows, pull_file to get screenshots, then read the dialog to decide how to handle it."
}

This turns a simple "opened" response into a full debugging workflow the AI follows automatically.

Pattern 4: Error diagnosis — auto-attach context on failure

// In your CLI code: when a command fails, try to diagnose WHY
let error_str = map.get("error").and_then(|v| v.as_str()).unwrap_or("");
if error_str.contains("not responding") {
    // Auto-run diagnostic command
    let diag = run_diagnostic_command();
    enriched.insert("_hint", json!("Add-in blocked by modal dialog. Run window_info to find it, screenshot it, dismiss with send_key."));
    enriched.insert("_diagnosis", diag);
}

The AI gets the error + diagnosis + recovery steps in one response — no back-and-forth needed.

Pattern 5: Prerequisite hints — tell AI what must be true first

{
  "success": false,
  "error": "Not an Electron document",
  "_hint": "Requires Electronics workspace active (isElectronics: true in get_app_state). The active tab is 3D PCB not PCB Editor. Switch with activate_document or open the .brd file."
}

Implementation: where to add hints

Add hints in the CLI's command routing layer — the single point where responses pass through before being printed to stdout. Don't put them in the backend/bridge; the CLI is the AI's interface.

// In commands.rs — match on command name and success/failure
match command {
    "export_gerbers" if success => add_hint("Files on Windows. Use pull_file..."),
    "open_schematic" if success => add_hint("WARNING: modals may appear..."),
    _ if error.contains("not responding") => add_hint("Blocked by dialog..."),
}

Pattern 6: Modal-first loops — teach AI to poll, not sleep

Desktop apps show modal dialogs that block everything. The AI's natural instinct is sleep 10 — but that wastes time and misses dialogs. Teach it to poll:

{
  "error": "Add-in not responding",
  "_hint": "BLOCKED by modal dialog. DO NOT sleep — check immediately: (1) window_info → find dialogs, (2) screenshot each → read text, (3) click Yes/OK to dismiss. IMPORTANT: Dialogs CASCADE — after dismissing one, check for MORE before proceeding. Loop until app_state returns success."
}

Key phrases that change AI behavior:

  • "DO NOT sleep" — overrides the default instinct
  • "check immediately" — creates urgency
  • "Dialogs CASCADE" — prevents the AI from assuming one dismissal fixes everything
  • "Loop until" — gives a clear termination condition

Rules for good hints

  1. Be specific — "Use pull_file with filePaths array" not "transfer the file"
  2. Include example commands — Show the exact CLI call the AI should make
  3. Warn about traps — "NOTE: Enter does NOT work on this dialog, use Escape"
  4. Chain commands — "After this, run X, then Y, then Z"
  5. Explain prerequisites — "Requires Electronics workspace active"
  6. Override bad instincts — "DO NOT sleep" when the AI should poll instead
  7. Keep it under 200 chars for simple hints, longer for complex recovery flows

Related Skills

  • adom-cli-design — Rust CLI conventions (output format, clap patterns, port registration)
  • skill-creator — How to write SKILL.md files
  • adom-wiki — Wiki CLI for publishing and managing pages

Skill Source

Edit AI Skill
---
name: tool-publisher
description: >
  Use when the user wants to publish a CLI tool or skill to the Adom Wiki for
  auto-discovery and auto-installation. Covers the full lifecycle: build CLI,
  upload the binary to the wiki public static path as a docker_binary asset,
  publish the wiki page with discovery metadata, bundle one-or-many SKILL.mds
  in the binary install subcommand, and make it installable via paste-into-Claude
  prompt. Trigger words: publish tool, publish cli, auto-discover, make tool
  discoverable, wiki discovery, distribute tool, release tool, tool
  distribution, publish to wiki, auto-install, structure private/public app.
---

# Publishing Tools to the Adom Wiki

This skill covers the **full lifecycle** for making a CLI tool or skill discoverable
and installable by any Adom user — without hardcoding it into `gallia/install.mjs`,
and without requiring the installing user to have GitHub access to `adom-inc/*`.

## ⚠ Strict-auth is ON as of 2026-05-01 — `release v$VERSION` will fail

The wiki server enforces `WIKI_STRICT_AUTH=1`. Every mutation (publish, edit, asset upload, asset delete, video add, video remove, skill add, skill remove) requires:

1. A real author identity (no `Developer (shared)` fallback). Run `adom-wiki set-author` once per container.
2. A real `--changelog` (≥10 chars, ≥2 words, no known-placeholder strings). Specifically rejected: `release v1.2.3`, `Initial publish from gallia`, `Auto-populated image metadata from Fusion 360...`, `Add metadata`, `update`, `publish`, `sync`, `republish`, and the adom-desktop pattern `Release X.Y.Z — assets + skills refreshed via release-publish.sh`.

**If your `publish.sh` still hardcodes `--changelog "release v$VERSION"`, its next release will fail with a 400.** Migrate to the canonical fallback chain in the section below.

The 6 sibling tools needing migration (as of 2026-05-01): `adom-chipfit`, `adom-jlcpcb`, `adom-mouser`, `adom-digikey`, `adom-tts`, `adom-parts-search`. Plus `adom-desktop`'s `release-publish.sh`.

For genuine CI flows (no human identity, e.g. `process-datasheets` cron), use a service-account token flagged with `is_service_account=1`. A `gallia-install` token exists but is currently parked (gallia install is read-only — it does not need to publish).

## Public vs Private: The Line

Every wiki-published tool has a **public side** and a **private side**, and the
demarcation matters for onboarding 3rd-party collaborators:

- **Public (wiki-hosted):** the compiled binary, the usage docs, the SKILL.md,
  screenshots, install hint. Anyone on the Adom platform — including 3rd-party
  collaborators with **zero** GitHub access — can see and install it by hitting
  `https://wiki-ufypy5dpx93o.adom.cloud/static/apps/<slug>/<binary>`. No auth, no
  GitHub token.
- **Private (GitHub, optional):** the source code, build system, issue tracker,
  internal design docs. Stays on `adom-inc/<repo>` with whatever read permissions
  you want. The wiki page MAY carry a `repo` link in metadata for team members
  who have GitHub access, but **the link is not required** and **is not used by
  the installer**.

**What this means when you publish:**

- Build the binary yourself (`cargo build --release`).
- Upload it to the wiki as a `docker_binary` asset via `adom-wiki asset upload`.
  Latest wins — the server auto-deletes any previous `docker_binary` on the same
  page before inserting the new one.
- Set `metadata.releases.adom_docker.install_hint` to a `curl` command that hits
  `/static/apps/<slug>/<asset_name>`, **not** `gh release download`.
- Bump the top-level `version:` field in the publish body (→ `pub_version` column,
  semver-validated on the wiki side).
- **Do not tell users to clone the private repo.** The install flow must work
  end-to-end from the wiki alone, no GitHub credentials.

### What lives where — quick-reference table

| Artifact | Lives in | Why |
|---|---|---|
| Source code (`*.rs`, `Cargo.toml`, tests, CI config) | **Private** GitHub repo (optional) | Internal dev, CI secrets, issue tracking. Choose whether to use GitHub at all. |
| Compiled CLI binary | **Public** wiki at `/static/apps/<slug>/<slug>` (uploaded as `docker_binary` asset) | Paste-prompt `curl` must work for any user — no repo access needed. |
| SKILL.md content | **Public** wiki page (`skill_source` field) **AND** embedded in the binary via `include_str!` | Users browse it on the wiki; `<tool> install` drops it to `~/.claude/skills/<slug>/`. |
| Install prompt (rendered) | **Public** — auto-generated on the wiki page from metadata | Users paste it into Claude Code to trigger the install. |
| Discovery triggers & pitch | **Public** wiki page metadata | Aggregated by `/discover` into the gallia auto-discover snippet. |
| Screenshots, README, examples, demos | **Public** wiki page content + assets | Browsable by anyone; drives adoption. |
| Internal design notes, WIP features, roadmap | **Private** GitHub repo | Not ready for distribution. |
| Secrets, API keys, signing keys | **Neither** — env vars / 1Password / CI secrets | Never committed to either. |

**Rule of thumb:** if a user on a fresh container needs it to install or run your
tool, it goes on the wiki. Everything else stays in the private repo (if you use
one at all).

## Architecture: 2-Layer Distribution

```
Layer 1: Source Code (private GitHub repo, optional link only)
    ↓  cargo build --release   +   adom-wiki asset upload
Layer 2: Adom Wiki (public: binary asset + discovery metadata + install prompt)
    ↓  gallia/hooks/refresh-wiki-catalog.mjs audits installed versions every 30 min
    ↓  stale tool? Claude asks user "Shall I upgrade?"
User Container (binary upgraded from wiki)
```

**Key principle:** `gallia/install.mjs` is for **core infrastructure only** (adom-cli,
adom-wiki, viewer, MCP servers). New tools use **wiki auto-discovery** instead —
they get discovered and installed on-demand, and the 30-min refresh hook keeps
users' installed versions in sync with the wiki's `pub_version`.

**Gallia's only role for new tools is carrying the auto-discover snippet.** Not
source, not binary, not SKILL.md, not docs — none of it lives in gallia. The
snippet is regenerated from wiki metadata on every container setup and on every
30-min hook tick. Ship updates by re-publishing the wiki page, not by opening a
gallia PR.

## When to Use Each Layer

| Distribution | When | Example |
|-------------|------|---------|
| **gallia/install.mjs** | Core infra every container needs | adom-cli, adom-wiki, viewer |
| **Wiki auto-discovery** | Tools users install on-demand | adom-molecule, shotlog, adom-tsci |
| **gallia/skills/** | Cross-cutting reference skills not tied to one app | *being phased out* — prefer wiki |

## Prerequisites

Before publishing, you need:
1. A working CLI (see `adom-cli-design` skill for Rust CLI conventions)
2. A SKILL.md embedded in the binary (see `skill-creator` skill)
3. A built `target/release/<tool>` binary — where you build it is up to you.
   A private `adom-inc/<tool>` GitHub repo is the usual home for source, but
   you do NOT need a GitHub release — the binary goes to the wiki.
4. The `adom-wiki` CLI installed
5. A `<tool> --version` command that prints a semver line (defaults to `--version`;
   override via `metadata.releases.adom_docker.version_command` if your CLI uses
   a different flag)

## Step 1: Build the CLI

Follow the `adom-cli-design` skill. Key requirements:

- Rust with `clap`, `ureq`, `serde_json`
- `OK:` / `ERROR:` output format
- `install` subcommand that deploys SKILL.md via `include_str!`
- `health` subcommand if it talks to a server
- `.gitignore` with `target/`

```toml
# Cargo.toml
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
ureq = { version = "2", features = ["json"] }
clap = { version = "4", features = ["derive", "env"] }

[profile.release]
strip = true
lto = "thin"
```

Build:
```bash
cargo build --release
sudo cp target/release/my-tool /usr/local/bin/
my-tool install  # deploys SKILL.md to ~/.claude/skills/my-tool/
```

## Step 2: Put source on GitHub (optional) and upload binary to the wiki

### 2a. Private source repo (optional)

Source code stays private. You choose whether to host it on GitHub at all; it's
fine to just keep the source in your local checkout. If you do use GitHub:

```bash
gh repo create adom-inc/my-tool --private --description "One-line description"
cd /path/to/my-tool
git init && git remote add origin https://github.com/adom-inc/my-tool.git
echo "target/" > .gitignore
git add -A && git commit -m "Initial release: my-tool v0.1.0"
git branch -M main && git push -u origin main
```

**Do NOT `gh release create`.** The compiled binary does not belong in a GitHub
release anymore — it goes to the wiki, where it's actually installable by people
without GitHub access.

### 2b. Upload the binary to the wiki

```bash
# Publish a minimal page first (Step 3 fills in the full metadata).
# If you're doing this as part of an update (page already exists), skip the
# publish and go straight to the asset upload.
adom-wiki asset upload apps/my-tool \
  --asset-type docker_binary \
  --file target/release/my-tool \
  --caption "v0.1.0 build for adom docker"
```

The wiki auto-deletes any previous `docker_binary` on this page before storing
the new file, so this command is idempotent — run it again on every release.

The canonical public download URL is:

```
https://wiki-ufypy5dpx93o.adom.cloud/static/apps/my-tool/my-tool
```

Verify it's reachable without any credentials:

```bash
curl -I https://wiki-ufypy5dpx93o.adom.cloud/static/apps/my-tool/my-tool
# expect HTTP/1.1 200 OK, content-type: application/octet-stream
```

## Step 3: Publish Wiki Page

Publish as page type `app` with full metadata including discovery triggers.

### 3a. Write the wiki content

Create a markdown file (`/tmp/my-tool-wiki.md`) with:
- What it does (1-2 paragraphs)
- What the `curl`-based install prompt does for them (they paste it, Claude runs it)
- Quick start examples
- Link to the repo (optional — informational only)

### 3b. Publish with metadata

**Critical:** Include metadata in the initial publish. The v1 publish endpoint
accepts a monotonically-increasing `version:` field and overwrites `content`,
`brief`, and `skill_source` on re-publish. Metadata updates, however, are best
done via the dedicated endpoint (see 3c below) or by delete+recreate if the
direct metadata edit doesn't stick — this gap is being closed; see the
"Wiki-side work" section at the bottom of this page.

```bash
WIKI_URL="https://wiki-ufypy5dpx93o.adom.cloud"

curl -s -X POST "$WIKI_URL/api/v1/pages" \
  -H "Authorization: Bearer adom-wiki-dev-2025" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "app",
    "slug": "my-tool",
    "title": "My Tool Name",
    "brief": "One-line description for search results and page header.",
    "content": "... markdown content ...",
    "skill_source": "... contents of SKILL.md ...",
    "metadata": {
      "repo": "adom-inc/my-tool",
      "repo_visibility": "private",
      "releases": {
        "adom_docker": {
          "asset_name": "my-tool",
          "install_hint": "curl -fsSL https://wiki-ufypy5dpx93o.adom.cloud/static/apps/my-tool/my-tool -o /usr/local/bin/my-tool && chmod +x /usr/local/bin/my-tool && my-tool install"
        }
      },
      "discovery_triggers": [
        "trigger phrase 1",
        "trigger phrase 2",
        "trigger phrase 3"
      ],
      "discovery_pitch": "One sentence explaining what this tool does and why you want it."
    },
    "version": "0.1.0",
    "changelog": "Initial release"
  }'
```

**Notes on the fields:**

- Top-level `version` → `pub_version` column on the page. Semver-validated on
  the wiki side. This is the **single source of truth** for the tool's version.
  Gallia's 30-min refresh hook reads `pub_version` directly; it ignores
  `metadata.version`. Don't bother setting `metadata.version`.
- `install_hint` must be self-contained (no GitHub access, no tokens). Use
  `curl -fsSL <wiki static URL>` — never `gh release download`.
- `repo` is purely informational. Team members with GitHub access may follow
  it to the source; 3rd-party collaborators will just ignore it.
- If your tool's `--version` flag isn't the default, also set
  `metadata.releases.adom_docker.version_command` to the exact shell command
  gallia should run (e.g. `"my-tool version --short"`).

### 3c. Verify

```bash
# Check the page exists and has metadata
adom-wiki page get apps/my-tool | python3 -c "
import sys, json
d = json.load(sys.stdin)
meta = json.loads(d['page']['metadata'])
print('Triggers:', meta.get('discovery_triggers'))
print('Pitch:', meta.get('discovery_pitch'))
print('Install:', meta.get('releases',{}).get('adom_docker',{}).get('install_hint'))
"

# Check the discover page includes your tool
# Visit: https://wiki-ufypy5dpx93o.adom.cloud/discover
```

## Metadata Fields Reference

### Required for Auto-Discovery

| Field | Type | Purpose |
|-------|------|---------|
| `discovery_triggers` | string[] | Phrases users might say. Claude matches these to suggest the tool. |
| `discovery_pitch` | string | One-sentence hook shown when Claude suggests the tool. |

### Required for Auto-Install

| Field | Type | Purpose |
|-------|------|---------|
| `releases.adom_docker.asset_name` | string | Binary filename on the wiki asset (must match `adom-wiki asset upload --file`'s filename). Also used as the `command -v` check and default `--version` bin. |
| `releases.adom_docker.install_hint` | string | Full install command. Must be self-contained (no GitHub auth). Use `curl -fsSL https://wiki-.../static/apps/<slug>/<asset>`. |

### Optional

| Field | Type | Purpose |
|-------|------|---------|
| `releases.adom_docker.version_command` | string | Shell command to check the installed version (default: `<asset_name> --version`). Output must contain a semver like `1.2.3`. |
| `repo` | string | GitHub repo (`owner/name`) — informational, not used by installer |
| `repo_visibility` | string | `"private"` or `"public"` |
| `releases.windows` | object | Windows installer info (for desktop apps) |

### NOT used anymore

- `metadata.version` — ignored. Use the top-level `version:` field, which
  writes to the `pub_version` column.

## How Auto-Discovery + Continuous Sync Works

1. The wiki's `/discover` page aggregates all pages with `discovery_triggers` metadata.
2. `gallia/hooks/refresh-wiki-catalog.mjs` fetches `/discover` and writes the
   concatenated SKILL.md snippet to `~/.claude/skills/adom-wiki-discover/SKILL.md`.
3. The same script also fetches `/api/v1/pages`, walks every page with a
   `releases.adom_docker` entry, HEAD-checks the binary's public download URL,
   runs the tool's version command, and compares `installed` to `pub_version`.
4. `gallia/hooks/check-updates.sh` runs the refresh script every ~30 min (via the
   `UserPromptSubmit` hook). On success, it updates `~/.adom/last-wiki-check`.
5. If any installed tool is older than its wiki `pub_version`, the hook injects a
   `<system-reminder>` telling Claude to ASK the user "Shall I upgrade <tool>
   from <installed> → <catalog>?". On confirmation, Claude runs the `install_hint`
   (which `curl`s the wiki asset). On decline, Claude drops it for the session.
6. `install.mjs` also calls the refresh script on container setup, so a fresh
   container starts with the current catalog instead of waiting 30 min for the
   first hook tick.

Gallia never downloads tools from GitHub anymore. Every install / upgrade path
is rooted in the wiki — which means any 3rd-party user with access to the Adom
container gets the same install experience as a team member.

## How the Install Prompt Works

The wiki renders an "Install this app" block on every app page. The prompt text is
auto-generated by `renderInstallPrompt()` in `wiki/lib/templates.js` based on the
page type and metadata.

For `app` pages, it uses `releases.adom_docker.install_hint` to build a prompt like:
> I want to install the "My Tool" app from the Adom Wiki. Run this:
> `curl -fsSL https://wiki-.../static/apps/my-tool/my-tool -o /usr/local/bin/my-tool && chmod +x /usr/local/bin/my-tool && my-tool install`
> Then verify the install works.

Users copy this prompt and paste it into Claude Code, which executes the install.

## Writing Good Discovery Triggers

Think about what a user would say when they need your tool:

**Good triggers** (specific, action-oriented):
```json
["create molecule", "import kicad files", "upload fusion files",
 "molecule from kicad", "optimize molecule 3d"]
```

**Bad triggers** (too generic, would match everything):
```json
["help", "tool", "create", "upload"]
```

Include 8-15 triggers covering:
- The primary action ("create molecule")
- Input formats ("import kicad files", "upload fusion files")
- Alternate phrasings ("molecule from kicad", "kicad to molecule")
- Specific features ("optimize molecule", "blender optimization")

## Writing a Good Discovery Pitch

One sentence that answers: "Why would I want this?"

**Good:** "Import KiCad, Fusion 360, or EasyEDA design files into Adom molecules
with one command. Handles file upload, 3D optimization, and molecule management."

**Bad:** "A CLI tool for molecules."

## The `install` subcommand: what it actually needs to do

The paste-prompt `curl ... && my-tool install` is the entire onboarding surface
for a new user. Don't under-spec this subcommand — users won't forgive a
half-broken install that leaves them to read a `--help` and figure it out.
Required responsibilities, in order:

1. **Check system prereqs.** If your tool needs `bun`, `node`, `ffmpeg`,
   `python3.11+`, or anything else the base container doesn't already have,
   check for it up front and print a single `ERROR:` line with the exact
   install command the user should run. Never proceed past a missing prereq —
   the user's first experience must not be a cryptic runtime crash.

   ```rust
   if which::which("bun").is_err() {
       eprintln!("ERROR: `bun` is required but not found.");
       eprintln!("Install with: curl -fsSL https://bun.sh/install | bash");
       std::process::exit(1);
   }
   ```

2. **Deploy the binary to `~/.local/bin/<tool>`** (or `/usr/local/bin/` if the
   `curl` in the install hint put it there — your `install` subcommand should
   handle the case where the binary is re-run from either location).

3. **Deploy every bundled SKILL.md** to `~/.claude/skills/<skill-name>/SKILL.md`.
   Use `include_str!` at compile time so each skill lives alongside the source
   in the private repo. Multi-skill apps (next section) drop multiple files
   here.

4. **Install shell completions** for the user's current shell. Silent
   best-effort — if the shell's completion dir doesn't exist, skip.
   Completions significantly improve discoverability of subcommands for
   non-programmer users who don't reflexively run `--help`.

   ```bash
   my-tool completions bash > ~/.local/share/bash-completion/completions/my-tool 2>/dev/null || true
   my-tool completions zsh  > ~/.zsh/completions/_my-tool 2>/dev/null || true
   ```

5. **Write an `OK:` line per action taken and a final summary.** Mirror the
   `adom-cli-design` output format. Users and Claude both read `install`
   output to confirm success.

6. **Be idempotent.** Re-running `install` should update everything to match
   the current binary (newer SKILL.md, newer completions) and print `OK:` for
   each step, not error out because files exist.

Minimum viable output:
```
OK: prereqs checked (bun 1.1.30, gh 2.55.0)
OK: binary at /usr/local/bin/my-tool
OK: deployed 2 skill(s) to ~/.claude/skills/
OK: bash completions installed
OK: my-tool ready. Try `my-tool --help`.
```

## Multi-skill apps: bundling more than one SKILL.md

Some apps ship a **reference** skill (how to drive the app's own commands)
*and* a **recipe** skill (higher-level workflow that uses the app). The
archetype is `adom-tsci`, which ships both:

- `adom-tsci` — reference skill for the binary's CLI (start, reload, open, …)
- `adom-tscircuit` — recipe skill for designing Adom Molecules in tscircuit
  using the `adom-tsci` preview workflow

Both belong in the same repo (`adom-inc/adom-tsci`) and both get deployed by
the same `adom-tsci install` subcommand. Layout:

```
adom-inc/adom-tsci/
├── Cargo.toml
├── src/
│   └── main.rs              // include_str! both SKILL.mds
├── skills/
│   ├── adom-tsci/
│   │   └── SKILL.md         // reference skill
│   └── adom-tscircuit/
│       └── SKILL.md         // recipe skill
└── README.md
```

In `main.rs`:

```rust
const SKILL_REFERENCE: &str = include_str!("../skills/adom-tsci/SKILL.md");
const SKILL_RECIPE:    &str = include_str!("../skills/adom-tscircuit/SKILL.md");

fn install() {
    write_skill("adom-tsci",      SKILL_REFERENCE);
    write_skill("adom-tscircuit", SKILL_RECIPE);
    // ... completions, prereqs, etc
}
```

Rules:
- **One wiki page per app**, not per skill. The wiki page's `skill_source`
  field carries the primary (reference) skill; secondary skills are
  documented in the page content and shipped in the binary.
- **Each SKILL.md has its own discovery triggers in its frontmatter** so
  Claude loads the right one for the right trigger phrase. The wiki page
  metadata's `discovery_triggers` covers the install trigger ("install
  adom-tsci", "preview tscircuit in webview", etc.).
- **Keep the recipe skill tightly scoped.** If a recipe skill grows features
  that aren't specific to this app, split it out into its own wiki page.

## Example: shotlog (reference implementation for the wiki-hosted model)

- **Source (private):** `adom-inc/shotlog` — source stays here, no public release.
- **Wiki page:** `apps/shotlog`
- **Wiki asset:** `docker_binary` uploaded via `adom-wiki asset upload`, public URL
  `https://wiki-ufypy5dpx93o.adom.cloud/static/apps/shotlog/shotlog`
- **Discovery triggers:** 14 phrases (screenshot log, visual debug loop, ...)
- **Install:** `curl -fsSL <wiki URL>/static/apps/shotlog/shotlog -o /usr/local/bin/shotlog && chmod +x /usr/local/bin/shotlog && shotlog install`
- **NOT in install.mjs** — discovered and installed on-demand via the wiki
- **Auto-upgrade:** gallia's 30-min refresh hook compares installed `shotlog --version`
  against the wiki `pub_version` and offers an upgrade when they diverge

## Updating a Published Tool

To release a new version:

```bash
# 1. Build the new binary
cargo build --release

# 2. Push the source change to the private repo (optional — if you use one)
git commit -am "..." && git push origin main

# 3. Upload the new binary to the wiki. The previous docker_binary asset on
#    this page is auto-deleted before the new one is stored.
adom-wiki asset upload apps/my-tool \
  --asset-type docker_binary \
  --file target/release/my-tool \
  --caption "v0.2.0 build for adom docker"

# 4. Bump the page's pub_version (and install_hint if it changed).
#    The publish endpoint accepts a monotonically-greater version and
#    overwrites content / skill_source / brief / title. If you also need
#    to change metadata and the field-edit path is flaky, fall back to
#    DELETE + recreate. See "Wiki-side work" below.
adom-wiki page publish apps/my-tool \
  --title "My Tool Name" \
  --brief "..." \
  --body-md /tmp/my-tool-wiki.md \
  --skill-source /path/to/SKILL.md \
  --version 0.2.0 \
  --changelog "..."

# 5. Update metadata (discovery triggers, install hint, etc.)
adom-wiki page edit apps/my-tool \
  --field metadata \
  --body-md /tmp/metadata.json
```

After republishing, the next time any Adom container's 30-min hook ticks, the
refresh script sees the new `pub_version` and (if the user has the old binary
installed) injects a system-reminder offering an upgrade. Propagation is
automatic from this point — you don't need to do anything else.

## Checklist

Before publishing:

- [ ] CLI builds clean with `cargo build --release`
- [ ] `my-tool --version` prints a semver (or you set `version_command` in the wiki metadata)
- [ ] `my-tool health` works (if applicable)
- [ ] `my-tool install` deploys SKILL.md correctly, checks prereqs, installs completions
- [ ] `my-tool install` is idempotent (re-running just re-syncs everything)
- [ ] Binary uploaded to the wiki as `docker_binary`
- [ ] `curl -I https://wiki-.../static/apps/<slug>/<bin>` returns 200 with no auth
- [ ] Wiki page has top-level `version: "x.y.z"` (→ `pub_version`)
- [ ] Wiki page has `discovery_triggers` and `discovery_pitch` in metadata
- [ ] Wiki page has `releases.adom_docker.install_hint` starting with `curl -fsSL https://wiki-` — **not** `gh release download`
- [ ] Install prompt on wiki page renders the new curl-based command
- [ ] Tool appears on `/discover` page
- [ ] End-to-end install tested in a **zero-credential environment** — a fresh
      container with no GitHub token must be able to run the install_hint and
      get a working binary
- [ ] **NOT added to install.mjs** (unless it's core infrastructure)

## Wiki-side work (tracked separately)

The wiki service is still closing a couple of gaps. Both affect smoothness of
the publish → update → distribute loop; neither blocks shipping a new tool
today. Workarounds below apply until they land.

1. **Metadata-only updates on an existing page.** The `adom-wiki page edit
   --field metadata` path is the intended update endpoint, but its propagation
   to `/discover` isn't fully deterministic yet. Workaround: bump `pub_version`
   on a full re-publish, or `DELETE` + re-POST if metadata doesn't stick.

2. **`adom-wiki asset upload --asset-type docker_binary`** is the documented
   path, but if your `adom-wiki` CLI build doesn't surface that value in its
   `--asset-type` enum, fall back to the raw curl upload against
   `/api/v1/pages/<slug>/assets` until the CLI catches up.

These are tracked on the wiki repo. The pattern described in this skill is the
forward-compatible one — don't code around either gap in ways that would break
once they're fixed.

## AI Hints: Making CLIs Smart for AI Callers

When AI (Claude) calls your CLI, it needs contextual guidance on what to do next. Add `_hint` fields to JSON responses that tell the AI:
- What went wrong and how to fix it
- What to do after a successful command
- What might go wrong next

This is the **single most important pattern** for making a CLI work well with AI. Without hints, the AI guesses. With hints, it acts correctly on the first try.

### Pattern 1: Success hints — tell AI the next step

```json
{
  "success": true,
  "output": "Exported gerbers to C:/tmp/gerbers.zip",
  "_hint": "Files saved on Windows. Use pull_file to get them to Docker: pull_file '{\"filePaths\":[\"C:/tmp/gerbers.zip\"],\"saveTo\":\"/tmp/mfg\"}'"
}
```

The AI reads `_hint` and knows to call `pull_file` — without this, it would just report "exported" and stop.

### Pattern 2: Warning hints — tell AI what can go wrong

```json
{
  "success": true,
  "output": "Opened BQ25792 from cloud",
  "_hint": "WARNING: May trigger 'Select Electronics Design File' dialog when switching to electronics workspace. If next command fails with 'add-in not responding', check fusion_window_info for blocking dialogs. Try fusion_send_key escape to dismiss."
}
```

The AI is now primed to handle the dialog if it appears, instead of getting stuck.

### Pattern 3: Verification hints — tell AI to check its work

```json
{
  "success": true,
  "output": "Launched schematic editor",
  "_hint": "WARNING: KiCad may show modal dialogs (save prompts, file warnings) that block further commands. ALWAYS check kicad_window_info for hasModalDialogs after this command. If true, use kicad_screenshot_all to capture all windows, pull_file to get screenshots, then read the dialog to decide how to handle it."
}
```

This turns a simple "opened" response into a full debugging workflow the AI follows automatically.

### Pattern 4: Error diagnosis — auto-attach context on failure

```rust
// In your CLI code: when a command fails, try to diagnose WHY
let error_str = map.get("error").and_then(|v| v.as_str()).unwrap_or("");
if error_str.contains("not responding") {
    // Auto-run diagnostic command
    let diag = run_diagnostic_command();
    enriched.insert("_hint", json!("Add-in blocked by modal dialog. Run window_info to find it, screenshot it, dismiss with send_key."));
    enriched.insert("_diagnosis", diag);
}
```

The AI gets the error + diagnosis + recovery steps in one response — no back-and-forth needed.

### Pattern 5: Prerequisite hints — tell AI what must be true first

```json
{
  "success": false,
  "error": "Not an Electron document",
  "_hint": "Requires Electronics workspace active (isElectronics: true in get_app_state). The active tab is 3D PCB not PCB Editor. Switch with activate_document or open the .brd file."
}
```

### Implementation: where to add hints

Add hints in the CLI's command routing layer — the single point where responses pass through before being printed to stdout. Don't put them in the backend/bridge; the CLI is the AI's interface.

```rust
// In commands.rs — match on command name and success/failure
match command {
    "export_gerbers" if success => add_hint("Files on Windows. Use pull_file..."),
    "open_schematic" if success => add_hint("WARNING: modals may appear..."),
    _ if error.contains("not responding") => add_hint("Blocked by dialog..."),
}
```

### Pattern 6: Modal-first loops — teach AI to poll, not sleep

Desktop apps show modal dialogs that block everything. The AI's natural instinct is `sleep 10` — but that wastes time and misses dialogs. Teach it to poll:

```json
{
  "error": "Add-in not responding",
  "_hint": "BLOCKED by modal dialog. DO NOT sleep — check immediately: (1) window_info → find dialogs, (2) screenshot each → read text, (3) click Yes/OK to dismiss. IMPORTANT: Dialogs CASCADE — after dismissing one, check for MORE before proceeding. Loop until app_state returns success."
}
```

Key phrases that change AI behavior:
- "DO NOT sleep" — overrides the default instinct
- "check immediately" — creates urgency
- "Dialogs CASCADE" — prevents the AI from assuming one dismissal fixes everything
- "Loop until" — gives a clear termination condition

### Rules for good hints

1. **Be specific** — "Use pull_file with filePaths array" not "transfer the file"
2. **Include example commands** — Show the exact CLI call the AI should make
3. **Warn about traps** — "NOTE: Enter does NOT work on this dialog, use Escape"
4. **Chain commands** — "After this, run X, then Y, then Z"
5. **Explain prerequisites** — "Requires Electronics workspace active"
6. **Override bad instincts** — "DO NOT sleep" when the AI should poll instead
7. **Keep it under 200 chars** for simple hints, longer for complex recovery flows

## Related Skills

- `adom-cli-design` — Rust CLI conventions (output format, clap patterns, port registration)
- `skill-creator` — How to write SKILL.md files
- `adom-wiki` — Wiki CLI for publishing and managing pages

Sub-Skills
?
What are Sub-Skills?

Sub-skills are community-contributed AI skill extensions for this component. They teach AI assistants about specific tools, configurators, or workflows.

Examples:

  • A manufacturer’s configuration tool for a motor controller
  • A community-written design guide for an amplifier circuit
  • An automated test/validation script for a sensor module

How to add one: Click Add Sub-Skill, provide the URL to your skill and a brief description. Submissions are reviewed by the Adom team before going live.

No sub-skills yet. Be the first to contribute one!

1 revision · Updated 2026-05-01 15:29:32