@spigell/my-nodejs-libs
v0.2.0
Published
Shared Node.js TypeScript utilities for workers, HTTP, logging, metrics, and messaging.
Readme
@spigell/my-nodejs-libs
Shared Node.js and TypeScript helpers for small services and workers.
This package currently groups together:
- app worker primitives for periodic, queue-based, and WebSocket-driven jobs
- HTTP server and client helpers
- Prometheus and OpenTelemetry metric helpers
- Winston-based logging and request middleware
- utility helpers such as retry, chunking, and coin amount conversion
- a Telegram sender wrapper
Installation
From npm
Install the package:
yarn add @spigell/my-nodejs-libsLocal development
Build the package locally:
yarn install
yarn buildLink it into another repository in one of these ways:
yarn link
In this repository:
yarn linkIn the consumer repository:
yarn link "@spigell/my-nodejs-libs"file:dependency
{
"dependencies": {
"@spigell/my-nodejs-libs": "file:../my-nodejs-libs"
}
}- Monorepo workspace dependency
{
"dependencies": {
"@spigell/my-nodejs-libs": "workspace:*"
}
}Exported modules
The package root exports everything from src/index.ts, including:
- app:
Worker,PeriodicWorker,QueueWorker,WebSocketWorker,CircularBuffer - HTTP:
Server,JsonAxiosInstance - logging:
Logging,createMiddleware - metrics:
MetricRegistry,CounterMetric,GaugeMetric,HistogramMetric,PromClient, and typed metrics errors - messaging:
TelegramSender - utils:
RetryError,simple,chunk,Coin
Consumers should import from the package root:
import {
Logging,
MetricRegistry,
PromClient,
Server,
} from '@spigell/my-nodejs-libs';Do not import from src/ in consumers. Published output comes from dist/.
Prometheus metrics
MetricRegistry owns an isolated OpenTelemetry meter provider and a
Prometheus exporter. Construction does not start a server or register a route.
import { MetricRegistry } from '@spigell/my-nodejs-libs';
const metrics = new MetricRegistry({
subsystem: 'vlad',
meterName: 'vlad-control-api',
defaultLabels: {
installation: 'uspio-workbench',
component: 'control-api',
},
defaultHistogramBoundaries: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
seriesLimit: 100,
});
const requests = metrics.counter({
name: 'api_requests_total',
help: 'Completed control API requests',
labelNames: ['method', 'route', 'status_class'],
});
const dutyActive = metrics.gauge({
name: 'duty_active',
help: 'Whether an unexpired duty period is active',
});
const duration = metrics.histogram({
name: 'api_request_duration_seconds',
help: 'Control API request duration',
unit: 's',
labelNames: ['method', 'route', 'status_class'],
});
const labels = {
method: 'GET',
route: '/v1/status',
status_class: '2xx',
};
requests.add(1, labels);
dutyActive.set(1);
duration.record(0.042, labels);
const snapshot = await metrics.collect();
// Fastify: reply.type(snapshot.contentType).send(snapshot.body)
await metrics.shutdown();Metric and label names use Prometheus naming rules. Every observation must
provide exactly the declared labels, and label values are strings. Default
labels cannot be overridden by observations. Counter names always emit one
_total suffix: the library adds it when omitted and preserves it when given.
The default active-series limit is 100 per metric. New series over the limit
are dropped and counted in
prom_client_observations_rejected_total{reason="series_limit"}. Set
seriesLimitBehavior: 'throw' on the registry or an instrument to receive a
MetricSeriesLimitError instead. Gauge remove(), clear(), and atomic
replace() retire stale series and release their cardinality slots.
shutdown() is asynchronous and idempotent. Observations and collections after
shutdown throw MetricsShutdownError. The old PromClient methods and the
MetricRegistry(subsystem, promClient) constructor remain available as
deprecated compatibility APIs for the 0.2.x release line.
Isolated Claude execution
Use createClaudeIsolation to give each Claude role its own prompt, settings,
skills, and MCP configuration while sharing only the Claude Code OAuth
credentials required for authentication.
For Claude, promptPath is copied into the isolated config directory and
passed to the CLI with --append-system-prompt-file; it is not installed as
CLAUDE.md memory context.
import {
claudeAdapter,
CliRunner,
createClaudeIsolation,
getClaudeUsage,
} from '@spigell/my-nodejs-libs';
const isolation = await createClaudeIsolation({
toolName: 'investigator',
promptPath: '/app/prompts/investigator.md',
settings: {
model: 'claude-opus-4-8',
},
mcpConfig: {
mcpServers: {
'github-mcp': {
type: 'http',
url: 'http://github-mcp:8080/mcp',
},
},
},
skillSources: [
{
rootDir: '/app/skills',
dirNames: ['repo-reader'],
},
],
agentSource: {
rootDir:
'/spigell-reforge-ai/my-shared-infra/my-agents/agents/claude/agents',
names: ['researcher'],
},
});
try {
const runner = new CliRunner({
command: 'claude',
adapter: claudeAdapter,
cwd: '/workspace',
env: isolation.env,
});
const firstRun = await runner.run('Investigate the failing workflow.', {
mcpConfigPath: isolation.mcpConfigPath,
strictMcpConfig: false,
permissionMode: 'dontAsk',
tools: ['Read', 'Glob', 'Grep', 'mcp__github-mcp__search_code'],
allowedTools: ['Read', 'Glob', 'Grep', 'mcp__github-mcp__search_code'],
});
console.log(firstRun.text, firstRun.tokenUsage, firstRun.permissionDenials);
const resumedRun = await runner.run('Check the proposed fix.', {
sessionId: firstRun.sessionId,
mcpConfigPath: isolation.mcpConfigPath,
permissionMode: 'dontAsk',
tools: ['Read', 'Glob', 'Grep', 'mcp__github-mcp__search_code'],
allowedTools: ['Read', 'Glob', 'Grep', 'mcp__github-mcp__search_code'],
});
console.log(resumedRun.text, resumedRun.tokenUsage);
const quotaUsage = await getClaudeUsage({
credentialsPath: isolation.credentialsPath,
});
console.log(quotaUsage.five_hour, quotaUsage.seven_day);
} finally {
await isolation.cleanup();
}getClaudeUsage() reads the Claude Code OAuth credential document. When
claudeAiOauth.expiresAt is near expiry, or when the usage endpoint returns
HTTP 401, it refreshes with claudeAiOauth.refreshToken, atomically persists
rotated tokens to the real shared credential file behind the isolation
symlink, and retries usage once. Concurrent refreshes for the same credential
file are coalesced within the process. Passing an explicit accessToken
disables credential-file refresh and persistence.
Isolation is ephemeral by default. cleanup() recursively removes its unique
config directory and can be called more than once. Set persistent: true when
the same tool must resume Claude sessions across separate isolation lifetimes;
in persistent mode the path is stable and cleanup() intentionally preserves
its state.
For untrusted classifier input, do not configure MCP servers, skills, or
subagents, and run with tools: [] plus permissionMode: 'dontAsk'. Supplying
an empty tool list emits --tools "", which disables Claude's built-in tools.
Investigators should receive an explicit allowlist of read-only built-in and
MCP tool names. Permission bypass is available only through the explicit
dangerouslySkipPermissions: true option and must not be used for untrusted
content. Claude MCP configuration is strict by default; set
strictMcpConfig: false only when configured subagents need access to MCP
servers outside the supplied configuration. Claude execution results expose
the terminal event's normalized permissionDenials, including the denied tool
name, tool-use ID, and structured input.
Development commands
yarn install
yarn typecheck
yarn lint
yarn build
yarn testRelease flow
This repository is configured to publish to the public npm registry via the shared workflow in spigell/my-shared-workflows.
Release steps:
- Push your changes to the default branch.
- Create and push a tag.
- GitHub Actions publishes the package to
registry.npmjs.org.
The release workflow is defined in .github/workflows/tags-package-release.yaml.
Notes and caveats
- This library is a shared internal toolkit, not a polished public SDK.
- Some worker abstractions assume long-running Node.js processes and do not yet expose lifecycle shutdown hooks.
src/fuel/wallet/wallet.tsis currently a compatibility stub because the referenced wallet implementation is not present in this repository.
