PortalJS is a framework for building data portals that present searchable datasets through a home page, catalog, and dataset showcase. It is for teams that want to choose a data backend, scaffold a Next.js portal, load CSV or JSON data, connect services such as CKAN, and deploy the result. The catalogue entries are agent commands, skills, instructions, and a plugin that guide portal creation and data loading.
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 datopian/portaljs --skill portaljs-connect-ckangit clone --depth 1 https://github.com/datopian/portaljsWrote 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/datopian/portaljs/portaljs-connect-ckan)<a href="https://agentmods.dev/skills/datopian/portaljs/portaljs-connect-ckan"><img src="https://agentmods.dev/badge/skills/datopian/portaljs/portaljs-connect-ckan/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/datopian/portaljs/portaljs-connect-ckan"><img src="https://agentmods.dev/badge/skills/datopian/portaljs/portaljs-connect-ckan.svg" alt="Reviewed on agentmods" width="80" height="20"></a>- NVIDIA SkillSpector warn
SkillSpector: 2 findings, up to medium
These are SkillSpector’s own severities. On a checked sample its high-severity flags on skills were ~96% false positives — a documented command, a public API, a “never do X” rule — so we show them as a caution to read, not a verdict. Why →
- medium MCP Rug Pull · line 61 npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.Fix: Pin the version: npx @scope/[email protected]
- medium MCP Rug Pull · line 70 npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.Fix: Pin the version: npx @scope/[email protected]
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.00074 | $0.01522 |
| Opus 5 | $0.00037 | $0.00761 |
| Sonnet 5 | $0.00015 | $0.00304 |
| Haiku 4.5 | $0.00007 | $0.00152 |
Grade A, and why
portaljs-connect-ckan 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 — 111 lines — stays where its author put it; the contents beside it link to each section on GitHub.
PortalJS — Connect CKAN
Overview
Connect an existing portaljs-catalog portal to a live CKAN backend for the "decoupled /
any backend" path. The portal stops reading the static datasets.json manifest (and files
in /public/data/) and instead feeds its two data surfaces — the /search catalog and
the /@namespace/slug showcases — straight from a CKAN instance's REST API
(package_search / package_show) through a generated fetch client. Output is plain,
editable Next.js code with no runtime dependency — never @portaljs/ckan, whose bundle
wires React UI components to React 18 internals and crashes at import under the template's
React 19. Pages fetch CKAN server-side in getStaticProps/getStaticPaths, so the catalog
is pre-rendered at build time and the site can still be statically deployed. Run this right
after portaljs-new-portal to swap a freshly scaffolded portal's sample data over to CKAN.
Prerequisites
- A scaffolded PortalJS portal (see
portaljs-new-portal) withpackage.json,pages/,datasets.json,pages/search.tsx, andpages/[owner]/[slug].tsxpresent. - A CKAN base URL that is publicly reachable, e.g.
https://demo.dev.datopian.com. - Node 18+ and npm available in the portal directory (no new packages are installed).
Instructions
The canonical, full step-by-step workflow is
.claude/commands/portaljs-connect-ckan.md —
the single source of truth. Read and follow it when executing. Summary:
- Gather input from
$ARGUMENTS— CKAN base URL (required), org filter (optional), group filter (optional), portal directory (default.). If the URL is missing, interview the user; never dead-end with a missing-input error. - Validate the target directory is a
portaljs-catalogportal; if not, suggestportaljs-new-portalinstead of failing silently. - Verify the CKAN backend is reachable via
package_search?rows=1, and validate each org filter viaorganization_show; on failure, explain and re-prompt rather than dead-ending. - Generate
lib/ckan.ts— a self-contained server-side fetch client wrappingpackage_searchandpackage_show, withDMS,ORG_FILTER,GROUP_FILTER, andMAX_DATASETSas editable constants. - Rewire
pages/search.tsxto list datasets frompackage_search, linking each to/@namespace/slugviadatasetHref; leavepages/index.tsxuntouched. - Overwrite
pages/[owner]/[slug].tsxto pre-render one page per dataset viagetStaticPathsand fetch details withpackage_show, previewing tabular resources through the existingTablecomponent. - Verify the build with
npx next build; fix any error before reporting success. - Report what changed: client, catalog, showcase, filters, and static page count.
What ships with it
1 file beside SKILL.md in the same directory: the scripts, references and assets a skill reads on demand. Not counted in the per-session cost; read them before you install if any of them is executable.
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 · 111 lines · 74 tokens per session scan A af7dc40dfcec
portaljs-connect-ckan is a skill published in the GitHub repository datopian/portaljs (2,350 stars, last pushed 3d ago), licensed MIT. It adds 74 tokens to every session and 1,522 once invoked, about $0.0004 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-30.
Other skills, from other repositories
nextjs-app-router
Full end-to-end tRPC setup for Next.js App Router. Covers route handler with fetchRequestHandler (GET + POST exports), TRPCProvider with QueryClientProvider, createTRPCOptionsProxy for RSC prefetching, HydrateClient/HydrationBoundary for hydration, useSuspenseQuery for Suspense, and server-side callers.
nextjs-pages-router
Set up tRPC in Next.js Pages Router with createNextApiHandler, createTRPCNext, withTRPC HOC, SSR via ssr option and ssrPrepass, SSG via createServerSideHelpers with getStaticProps, and server-side helpers for getServerSideProps prefetching.
openapi
Generate OpenAPI 3.1 spec from a tRPC router with @trpc/openapi CLI or programmatic API. Generate typed REST client with @hey-api/openapi-ts and configureTRPCHeyApiClient(). Configure transformers (superjson, EJSON) for generated clients. Alpha status.
server-setup
Initialize tRPC with initTRPC.create(), define routers with t.router(), create procedures with .query()/.mutation()/.subscription(), configure context with createContext(), export AppRouter type, merge routers with t.mergeRouters(), lazy-load routers with lazy().
subscriptions
Set up real-time event streams with async generator subscriptions using .subscription(async function() { yield }). SSE via httpSubscriptionLink is recommended over WebSocket. Use tracked(id, data) from @trpc/server for reconnection recovery with lastEventId. WebSocket via wsLink and createWSClient from @trpc/client…
client-setup
Create a vanilla tRPC client with createTRPCClient (), configure link chain with httpBatchLink/httpLink, dynamic headers for auth, transformer on links (not client constructor). Infer types with inferRouterInputs and inferRouterOutputs. AbortController signal support. TRPCClientError typing.