Getting it into your agent
One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.
npx skills add bendaamerahmed/backstage-idp-plugin --skill backstage-cataloggit clone --depth 1 https://github.com/bendaamerahmed/backstage-idp-pluginWrote this? Show the measurements
A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.
[](https://agentmods.dev/skills/bendaamerahmed/backstage-idp-plugin/backstage-catalog)<a href="https://agentmods.dev/skills/bendaamerahmed/backstage-idp-plugin/backstage-catalog"><img src="https://agentmods.dev/badge/skills/bendaamerahmed/backstage-idp-plugin/backstage-catalog/github.svg" alt="Measured on agentmods" height="20"></a>Or the 80×15 button, for a site that already has a row of RSS and ATOM ones. Only the verdict fits; the numbers stay here.
<a href="https://agentmods.dev/skills/bendaamerahmed/backstage-idp-plugin/backstage-catalog"><img src="https://agentmods.dev/badge/skills/bendaamerahmed/backstage-idp-plugin/backstage-catalog.svg" alt="Reviewed on agentmods" width="80" height="20"></a>What it costs to keep this loaded
Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.
| Model | Per session | Once invoked |
|---|---|---|
| Fable 5.1 | $0.00039 | $0.03347 |
| Opus 5 | $0.00019 | $0.01673 |
| Sonnet 5 | $0.00008 | $0.00669 |
| Haiku 4.5 | $0.00004 | $0.00335 |
Grade A, and why
backstage-catalog scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 11d ago.
A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.
Nothing flagged
None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.
How it starts
The opening of the file, as written. The whole thing — 95 lines — stays where its author put it; the contents beside it link to each section on GitHub.
Backstage Software Catalog
Model entities correctly, ingest them from external systems without destroying data, and diagnose what the catalog actually believes.
Preconditions
- Release line from
backstage.json; catalog packages resolved viayarn why @backstage/plugin-catalog-backend. - Backend generation:
packages/backend/src/index.tsusingcreateBackend()+backend.add(import('@backstage/plugin-catalog-backend'))is the new backend system. ACatalogBuilderinpackages/backend/src/plugins/catalog.tsis the legacy backend — migrate it (backstage-plugin-migrate) before adding modules, or register through the builder and say so in your report. - Exact interface shapes (
EntityProvider,EntityProviderConnection,CatalogProcessor,DeferredEntity,processingResult) read from the installed@backstage/plugin-catalog-nodetypes, not from memory. - A running local backend or a reachable catalog base URL, plus a token if auth is enforced, before any debugging step.
Procedure
- Read the catalog's current belief before changing anything. Query the API rather than guessing:
GET /api/catalog/entities/by-query?filter=kind=component&fields=metadata.name,metadata.annotations— what exists and where it came from.POSTto the same path for$all/$any/$not/$exists/$inpredicates.GET /api/catalog/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true— the orphan set.GET /api/catalog/entity-facets?facet=kind— a kind census, fastest way to spot a whole integration that stopped ingesting.GET /api/catalog/locations— registered roots. Staticcatalog.locationsentries cannot be removed through this API.
- Model the entity before writing ingestion code. Envelope is
apiVersion+kind+metadata+spec.metadata.nameis 1–63 chars of alphanumerics separated by[-_.], unique per kind per namespace;metadata.namespacedefaults todefault. Kinds: Component, API, Resource, System, Domain, Group, User, Location, Template. Usemetadata.titlefor display strings that cannot be a valid name.metadata.uidis output-only — never reference entities by uid. - Write entity refs as
[<kind>:][<namespace>/]<name>, lowercased. Kind and namespace default from context (spec.ownerdefaults to Group-ish org kinds,providesApistoapi, namespace to the referring entity's). Produce refs withstringifyEntityReffrom@backstage/catalog-modeland parse withparseEntityRef; compare case-insensitively. Never hand-build refs with string concatenation across namespaces. - Express relations through spec fields, never by hand. Processors emit relations from the spec; stitching merges incoming and outgoing edges into the final entity.
relationsandstatuswritten into a descriptor are discarded.spec.owner→ownedBy/ownerOf. This is the whole of ownership resolution: one owner ref per entity, normally a Group.spec.system,spec.domain,spec.subcomponentOf→partOf/hasPart.spec.providesApis,spec.consumesApis→providesApi/apiProvidedBy,consumesApi/apiConsumedBy.spec.dependsOn→dependsOn/dependencyOf;spec.memberOf→memberOf/hasMember;spec.parent,spec.children→parentOf/childOf.
- Choose the ingestion mechanism deliberately.
- External system, scheduled or webhook-driven, fits in memory → EntityProvider.
- Enrichment, custom-kind validation, or a custom file format already inside the processing loop → CatalogProcessor.
- Paginated source too large to hold in memory (100k+ records) → incremental entity provider from
@backstage/plugin-catalog-backend-module-incremental-ingestion. Processors cannot delete entities; providers can, eagerly. That asymmetry decides most cases. Before writing anything, check whether a built-in or@backstage-community/plugin-catalog-backend-module-*provider already covers the source (backstage-repo-discovery).
- Scaffold rather than hand-roll:
yarn new --select catalog-provider-moduleoryarn new --select catalog-processor-module. Both generate aplugins/catalog-backend-module-<id>-*package with the class,readProviderConfigs, schedule wiring,config.d.ts, tests, and amodule.tsregistered frompackages/backend/src/index.ts. - Wire the module against the right extension point in
createBackendModule({ pluginId: 'catalog', moduleId: ... }):catalogProcessingExtensionPoint(@backstage/plugin-catalog-node) →addEntityProvider(...),addProcessor(...).catalogModelExtensionPoint(@backstage/plugin-catalog-node/alpha) →setEntityDataParser(...)for non-catalog-info.yamlformats,setFieldValidators(...)for envelope/metadata rules.incrementalIngestionProvidersExtensionPoint→addProvider({ provider, options }). Confirm the method names against the installed package's.d.tsbefore writing the call.
- Make the provider identity stable.
getProviderName()names the provider's private entity bucket in the database and must be unique and unchanged across restarts and deploys. Renaming it abandons the old bucket; with the defaultorphanProviderStrategythose entities are deleted. - Stamp every emitted entity with
ANNOTATION_LOCATION(backstage.io/managed-by-location) andANNOTATION_ORIGIN_LOCATION(backstage.io/managed-by-origin-location), both in<type>:<target>form (targets may contain colons — never split on the first one). Entities missing these are dropped at ingestion with only a warning log. - Pick the mutation type.
type: 'full'replaces the whole bucket — correct when you can batch-fetch the complete set, and only then.type: 'delta'withadded/removedis correct for webhook and event streams, where you never see the whole set. Do not emit afullmutation built from a partially successful fetch; let the task throw and retry on the next schedule instead. - Set
locationKeyon everyDeferredEntityto a string identifying the provider instance (e.g.frobs-provider:${id}), and keep it constant. On a duplicate entity ref the catalog resolves:- existing entity has no location key → the incoming entity wins and takes it over;
- existing key matches the incoming key → update;
- existing key differs → the incoming entity is discarded, silently.
This is the only defence against one provider taking over another's entities, so an entity emitted without a
locationKeyis permanently up for grabs.
- Handle upstream pagination and rate limits in the provider, not the processor. Schedule via
scheduler.createScheduledTaskRunnerwith afrequency/timeoutread fromcatalog.providers.<name>.schedule. For incremental providers, tuneburstLength,burstInterval,restLength,backoff, and setrejectEmptySourceCollections: trueplusrejectRemovalsAbovePercentageso a degraded upstream cannot delete the catalog. - In processors, do no network I/O. Every processor runs on every entity every cycle. If you must call out, use the
CatalogProcessorCachepassed intopreProcessEntity/postProcessEntitywith an ETag andIf-None-Match, and bump the cache key string whenever the cached shape or the processor logic changes. - Implement processor methods for their actual stage, all of which run on every entity on every cycle:
preProcessEntity— enrichment, before validation. Filter by kind first; skip when the field already has a value so acatalog-info.yamlcan override you.validateEntityKind—truefor a kind you own and validated,falsefor a kind you do not recognise (passing it to other processors), throw to mark the entity invalid. Build it fromentityKindSchemaValidator(schema)over a JSON schema exported from an isomorphic*-commonpackage so frontend and backend share it.postProcessEntity— emit relations and child entities viaprocessingResult.relation/.entity/.location, errors via.generalError/.inputError/.notFoundError.readLocation— only for genuinely new location types; prefer a provider.
- Register new kinds in config. If
catalog.ruleshas anallowlist, add the kind or nothing will be ingested. Usecatalog.processorOptions.<processorName>.priority(default20, lower runs earlier) when order matters — registration order is only guaranteed within a single module. - Run locally and prove the loop:
yarn start-backend, then trigger the provider's schedule and re-query the endpoints from step 1.
What this file has done since we first saw it
Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.
- 11d ago First seen · 95 lines · 39 tokens per session scan A aa6078684b7c
backstage-catalog is a skill published in the GitHub repository bendaamerahmed/backstage-idp-plugin (1 stars, last pushed 1mo ago), licensed MIT. It adds 39 tokens to every session and 3,347 once invoked, about $0.0002 per session on Opus 5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-31.
Other skills, from other repositories
ash-framework
Ash Framework — resources, actions, policies, aggregates, calculations, AshPhoenix.Form, LiveView, migrations. Use when generating resources via mix ash.codegen, editing changes, checks, types, validations, or domain code interfaces.
deploy
Elixir/Phoenix deployment patterns — Dockerfile, fly.toml, runtime.exs, mix release, rel/ overlays. Use when configuring Fly.io, Docker, CI/CD, health checks, or production migrations.
liveview-patterns
Build LiveView: async data (assignasync), PubSub (check connected?), phx-change events, form components/modals/uploads, streams for lists, livepatch. Use when handling interactions, debugging events, or tracking Presence.
tidewave-integration
Tidewave MCP runtime tools — debugging, smoke testing, live state inspection, SQL queries, hex docs. Use when evaluating code in a running Phoenix app.
oban
Oban job processing — workers, perform/1 (OSS) and process/1 (Pro), queues, cron, retries, unique jobs, idempotency, Oban Pro (Workflow, Batch, Chunk, Smart Engine), Testing. Use when writing Oban workers, queue config, or debugging jobs.
perf
Analyze Elixir/Phoenix performance — N+1 queries, assign bloat, ecto optimization, genserver bottlenecks. Use when slowness, timeouts, or high memory reported.