npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@skopiklabs/sdk

v0.5.1

Published

Skopik SDK — sessions, messages, remotes, and the agent control plane.

Readme

@skopiklabs/sdk

One client for Skopik Sessions, Messages, Remotes, and control-plane resources. The root entry is browser-safe and hides transport management.

import { Skopik } from '@skopiklabs/sdk'

const skopik = new Skopik({ apiKey: process.env.SKOPIK_API_KEY })
const session = skopik.session({ agent: 'support', key: 'chat:42' })
const answer = await session.send('Summarize the latest customer thread')
console.log(answer.parts)

Turn is awaitable and async iterable, so the same call supports final-answer and live-snapshot styles:

for await (const message of session.send('Now draft a reply')) {
	render(message)
}

Transport

The SDK uses one multiplexed WebSocket for watching sessions, sending, approvals, lifecycle operations, notifications, and agent status. It reconnects from each subscription cursor.

Every subscription has an HTTP/SSE fallback carrying the same frames from the same cursor, so a blocked upgrade or a runtime without WebSockets degrades instead of failing:

| Call | WebSocket | Fallback | | --- | --- | --- | | session.send() | send op | POST /sessions[/{id}/messages] (SSE body) | | session.watch(), sessions.stream() | { session } | GET /sessions/{id}/stream | | notifications() | { org: 'notifications' } | GET /notifications/stream | | watchAgents() | { org: 'agents' } | GET /agents/stream | | approve(), cancel(), pause(), resume() | op | the REST equivalent |

Browsers and Node 22+ need no setup. On Node 20 and 21 the SDK loads the optional ws peer if it is installed; otherwise it stays on HTTP/SSE. To pin an implementation — a custom runtime, or a test double — pass it in:

import WebSocket from 'ws'

const skopik = new Skopik({ apiKey, webSocket: WebSocket })

Pass webSocket: null to force the HTTP/SSE transport.

In a browser, authenticate with getToken (a short-lived token) rather than apiKey: the socket carries its credential in a subprotocol, so an org API key would be shipped to the client.

The existing camelCase resource client remains available on the same object:

await skopik.agents.create({ /* … */ })
await skopik.templates.create({ /* … */ })
await skopik.sessions.open({ prompt: 'Prepare a concise release draft.' })

Resources

| Accessor | What it manages | | --- | --- | | skopik.agents | Agents | | skopik.templates | Reusable agent templates | | skopik.skills | The reusable skill catalog | | skopik.sessions | Agent work: open, continue, send, stream, inspect, and cancel | | skopik.automations | Scheduled / event-triggered sessions | | skopik.runs | Runtime executor wire for serving sessions on Remotes | | skopik.files | Files owned by Agents, Templates, Skills, and Sessions | | skopik.search | Semantic and reference search | | skopik.remotes | Remote compute mounts | | skopik.apiKeys | API key management |

skopik.me() returns the authenticated caller (whoami). Workspace and identity surfaces are first-party concerns. Each first-party app owns the small local wrappers it needs; Console layers its user, org, and onboarding resources onto this client.

Agents

const agent = await skopik.agents.upsert({
	name: 'Release Helper',
	description: 'Prepares release notes and follows deployment checklists.',
	status: 'active',
	defaultModelId: 'gpt-5',
	canWrite: true,
	dailyBudgetUsd: 20,
})

await skopik.files.writeText({
	target: agent.agentId,
	path: 'AGENTS.md',
}, {
	content: '# Release Helper\n\nYou prepare release notes and checklist updates.',
	contentType: 'text/markdown',
})

const stats = await skopik.agents.stats(agent.agentId, { window: '30d' })
console.log(stats.uptimeRatio, stats.usage.totalCostUsd)

for await (const status of skopik.watchAgents()) {
	renderAgentStatus(status)
}

watchAgents() uses one org-wide state+tail WebSocket subscription (or the /agents/stream SSE twin) for the directory snapshot and live running / idle / blocked / offline changes. agents.stats() is the durable read-side rollup over Sessions, runtime leases, Problems, and usage events.

Templates and skills

Templates are reusable agent blueprints; skills are reusable capability packages. Both catalogs live at the SDK root. Use upsert when the immutable name should update an existing item or create it on first use.

const supportSkill = await skopik.skills.upsert({
	name: 'support-policy',
	displayName: 'Support Policy',
	description: 'Applies the support escalation policy.',
	tags: [ 'support' ],
})

// Author an agent template and add a file.
const template = await skopik.templates.upsert({
	name: 'support-triage',
	displayName: 'Support Triage',
	description: 'Triages inbound support email and drafts replies.',
	skills: [ supportSkill.skillId ],
	instructions: 'Escalate security and billing issues instead of guessing.',
	tags: [ 'support' ],
})
await skopik.files.writeText({
	target: template.templateId,
	path: 'AGENTS.md',
}, {
	content: '# Support Triage\n\nYou triage inbound support requests.',
	contentType: 'text/markdown',
})

// Spin up a new agent from a template.
const { agent } = await skopik.templates.use(template.slug, {
	name: 'Support Bot',
})

// Browse and install skills.
const { data: skills } = await skopik.skills.list({ tag: 'email' })
await skopik.agents.skills.install(agent.agentId, { skillId: skills[0]!.skillId })

Sessions

const result = await skopik.run({
	agent: agent.agentId,
	prompt: 'Summarize the shipped API and SDK changes into launch notes.',
	waitTimeoutMs: 5 * 60_000,
})
console.log(result.text)
console.log(result.usage.totalTokens, result.usage.costUsd)
console.log(result.timing.durationMs)

Client tools

A client tool is a function tool your application executes itself: the model sees the definition, a call parks the session at waiting { kind: "tool_outputs" }, and the session resumes when the result is submitted. Declare tools with createClientTool — a zod schema or raw JSON Schema — and give them an execute to have the SDK serve them automatically inside skopik.run() and while session.watch() is consuming:

import { createClientTool, createSkopik } from '@skopiklabs/sdk'
import { z } from 'zod'

const lookupOrder = createClientTool({
	name: 'lookup_order',
	description: 'Look up an order in the local commerce database.',
	inputSchema: z.object({ order_id: z.string() }),
	execute: async ({ order_id }) => db.orders.find(order_id),
})

const result = await skopik.run({
	agent: 'support-bot',
	prompt: 'Where is order A1?',
	clientTools: [ lookupOrder ],
})

Tools without an execute surface for the caller to resolve — through session.on('tool_call') handles, the pendingToolCalls head field, or the raw endpoints:

const session = skopik.session({ agent: 'support-bot', clientTools: [ confirmRefund ] })
session.on('tool_call', async (call) => {
	await call.submit(await confirmInUi(call.input))   // or call.error('declined')
})

// Raw resource surface: singles, batches, and error results.
await skopik.sessions.submitToolOutput(sessionId, { tool, toolCallId, output })
await skopik.sessions.submitToolOutputs(sessionId, [
	{ tool: 'lookup_order', toolCallId: 'call_a', output: { status: 'shipped' } },
	{ tool: 'check_stock', toolCallId: 'call_b', error: { code: 'timeout', message: 'inventory API timed out' } },
])

Parallel calls resolve in any order; the parked execution wakes when the awaiting step has no unresolved call left (delivery stays "queued" until then). Sending a plain message to a waiting session supersedes its still-open client-tool calls as tool errors.

For detached execution, open a session directly and attach to its resumable stream:

const session = await skopik.sessions.open({
	template: 'templates/software-engineer',
	skills: [ 'skill_123' ],
	prompt: 'Prepare a concise release draft.',
})
for await (const frame of skopik.sessions.stream(session.sessionId)) {
	console.log(frame.envelope)
}

For recurring work, use skopik.automations (schedule- or event-triggered):

const automation = await skopik.automations.create({
	name: 'Morning brief',
	trigger: {
		type: 'schedule',
		schedule: { kind: 'cron', expression: '0 9 * * ? *', timezone: 'America/New_York' },
	},
	session: {
		agent: 'agent_1',
		prompt: 'Summarize overnight activity.',
		policy: { completeWhen: 'agent_done' },
	},
})
await skopik.automations.run(automation.automationId)

React, AI SDK, and Remotes

The optional entry points keep framework and Node-only code out of the browser-safe root:

import { useSession } from '@skopiklabs/sdk/react'
import { SkopikChatTransport, toUIMessageStream } from '@skopiklabs/sdk/aisdk'
import { createRemote, createRemoteTool } from '@skopiklabs/sdk/remote'

SkopikChatTransport is a useChat transport whose AI SDK chunks are an explicit API-edge projection of the native Messages Protocol:

import { useChat } from '@ai-sdk/react'
import { SkopikChatTransport } from '@skopiklabs/sdk/aisdk'

const chat = useChat({
	transport: new SkopikChatTransport({ client: skopik, sessionId }),
})

The transport also carries the client-tool loop: with sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls, results recorded through addToolResult are submitted to tool_outputs and the woken turn streams back into the chat.

Use toUIMessageStream when an existing AI SDK consumer needs a projected ReadableStream; new Skopik-native UI can use useSession directly — it exposes pendingToolCalls and submitToolOutput alongside the message stream, and tools declared with an execute serve themselves while the hook watches.

Files

const body = new TextEncoder().encode('# Release checklist\n')
const upload = await skopik.files.createUploadUrl({
	target: agent.agentId,
	path: 'references/release-checklist.md',
	contentType: 'text/markdown',
	contentLength: body.byteLength,
})

await fetch(upload.uploadUrl, {
	method: 'PUT',
	headers: upload.uploadHeaders,
	body,
})

const file = await skopik.files.stat({
	target: agent.agentId,
	path: 'references/release-checklist.md',
})
console.log(file.etag, file.contentLength)

const changed = await skopik.files.readTextIfChanged(
	{ target: file.target, path: file.path },
	file.etag!,
)
if (changed === null) console.log('Already current')

Pagination

Current v1 list routes return one page in { data }. The exported paginate helper also follows page.nextCursor when a route exposes it.

import { paginate } from '@skopiklabs/sdk'

for await (const agent of paginate((params) => skopik.agents.list(params), { limit: 50 })) {
	console.log(agent.agentId, agent.name)
}