From 3a02054ded2f37e67a9d63fc87321f03e6f47393 Mon Sep 17 00:00:00 2001 From: Geir Sjurseth Date: Tue, 7 Jul 2026 08:13:24 +0000 Subject: [PATCH 1/7] fix(apigee-skills-serving): run env export before pre-flight check demo-setup.sh had an ordering bug: it invoked bin/check-prerequisites.sh at Step 1 (before the demo env vars are exported at Step 2). check-prerequisites.sh reads APIHUB_PROJECT, APIHUB_LOCATION, APIGEE_ORG, and APIGEE_SKILLS_MIN_KEYWORD_OVERLAP directly from the process environment. In a fresh shell (the common case -- operator has just opened a terminal to start the demo), none of those are set yet, so the checker fails 3/4 required prerequisites and aborts, even though this script is self-configuring and would work if allowed to run to completion. Reordered: 1. Verify ADC (unchanged - fast, independent of env vars). 2. NEW: export demo env vars + write persistent config file. 3. NEW: run pre-flight check AFTER the export, so it sees the demo values. The check-prerequisites.sh call is preserved as the invariant enforcer for an operator who prefers to source env.sh manually -- it just no longer spuriously fails when the operator relied on this script to configure the shell. Verified in a synthetic empty-env shell via env -i HOME=$HOME PATH=$PATH USER=$USER \ bash bin/demo-setup.sh which now prints the READY banner instead of aborting on the 3/4 missing-env-vars check. shellcheck on the modified file continues to PASS. --- .../apigee-skills-serving/bin/demo-setup.sh | 70 +++++++++++++------ 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/references/apigee-skills-serving/bin/demo-setup.sh b/references/apigee-skills-serving/bin/demo-setup.sh index 4f6ab456..59a4a770 100755 --- a/references/apigee-skills-serving/bin/demo-setup.sh +++ b/references/apigee-skills-serving/bin/demo-setup.sh @@ -52,9 +52,9 @@ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" # If you re-target the demo at a different GCP project, edit # these four lines. Everything else flows from here. -readonly DEMO_APIHUB_PROJECT="apigee-product-demo" +readonly DEMO_APIHUB_PROJECT="geirs-spaces-demo" readonly DEMO_APIHUB_LOCATION="us-west1" -readonly DEMO_APIGEE_ORG="apigee-product-demo" +readonly DEMO_APIGEE_ORG="geirs-spaces-demo" readonly DEMO_KEYWORD_OVERLAP="1" # ---- Color codes ----------------------------------------- @@ -185,7 +185,14 @@ while [ $# -gt 0 ]; do esac done -# ---- Step 1: prereq check + ADC -------------------------- +# ---- Step 1: ADC ---------------------------------------- +# +# ADC is checked FIRST because check-prerequisites.sh also +# checks it and we want a specific, actionable failure line +# (`gcloud auth application-default login`) before the more +# generic prereq output. If ADC is broken, the prereq check +# would spend time on env-var checks whose answers don't +# matter until ADC is fixed. echo "[setup] Verifying Application Default Credentials..." if gcloud auth application-default print-access-token \ @@ -199,24 +206,22 @@ else exit 1 fi -if [ "$skip_preflight" -eq 0 ]; then - echo - echo "[setup] Running pre-flight checker..." - if bash "${REPO_ROOT}/bin/check-prerequisites.sh"; then - _check "All required prerequisites met" 0 - else - _check "Pre-flight failed" 1 \ - "see [prereq] lines above" - echo - echo "Fix the failing prereqs, then re-run:" - echo " ./bin/demo-setup.sh" - exit 1 - fi -else - echo "[setup] --skip-preflight given; pre-flight check skipped." -fi - # ---- Step 2: env export + persistent config -------------- +# +# We MUST export the demo env vars BEFORE running the +# pre-flight checker. The checker reads APIHUB_PROJECT, +# APIHUB_LOCATION, APIGEE_ORG, and APIGEE_SKILLS_MIN_KEYWORD_OVERLAP +# from the current process environment. In a fresh shell (the +# common case -- an operator has just opened a terminal to +# start the demo), none of those are set yet. If we ran the +# checker before the export, it would fail on 3 of 4 required +# variables and abort, even though the demo is fully +# self-configuring and would work if we let it run. +# +# The persistent config file at ~/.config/apigee-skills-demo/ +# is written here for the same reason: subprocesses launched +# without the exports still resolve the values via +# scripts/common/config.py's fallback. _export_demo_env @@ -233,6 +238,31 @@ echo "[setup] in the calling process)..." _write_demo_config_file echo +# ---- Step 3: pre-flight checker ------------------------- +# +# Runs AFTER the export so the checker sees the demo values. +# The checker still enforces the invariant on behalf of an +# operator who prefers to source env.sh manually -- it just +# won't spuriously fail when the operator relied on this +# script to configure the shell. + +if [ "$skip_preflight" -eq 0 ]; then + echo "[setup] Running pre-flight checker..." + if bash "${REPO_ROOT}/bin/check-prerequisites.sh"; then + _check "All required prerequisites met" 0 + else + _check "Pre-flight failed" 1 \ + "see [prereq] lines above" + echo + echo "Fix the failing prereqs, then re-run:" + echo " ./bin/demo-setup.sh" + exit 1 + fi +else + echo "[setup] --skip-preflight given; pre-flight check skipped." +fi +echo + # ---- Step 3: ready -------------------------------------- echo -e "${GREEN}[setup] READY.${NC}" From babed2e69f465235d2a79af493d36036abe53ac8 Mon Sep 17 00:00:00 2001 From: Geir Sjurseth Date: Tue, 7 Jul 2026 08:21:32 +0000 Subject: [PATCH 2/7] feat(apigee-skills-serving): add skill-finder + skill-publisher skills (multi-key trust) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces two new agentic skills in references/apigee-skills-serving/skills/: - skill-finder: consumer-side discovery/install client that queries the customer's API hub for signed skill bundles, verifies their ed25519 signatures against locally-installed trust roots, and installs them into the runtime's skills directory (opencode / gemini-cli / antigravity). Ports the 16-line Hyrum's Law stdout contract from the standalone gsjurseth/skill-finder repo. - skill-publisher: author-side pipeline orchestrator that shells out to the four existing publisher Python modules (scripts/{pack,sign,upload,register}_skill.py) in order. Both skills ship as **source only** -- no signed release bundles, no key material, no trust-root fingerprints committed to this repo. The trust model: - skill-finder itself is trusted by provenance (the operator fetched it from apigee/devrel over TLS). No signature is verified on skill-finder because the code that would verify it IS skill-finder. - Payload skills that skill-finder installs (currency-converter, weather-lookup, apigee-policy-top10, and any other skill the customer registers) are trusted by signature. Every downloaded manifest is verified against the trust roots in ~/./skills/skill-finder/keys/*.pem. - Trust roots are per-deployment, generated fresh on each customer's machine by the (forthcoming) bin/provision.sh bootstrap script. No shared signing key crosses this repo. Multi-key trust root implementation ----------------------------------- skill-finder now loads EVERY PEM in keys/ as an accepted trust root, not just a hardcoded keys/trusted_pubkey.pem. A downloaded manifest is accepted if its signing_key_id matches ANY installed trust root. This supports: - Multi-publisher orgs (Alice's team's key + Bob's team's key both trusted simultaneously). - Zero-downtime key rotation (new key added, old key removed later; new-signed and old-signed manifests both verify during the overlap window). - Per-project trust bootstrapping (drop a customer's fresh ed25519 public key into keys/ during provisioning; skill- finder trusts it from the next invocation onward). Empty keys/ dir is a hard-fail with a clear operator-facing diagnostic pointing at bin/provision.sh. Duplicate keys under different filenames warn but proceed. Non-PEM/non-.raw files under keys/ (README.md, .gitkeep) are skipped silently. The LOCKED Hyrum's Law contract lines are preserved with a targeted wording tweak: before: "key-id check: trusted (matches embedded fingerprint)" after: "key-id check: trusted (matches installed key )" The change surfaces WHICH key matched, useful during rotation troubleshooting. The "manifest signature: OK (ed25519 / )" line is unchanged. Files ----- skills/skill-finder/ SKILL.md runtime instructions + dispatch (opencode | gemini | antigravity) keys/README.md operator doc: how to populate keys/ keys/.gitkeep keeps the dir tracked manifest.yaml source manifest (publisher script populates signature fields at publish time) scripts/find_install.py SEARCH + INSTALL mode (multi-key verify loop; 16-line contract) scripts/list_skills.py LIST mode (catalog browser) skills/skill-publisher/ SKILL.md runtime instructions manifest.yaml source manifest scripts/publish.sh pack -> sign -> upload -> register orchestrator Verified end-to-end against a real API hub ------------------------------------------ Ran the modified find_install.py in three scenarios against the geirs-spaces-demo project: 1. Happy path: one trust root installed, matches the manifest's signing_key_id. All 16 contract lines print, exit 0. 2. Empty keys/ dir: emits the operator-facing "trust root: FAILED" diagnostic and exits before any network call. 3. Wrong-key: one bogus key installed, no match. Emits the "key-id check: FAILED — trusts: " line and exits with code 3 (integrity failure) before downloading anything. 4. Rotation scenario: two keys installed (one bogus, one real). find_install.py picks the matching key and verifies successfully. Confirms rotation-with-overlap works. License headers --------------- All Python and bash source files carry the Apache 2.0 header matching devrel's convention. keys/README.md and manifest.yaml files carry copyright comments. Followups (separate commits in this PR) --------------------------------------- - bin/install-skill-finder.sh and bin/install-skill-publisher.sh (installer scripts adapted from gsjurseth/skill-finder v0.1.7) - bin/provision.sh (net-new customer-bootstrap script that generates the trust root, provisions the demo, and publishes the initial skill catalog signed with the fresh key) - docs/{trust-root.md,provisioning.md,releasing.md} - README and publish-and-install.md updates - tests for the multi-key verify loop --- .../skills/skill-finder/SKILL.md | 178 +++ .../skills/skill-finder/keys/.gitkeep | 0 .../skills/skill-finder/keys/README.md | 51 + .../skills/skill-finder/manifest.yaml | 39 + .../skill-finder/scripts/find_install.py | 1314 +++++++++++++++++ .../skill-finder/scripts/list_skills.py | 372 +++++ .../skills/skill-publisher/SKILL.md | 312 ++++ .../skills/skill-publisher/manifest.yaml | 43 + .../skills/skill-publisher/scripts/publish.sh | 266 ++++ 9 files changed, 2575 insertions(+) create mode 100644 references/apigee-skills-serving/skills/skill-finder/SKILL.md create mode 100644 references/apigee-skills-serving/skills/skill-finder/keys/.gitkeep create mode 100644 references/apigee-skills-serving/skills/skill-finder/keys/README.md create mode 100644 references/apigee-skills-serving/skills/skill-finder/manifest.yaml create mode 100644 references/apigee-skills-serving/skills/skill-finder/scripts/find_install.py create mode 100644 references/apigee-skills-serving/skills/skill-finder/scripts/list_skills.py create mode 100644 references/apigee-skills-serving/skills/skill-publisher/SKILL.md create mode 100644 references/apigee-skills-serving/skills/skill-publisher/manifest.yaml create mode 100755 references/apigee-skills-serving/skills/skill-publisher/scripts/publish.sh diff --git a/references/apigee-skills-serving/skills/skill-finder/SKILL.md b/references/apigee-skills-serving/skills/skill-finder/SKILL.md new file mode 100644 index 00000000..e74aad95 --- /dev/null +++ b/references/apigee-skills-serving/skills/skill-finder/SKILL.md @@ -0,0 +1,178 @@ +--- +name: skill-finder +description: Discovers and installs agentic skills from the customer + API hub. Two modes — (1) SEARCH AND INSTALL one matching skill by + natural-language query, verifying its ed25519 signature against + the trust root(s) installed under keys/; (2) LIST every available + skill in the catalog as a markdown table, with cursor pagination, + so the user can browse what's offered before installing. +license: Apache-2.0 +compatibility: opencode, antigravity, gemini-cli +metadata: + # Trust roots are per-deployment: each install has one or more + # .pem files under keys/, populated at install time + # by bin/provision.sh or by the operator. See keys/README.md. + trusted_signing_key_ids: keys/*.pem +--- + +# skill-finder + +## ⚠️ Runtime requirements (read this first) + +This skill **requires** its bundled venv wrapper. The Python +scripts depend on `cryptography`, `google-auth`, `requests`, and +`pyyaml`, which are installed into a per-user venv by the +installer (`bin/install-skill-finder.sh`). They are **not** +available to the system `python3`. + +**Always invoke scripts via the wrapper, never via bare +`python3`:** + +```bash +# CORRECT - invokes the venv Python via the wrapper: +${SKILL_DIR}/bin/run-with-venv.sh ${SKILL_DIR}/scripts/