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

@availsync/node

v0.1.9

Published

Node SDK for Availsync coding-agent work guardrails.

Readme

@availsync/node

Node SDK for Availsync coding-agent work guardrails.

Use it before Codex, Claude, Cursor, OpenClaw, cron jobs, deploy scripts, or other automations touch a protected repo or project.

Install

npm install @availsync/node

The package is public and stable enough for production pilots. Pin an exact version in critical automations when you need fully repeatable installs.

Hello World

import { createAvailsyncClient } from '@availsync/node';

const availsync = createAvailsyncClient({
  apiKey: process.env.AVAILSYNC_API_KEY!,
  agentId: process.env.AVAILSYNC_AGENT_ID!,
});

const result = await availsync.work.withClaim('repo:owner/repo', { durationMinutes: 30 }, async () => {
  console.log('safe to run guarded work');
});
console.log(result.action);

Environment

AVAILSYNC_API_KEY=avs_live_...
AVAILSYNC_AGENT_ID=agent_uuid
AVAILSYNC_API_URL=https://availsync.dev

Guard a coding-agent run

import { createAvailsyncClient } from '@availsync/node';

const availsync = createAvailsyncClient({
  apiKey: process.env.AVAILSYNC_API_KEY!,
  agentId: process.env.AVAILSYNC_AGENT_ID!,
  baseUrl: process.env.AVAILSYNC_API_URL,
  timeoutMs: 30_000,
});

const result = await availsync.work.withClaim(
  'repo:owner/repo',
  {
    reason: 'scheduled Codex run',
    durationMinutes: 45,
    autoExtendIntervalMs: 40 * 60_000,
    autoExtendDurationMinutes: 45,
    claimHeartbeatIntervalMs: 60_000,
    claimHeartbeatTimeoutSeconds: 300,
    onHeartbeatError(error) {
      console.error('Claim heartbeat failed; stop this run safely:', error.message);
    },
    onAutoExtendError(error) {
      console.error('Lease renewal failed; stop this run safely:', error.message);
    },
    autoExtendWorkingSet: {
      resource: 'repo:owner/repo',
      mode: 'edit',
      paths: ['frontend/components/Hero.tsx', 'frontend/app/page.tsx'],
      leaseSeconds: 600,
    },
  },
  async ({ claim, shadow }) => {
    if (shadow.wouldHaveBlocked) {
      console.warn('Observe-only: Availsync would have skipped this run.');
    }

    await claim?.updateWorkingSet({
      resource: 'repo:owner/repo',
      mode: 'edit',
      paths: ['frontend/components/Hero.tsx', 'frontend/app/page.tsx'],
      leaseSeconds: 600,
    });
    await doWork();
  },
);

if (result.action === 'skip_run') {
  console.log(result.reason);
} else if (result.finishError) {
  console.warn('Work succeeded, but Availsync finish cleanup failed:', result.finishError.message);
} else if (result.heartbeatError) {
  console.warn('Work succeeded, but at least one claim heartbeat failed:', result.heartbeatError.message);
} else if (result.autoExtendError) {
  console.warn('Work succeeded, but at least one lease extension failed:', result.autoExtendError.message);
}

withClaim generates a per-run idempotency key when you do not pass one, so a lost response and retry do not create duplicate claims for the same invocation. withClaim also enables claim-level heartbeat by default. Heartbeats do not extend the lease; they let Availsync mark a disappeared SDK process as lost after the configured timeout. Set claimHeartbeat: false for legacy lease-only behavior.

API surface

  • createAvailsyncClient({ apiKey, agentId, baseUrl?, timeoutMs? })
  • client.work.withClaim(resource, options, fn)
  • client.work.start({ resource, durationMinutes?, heartbeatTimeoutSeconds?, reason?, idempotencyKey?, metadata? })
  • client.work.check({ resource, durationMinutes?, reason?, metadata? })
  • client.work.get(claimId)
  • client.work.list({ resource?, status?, limit?, offset? })
  • client.work.extend(claimId, { durationMinutes?, workingSet? })
  • client.work.heartbeat(claimId)
  • client.work.updateWorkingSet(claimId, { paths, mode?, resource?, leaseSeconds? })
  • claim.heartbeat()
  • claim.updateWorkingSet({ paths, mode?, resource?, leaseSeconds? })
  • client.work.finish(claimId, { outcome?, reason?, metadata? })

Manual flow

const started = await availsync.work.start({
  resource: 'repo:owner/repo',
  reason: 'deploy automation',
  durationMinutes: 45,
  idempotencyKey: `deploy-${process.env.GITHUB_RUN_ID}`,
});

if (started.action === 'skip_run') {
  console.log(started.reason);
  process.exit(0);
}

if (started.shadow_mode && started.would_have_blocked) {
  console.warn('Observe-only: Availsync would have skipped this run.');
}

const extendTimer = started.claim
  ? setInterval(() => availsync.work.extend(started.claim!.id, { durationMinutes: 45 }).catch(console.error), 40 * 60_000)
  : null;
let outcome: 'finished' | 'error' = 'finished';

try {
  await doWork();
} catch (error) {
  outcome = 'error';
  throw error;
} finally {
  if (extendTimer) clearInterval(extendTimer);
  if (started.claim) {
    await availsync.work.finish(started.claim.id, { outcome });
  }
}

Observe-only pilots

Set an agent to Observe in the dashboard when you want Availsync to report what it would have blocked without stopping the automation. Observe mode returns action: "proceed" and claim: null; no lease is created, so extend and finish are no-ops you should skip.

File-aware coordination

Use working set claims when repo-level locking is too broad. A run can keep its repo/project claim while declaring the files it is actively editing or reviewing.

await claim.updateWorkingSet({
  resource: 'repo:owner/repo',
  mode: 'edit',
  paths: ['frontend/components/Hero.tsx', 'frontend/app/page.tsx'],
  leaseSeconds: 600,
});

read and review working sets are non-exclusive. edit and delete conflict with other active edit or delete claims for the same resource and path. Availsync stores paths and metadata only, not file contents.

For long-running work, pass the current working set when you extend. That refreshes the run lease and checks whether the files are still safe:

const extended = await claim.extend({
  durationMinutes: 45,
  workingSet: {
    resource: 'repo:owner/repo',
    mode: 'edit',
    paths: currentFiles,
    leaseSeconds: 600,
  },
});

if (extended.working_set?.action === 'blocked') {
  throw new Error('Working set is blocked by another agent');
}

Error handling

The SDK throws AvailsyncError for auth, validation, plan-limit, network, and unexpected API errors. The error includes code, status, and safe details from the API response. It never logs or exposes API keys.

If your callback succeeds but final finish cleanup fails, withClaim still returns your successful result and includes finishError. If an automatic lease extension failed while your work continued, the result includes autoExtendError and calls onAutoExtendError if you provided one. This avoids reporting successful work as failed while still making cleanup issues visible.

By default, if Availsync is unavailable before a run starts, withClaim fails closed and throws. For observe-only pilots where availability matters more than coordination coverage, you can choose fail-open explicitly:

await availsync.work.withClaim(
  'repo:owner/repo',
  { reason: 'observe pilot', onUnavailable: 'proceed' },
  async ({ unavailable }) => {
    if (unavailable) console.warn('Running without Availsync coordination:', unavailable.code);
    await doWork();
  },
);

Use fail-open carefully in enforce-mode automations. The SDK only treats network errors, timeouts, aborts, and gateway/service-unavailable responses (502, 503, 504) as unavailable. Generic 500 responses still throw because they may indicate a request or server bug rather than an outage. If the server creates a claim but the response is lost before the SDK receives the claim id, the SDK cannot finish that claim and it will remain active until its lease expires. Fail-closed is the recommended default for repo edits, deploys, migrations, and other production-like work.

Resource scope

  • repo:owner/repo is the safe default. Only one active agent can work in the repo.
  • project:homepage allows parallel work, but only use it for independent work streams.
  • Availsync does not yet infer dependencies between different resources.

Local development

npm run build:sdk
npm run test:sdk