Getting the packages
The SDKs are private packages in a GitHub organisation. Access, tokens, .npmrc and Docker builds — the whole path from no account to a working npm install.
npm install @pragyacyber/harness-sdk does not work out of the box, and it will not tell
you why in a useful way. This page is the part that is usually missing.
Two separate things have to be true before an install succeeds:
- Your identity has been granted read access to the package. This is a manual decision by someone in the PragyaCyber organisation. There is no self-service path.
- You are presenting a token that proves that identity, to the right registry.
Neither implies the other, and the error you get when either is missing is the same one.
Where the packages live
| Registry | https://npm.pkg.github.com (GitHub Packages) |
| Scope | @pragyacyber |
| Organisation | PragyaCyber on github.com |
| Visibility | private |
| Packages | @pragyacyber/harness-sdk, @pragyacyber/engine-contract |
They are not on npmjs.com. An npm install that reaches the public registry and returns
404 Not Found has not failed authentication — it never got as far as GitHub.
Step 1 — get access
Access is granted per consumer, and a person and a repository are different consumers:
For you, as a developer. Your GitHub account needs read access to the package —
either as a member of the PragyaCyber organisation with package read, or by being
added to the package directly. Ask for it and give your GitHub username.
For a repository's CI. This is the one that surprises people. A GitHub Actions workflow in your repository cannot read a package owned by another repository unless that package has explicitly granted your repository access. The grant is made on the package's own page:
github.com/orgs/PragyaCyber/packages
→ the package
→ Package settings
→ Manage Actions access
→ Add repository
→ select your repository, set the role to ReadIt is a manual UI action, once per consuming repository. There is no REST API for
it — you cannot script it, and a platform engineer cannot batch it. Give the full
owner/repo of every repository that will install the package.
What to send when you ask. Bundle it into one message so it takes one round trip:
GitHub username: <your-username>
Repositories that will install:
<owner>/<repo> (CI: needs Manage Actions access → Read)
<owner>/<other-repo>
Packages needed: @pragyacyber/harness-sdk, @pragyacyber/engine-contract
Reason: building a <harness | MCP> for <what>Send it to whoever handed you this documentation — the VERIFI platform team at Pragya Cyber. This site deliberately does not publish a contact address, because the right recipient depends on who is sponsoring your work.
Step 2 — get a token
The registry authenticates with a GitHub token carrying the read:packages scope.
You never need write:packages to consume.
If you have the gh CLI — the easy path
gh already holds a token for your account. Add the scope to it:
gh auth refresh -s read:packagesThat opens a browser once, then re-issues the CLI's own token with the extra scope. From then on:
export NODE_AUTH_TOKEN=$(gh auth token)This is the recommended path, and not only for convenience: the value goes straight from
gh into a process environment variable. It is never typed, never pasted, and never
written to a file where it can be committed.
Without gh — a personal access token
Create one at github.com/settings/tokens with the single scope read:packages, then
put it in your environment the same way:
export NODE_AUTH_TOKEN=<paste-it-here-once-then-clear-your-shell-history>Step 3 — point npm at the registry
Two lines in .npmrc, in your project root:
@pragyacyber:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}npm expands ${NODE_AUTH_TOKEN} from the environment when it reads the file.
Then install:
npm init -y
npm pkg set type=module
npm i @pragyacyber/harness-sdk@^1.0.0 @pragyacyber/engine-contract@^1.5.0
npm i -D typescript tsx @types/nodeProve the auth works before you write code
npm view @pragyacyber/harness-sdk versionA version number means identity, token and registry routing are all correct, and any later failure is your code. This takes two seconds and saves an afternoon of debugging a build that was never going to resolve.
| What you see | What it means |
|---|---|
| a version number | working — go build something |
401 Unauthorized | the token is missing, unscoped, expired, or fine-grained |
404 Not Found | almost always step 1 — your identity has no read on the package |
| resolves to npmjs.com | the @pragyacyber:registry line is missing or misspelled |
Step 4 — CI
In GitHub Actions, setup-node reads NODE_AUTH_TOKEN for you:
- uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://npm.pkg.github.com
scope: '@pragyacyber'
- run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}The built-in secrets.GITHUB_TOKEN works only if your repository was granted read on
the package in step 1. Without that grant it authenticates fine and then 404s, which
reads like a missing package and is not. If you cannot get the cross-repo grant, fall
back to a PAT stored as a repository secret and reference that instead.
Step 5 — Docker builds
A container build needs the token to run npm install, and how you pass it in decides
whether you have shipped a credential to everyone who ever pulls the image.
Use a BuildKit secret. It is mounted into a single RUN and never enters a layer:
FROM node:22-slim AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
# The registry token arrives as a BuildKit SECRET, never an ARG — an ARG is readable
# in the image history, which would bake a credential into every pulled layer.
RUN --mount=type=secret,id=npm_token \
corepack enable && \
{ echo "@pragyacyber:registry=https://npm.pkg.github.com"; \
echo "//npm.pkg.github.com/:_authToken=$(cat /run/secrets/npm_token)"; } > .npmrc && \
pnpm install --prod --frozen-lockfile && \
rm -f .npmrcBuild it by handing the secret in from the environment:
export NPM_TOKEN=$(gh auth token)
DOCKER_BUILDKIT=1 docker build \
--secret id=npm_token,env=NPM_TOKEN \
-t my-harness:dev .Three details in that RUN are load-bearing:
- The
.npmrcis generated inside theRUN, with the value inline. That is the one place a literal token is acceptable, because this stage is a build stage that is discarded — nothing from it reaches the runtime image. pnpm did not expand the${NODE_AUTH_TOKEN}form reliably, which is why the value is substituted rather than referenced here. rm -f .npmrcruns in the sameRUN. A separate layer would keep the file.- The runtime stage copies the checked-in
.npmrc— the one with the variable reference — not this one.
In GitHub Actions:
- uses: docker/build-push-action@v6
with:
context: .
secrets: |
npm_token=${{ secrets.GITHUB_TOKEN }}Version ranges
npm i @pragyacyber/harness-sdk@^1.0.0 @pragyacyber/engine-contract@^1.5.0Both packages are at 1.x, and a caret range is the right thing for both.
engine-contract is safe at a caret because the major is additive-only and
isCompatible() compares MAJOR only: any 1.x engine understands any 1.x harness. A
caret on the contract also means you pick up things like the 1.5.1 Bedrock
inference-profile fix without a code change.