Treeship
SDK

@treeship/sdk

TypeScript SDK for Treeship. Wraps the CLI binary for signing, verification, and hub connections.

Install

npm install @treeship/sdk

The SDK shells out to the treeship CLI binary. The CLI must be installed and initialized (treeship init) before using the SDK. WASM-based signing is planned for a future version.

Quick start

import { ship } from '@treeship/sdk';

const s = ship();

// Attest an action
const { artifactId } = await s.attest.action({
  actor: 'agent://my-agent',
  action: 'tool.call',
});

// Verify it
const result = await s.verify.verify(artifactId);
console.log(result.outcome); // "pass"

// Push to Hub
const { hubUrl } = await s.hub.push(artifactId);
console.log(hubUrl); // https://treeship.dev/verify/art_xxx

Ship class

Ship.checkCli()

Static method that checks whether the treeship CLI binary is installed and available on PATH. Throws a descriptive error if the binary is not found.

It shells out to treeship version and returns that command's whole output line, not a bare semver — e.g. "treeship 0.31.9 (rust)". Parse it if you need the number; do not compare it to a fixed string.

import { Ship } from '@treeship/sdk';

const line = await Ship.checkCli();      // "treeship 0.31.9 (rust)"
const version = line.split(' ')[1];      // "0.31.9"
console.log(`Treeship CLI version: ${version}`);

Call this early in your startup sequence to fail fast if the CLI is missing, rather than getting a confusing error on the first attestation call.

Instance properties

PropertyTypeDescription
attestAttestModuleAttestation methods (action, decision, approval, handoff)
verifyVerifyModuleVerification methods
hubHubModuleHub push/pull and connection status

Attest module

All attestation methods live under s.attest.

s.attest.action(params)

Record that an actor performed an action.

const { artifactId } = await s.attest.action({
  actor: 'agent://my-agent',
  action: 'tool.call',
  meta: { model: 'gpt-4o', tokens: 1247 },
});

Params: ActionParams

FieldTypeRequiredDescription
actorstringYesActor URI, e.g. agent://my-agent
actionstringYesLabel for the action performed
parentIdstringNoParent artifact ID for chain linking
approvalNoncestringNoNonce from an existing approval
metaRecord<string, unknown>NoArbitrary metadata attached to the artifact

Returns: ActionResult -- { artifactId: string }

s.attest.decision(params)

Record an LLM inference decision with token usage and confidence scoring.

await s.attest.decision({
  actor: 'agent://analyst',
  model: 'claude-opus-4',
  tokensIn: 8432,
  tokensOut: 1247,
  summary: 'Contract looks standard.',
  confidence: 0.91,
});

Params: DecisionParams

FieldTypeRequiredDescription
actorstringYesActor URI
modelstringNoModel identifier
tokensInnumberNoInput token count
tokensOutnumberNoOutput token count
promptDigeststringNoSHA-256 digest of the prompt
summarystringNoPlain-text summary of the decision
confidencenumberNoConfidence score between 0 and 1
parentIdstringNoParent artifact ID for chain linking

Note: The CLI's attest decision accepts --model-version, but the TypeScript SDK's modelVersion field is not yet wired to it -- setting it has no effect. Use the model field to identify the model and version together (e.g., "claude-opus-4-20260301") until the SDK forwards it.

Returns: ActionResult -- { artifactId: string }

s.attest.approval(params)

Record that an approver authorized an intent. Returns a nonce that can be echoed back when attesting the approved action.

Currently broken against CLI 0.24. ApprovalParams has no scope fields, so the SDK sends none, and the CLI refuses every scopeless approval: approval has no scope (no --allowed-actor / --allowed-action / --allowed-subject / --max-uses). There is no combination of documented params that succeeds. Mint approvals through the CLI until ApprovalParams grows allowedActor / allowedAction / allowedSubject / maxUses / unscoped:

treeship attest approval --approver human://alice \
  --allowed-actor agent://checkout --allowed-action stripe.charge.create \
  --max-uses 1 --expires 2026-12-31T23:59:59Z --format json
const { artifactId, nonce } = await s.attest.approval({
  approver: 'human://alice',
  description: 'approve payment max $500',
});

Params: ApprovalParams

FieldTypeRequiredDescription
approverstringYesApprover identity URI
descriptionstringYesPlain-text description of what is authorized
expiresstringNoISO-8601 timestamp when the approval expires, e.g. "2026-12-31T00:00:00Z"
subjectstringNoArtifact ID being approved -- maps to the CLI --subject flag

Returns: ApprovalResult -- { artifactId: string, nonce: string }

s.attest.handoff(params)

Record a transfer of work between actors.

await s.attest.handoff({
  from: 'agent://researcher',
  to: 'agent://executor',
  artifacts: ['art_abc123'],
});

Params: HandoffParams

FieldTypeRequiredDescription
fromstringYesSource actor URI
tostringYesDestination actor URI
artifactsstring[]YesArtifact IDs being transferred
approvalsstring[]NoApproval IDs the receiver inherits
obligationsstring[]NoObligations the receiver must satisfy

Returns: ActionResult -- { artifactId: string }

Verify module

s.verify.verify(artifactId)

Verify an artifact and walk its chain.

const result = await s.verify.verify('art_abc123');
// { outcome: 'pass', chain: 3, target: 'art_abc123' }

Returns: VerifyResult

FieldTypeDescription
outcome'pass' | 'fail' | 'error'Verification result
chainnumberNumber of artifacts in the chain
targetstringThe artifact ID that was verified

Offline verification methods

VerifyModule also carries five WASM-backed methods that verify in-process without shelling out to the CLI. They are the same entry points @treeship/verify exposes:

MethodSignature
verifyReceipt(target: VerifyTarget) => Promise<VerifyReceiptResult>
verifyCertificate(target: VerifyTarget, now?: Date | string, trustRoots?: TrustRootInput[]) => Promise<VerifyCertificateResult>
crossVerify(receipt: VerifyTarget, certificate: VerifyTarget, now?: Date | string, trustRoots?: TrustRootInput[]) => Promise<CrossVerifyResult>
verifyResolution(bundle: ResolutionBundleInput, trustRoots?: TrustRootInput[], now?: Date | string) => Promise<ResolutionVerdict>
verifyPresentation(presentation: Record<string, unknown>, trustRoots?: TrustRootInput[], opts?: { nonce?: string; now?: Date | string }) => Promise<PresentationVerdict>

VerifyTarget is string | URL | Record<string, unknown> — an artifact id, a receipt URL, or an already-parsed receipt object.

These five load @treeship/core-wasm; s.verify.verify() shells out to the CLI instead. See the @treeship/verify page for the WASM package's Node/browser support.

Hub module

s.hub.push(artifactId)

Push an artifact to the Treeship Hub.

const { hubUrl } = await s.hub.push('art_abc123');
// https://treeship.dev/verify/art_abc123

Returns: PushResult

FieldTypeDescription
hubUrlstringPublic URL for the artifact on the Hub
rekorIndexnumber | undefinedRekor transparency log index, if available

s.hub.pull(id)

Pull an artifact from the Treeship Hub to the local store.

await s.hub.pull('art_abc123');

Returns void. Throws if the artifact is not found or the network request fails.

s.hub.status()

Check the current hub connection status.

const status = await s.hub.status();
if (status.connected) {
  console.log(`Connected to ${status.endpoint}`);
}

Returns:

FieldTypeDescription
connectedbooleanWhether the hub connection is currently active
endpointstring | undefinedHub endpoint URL, if connected
hubIdstring | undefinedHub connection identifier, if connected

If the status check fails for any reason (CLI not installed, no config), returns { connected: false } instead of throwing.

Exported types

All types are re-exported from the top-level @treeship/sdk import.

ActionParams

interface ActionParams {
  actor: string;
  action: string;
  parentId?: string;
  approvalNonce?: string;
  meta?: Record<string, unknown>;
}

ApprovalParams

interface ApprovalParams {
  approver: string;
  description: string;
  expires?: string;   // ISO-8601 timestamp
  subject?: string;   // artifact ID being approved
}

HandoffParams

interface HandoffParams {
  from: string;
  to: string;
  artifacts: string[];
  approvals?: string[];
  obligations?: string[];
}

DecisionParams

interface DecisionParams {
  actor: string;
  model?: string;
  modelVersion?: string;
  tokensIn?: number;
  tokensOut?: number;
  promptDigest?: string;
  summary?: string;
  confidence?: number;
  parentId?: string;
}

ActionResult

interface ActionResult {
  artifactId: string;
}

ApprovalResult

interface ApprovalResult {
  artifactId: string;
  nonce: string;
}

VerifyResult

interface VerifyResult {
  outcome: "pass" | "fail" | "error";
  chain: number;
  target: string;
}

PushResult

interface PushResult {
  hubUrl: string;
  rekorIndex?: number;
}

Error handling

The SDK throws TreeshipError when the CLI exits with a non-zero code.

TreeshipError

class TreeshipError extends Error {
  readonly args: string[];  // the CLI arguments that were passed
  readonly name: "TreeshipError";
}

TreeshipError extends the standard Error class. The args property contains the exact CLI arguments that were passed, which is useful for debugging. The message property contains the stderr output from the CLI.

import { TreeshipError } from '@treeship/sdk';

try {
  await s.attest.action({ actor: 'agent://x', action: 'test' });
} catch (e) {
  if (e instanceof TreeshipError) {
    console.error('CLI failed:', e.message);
    console.error('Args:', e.args);
  }
}

Design rules

CLI-backed

Shells out to the treeship binary. No native crypto in the SDK yet.

Never stores sensitive content

Hash payloads, not content.

Explicit errors

CLI failures throw TreeshipError. Verify returns a typed VerifyResult with an outcome field instead of throwing.