@treeship/sdk
TypeScript SDK for Treeship. Wraps the CLI binary for signing, verification, and hub connections.
Install
npm install @treeship/sdkThe 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_xxxShip 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
| Property | Type | Description |
|---|---|---|
attest | AttestModule | Attestation methods (action, decision, approval, handoff) |
verify | VerifyModule | Verification methods |
hub | HubModule | Hub 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
| Field | Type | Required | Description |
|---|---|---|---|
actor | string | Yes | Actor URI, e.g. agent://my-agent |
action | string | Yes | Label for the action performed |
parentId | string | No | Parent artifact ID for chain linking |
approvalNonce | string | No | Nonce from an existing approval |
meta | Record<string, unknown> | No | Arbitrary 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
| Field | Type | Required | Description |
|---|---|---|---|
actor | string | Yes | Actor URI |
model | string | No | Model identifier |
tokensIn | number | No | Input token count |
tokensOut | number | No | Output token count |
promptDigest | string | No | SHA-256 digest of the prompt |
summary | string | No | Plain-text summary of the decision |
confidence | number | No | Confidence score between 0 and 1 |
parentId | string | No | Parent 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 jsonconst { artifactId, nonce } = await s.attest.approval({
approver: 'human://alice',
description: 'approve payment max $500',
});Params: ApprovalParams
| Field | Type | Required | Description |
|---|---|---|---|
approver | string | Yes | Approver identity URI |
description | string | Yes | Plain-text description of what is authorized |
expires | string | No | ISO-8601 timestamp when the approval expires, e.g. "2026-12-31T00:00:00Z" |
subject | string | No | Artifact 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
| Field | Type | Required | Description |
|---|---|---|---|
from | string | Yes | Source actor URI |
to | string | Yes | Destination actor URI |
artifacts | string[] | Yes | Artifact IDs being transferred |
approvals | string[] | No | Approval IDs the receiver inherits |
obligations | string[] | No | Obligations 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
| Field | Type | Description |
|---|---|---|
outcome | 'pass' | 'fail' | 'error' | Verification result |
chain | number | Number of artifacts in the chain |
target | string | The 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:
| Method | Signature |
|---|---|
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_abc123Returns: PushResult
| Field | Type | Description |
|---|---|---|
hubUrl | string | Public URL for the artifact on the Hub |
rekorIndex | number | undefined | Rekor 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:
| Field | Type | Description |
|---|---|---|
connected | boolean | Whether the hub connection is currently active |
endpoint | string | undefined | Hub endpoint URL, if connected |
hubId | string | undefined | Hub 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.