Node SDK quickstart

Connect, read, and write from JavaScript.

Written By Dustin

Last updated 22 minutes ago

The Node SDK (tovio-node-sdk) is a typed TypeScript API over the native NAPI bindings — the same engine the CLI uses, not a wrapper that shells out. It owns no version-control logic and no cryptography: every call crosses into the Rust engine, which re-runs full enforcement each time.

Connect

import { TovioClient, unwrap } from "tovio-node-sdk";const client = TovioClient.connect("/path/to/repo", "cap_…");console.log(client.scope);

connect is synchronous — there is nothing to await. It opens the repository, verifies the native module's ABI, resolves the token, runs the handshake, and echoes the effective scope back so you know what you actually hold before you try to use it. The second argument is either a token id (resolved from the repo's token store) or the raw signed token bytes. An optional third argument is the prompt bytes, hashed engine-side into the commit's prompt_hash.

A refused token throws — there is no half-open client. An ABI mismatch between the SDK and the loaded native module throws TVO-CORE-001 rather than partially working.

The scope echo is informational

client.scope carries agent, pathScope, deniedPathScope, branchScope, secretClearance, allowedOps, and promptHash. Use it to decide what to attempt. Do not use it to decide what to allow — the engine re-checks scope on every single call, and that is the only enforcement point.

Read

MethodReturns
readFile(path)The bytes, plus text when they round-trip as UTF-8. Decrypts an authorized protected path and appends a signed read-audit entry first.
status()Current lane, lane-tip change, scope-filtered open conflicts.
log(limit?)First-parent history from HEAD, metadata only.
diff()The current change's per-file diff, scope-filtered.
listConflicts()Paths with a materialized conflict in HEAD.
symbolContext(symbols, depth?)The bounded symbol graph around SCIP monikers.
symbolRelations(moniker, relation)One hop along callers, tested_by, or supertypes.
searchSymbols(query, limit?)Ranked search — the question you can ask without already knowing a moniker.
changeImpact(symbols, depth?)Reverse dependency closure plus its test-coverage layer. Advisory: an empty result is not proof of no impact.
projectContext(paths, task?)The pre-write briefing — relevant symbols, overlapping peer intents, readable rationale, matching conventions.
provenanceGraph(target, depth?)The derivation graph for a chg:/commit/task:/session: handle. A sealed node you cannot decrypt comes back as a typed locked marker, never its content.
blake3Hex(bytes)A bare engine passthrough — useful for verifying a prompt_hash.

Write

const session = client.openWriteSession();session.writeFile("src/new.rs", "pub fn added() {}\n");const { changeId, branch, verified } = unwrap(session.commit("feat: add added()"));

openWriteSession() is synchronous too. The session stages edits into an in-memory working change: writeFile, deleteFile, moveFile, resolveConflict, then commit lands them on the token's agent/<name> lane. stagedPaths() shows what is staged; session.branch names the lane. Sessions are reusablecommit resets the staged state in place, so one handle supports stage → commit → stage again.

commit takes an optional reasoning summary and a structured rationale, so an automated commit carries the same provenance a person's does. verified in the result means a signed audit entry was appended; false still means the commit landed.

Delegate

const { token, tokenId } = unwrap(session.issueSubToken({  paths: ["src/api/**"],  ops: ["read", "write", "commit"],  expiresAt: Math.floor(Date.now() / 1000) + 3600,}));

A monotonically narrower leaf, signed by the agent's own key. expiresAt is required (unix seconds, inside the parent's window); omitted scope dimensions inherit; canDelegate never inherits — absent means false. The leaf's parent links persist, so a later connect rebuilds and revalidates the whole chain, including ancestor revocation.

Get a token first

tovio agent new my-integration \  --model script --task "nightly dependency bump" --expires-in 4

--expires-in is a whole number of hours (default 24), not a duration string. Add --can-delegate only if the integration actually issues sub-tokens; it is off by default.