stillrunning
v0.1.0
Published
One-line monitoring for any async job, agent, or script. Wrap a function and StillRunning gets a ping on success/failure with duration, and optional cost, tokens, and model.
Maintainers
Readme
stillrunning
Monitoring for any async job, agent, or script, in one line.
Wrap a function and StillRunning gets a ping on success or failure with the run's duration, plus optional cost, tokens, and model. Get alerted the moment a cron job stops, an agent fails, or a script runs too long or costs too much, with no ping plumbing.
For the Vercel AI SDK specifically, use stillrunning-vercel-ai-sdk
(it auto-extracts tokens and cost). This package is the framework-agnostic option for everything else.
npm install stillrunning30-second quickstart
Create a workflow at stillrunning.ai/app/new and copy its token.
STILLRUNNING_TOKEN=your_token_hereWrap your work:
import { stillrunning } from 'stillrunning' const sr = stillrunning() // reads STILLRUNNING_TOKEN await sr.run(() => runNightlyImport())
run times the function, pings success when it resolves, pings fail with the error message if
it throws (then rethrows untouched), and returns the function's result.
Attaching AI metrics
Pass meta, statically or derived from the result. If you give model plus token counts and no
costUsd, cost is estimated from a built-in pricing table (Claude / GPT / Gemini):
const answer = await sr.run(() => callMyAgent(prompt), {
meta: (result) => ({
tokensIn: result.usage.inputTokens,
tokensOut: result.usage.outputTokens,
model: result.model, // → costUsd estimated automatically
toolCalls: result.steps.length,
}),
})Wrapping a function
wrap returns a monitored version with the same signature, handy for a function you call in many places:
const monitoredSync = sr.wrap(syncCustomerData)
await monitoredSync('acme') // every call is monitoredHeartbeats and manual pings
For a cron job that just needs to say "I ran", or when you can't wrap the work in a single function:
await sr.heartbeat() // bare success ping
await sr.ping({ event: 'start', traceId }) // low-level escape hatch (success | fail | start | log)Grouping multi-step runs with withTrace
By default each run is its own logical run. Group several under one trace so StillRunning stitches
them into a single outcome chain:
import { stillrunning, withTrace } from 'stillrunning'
const sr = stillrunning()
await withTrace(async () => {
await sr.run(() => stepOne())
await sr.run(() => stepTwo())
}) // both pings share one traceIdConfiguration
stillrunning({
token, // ping token; defaults to process.env.STILLRUNNING_TOKEN
baseUrl, // defaults to https://stillrunning.ai
awaitPing, // default true; false = fire-and-forget (lowest latency)
pingTimeoutMs, // default 3000
onError, // (err) => void , observe ping delivery failures
fetch, // custom fetch (testing / non-global-fetch runtimes)
})Monitoring never throws into your code: a ping that fails to send routes to onError and is
otherwise swallowed. The ping is bounded by pingTimeoutMs, so a slow or down StillRunning never
hangs your job.
Requirements
Node 18+ (or any runtime with fetch and AsyncLocalStorage).
License
MIT
