A lean, framework-agnostic Node.js SDK for running AgentPM tools from your app or agent runtime.
- 🔎 Discovers tools installed by
agentpm install
in.agentpm/tools
(project) and~/.agentpm/tools
(user), withAGENTPM_TOOL_DIR
override. - 🚀 Executes entrypoints in a subprocess (
node
/python
) and exchanges JSON over stdin/stdout. - đź§© Metadata-aware:
withMeta
returnsfunc + meta
(name, version, description, inputs, outputs). - đź§Ş Adapters: tiny helpers (e.g. LangChain) without forcing extra deps.
Requires Node 20+. Ships ESM + CJS builds. Types included.
Using pnpm (recommended):
pnpm add @agentpm/sdk
With npm:
npm i @agentpm/sdk
With yarn:
yarn add @agentpm/sdk
If you'll use the optional LangChain adapter:
pnpm add @langchain/core
# npm i @langchain/core
# yarn add @langchain/core
import { load } from '@agentpm/sdk';
// spec format: "@scope/name@version"
const summarize = await load('@zack/summarize@0.1.0');
const result = await summarize({ text: 'Long document content...' });
console.log(result.summary);
import { load, toLangChainTool } from '@agentpm/sdk';
const loaded = await load('@zack/summarize@0.1.0', { withMeta: true });
const { func: summarize, meta } = loaded;
const richDescription =
`${meta.description ?? ''} ` +
`Inputs: ${JSON.stringify(meta.inputs)}. ` +
`Outputs: ${JSON.stringify(meta.outputs)}.`;
console.log(richDescription);
console.log((await summarize({ text: 'hello' })).summary);
// Optional: turn it into a LangChain tool (requires @langchain/core)
const lcTool = await toLangChainTool(loaded);
const { load } = require('@agentpm/sdk');
Resolution order:
AGENTPM_TOOL_DIR
(environment variable)./.agentpm/tools
(project-local)~/.agentpm/tools
(user-local)
Each tool lives in a directory like:
.agentpm/
tools/
@zack/summarize/
0.1.0/
agent.json
(tool files…)
agent.json
(minimal fields used by the SDK):
{
"name": "@zack/summarize",
"version": "0.1.0",
"description": "Summarize long text.",
"inputs": {
"type": "object",
"properties": { "text": { "type": "string", "description": "Text to summarize" } },
"required": ["text"]
},
"outputs": {
"type": "object",
"properties": { "summary": { "type": "string", "description": "Summarized text" } },
"required": ["summary"]
},
"entrypoint": {
"command": "node",
"args": ["dist/cli.js"],
"cwd": ".",
"timeout_ms": 60000,
"env": {}
}
}
Execution contract:
- SDK writes inputs JSON to the process stdin.
- Tool writes a single outputs JSON object to stdout.
- Non-JSON logs should go to stderr.
- Process must exit with code 0 on success.
Interpreter whitelist: node
, nodejs
, python
, python3
.
The SDK validates the interpreter and checks it’s present on PATH
.
- ESM import:
import { load } from "@agentpm/sdk"
→ loadsdist/index.js
(ESM). - CJS require:
const { load } = require("@agentpm/sdk")
→ loadsdist/index.cjs
(CJS). - Types:
dist/index.d.ts
.
If you see
ERR_MODULE_NOT_FOUND
forindex.mjs
, ensure your package.json maps the exports todist/index.js
(ESM) anddist/index.cjs
(CJS).
pnpm i
# build / test / lint / format
pnpm build
pnpm test
pnpm lint
pnpm format
# dev watch
pnpm dev
pnpm dlx husky@latest init
printf 'pnpm lint-staged
' > .husky/pre-commit
chmod +x .husky/pre-commit
package.json
essentials:
Create eslint.config.mjs
:
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import prettier from 'eslint-config-prettier';
export default tseslint.config(
{ ignores: ['dist/**'] },
js.configs.recommended,
...tseslint.configs.recommended,
prettier,
);
Add
@types/node
in dev deps for TypeScript Node built-ins:pnpm add -D @types/node
This SDK provides toLangChainTool(loaded, ...)
which returns a dynamic tool. We keep @langchain/core
as an optional peer. Install it only if you need the adapter:
pnpm add @langchain/core
In code:
import { load, toLangChainTool } from '@agentpm/sdk';
const loaded = await load('@zack/summarize@0.1.0', { withMeta: true });
const tool = await toLangChainTool(loaded);
# ensure your exports map and files list are correct
pnpm install
pnpm test && pnpm build
npm pack # confirm the tarball contents
npm publish --dry-run # double-check what would be published
# publish (scoped packages default to private without access flag)
pnpm publish --access public
Optional in package.json
:
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
}
Some AgentPM tools run on Node, some on Python—and your agent may need to spawn both. Using Docker gives you a single, reproducible environment where both interpreters are installed and on PATH, which avoids the common “interpreter not found” issues that pop up on PaaS/CI or IDEs.
Why Docker?
âś… Hermetic: Python + Node versions are pinned inside the image.
âś… No PATH drama: node/python are present and discoverable.
âś… Prod/CI parity: the same image runs on your laptop, CI, and servers.
âś… Easy secrets: pass API keys via env at docker run/Compose time.
âś… Fewer surprises: consistent OS libs for LLM clients, SSL, etc.
- You deploy to platforms that don’t let you apt-get both runtimes.
- Your agent uses tools with different interpreters (Node + Python).
- Your local dev/IDE PATH differs from production and causes failures.
- You want reproducible builds and easy rollback.
- Copy the provided Dockerfile into your repo.
- (Optional) Pre-install tools locally with agentpm install ... and commit or copy .agentpm/tools/ into the image, or run agentpm install at build time if your CLI is available in the image.
- Build & run:
docker build -t agent-app .
docker run --rm -e OPENAI_API_KEY=$OPENAI_API_KEY agent-app
- For development, use the docker-compose.yml snippet to mount your source and pass env vars conveniently.
- Set
AGENTPM_DEBUG=1
to print the SDK’s project root, search paths, merged PATH, and resolved interpreters. - You can force interpreters via:
AGENTPM_NODE=/usr/bin/node
AGENTPM_PYTHON=/usr/local/bin/python3.11
- Prefer absolute interpreters in agent.json.entrypoint.command for production (e.g., /usr/bin/node). The SDKs still enforce the Node/Python family.
-
ERR_MODULE_NOT_FOUND for ESM Your
package.json
likely points todist/index.mjs
but your build emitsdist/index.js
. Fix theexports
map or configure tsup to output.mjs
. -
Husky "add is DEPRECATED" In v9+, use
husky init
and write hook files directly (see above). -
ESLint can't find config ESLint v9 uses flat config. Create
eslint.config.mjs
as shown. -
Types for
@langchain/core
Keep@langchain/core
as a peer (optional). For build-time types in this repo, install it as a devDependency and mark itexternal
intsup.config.ts
.
MIT — see LICENSE
.