gaiadesk

package module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 8, 2026 License: MIT Imports: 34 Imported by: 0

README

GaiaDesk SDK for Go

Drive your GaiaDesk machines ("desks") from Go through GaiaDesk's Platform API: list them and see why one is offline, wake them, run commands and get exit codes back, stream output, copy files, run background jobs, read stats, mint and revoke scoped agent tokens, read the audit trail, manage webhooks, and create support sessions for the embed SDK. Desk operations are end-to-end encrypted whenever the desk can open them.

  • Module: github.com/Gaia-Desk/gaiadesk-go, package gaiadesk (Go 1.25+)
  • One dependency: golang.org/x/crypto (XChaCha20-Poly1305); X25519 and HKDF come from the standard library
  • Three transports, one API: the hosted API (New), a desk's own API for code running on it (NewLocal), and a desk's LAN gateway (NewLAN)

Other GaiaDesk developer tools: the TypeScript SDK, the Python SDK and the MCP server. This SDK covers the same API transport as theirs, plus the fleet, audit, webhook and support routes. The API contract is GaiaDesk's OpenAPI document (api/openapi.yaml); results are the same JSON shapes gaiadesk-cli --json prints.

MIT-licensed. GaiaDesk itself is proprietary and not covered by this license.

Contents

Install

go get github.com/Gaia-Desk/gaiadesk-go@v0.1.2

Quick start

package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/Gaia-Desk/gaiadesk-go"
)

func main() {
	gd, err := gaiadesk.New(os.Getenv("GAIADESK_API_KEY"), // ak_…
		gaiadesk.WithDeskToken(os.Getenv("GAIADESK_TOKEN"))) // gdagt_…, verified by the desk
	if err != nil {
		log.Fatal(err)
	}
	r, err := gd.Exec(context.Background(), "123456789", gaiadesk.ExecRequest{Command: "uname -a"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("exit %d\n%s", r.Exit, r.Stdout)
}

Every method takes a context.Context: cancel it (or give it a deadline) to stop the call. A Client is safe for concurrent use.

Credentials

Credential Where What it does
Atlas API key, ak_… New(apiKey) the key's scopes: desks:read, desks:write, exec, files, jobs, tokens, audit:read, webhooks, support
A signed-in person's session or OAuth token New(token) every scope, on their own and their team's desks
Scoped agent token, gdagt_… WithDeskToken(t), or UseDeskToken(t) per call sent as X-GaiaDesk-Desk-Token; the desk verifies it (its scopes, cwd, low-privilege user)
The desk's local admin token, gdlocal_… NewLocal() reads it from ~/.gaiadesk/api-token, or WithAdminToken the desk owner, on the desk only

From an API key, desk operations need an agent token as well: a key never speaks for its account to a desk. Token administration (CreateToken, Tokens, RevokeToken) is the desk owner's: a signed-in person's session.

Client options: WithBaseURL, WithDeskToken, WithHTTPClient, WithE2E, WithE2EKeys, WithWarningHandler, WithRetry, WithResponseTimeout, WithIdleTimeout, WithUserAgent (and for NewLocal: WithSocketPath, WithAdminToken, WithEnv). Per-call options: UseDeskToken, WakeFor, WithIdempotencyKey, CaptureResponse, WithoutRetry.

Desks

list, _ := gd.Desks(ctx) // every desk on the account and its team, online first
for _, d := range list.Devices {
	fmt.Println(d.DeskID, d.Name, d.Online, d.OfflineReason)
}
d, _ := gd.Desk(ctx, "123456789")                                    // one, with wake hints and e2e key
log, _ := gd.Reach(ctx, "123456789", &gaiadesk.ReachOptions{Limit: 20}) // its online/offline history
w, err := gd.Wake(ctx, "123456789", 30*time.Second)                   // ring it, wait up to 30 s

A sleeping desk is also rung before any desk operation: WakeFor(d) sets how long to wait (the API's wake_s, default 60 s, at most 120 s). A desk with nothing to ring is ErrUnreachable, reason no_wake_path.

Commands

r, err := gd.Exec(ctx, desk, gaiadesk.ExecRequest{
	Command: "make test",                 // one command line; or Argv: []string{"make", "test"}
	Shell:   gaiadesk.ShellBash,          // default: the desk's own
	Cwd:     "src/app",
	Env:     map[string]string{"CI": "1"}, // values never logged by the server or the desk
	Stdin:   strings.NewReader("input"),  // read whole, sent up front as text
	Timeout: 10 * time.Minute,            // the API caps every call at 15 minutes
	Check:   true,                        // a non-zero exit is a *CommandError
})

A command that ran is a result whatever its exit code (r.Exit, r.RemoteCode, r.Stdout, r.Stderr, r.TimedOut, r.Truncated past 8 MB). One the desk refused to start is an error (ErrRefused).

Streaming (?stream=1, Server-Sent Events):

st, err := gd.ExecStream(ctx, desk, gaiadesk.ExecRequest{Argv: []string{"make", "release"}})
if err != nil { // refused, unreachable, … before it started
	return err
}
defer st.Close()                         // stops the command if you leave early
exit, err := st.Copy(os.Stdout, os.Stderr) // or: for c := range st.Chunks() { … }, or st.Recv()
fmt.Println(exit.ExitCode, exit.Err())   // exit.Err(): why it broke off (desk lost, …), or nil

A character split across chunks arrives whole. The Exit says how it ended: the command's ExitCode and Result, or an Error (connection_lost when the desk went away mid-stream), or 130 when you closed it or cancelled the context. The API takes stdin up front only (as text); it has no input while the command runs.

Administrator work (root / SYSTEM) is only available through gaiadesk-cli exec --admin, not the API: the API refuses it with admin_not_via_api (ErrRefused).

Background jobs

yes := true
job, _ := gd.StartJob(ctx, desk, gaiadesk.JobRequest{
	Name: "nightly", Command: "./build.sh --release", Shell: gaiadesk.ShellBash,
	Env: map[string]string{"CI": "1"}, Priority: "low", CPUPercent: 50, MemMB: 2048, KeepAwake: &yes,
})
jobs, _ := gd.Jobs(ctx, desk)
logs, _ := gd.JobLogs(ctx, desk, "nightly", &gaiadesk.LogsOptions{Tail: 4096}) // the last 4 KiB
follow, _ := gd.FollowJobLogs(ctx, desk, "nightly", nil)                       // a *Stream until it ends
done, _ := gd.WaitJob(ctx, desk, "nightly", gaiadesk.WaitForever)             // or a timeout; 0: now
stopped, _ := gd.KillJob(ctx, desk, "nightly")

WaitJob answers the job as it ended, or (TimedOut) as it stands. The API holds one wait at most 870 s; longer waits are asked again. A held answer (GaiaDesk-Held: 1) that failed after its 200 began is returned as its typed error, like any other failure.

Files

res, err := gd.UploadFile(ctx, "report.csv", desk, "/tmp/")     // a remote ending in / keeps the name
res, err = gd.Upload(ctx, desk, "notes/a.txt", strings.NewReader("hello"))
n, err := gd.Download(ctx, desk, "/var/log/app.log", os.Stdout) // to any io.Writer
b, err := gd.DownloadBytes(ctx, desk, "notes/a.txt")
res, err = gd.DownloadFile(ctx, desk, "/tmp/report.csv", "./") // appears only once complete
rc, err := gd.OpenDownload(ctx, desk, "big.bin")              // an io.ReadCloser

At most 256 MB per file through the API (FileLimit; larger files go direct with gaiadesk-cli cp). An io.ReadSeeker (an *os.File, a *bytes.Reader) is streamed and can be resent on a retry; any other reader is read into memory first, as the API needs the size up front. A download that breaks off is an error (ErrConnectionLost), never a clean short file.

Agent tokens

owner, _ := gaiadesk.New(sessionToken) // a signed-in person: the desk owner
minted, err := owner.CreateToken(ctx, gaiadesk.TokenRequest{
	Desks: []string{"123456789"}, Name: "ci-bot", Expires: 7 * 24 * time.Hour,
	Scopes: []string{gaiadesk.ScopeExec, gaiadesk.ScopeCp, gaiadesk.ScopeJobs}, Cwd: "/srv/ci",
})
fmt.Println(minted.Tokens[0].Secret) // shown once
tokens, _ := owner.Tokens(ctx, "123456789")
_, _ = owner.RevokeToken(ctx, "123456789", tokens[0].ID)

Scopes: screen, exec, shell, cp, forward, jobs. If a later desk fails, CreateToken returns the tokens already minted along with the error.

Audit, webhooks, support sessions

events, _ := gd.Audit(ctx, &gaiadesk.AuditFilter{Desk: "123456789", Action: "api.*", Limit: 50})
for e, err := range gd.AuditAll(ctx, &gaiadesk.AuditFilter{Since: time.Now().Add(-24 * time.Hour)}) {
	if err != nil { break }
	fmt.Println(e.Action, e.Actor.ID)
}

hook, _ := gd.CreateWebhook(ctx, gaiadesk.WebhookRequest{URL: "https://example.com/hooks/gaiadesk",
	Events: []string{gaiadesk.EventDeskOffline, gaiadesk.EventJobFinished}})
// hook.Secret is shown once. In your handler, over the raw body:
ev, err := gaiadesk.ParseWebhook(secret, r.Header.Get("GaiaDesk-Signature"), body)

s, _ := gd.CreateSupportSession(ctx, gaiadesk.SupportSessionRequest{Mode: gaiadesk.SupportCobrowse,
	Customer: map[string]any{"name": "Ada"}, ExpiresIn: 30 * time.Minute, Origin: "https://app.example.com"})
// hand s.EmbedToken (shown once) to the page; agents join with s.JoinCode
open, _ := gd.SupportSessions(ctx, nil)

VerifyWebhook checks t=<unix>,v1=<hex HMAC-SHA256 of "<t>.<raw body>"> in constant time and refuses a t more than five minutes off; de-duplicate deliveries by ev.ID. SignWebhook makes the header, for testing handlers.

End-to-end encryption

On the hosted API, desk operations are sealed so GaiaDesk's servers relay only ciphertext: the command, its Env, Stdin, file paths and bytes, and all output and results are readable by you and the desk only. The server still sees the credentials, the route (the operation and the desk, a job name or token id in the path), stream/follow/wake_s, sizes, and how the operation ended (an exit, or an error's kind and reason). The local and LAN transports never leave the desk or the LAN and are not sealed.

Before an operation the SDK reads the desk's X25519 key (e2e_pub from GET /desks/{id}, cached for 5 minutes) and seals the request to a fresh ephemeral key: X25519, HKDF-SHA256, XChaCha20-Poly1305, every message bound to the desk, the operation and its place in the stream (seq). Results, streams, errors and file bytes come back exactly as in the clear; a desk's error carries its own message. The SDK reproduces the protocol's fixed test vectors byte for byte (testdata/e2e-vectors.json).

gd, err := gaiadesk.New(apiKey, gaiadesk.WithDeskToken(token),
	gaiadesk.WithE2E(gaiadesk.E2ERequire),                                 // E2EAuto (default) | E2ERequire | E2EOff
	gaiadesk.WithE2EKeys(map[string]string{"123456789": "B6N8vBQgk8i3…"})) // optional: pin a desk's e2e_pub
  • E2EAuto: sealed when the desk lists a key; otherwise sent in the clear with a one-time warning per desk (WithWarningHandler, default the log package), unless the desk requires it: then it is woken and asked again, and sealed or refused.
  • E2ERequire: never in the clear. A desk that lists no key (asleep, offline, or a GaiaDesk from before end-to-end encryption) is woken and asked again; still none is ErrE2E (also ErrRefused, reason e2e_unavailable), and nothing is sent.
  • E2EOff: plaintext.
  • Pinned keys seal even while the desk lists none; a different key from the server is ErrE2E (e2e_key_mismatch) and nothing is sent.
  • A plaintext call refused e2e_required (409) is sealed and sent once more; a sealed one the desk could not open (e2e_decrypt_failed: its key rotated) is sealed to the key read again, once. Answers that do not open (altered, reordered, or a plaintext answer to a sealed call) are ErrProtocol (e2e_decrypt_failed, e2e_malformed, e2e_unsealed_answer).
  • Reading a desk's key needs the API key's desks:read scope (waking it, desks:write); in E2EAuto, a key that cannot be read means plaintext with the warning.

CaptureResponse(&info) sets info.Sealed to say whether a call went sealed.

Local and LAN

On the desk (no server, no internet):

gd, err := gaiadesk.NewLocal() // finds the socket or pipe and the admin token
// or: gaiadesk.NewLocal(gaiadesk.WithDeskToken(agentToken)) — its scopes, cwd and user apply
macOS, Linux Windows
where $GAIADESK_API_DIR/api.sock, else ~/.gaiadesk/api.sock $GAIADESK_API_PIPE, else \\.\pipe\gaiadesk-api-<user>
admin token $GAIADESK_API_DIR/api-token, else ~/.gaiadesk/api-token %USERPROFILE%\.gaiadesk\api-token

No socket or pipe (the app is not running, or Settings → GaiaDesk API → Local API is off) is ErrUnreachable, reason local_api_unavailable. LocalSocketPath, LocalTokenPath, LocalPipeName and PipeUser give the defaults. The Windows pipe is opened for overlapped I/O, so reads, writes and cancellation work as on a socket, with no extra dependency.

A desk's LAN gateway (opt-in on the desk, port 7443):

gd, err := gaiadesk.NewLAN("https://gaiadesk-123456789.local:7443/v1",
	"ab:cd:…",                               // the SHA-256 fingerprint the desk's Settings show
	gaiadesk.WithDeskToken(agentToken))      // agent tokens only

The certificate is self-signed, so its SHA-256 is pinned instead of the chain, and checked during the TLS handshake, before any byte of the request is sent. A different certificate is a *FingerprintMismatchError (ErrFingerprintMismatch, also ErrUnreachable): do not proceed, it may not be your desk.

Both serve the desk operations and Desks; the hosted-only routes (Desk, Reach, Wake, audit, webhooks, support) are a usage error that sends nothing.

Errors

Every failure is an *Error (or wraps one): the API's error envelope {"error": {"kind", "message", "reason", "desk", "request_id"}}, or the SDK's own in the same terms.

r, err := gd.Exec(ctx, desk, req)
switch {
case errors.Is(err, gaiadesk.ErrUnreachable): // offline, unknown desk, network, …
case errors.Is(err, gaiadesk.ErrRefused):     // credential, scope, rate limit, desk said no
case errors.Is(err, gaiadesk.ErrInterrupted): // the context was cancelled
}
if e := gaiadesk.AsError(err); e != nil {
	fmt.Println(e.Class, e.Kind, e.Reason, e.Status, e.RequestID, e.RetryAfter)
}
Class (sentinel) HTTP means
usage (ErrUsage) 400 fix the call (bad_body, idempotency_key_reused, too_large, …); the SDK's own checks too, which send nothing
refused (ErrRefused) 401, 403, 409, 429 unauthenticated, missing_scope, desk_token_required, rate_limited, desk_busy, e2e_required, admin_not_via_api, …
unreachable (ErrUnreachable) 404, 409, 503, 504 unknown_desk, offline, no_wake_path, network, local_api_unavailable, …
connection_lost (ErrConnectionLost) 502 the desk went away mid-operation; a broken download
failed (ErrFailed) 422 allowed, and it did not succeed (no such job, a file that failed)
protocol (ErrProtocol) 409, 502 an answer this client cannot use (desk_too_old, an envelope missing, sealed events that do not open)

Kind is the finest word, matching the TypeScript and Python SDKs' kind (offline, unknown_desk, network, timeout, interrupted, local, …). ExitCode is what gaiadesk-cli would exit with (254 refused, 1 failed, 130 interrupted, 255 the rest). Also: ErrE2E, ErrCommand (*CommandError, with Result), ErrFingerprintMismatch, and e.Temporary().

Retries, idempotency, rate limits

Retries. A request is sent again only when that cannot run anything twice:

  • The connection was never made (DNS, refused, TLS handshake): any method — nothing was sent.
  • The connection was lost after sending, or the answer was 502, 503 or 504: GETs only (reads). A 503 that says the API or desk operations are switched off is not retried.
  • 429 (rate_limited, desk_busy) and 409 idempotency_key_in_flight: any method — the server refused it before acting.

Timeouts are never retried, and nothing is retried once its answer has begun. A call that changes something (POST, PUT, DELETE) is never sent again after it may have reached the server; an Idempotency-Key is sent but does not make a call retryable. 429 and 503 wait for Retry-After; one longer than RetryPolicy.MaxRetryWait (default 60 s) is not waited for — the error carries it. Otherwise the wait is exponential backoff with jitter: RetryPolicy.BaseDelay (default 250 ms) doubling up to RetryPolicy.MaxDelay (default 8 s), times a random 0.5–1.0. RetryPolicy.MaxAttempts (default 3: 2 retries, so 3 attempts in all) sets how many times; 1 (WithRetry(gaiadesk.NoRetry), or WithoutRetry() per call) turns retries off. Each retry of a sealed operation is sealed afresh.

Go's net/http itself re-sends only a GET (once, when the pooled connection it used was closed before any answer), or a request it could not write at all. The SDK hands it every other request with a body it can neither rewind nor treat as absent, the two cases in which it would re-send one (HTTP/1 re-sends any request carrying Idempotency-Key with a rewindable body; HTTP/2 a bodiless one on some stream errors), so a POST, PUT or DELETE is never re-sent behind your back.

Timeouts (WithResponseTimeout, WithIdleTimeout, on every transport: New, NewLocal, NewLAN) make a server or proxy that stops answering an error, never a hang:

  • WithResponseTimeout (default DefaultResponseTimeout, 16 minutes, above the API's 15-minute call limit): the longest wait for an answer to begin, sending the request included. Exceeded: ErrUnreachable, kind timeout.
  • WithIdleTimeout (default DefaultIdleTimeout, 90 s; streams and held waits send a keep-alive every 15 s): the longest silence while reading a body (JSON, a download, an event stream), per read, so a long download that keeps flowing never times out. Exceeded mid-answer: ErrConnectionLost, kind timeout (a Stream ends with that error in its Exit, exit code 255).
  • 0 is no limit; a negative value is a usage error. Both hold for a client given with WithHTTPClient too. A connection that timed out is closed, not pooled; cancelling the context still stops a call at once.
  • A connection closed or reset before any answer is ErrUnreachable (kind network) at once; a connect that times out is kind timeout. What is retried, and what net/http re-sends by itself: see Retries above.

CaptureResponse(&info) fills ResponseInfo: RequestID, RateLimitLimit, RateLimitRemaining, RateLimitReset, IdempotentReplayed, Held, Sealed.

API coverage

Endpoint Method
GET /desks Desks
GET /desks/{id} Desk
GET /desks/{id}/reach Reach
POST /desks/{id}/wake Wake
POST /desks/{id}/exec Exec
POST /desks/{id}/exec?stream=1 ExecStream
POST /desks/{id}/jobs StartJob
GET /desks/{id}/jobs Jobs
DELETE /desks/{id}/jobs/{name} KillJob
GET /desks/{id}/jobs/{name}/logs JobLogs
GET /desks/{id}/jobs/{name}/logs?follow=1 FollowJobLogs
GET /desks/{id}/jobs/{name}/wait WaitJob
GET /desks/{id}/stats Stats
PUT /desks/{id}/files Upload, UploadFile
GET /desks/{id}/files OpenDownload, Download, DownloadBytes, DownloadFile
POST /desks/{id}/tokens CreateToken
GET /desks/{id}/tokens Tokens
DELETE /desks/{id}/tokens/{token_id} RevokeToken
GET /audit Audit, AuditAll
GET /webhooks Webhooks
POST /webhooks CreateWebhook
DELETE /webhooks/{webhook_id} DeleteWebhook
webhook deliveries (GaiaDesk-Signature) VerifyWebhook, ParseWebhook, SignWebhook
POST /support/sessions CreateSupportSession
GET /support/sessions SupportSessions
GET /support/sessions/{session_id} SupportSession

Not in this SDK, because the API does not serve them: interactive shells, stdin written while a command runs, folder copies, port forwarding, screen tools and MCP. Those are gaiadesk-cli's (or the TypeScript/Python SDKs' CLI and native backends, which run GaiaDesk's client on your machine).

Examples

Development

go vet ./...
go test -race ./...
go run honnef.co/go/tools/cmd/staticcheck@v0.8.1 ./...

The tests run against internal/mockapi, a mock of the API and the desks behind it (each desk opens sealed requests with its own key, runs canned operations, seals its events back, and records every request raw), so they prove both that a sealed call answers exactly what the plaintext one does and that the API saw none of its command, environment, stdin, paths or bytes. internal/e2e holds the crypto and its vector tests.

Documentation

Overview

Package gaiadesk is the official Go SDK for GaiaDesk's Platform API (https://api.gaiadesk.net/v1): your fleet of desks, their reachability, waking them, the audit trail, webhooks, support sessions, and desk operations (commands, background jobs, files, tokens, stats) relayed to your desks, end-to-end encrypted when the desk can open them.

Three transports speak the same /v1 API, with the same methods, results and errors:

  • New: the hosted API, with an Atlas API key (`ak_…`) or a signed-in person's session token. Desk operations from an API key also need a scoped agent token (WithDeskToken, `gdagt_…`), which the desk verifies.
  • NewLocal: code running ON a desk, through the GaiaDesk app's own API over its Unix socket (~/.gaiadesk/api.sock) or Windows named pipe (\\.\pipe\gaiadesk-api-<user>), with the desk's local admin token or an agent token.
  • NewLAN: a desk's opt-in LAN gateway (https://<desk>:7443/v1), its self-signed certificate pinned by SHA-256 fingerprint; agent tokens only.

Every method takes a context.Context; cancelling it stops the call (a stream ends with exit 130). Failures are *Error values carrying the API's error envelope (Class, Reason, Desk, Status, RequestID, RetryAfter); match them with errors.Is against ErrRefused, ErrUnreachable, ErrUsage, … or read them with AsError.

End-to-end encryption: on the hosted API, a desk operation is sealed to the desk's X25519 key (X25519, HKDF-SHA256, XChaCha20-Poly1305) whenever the desk publishes one, so the API relays only ciphertext; every result, stream event and error is what the plaintext call gives. See WithE2E and WithE2EKeys.

Index

Constants

View Source
const (
	ReasonUnauthenticated     = "unauthenticated"
	ReasonMissingScope        = "missing_scope"
	ReasonRateLimited         = "rate_limited"
	ReasonDeskBusy            = "desk_busy"
	ReasonDeskTokenRequired   = "desk_token_required"
	ReasonDeskOptedOut        = "desk_opted_out"
	ReasonDeskTooOld          = "desk_too_old"
	ReasonDeskOpsDisabled     = "desk_ops_disabled"
	ReasonAgentCannotAdmin    = "agent_cannot_admin"
	ReasonUnknownDesk         = "unknown_desk"
	ReasonNoWakePath          = "no_wake_path"
	ReasonNoSuchRoute         = "no_such_route"
	ReasonTooLarge            = "too_large"
	ReasonIsFolder            = "is_folder"
	ReasonIdempotencyReused   = "idempotency_key_reused"
	ReasonIdempotencyInFlight = "idempotency_key_in_flight"
	ReasonIncomplete          = "incomplete"
	ReasonNetwork             = "network"
	ReasonLocalAPIUnavailable = "local_api_unavailable"
	ReasonFingerprintMismatch = "fingerprint_mismatch"

	// End-to-end encryption.
	ReasonE2ERequired       = "e2e_required"
	ReasonE2EUnavailable    = "e2e_unavailable"
	ReasonE2EKeyMismatch    = "e2e_key_mismatch"
	ReasonE2EDecryptFailed  = "e2e_decrypt_failed"
	ReasonE2EMalformed      = "e2e_malformed"
	ReasonE2EUnsealedAnswer = "e2e_unsealed_answer"
	ReasonE2EUnsupported    = "e2e_unsupported"
	ReasonE2EStale          = "e2e_stale"
	ReasonE2EReplayed       = "e2e_replayed"
	ReasonE2EOpMismatch     = "e2e_op_mismatch"

	// Administrator work (root / SYSTEM) is not available over the API: an
	// exec asking for it, or a token minted with the `admin` scope, is refused
	// with this reason. It runs only through `gaiadesk-cli exec --admin`.
	ReasonAdminNotViaAPI    = "admin_not_via_api"
	ReasonBlockedByOSPolicy = "blocked_by_os_policy"
)

Reasons the API, the desk or the SDK give (`error.reason`). Not a closed list: compare with the constants you care about.

View Source
const (
	// SupportView: the agent sees the shared tab.
	SupportView = "view"
	// SupportCobrowse: the agent may also point, highlight, click, scroll
	// and type inside the page (applied by the embed to its own DOM).
	SupportCobrowse = "cobrowse"
)

Support session modes.

View Source
const (
	ScopeScreen  = "screen"
	ScopeExec    = "exec"
	ScopeShell   = "shell"
	ScopeCp      = "cp"
	ScopeForward = "forward"
	ScopeJobs    = "jobs"
)

Agent token scopes (what a token may do on the desk).

View Source
const (
	EventDeskOnline           = "desk.online"
	EventDeskOffline          = "desk.offline"
	EventDeskWoke             = "desk.woke"
	EventJobFinished          = "job.finished"
	EventSupportSessionJoined = "support.session.joined"
	EventSupportSessionEnded  = "support.session.ended"
)

Webhook event types.

View Source
const DefaultBaseURL = "https://api.gaiadesk.net/v1"

DefaultBaseURL is the hosted GaiaDesk API.

View Source
const DefaultIdleTimeout = 90 * time.Second

DefaultIdleTimeout is the default WithIdleTimeout: 90 seconds (the API's streams and held waits send a keep-alive every 15 s).

View Source
const DefaultResponseTimeout = 16 * time.Minute

DefaultResponseTimeout is the default WithResponseTimeout: 16 minutes, above the API's 15-minute limit on a call (a buffered Exec answers when its command ends).

View Source
const FileLimit = 256 * 1024 * 1024

FileLimit is the most one file may be through the API (256 MB); larger files go direct, through gaiadesk-cli.

View Source
const LocalAPIUnavailable = "GaiaDesk is not serving its local API here: is the app running, and is Settings → GaiaDesk API → Local API on?"

LocalAPIUnavailable is what a missing socket or pipe means.

View Source
const Version = "0.1.2"

Version is this SDK's version.

View Source
const WaitForever time.Duration = -1

WaitForever is WaitJob's timeout for "until the job ends" (or the context is done).

View Source
const WaitMax = 870 * time.Second

WaitMax is the longest one `GET …/jobs/{name}/wait` holds (870 s); Client.WaitJob waits again for longer.

Variables

View Source
var (
	ErrUsage          = errors.New("gaiadesk: usage")
	ErrRefused        = errors.New("gaiadesk: refused")
	ErrUnreachable    = errors.New("gaiadesk: unreachable")
	ErrConnectionLost = errors.New("gaiadesk: connection lost")
	ErrFailed         = errors.New("gaiadesk: failed")
	ErrProtocol       = errors.New("gaiadesk: protocol")
	// ErrInterrupted: the call's context was cancelled (or its stream closed).
	ErrInterrupted = errors.New("gaiadesk: interrupted")
	// ErrE2E: the SDK would not send an operation in the clear, or the server
	// handed out a key other than the pinned one (also an ErrRefused).
	ErrE2E = errors.New("gaiadesk: end-to-end encryption")
	// ErrCommand: Exec with Check and a non-zero exit (a *CommandError).
	ErrCommand = errors.New("gaiadesk: command failed")
	// ErrFingerprintMismatch: the LAN gateway is not the desk you pinned
	// (a *FingerprintMismatchError, also an ErrUnreachable).
	ErrFingerprintMismatch = errors.New("gaiadesk: fingerprint mismatch")
)

Sentinel errors for errors.Is: every *Error matches the one for its class, so `errors.Is(err, gaiadesk.ErrRefused)` is true for any refusal.

View Source
var (
	ErrWebhookSignature = errors.New("gaiadesk: webhook signature does not verify")
	ErrWebhookTimestamp = errors.New("gaiadesk: webhook timestamp is more than five minutes off")
)

Webhook verification failures.

View Source
var DefaultRetry = RetryPolicy{MaxAttempts: 3, BaseDelay: 250 * time.Millisecond, MaxDelay: 8 * time.Second, MaxRetryWait: 60 * time.Second}

DefaultRetry is the default policy: three tries, backoff from 250 ms up to 8 s, a Retry-After of up to 60 s waited.

View Source
var DefaultScopes = []string{ScopeExec, ScopeCp, ScopeJobs}

DefaultScopes are a new token's scopes when none are given.

View Source
var NoRetry = RetryPolicy{MaxAttempts: 1}

NoRetry makes every call once.

Functions

func LocalAPIDir

func LocalAPIDir(getenv func(string) string, home, goos string) string

LocalAPIDir is the directory of the local API's socket and token: $GAIADESK_API_DIR when it is absolute, else <home>/.gaiadesk. goos is runtime.GOOS's form.

func LocalPipeName

func LocalPipeName(getenv func(string) string, username string) string

LocalPipeName is the local API's Windows pipe: $GAIADESK_API_PIPE, else `\\.\pipe\gaiadesk-api-<user>` (<user>: $USERNAME, else username).

func LocalSocketPath

func LocalSocketPath(getenv func(string) string, home, goos string) string

LocalSocketPath is the local API's Unix socket (macOS, Linux).

func LocalTokenPath

func LocalTokenPath(getenv func(string) string, home, goos string) string

LocalTokenPath is the file holding the desk's local admin token (`gdlocal_` + 64 hex digits).

func NormalizeFingerprint

func NormalizeFingerprint(fp string) (string, error)

NormalizeFingerprint reads a SHA-256 certificate fingerprint as the desk shows it (with or without colons or spaces, any case) and writes it as 32 lowercase hex pairs joined by `:`.

func ParseDuration

func ParseDuration(s string) (time.Duration, error)

ParseDuration reads a duration as gaiadesk-cli writes them: `90` (seconds), `30s`, `10m`, `1h30m`, `7d`, `2w`. Days and weeks are 24 and 168 hours.

func PipeUser

func PipeUser(name string) string

PipeUser is the pipe-name form of a user name: lowercased, [a-z0-9._-] kept, the rest `_`, at most 64 characters, `user` if empty.

func SignWebhook

func SignWebhook(secret string, body []byte, t time.Time) string

SignWebhook makes a GaiaDesk-Signature header for body at t, as the API does: for testing a webhook handler.

func VerifyWebhook

func VerifyWebhook(secret, signature string, body []byte, now time.Time) error

VerifyWebhook checks a delivery's GaiaDesk-Signature header (`t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">`) against the webhook's secret over the raw body, in constant time, and that t is within five minutes of now.

Types

type AuditEvent

type AuditEvent struct {
	ID           string `json:"id"`
	Action       string `json:"action"`
	Stream       string `json:"stream"`
	OccurredAtMS int64  `json:"occurred_at_ms"`
	Actor        struct {
		Type string `json:"type"`
		ID   string `json:"id,omitempty"`
	} `json:"actor"`
	Target *struct {
		Type string `json:"type,omitempty"`
		ID   string `json:"id,omitempty"`
		Name string `json:"name,omitempty"`
	} `json:"target,omitempty"`
	Metadata json.RawMessage `json:"metadata"`
}

AuditEvent is one audit event (Atlas audit events).

type AuditFilter

type AuditFilter struct {
	// Desk: only events about this desk (one of the caller's own).
	Desk string
	// Actor: only events by this actor id (an email, a token id).
	Actor string
	// Action: only this action, or every action under it when it ends in
	// `.*` (`api.*`).
	Action string
	// Token: only events by this agent token id or API key id.
	Token string
	// Since and Until bound when it happened (inclusive; zero: unbounded).
	Since, Until time.Time
	// Limit is the most events per call (default 100, at most 500).
	Limit int
}

AuditFilter narrows the audit trail.

type CallOption

type CallOption func(*callConfig)

CallOption configures one call.

func CaptureResponse

func CaptureResponse(info *ResponseInfo) CallOption

CaptureResponse fills info with what the response's headers said (its request id, rate limit, replay) once the call is answered.

func UseDeskToken

func UseDeskToken(token string) CallOption

UseDeskToken sends this scoped agent token (`gdagt_…`) with this call, instead of the client's.

func WakeFor

func WakeFor(d time.Duration) CallOption

WakeFor rings a sleeping desk and waits up to d (0 to 120 s; the API's `wake_s`, default 60 s) for it before a desk operation.

func WithIdempotencyKey

func WithIdempotencyKey(key string) CallOption

WithIdempotencyKey sends `Idempotency-Key` on a POST: a retry of yours with the same key and the same request within 24 hours gets the first answer again. (The SDK itself never sends a POST again after it may have reached the server, key or not.)

func WithoutRetry

func WithoutRetry() CallOption

WithoutRetry makes this call once, whatever the client's retry policy.

type Chunk

type Chunk struct {
	// Stream is "stdout" or "stderr" (a job's log is all "stdout").
	Stream string
	// Data is the text, as bytes (a character split across chunks arrives
	// whole in the later one).
	Data []byte
}

Chunk is a piece of a stream's output.

func (Chunk) Text

func (c Chunk) Text() string

Text is the chunk's data as a string.

type Class

type Class string

Class is one of the six kinds every GaiaDesk error envelope carries (`error.kind`), the same six gaiadesk-cli prints. Branch on it first, then on Error.Reason where you need finer detail.

const (
	ClassUsage          Class = "usage"           // bad arguments: fix the call
	ClassRefused        Class = "refused"         // the desk or the server said no
	ClassUnreachable    Class = "unreachable"     // the desk or the server could not be reached
	ClassConnectionLost Class = "connection_lost" // reached, then the connection went away
	ClassFailed         Class = "failed"          // allowed, and it did not succeed
	ClassProtocol       Class = "protocol"        // an answer this client cannot use (often a desk too old)
)

The six error classes.

type CliError

type CliError struct {
	Kind    string `json:"kind"`
	Message string `json:"message"`
	Reason  string `json:"reason,omitempty"`
	Desk    string `json:"desk,omitempty"`
}

CliError is what went wrong, as the API and gaiadesk-cli report it: the object inside `{"error": …}` (and exec's `error`).

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client drives GaiaDesk desks through a /v1 API: the hosted one (New), a desk's own (NewLocal), or a desk's LAN gateway (NewLAN). Every method takes a context; cancelling it stops the call. A Client is safe for concurrent use.

func New

func New(apiKey string, opts ...Option) (*Client, error)

New is a client of the hosted GaiaDesk API, authenticated with an Atlas API key (`ak_…`) or a signed-in person's session token. Desk operations from an API key need an agent token too (WithDeskToken or UseDeskToken).

func NewLAN

func NewLAN(baseURL, fingerprint string, opts ...Option) (*Client, error)

NewLAN is a client of a desk's LAN gateway (`https://<desk>:7443/v1`, or `https://gaiadesk-<desk id>.local:7443/v1`), its certificate pinned by the SHA-256 fingerprint its Settings show. It takes agent tokens only (WithDeskToken, or UseDeskToken per call). Options: WithDeskToken, WithRetry, WithUserAgent.

func NewLocal

func NewLocal(opts ...Option) (*Client, error)

NewLocal is a client of the desk's own API, for code running on the desk: the same desk operations as the hosted API (and Desks, this desk), over its socket or pipe. Options: WithSocketPath, WithAdminToken, WithDeskToken, WithEnv, WithRetry, WithUserAgent.

func (*Client) Audit

func (c *Client) Audit(ctx context.Context, f *AuditFilter, opts ...CallOption) ([]AuditEvent, error)

Audit reads audit events about the caller and their own desks, newest first: `GET /audit`. AuditAll pages through all of them.

func (*Client) AuditAll

func (c *Client) AuditAll(ctx context.Context, f *AuditFilter, opts ...CallOption) iter.Seq2[AuditEvent, error]

AuditAll iterates over every audit event the filter matches, newest first, a page (Limit, default 100) at a time; an error ends the iteration with it.

func (*Client) BaseURL

func (c *Client) BaseURL() string

BaseURL is the API's base URL (`…/v1`).

func (*Client) CreateSupportSession

func (c *Client) CreateSupportSession(ctx context.Context, s SupportSessionRequest, opts ...CallOption) (*SupportSessionCreated, error)

CreateSupportSession creates a support session: `POST /support/sessions`. Hand the result's EmbedToken (shown once) to the customer's page or app; the support team joins with JoinCode.

func (*Client) CreateToken

func (c *Client) CreateToken(ctx context.Context, t TokenRequest, opts ...CallOption) (*MintResult, error)

CreateToken mints a scoped agent token on each desk: `POST /desks/{id}/tokens` once per desk. Token administration is the desk OWNER's (a signed-in person's session, not an API key's agent token). If a later desk fails, the result holds the tokens already minted alongside the error: their secrets are shown once.

func (*Client) CreateWebhook

func (c *Client) CreateWebhook(ctx context.Context, w WebhookRequest, opts ...CallOption) (*WebhookCreated, error)

CreateWebhook subscribes an endpoint: `POST /webhooks`. Keep the result's Secret (it is never shown again) to verify deliveries with VerifyWebhook.

func (*Client) DeleteWebhook

func (c *Client) DeleteWebhook(ctx context.Context, id string, opts ...CallOption) error

DeleteWebhook unsubscribes (deliveries still queued are dropped): `DELETE /webhooks/{webhook_id}`.

func (*Client) Desk

func (c *Client) Desk(ctx context.Context, desk string, opts ...CallOption) (*DeskDetail, error)

Desk reads one desk, with its reachability, wake hints and end-to-end key: `GET /desks/{id}`.

func (*Client) Desks

func (c *Client) Desks(ctx context.Context, opts ...CallOption) (*DeskList, error)

Desks lists the desks on the account and its team, online first: `GET /desks` (on the local and lan transports, the desk itself).

func (*Client) Download

func (c *Client) Download(ctx context.Context, desk, remote string, w io.Writer, opts ...CallOption) (int64, error)

Download copies a file on the desk to w, returning the bytes written. On an error, w may hold part of the file.

func (*Client) DownloadBytes

func (c *Client) DownloadBytes(ctx context.Context, desk, remote string, opts ...CallOption) ([]byte, error)

DownloadBytes reads a file on the desk into memory.

func (*Client) DownloadFile

func (c *Client) DownloadFile(ctx context.Context, desk, remote, local string, opts ...CallOption) (*CopyResult, error)

DownloadFile copies a file on the desk to a local file (a local folder, or a path ending in a separator, keeps the remote name). The file appears only once complete.

func (*Client) Exec

func (c *Client) Exec(ctx context.Context, desk string, req ExecRequest, opts ...CallOption) (*ExecResult, error)

Exec runs one command on the desk: `POST /desks/{id}/exec`. A command that ran is a result whatever its exit code; one the desk refused to start (or that never ran) is its typed error. With Check, a non-zero exit is a *CommandError.

func (*Client) ExecStream

func (c *Client) ExecStream(ctx context.Context, desk string, req ExecRequest, opts ...CallOption) (*Stream, error)

ExecStream runs one command on the desk and streams its output: `POST /desks/{id}/exec?stream=1`. The error is the call's failure before the stream started (refused, unreachable, …); a failure after is the stream's Exit. Close the stream (or read it to the end) when done.

func (*Client) FollowJobLogs

func (c *Client) FollowJobLogs(ctx context.Context, desk, name string, o *LogsOptions, opts ...CallOption) (*Stream, error)

FollowJobLogs streams a job's output as it is written, until the job ends: `GET /desks/{id}/jobs/{name}/logs?follow=1`. Closing the stream stops following; the job goes on.

func (*Client) JobLogs

func (c *Client) JobLogs(ctx context.Context, desk, name string, o *LogsOptions, opts ...CallOption) (*JobLogs, error)

JobLogs reads a job and the end of its output: `GET /desks/{id}/jobs/{name}/logs`.

func (*Client) Jobs

func (c *Client) Jobs(ctx context.Context, desk string, opts ...CallOption) ([]Job, error)

Jobs lists the desk's background jobs: `GET /desks/{id}/jobs`.

func (*Client) KillJob

func (c *Client) KillJob(ctx context.Context, desk, name string, opts ...CallOption) (*Job, error)

KillJob stops a job and everything it started: `DELETE /desks/{id}/jobs/{name}`.

func (*Client) OpenDownload

func (c *Client) OpenDownload(ctx context.Context, desk, remote string, opts ...CallOption) (io.ReadCloser, error)

OpenDownload opens a file on the desk for reading: `GET /desks/{id}/files?path=`. Read it to io.EOF: a transfer that breaks off is an error (ErrConnectionLost), never a clean short file. Close it.

func (*Client) Reach

func (c *Client) Reach(ctx context.Context, desk string, o *ReachOptions, opts ...CallOption) (*ReachLog, error)

Reach reads the desk's online and offline history, newest first: `GET /desks/{id}/reach`.

func (*Client) RevokeToken

func (c *Client) RevokeToken(ctx context.Context, desk, token string, opts ...CallOption) (*Revoked, error)

RevokeToken revokes an agent token by id or name; its live sessions and jobs end: `DELETE /desks/{id}/tokens/{token_id}`.

func (*Client) StartJob

func (c *Client) StartJob(ctx context.Context, desk string, job JobRequest, opts ...CallOption) (*Job, error)

StartJob starts a background job: `POST /desks/{id}/jobs`. It runs under the desk's GaiaDesk and outlives the call; its end is the `job.finished` webhook (or WaitJob).

func (*Client) Stats

func (c *Client) Stats(ctx context.Context, desk string, opts ...CallOption) (*StatsReport, error)

Stats reads the desk's figures (CPU, memory, disks, running jobs): `GET /desks/{id}/stats`.

func (*Client) SupportSession

func (c *Client) SupportSession(ctx context.Context, id string, opts ...CallOption) (*SupportSession, error)

SupportSession reads one support session's state: `GET /support/sessions/{session_id}`.

func (*Client) SupportSessions

func (c *Client) SupportSessions(ctx context.Context, f *SupportSessionFilter, opts ...CallOption) ([]SupportSession, error)

SupportSessions lists the support sessions of the caller's account and team, newest first (the console's queue): `GET /support/sessions`.

func (*Client) Tokens

func (c *Client) Tokens(ctx context.Context, desk string, opts ...CallOption) ([]TokenInfo, error)

Tokens lists the desk's agent tokens (never their secrets): `GET /desks/{id}/tokens`.

func (*Client) Transport

func (c *Client) Transport() Transport

Transport says which transport the client uses.

func (*Client) Upload

func (c *Client) Upload(ctx context.Context, desk, remote string, r io.Reader, opts ...CallOption) (*CopyResult, error)

Upload writes r's bytes to remote, a file on the desk (relative paths start in the desk user's home, or a confined token's folder; a path that names an existing folder receives it under that folder's last name): `PUT /desks/{id}/files?path=`. At most FileLimit (256 MB). An io.ReadSeeker (an *os.File, a *bytes.Reader) is streamed; any other reader is read into memory first, as the API needs the size up front.

func (*Client) UploadFile

func (c *Client) UploadFile(ctx context.Context, local, desk, remote string, opts ...CallOption) (*CopyResult, error)

UploadFile copies a local file to the desk. A remote ending in `/` (or "") is a folder: the file keeps its name.

func (*Client) WaitJob

func (c *Client) WaitJob(ctx context.Context, desk, name string, timeout time.Duration, opts ...CallOption) (*JobWaitResult, error)

WaitJob waits until a job is no longer running, or timeout passes (0: answer at once; WaitForever: until it ends): the job as it ended, or (TimedOut) as it stands. The API holds one wait at most WaitMax, so a longer one waits again. A held answer that failed after its 200 began is its typed error, as any other failure.

func (*Client) Wake

func (c *Client) Wake(ctx context.Context, desk string, wait time.Duration, opts ...CallOption) (*WakeResult, error)

Wake rings the desk's doorbell and asks its LAN siblings to Wake-on-LAN it; with wait > 0, waits up to that long (at most 90 s) for it to come online and says whether it woke: `POST /desks/{id}/wake`. A desk with nothing to ring is ErrUnreachable, reason ReasonNoWakePath.

func (*Client) Webhooks

func (c *Client) Webhooks(ctx context.Context, opts ...CallOption) ([]Webhook, error)

Webhooks lists the account's webhook subscriptions (never their secrets): `GET /webhooks`.

type CommandError

type CommandError struct {
	// Err says how (class failed; ExitCode is the command's exit).
	Err    *Error
	Result *ExecResult
}

CommandError is Client.Exec with Check set when the command exited non-zero (or timed out): the result is in Result.

func (*CommandError) Error

func (e *CommandError) Error() string

Error is the *Error's message.

func (*CommandError) Is

func (e *CommandError) Is(target error) bool

Is matches ErrCommand and ErrFailed.

func (*CommandError) Unwrap

func (e *CommandError) Unwrap() error

Unwrap returns the *Error.

type CopyFailure

type CopyFailure struct {
	Path    string `json:"path"`
	Message string `json:"message"`
}

CopyFailure is one file that failed to copy.

type CopyResult

type CopyResult struct {
	Direction    string        `json:"direction"`
	Desk         string        `json:"desk"`
	Destination  string        `json:"destination"`
	Files        uint32        `json:"files"`
	Dirs         uint32        `json:"dirs"`
	Bytes        uint64        `json:"bytes"`
	ResumedBytes uint64        `json:"resumed_bytes"`
	Failed       []CopyFailure `json:"failed"`
	Seconds      float64       `json:"seconds"`
}

CopyResult is what a copy did: `cp --json`.

type Desk

type Desk struct {
	DeskID         string        `json:"desk_id"`
	Name           string        `json:"name,omitempty"`
	Online         bool          `json:"online"`
	OS             string        `json:"os,omitempty"`
	AppVersion     string        `json:"app_version,omitempty"`
	Owner          string        `json:"owner,omitempty"`
	Sources        []string      `json:"sources"`
	LastSeen       *int64        `json:"last_seen,omitempty"`
	SignalIdleSecs *uint64       `json:"signal_idle_secs,omitempty"`
	Anytime        *bool         `json:"anytime,omitempty"`
	Reachable      *bool         `json:"reachable,omitempty"`
	LastOK         *ReachSuccess `json:"last_ok,omitempty"`
	LastFailure    *ReachFailure `json:"last_failure,omitempty"`
	// OfflineSince is when it went offline (Unix seconds), with
	// OfflineReason (`closed`, `silent`, `error`, `updating`,
	// `server-restart`, `id-changed`, `unknown`) and its text.
	OfflineSince      *int64 `json:"offline_since,omitempty"`
	OfflineReason     string `json:"offline_reason,omitempty"`
	OfflineReasonText string `json:"offline_reason_text,omitempty"`
	OfflineDetail     string `json:"offline_detail,omitempty"`
	// Features is what it takes now, while online: `desk_op`, `desk_op_e2e`.
	Features []string `json:"features,omitempty"`
	// E2EPub is its end-to-end X25519 public key (base64url) while online
	// and able to open sealed operations.
	E2EPub string `json:"e2e_pub,omitempty"`
	// E2ERequired: its owner requires end-to-end encryption for API commands.
	E2ERequired bool `json:"e2e_required,omitempty"`
}

Desk is one desk as `GET /desks` lists it: the CLI's `devices --json` row plus the reach log's word on an offline one and its end-to-end key.

type DeskDetail

type DeskDetail struct {
	Desk
	Wake *WakeHints `json:"wake,omitempty"`
}

DeskDetail is `GET /desks/{id}`: one desk, with its wake hints.

type DeskEventData

type DeskEventData struct {
	Desk struct {
		DeskID     string `json:"desk_id"`
		Owner      string `json:"owner"`
		Reason     string `json:"reason"`
		ReasonText string `json:"reason_text"`
		Version    string `json:"version,omitempty"`
		// WokeAfterMS is desk.woke's: how long after the ring it came online.
		WokeAfterMS *int64 `json:"woke_after_ms,omitempty"`
	} `json:"desk"`
}

DeskEventData is the data of desk.online, desk.offline and desk.woke.

type DeskList

type DeskList struct {
	Devices  []Desk    `json:"devices"`
	Sources  []string  `json:"sources"`
	Notes    []string  `json:"notes"`
	Identity *Identity `json:"identity,omitempty"`
}

DeskList is `GET /desks`: the CLI's `devices --json` object.

type DiskStat

type DiskStat struct {
	Mount   string `json:"mount"`
	TotalMB uint64 `json:"total_mb"`
	FreeMB  uint64 `json:"free_mb"`
}

DiskStat is one disk of a desk.

type E2EMode

type E2EMode string

E2EMode is the end-to-end encryption of desk operations on the hosted API.

const (
	// E2EAuto (default): sealed when the desk lists a key (or requires it);
	// else in the clear, with a warning once per desk.
	E2EAuto E2EMode = "auto"
	// E2ERequire: never in the clear; a desk without a key is woken and
	// asked again, else the call fails (ErrE2E) and nothing is sent.
	E2ERequire E2EMode = "require"
	// E2EOff: never sealed.
	E2EOff E2EMode = "off"
)

The modes.

type Error

type Error struct {
	// Class is the envelope's kind: one of the six ("" for the SDK's own
	// interrupted and local errors).
	Class Class
	// Kind is the SDK's finest kind (see ErrorKind).
	Kind ErrorKind
	// Message is what went wrong, for a person.
	Message string
	// Reason is the finer cause (`offline`, `rate_limited`, `e2e_required`,
	// `admin_not_via_api`, …), or "".
	Reason string
	// Desk is the desk the error concerned, when the API said.
	Desk string
	// Op is the request: `POST /desks/123456789/exec`.
	Op string
	// Status is the HTTP status of the failed request (0 when none was
	// answered; on a held answer, the status it would have had).
	Status int
	// RequestID is the request's id (`req_…`), to quote to support.
	RequestID string
	// RetryAfter is how long the API asked to wait before retrying (429).
	RetryAfter time.Duration
	// ExitCode is what gaiadesk-cli exits with for this error (254 refused,
	// 1 failed, 130 interrupted, 255 the rest), for CLI parity.
	ExitCode int
	// Body is the raw error body, when there was one.
	Body json.RawMessage
	// Err is the underlying cause (a network error, the context's error), or nil.
	Err error
	// contains filtered or unexported fields
}

Error is every failure the SDK returns: the API's error envelope (`{"error": {"kind", "message", "reason", "desk", "request_id"}}`), or the SDK's own in the same terms.

func AsError

func AsError(err error) *Error

AsError returns err's *Error (through CommandError and wrapping), or nil.

func (*Error) Error

func (e *Error) Error() string

Error says what went wrong, with the request and its id.

func (*Error) Is

func (e *Error) Is(target error) bool

Is matches the sentinel for the error's class (and ErrE2E, ErrInterrupted).

func (*Error) Temporary

func (e *Error) Temporary() bool

Temporary reports whether retrying the same call later may succeed: a rate limit, a busy desk, a desk that went away, a network failure.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the underlying cause.

type ErrorKind

type ErrorKind string

ErrorKind is the SDK's finest word on an error: the envelope's reason when it is one of the kinds below (so `offline` stays `offline`), else its Class. It matches the TypeScript and Python SDKs' `kind`.

const (
	KindUsage          ErrorKind = "usage"
	KindOffline        ErrorKind = "offline"
	KindUnknownDesk    ErrorKind = "unknown_desk"
	KindNotOnline      ErrorKind = "not_online"
	KindRefused        ErrorKind = "refused"
	KindNetwork        ErrorKind = "network"
	KindNotSignedIn    ErrorKind = "not_signed_in"
	KindTimeout        ErrorKind = "timeout"
	KindConnectionLost ErrorKind = "connection_lost"
	KindLocal          ErrorKind = "local"
	KindFailed         ErrorKind = "failed"
	KindInterrupted    ErrorKind = "interrupted"
	KindProtocol       ErrorKind = "protocol"
	KindUnreachable    ErrorKind = "unreachable"
)

The SDK's kinds.

type ExecExit

type ExecExit struct {
	Exit       int       `json:"exit"`
	RemoteCode *int      `json:"remote_code"`
	DurationMS uint64    `json:"duration_ms"`
	Desk       string    `json:"desk"`
	Route      string    `json:"route,omitempty"`
	Mode       string    `json:"mode,omitempty"`
	Shell      string    `json:"shell,omitempty"`
	TimedOut   bool      `json:"timed_out"`
	Notes      []string  `json:"notes"`
	Error      *CliError `json:"error"`
}

ExecExit is the end of a streamed run, without its output.

type ExecRequest

type ExecRequest struct {
	// Command is ONE command line, given to the desk's shell verbatim.
	Command string
	// Argv is an argument vector instead: each entry quoted for the shell,
	// so the program receives exactly it (ShellNone: run directly).
	Argv []string
	// Shell runs the command (default: the desk's own).
	Shell Shell
	// Cwd is the directory it runs in (relative: from the desk user's home,
	// or a confined token's folder).
	Cwd string
	// Env are environment variables for the command (never logged by the
	// server or the desk: at most their names).
	Env map[string]string
	// Stdin is read whole and sent up front, as text (UTF-8). The API takes
	// no stdin while the command runs.
	Stdin io.Reader
	// Timeout stops the command after this long (rounded up to seconds;
	// 0: the API's limit, 15 minutes).
	Timeout time.Duration
	// Check makes a non-zero exit (or a timeout) a *CommandError.
	Check bool
}

ExecRequest is one command to run on a desk (the API's ExecSpec).

type ExecResult

type ExecResult struct {
	Exit       int       `json:"exit"`
	RemoteCode *int      `json:"remote_code"`
	Stdout     string    `json:"stdout"`
	Stderr     string    `json:"stderr"`
	DurationMS uint64    `json:"duration_ms"`
	Desk       string    `json:"desk"`
	Route      string    `json:"route,omitempty"`
	Mode       string    `json:"mode,omitempty"`
	Shell      string    `json:"shell,omitempty"`
	TimedOut   bool      `json:"timed_out"`
	Truncated  bool      `json:"truncated"`
	Notes      []string  `json:"notes"`
	Error      *CliError `json:"error"`
}

ExecResult is how a command ended: `exec --json`. Exit is what gaiadesk-cli exits with (the command's code, 124 timed out, 130 interrupted, 254 refused); Error is nil when the command ran and ended on its own.

type Exit

type Exit struct {
	// ExitCode is what gaiadesk-cli would exit with: the command's own code
	// (exec), 0 for a log follow that ended or was stopped, 130 when the
	// stream was closed or its context cancelled, 254 refused, 255 the rest.
	ExitCode int
	// StderrTail is the reason, when it failed (or what ended a log follow).
	StderrTail string
	// Result is how the command ended, without its output (exec streams).
	Result *ExecExit
	// Job is the job as it ended (a log follow's `end`).
	Job *Job
	// Error is why it never ran, was stopped or broke off, or nil.
	Error *CliError
}

Exit is how a stream ended.

func (*Exit) Err

func (e *Exit) Err() error

Err is the exit's Error as a typed *Error, or nil. (A command that ran and exited non-zero is not an error: see ExitCode.)

type FingerprintMismatchError

type FingerprintMismatchError struct {
	// Err is the error (class unreachable, reason fingerprint_mismatch).
	Err *Error
	// Expected is the pinned fingerprint, Actual the one the server
	// presented (`ab:cd:…`).
	Expected, Actual string
}

FingerprintMismatchError is the LAN gateway's certificate not matching the pinned fingerprint: it is not the desk you pinned. Do not proceed.

func (*FingerprintMismatchError) Error

func (e *FingerprintMismatchError) Error() string

Error is the *Error's message.

func (*FingerprintMismatchError) Is

func (e *FingerprintMismatchError) Is(target error) bool

Is matches ErrFingerprintMismatch and ErrUnreachable.

func (*FingerprintMismatchError) Unwrap

func (e *FingerprintMismatchError) Unwrap() error

Unwrap returns the *Error.

type Identity

type Identity struct {
	Source  string `json:"source"`
	Account string `json:"account,omitempty"`
}

Identity is who the caller is: `{source, account}`.

type Job

type Job struct {
	Name        string     `json:"name"`
	Command     string     `json:"command"`
	State       string     `json:"state"`
	StartedAtMS uint64     `json:"started_at_ms"`
	EndedAtMS   *uint64    `json:"ended_at_ms,omitempty"`
	ExitCode    *int       `json:"exit_code,omitempty"`
	PID         *uint32    `json:"pid,omitempty"`
	Reason      string     `json:"reason,omitempty"`
	By          string     `json:"by,omitempty"`
	LogBytes    uint64     `json:"log_bytes,omitempty"`
	Limits      *JobLimits `json:"limits,omitempty"`
	Enforcement []string   `json:"enforcement,omitempty"`
}

Job is a background job.

type JobFinishedData

type JobFinishedData struct {
	Desk struct {
		DeskID string `json:"desk_id"`
		Owner  string `json:"owner"`
	} `json:"desk"`
	Job Job `json:"job"`
}

JobFinishedData is the data of job.finished.

type JobLimits

type JobLimits struct {
	Priority   string  `json:"priority,omitempty"`
	CPUPercent *uint32 `json:"cpu_percent,omitempty"`
	MemMB      *uint64 `json:"mem_mb,omitempty"`
	KeepAwake  *bool   `json:"keep_awake,omitempty"`
}

JobLimits are a job's resource caps.

type JobLogs

type JobLogs struct {
	Job    *Job   `json:"job,omitempty"`
	Output string `json:"output"`
}

JobLogs is a job and the end of its output.

type JobRequest

type JobRequest struct {
	// Name names the job: letters, digits, . _ - (not starting with -).
	Name string
	// Command is the command line (or Argv its pieces).
	Command string
	Argv    []string
	// Cwd is the directory the job starts in on the desk.
	Cwd string
	// Shell runs the command (default: `sh -c` on macOS/Linux, `cmd /c` on
	// Windows; never ShellNone or ShellDefault).
	Shell Shell
	// Env are environment variables for the job (never logged by the desk).
	Env map[string]string
	// Priority is "low", "normal" or "high".
	Priority string
	// CPUPercent caps its share of the whole machine, 1 to 100 (0: none).
	CPUPercent int
	// MemMB caps its memory in megabytes (0: none).
	MemMB uint64
	// KeepAwake keeps the desk awake while it runs (nil: the desk's default).
	KeepAwake *bool
}

JobRequest starts a background job (the API's JobSpec, as `gaiadesk-cli run --detach`).

type JobWaitResult

type JobWaitResult struct {
	Job      Job  `json:"job"`
	TimedOut bool `json:"timed_out"`
}

JobWaitResult is the job as it ended or, TimedOut, as it stands, still running. Its ExitCode is a result, not an error.

type LogsOptions

type LogsOptions struct {
	// Tail is the last this many bytes (0: all the desk keeps).
	Tail int
}

LogsOptions says how much of a job's output to read.

type MintResult

type MintResult struct {
	Tokens []MintedToken `json:"tokens"`
}

MintResult is Client.CreateToken: one token per desk.

type MintedToken

type MintedToken struct {
	Desk   string    `json:"desk"`
	Token  TokenInfo `json:"token"`
	Secret string    `json:"secret"`
}

MintedToken is one desk's new token and its secret (shown once).

type Option

type Option func(*config)

Option configures a Client.

func WithAdminToken

func WithAdminToken(t string) Option

WithAdminToken sets the desk's local admin token (`gdlocal_…`; NewLocal; default: read from its file on each request).

func WithBaseURL

func WithBaseURL(u string) Option

WithBaseURL sets the API's base URL (default DefaultBaseURL), for a local signal server or a staging API.

func WithDeskToken

func WithDeskToken(t string) Option

WithDeskToken sets the scoped agent token (`gdagt_…`) sent as X-GaiaDesk-Desk-Token with every desk operation; the desk verifies it. A call may give its own with UseDeskToken.

func WithE2E

func WithE2E(mode E2EMode) Option

WithE2E sets end-to-end encryption of desk operations (hosted API only): E2EAuto (default), E2ERequire or E2EOff.

func WithE2EKeys

func WithE2EKeys(keys map[string]string) Option

WithE2EKeys pins desk keys, {deskID: e2e_pub (base64url)}: a different key from the server is refused, and a pinned key seals while the server lists none.

func WithEnv

func WithEnv(env map[string]string) Option

WithEnv sets the environment the local transport's defaults are read from (GAIADESK_API_DIR, GAIADESK_API_PIPE, USERNAME; default: this process's).

func WithHTTPClient

func WithHTTPClient(h *http.Client) Option

WithHTTPClient sets the http.Client of the hosted API transport (default: one of the SDK's own). Do not give it a Timeout: streams and held waits last long; use contexts. The SDK's response and idle timeouts hold for it too.

func WithIdleTimeout added in v0.1.1

func WithIdleTimeout(d time.Duration) Option

WithIdleTimeout sets the longest silence while reading an answer's body (a JSON result, a download, an event stream, a held wait): a limit on each read, not on the whole body, so a long download that keeps flowing never times out (default DefaultIdleTimeout; 0: no limit). Exceeded: ErrConnectionLost, kind timeout (a Stream ends with it in its Exit); it is not retried.

func WithResponseTimeout added in v0.1.1

func WithResponseTimeout(d time.Duration) Option

WithResponseTimeout sets the longest wait for an answer to begin (its status and headers), sending the request (and its body) included (default DefaultResponseTimeout; 0: no limit). Exceeded: ErrUnreachable, kind timeout; it is not retried.

func WithRetry

func WithRetry(p RetryPolicy) Option

WithRetry sets the retry policy (default DefaultRetry). NoRetry turns retries off.

func WithSocketPath

func WithSocketPath(p string) Option

WithSocketPath sets the local API's socket path or pipe name (NewLocal; default: see LocalSocketPath and LocalPipeName).

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent sets the User-Agent (default `gaiadesk-go/<Version>`).

func WithWarningHandler

func WithWarningHandler(f func(message string)) Option

WithWarningHandler sets where the SDK's warnings go (default: the log package's standard logger).

type ReachEvent

type ReachEvent struct {
	At         int64  `json:"at"`
	Online     bool   `json:"online"`
	Reason     string `json:"reason"`
	ReasonText string `json:"reason_text"`
	Detail     string `json:"detail,omitempty"`
	Version    string `json:"version,omitempty"`
}

ReachEvent is one online/offline transition of a desk.

type ReachFailure

type ReachFailure struct {
	At      int64  `json:"at"`
	Kind    string `json:"kind"`
	Message string `json:"message"`
}

ReachFailure is a desk's last failed connection, as the CLI keeps it.

type ReachLog

type ReachLog struct {
	DeskID string       `json:"desk_id"`
	Since  int64        `json:"since"`
	Events []ReachEvent `json:"events"`
}

ReachLog is `GET /desks/{id}/reach`: transitions, newest first.

type ReachOptions

type ReachOptions struct {
	// Since is the oldest transition (default seven days ago; the log keeps
	// thirty).
	Since time.Time
	// Limit is the most transitions, newest first (default 200, at most 1000).
	Limit int
}

ReachOptions bound a desk's reach log.

type ReachSuccess

type ReachSuccess struct {
	At        int64   `json:"at"`
	Route     string  `json:"route"`
	ConnectMS *uint64 `json:"connect_ms,omitempty"`
	RTTMS     *uint64 `json:"rtt_ms,omitempty"`
}

ReachSuccess is a desk's last successful connection, as the CLI keeps it.

type ResponseInfo

type ResponseInfo struct {
	// Status is the HTTP status.
	Status int
	// RequestID is the request's id (`req_…`), to quote to support.
	RequestID string
	// RateLimitLimit, RateLimitRemaining and RateLimitReset are the key's
	// budget (requests per window, left, seconds until it resets); -1 when
	// the response did not say.
	RateLimitLimit, RateLimitRemaining, RateLimitReset int
	// IdempotentReplayed: this answer is a stored one, replayed for a
	// repeated Idempotency-Key.
	IdempotentReplayed bool
	// Held: the 200 was sent before its outcome was known (a held wait).
	Held bool
	// Sealed: the operation went end-to-end encrypted.
	Sealed bool
}

ResponseInfo is what a response's headers said.

type RetryPolicy

type RetryPolicy struct {
	// MaxAttempts is the most tries in all: 3 (two retries) by default; 1
	// (or 0) turns retries off.
	MaxAttempts int
	// BaseDelay is the first backoff (250 ms by default); each next one
	// doubles, up to MaxDelay.
	BaseDelay time.Duration
	// MaxDelay caps a backoff (8 s by default).
	MaxDelay time.Duration
	// MaxRetryWait is the longest Retry-After waited (60 s by default; 0
	// also means 60 s). A longer one is returned as the error at once.
	MaxRetryWait time.Duration
}

RetryPolicy says which failed calls the SDK tries again, and when. The rule (the same in every GaiaDesk SDK): a request is sent again only when that cannot run anything twice.

  • The connection was never made (DNS, refused, the TLS handshake, a missing socket or pipe): any method; nothing was sent. A connect that times out, or a certificate that does not verify, is not retried.
  • The connection was lost after sending, or the answer was 502, 503 or 504: GETs only. A 503 saying the API or desk operations are switched off is not retried.
  • 429 (`rate_limited`, `desk_busy`) and 409 `idempotency_key_in_flight`: any method; the server refused it before acting.

Timeouts are never retried, nor anything whose answer has begun; an Idempotency-Key does not make a call retryable. 429 and 503 wait for Retry-After (one longer than MaxRetryWait is not waited: the error carries it); otherwise the wait is min(MaxDelay, BaseDelay·2ⁿ) times a random 0.5–1.0. Desk operations sealed end to end are sealed afresh for each try.

type Revoked

type Revoked struct {
	Revoked         string `json:"revoked"`
	StoppedSessions uint32 `json:"stopped_sessions"`
}

Revoked is `DELETE /desks/{id}/tokens/{token_id}`.

type Shell

type Shell string

Shell is the shell a command runs under.

const (
	ShellDefault    Shell = "default"
	ShellNone       Shell = "none"
	ShellSh         Shell = "sh"
	ShellBash       Shell = "bash"
	ShellZsh        Shell = "zsh"
	ShellCmd        Shell = "cmd"
	ShellPwsh       Shell = "pwsh"
	ShellPowerShell Shell = "powershell"
)

The shells. ShellDefault is the desk's own (login shell on macOS/Linux, cmd.exe on Windows); ShellNone runs the first argument as the program. ShellPowerShell is the same as ShellPwsh (sent as `pwsh`).

type StatsReport

type StatsReport struct {
	Desk        string     `json:"desk"`
	Hostname    string     `json:"hostname"`
	OS          string     `json:"os"`
	OSVersion   string     `json:"os_version,omitempty"`
	CPUPercent  float64    `json:"cpu_percent"`
	CPUs        uint32     `json:"cpus"`
	Load        []float64  `json:"load,omitempty"`
	MemTotalMB  uint64     `json:"mem_total_mb"`
	MemFreeMB   uint64     `json:"mem_free_mb"`
	UptimeSecs  uint64     `json:"uptime_secs"`
	JobsRunning uint32     `json:"jobs_running"`
	Disks       []DiskStat `json:"disks,omitempty"`
}

StatsReport is a desk's figures: `stats --json`.

type Stream

type Stream struct {
	// contains filtered or unexported fields
}

Stream is a running command (ExecStream) or a log follow (FollowJobLogs): its output as it comes (Recv, Chunks, Copy), then its Exit. Recv is not safe for concurrent use; Close may be called from any goroutine.

func (*Stream) Chunks

func (s *Stream) Chunks() iter.Seq[Chunk]

Chunks iterates over the output; the iteration ends with the stream (Exit then says how).

func (*Stream) Close

func (s *Stream) Close() error

Close stops the stream: the request is closed (the server stops the command, or stops following the job). The Exit is 130 interrupted unless it had already ended.

func (*Stream) Copy

func (s *Stream) Copy(stdout, stderr io.Writer) (*Exit, error)

Copy writes the output to stdout and stderr (either may be nil to discard) until the stream ends, and returns its Exit. An error writing stops the stream and is returned.

func (*Stream) Exit

func (s *Stream) Exit() *Exit

Exit is how the stream ended, or nil while it runs.

func (*Stream) Recv

func (s *Stream) Recv() (Chunk, error)

Recv returns the next chunk of output; io.EOF once the stream has ended (Exit then says how).

func (*Stream) Wait

func (s *Stream) Wait() *Exit

Wait reads (and discards) the rest of the output and returns the Exit.

type SupportSession

type SupportSession struct {
	ID               string         `json:"id"`
	State            string         `json:"state"`
	Mode             string         `json:"mode"`
	Customer         map[string]any `json:"customer"`
	CustomerPresent  bool           `json:"customer_present"`
	CustomerVerified bool           `json:"customer_verified"`
	JoinCode         string         `json:"join_code"`
	JoinURL          string         `json:"join_url"`
	DeskID           string         `json:"desk_id,omitempty"`
	Origin           string         `json:"origin,omitempty"`
	Owner            string         `json:"owner"`
	CreatedAt        int64          `json:"created_at"`
	ExpiresAt        int64          `json:"expires_at"`
	JoinedAt         *int64         `json:"joined_at,omitempty"`
	JoinedBy         string         `json:"joined_by,omitempty"`
	EndedAt          *int64         `json:"ended_at,omitempty"`
	EndReason        string         `json:"end_reason,omitempty"`
}

SupportSession is a support session for the web embed SDK.

type SupportSessionCreated

type SupportSessionCreated struct {
	SupportSession
	EmbedToken string `json:"embed_token"`
}

SupportSessionCreated is a new session with its embed token (shown once).

type SupportSessionEventData

type SupportSessionEventData struct {
	SupportSession SupportSession `json:"support_session"`
}

SupportSessionEventData is the data of support.session.joined and support.session.ended (never the join code, the desk or the embed token).

type SupportSessionFilter

type SupportSessionFilter struct {
	// All adds ended and expired sessions to the open ones.
	All bool
	// Limit is the most sessions (default 50, at most 200).
	Limit int
}

SupportSessionFilter narrows the support sessions listed.

type SupportSessionRequest

type SupportSessionRequest struct {
	// Mode is SupportView (default) or SupportCobrowse.
	Mode string
	// Customer is who the customer is, as the company knows them: at most 16
	// fields of short strings, numbers or booleans (never in the audit trail).
	Customer map[string]any
	// ExpiresIn ends the session after this long (60 s to 24 h; default 1 h).
	ExpiresIn time.Duration
	// Origin is the page origin the embed must run on
	// (`https://app.example.com`; `http://` only for localhost). Leave it
	// out for the native (desktop) embed.
	Origin string
}

SupportSessionRequest creates a support session for the web embed SDK.

type TokenInfo

type TokenInfo struct {
	ID          string   `json:"id"`
	Label       string   `json:"label"`
	IssuedAtMS  uint64   `json:"issued_at_ms"`
	ExpiresAtMS uint64   `json:"expires_at_ms"`
	LastUsedMS  *uint64  `json:"last_used_ms,omitempty"`
	Scopes      []string `json:"scopes,omitempty"`
	Cwd         string   `json:"cwd,omitempty"`
	LowPriv     bool     `json:"low_priv,omitempty"`
	Revoked     bool     `json:"revoked,omitempty"`
}

TokenInfo is an agent token as the desk describes it (never its secret).

type TokenRequest

type TokenRequest struct {
	// Desks to mint a token on.
	Desks []string
	// Name names the token (required).
	Name string
	// Expires is its lifetime (rounded up to seconds; default 7 days).
	Expires time.Duration
	// Scopes it holds (default DefaultScopes).
	Scopes []string
	// Cwd confines its work to this directory on the desk.
	Cwd string
	// LowPriv runs its work as the desk's low-privilege agent user, or refuses.
	LowPriv bool
}

TokenRequest mints scoped agent tokens, one per desk, under one name.

type Transport

type Transport string

Transport is how a Client reaches a /v1 API.

const (
	// TransportAPI is the hosted API (New).
	TransportAPI Transport = "api"
	// TransportLocal is the desk's own API over its Unix socket or named
	// pipe, for code running on the desk (NewLocal).
	TransportLocal Transport = "local"
	// TransportLAN is a desk's LAN gateway over pinned TLS (NewLAN).
	TransportLAN Transport = "lan"
)

The transports.

type WakeHints

type WakeHints struct {
	DoorbellSockets int  `json:"doorbell_sockets"`
	LANWake         bool `json:"lan_wake"`
}

WakeHints say how a desk could be woken now.

type WakeResult

type WakeResult struct {
	DeskID        string `json:"desk_id"`
	Online        bool   `json:"online"`
	Woke          bool   `json:"woke"`
	AlreadyOnline bool   `json:"already_online"`
	Rang          struct {
		Doorbell   int `json:"doorbell"`
		LANHelpers int `json:"lan_helpers"`
	} `json:"rang"`
	WaitedMS int64 `json:"waited_ms"`
}

WakeResult is `POST /desks/{id}/wake`.

type Webhook

type Webhook struct {
	ID          string   `json:"id"`
	URL         string   `json:"url"`
	Events      []string `json:"events"`
	Description string   `json:"description"`
	CreatedAt   int64    `json:"created_at"`
}

Webhook is a webhook subscription (never its secret).

type WebhookCreated

type WebhookCreated struct {
	Webhook
	Secret string `json:"secret"`
}

WebhookCreated is a new subscription with its signing secret (shown once).

type WebhookEvent

type WebhookEvent struct {
	ID      string          `json:"id"`
	Type    string          `json:"type"`
	Created int64           `json:"created"`
	Data    json.RawMessage `json:"data"`
}

WebhookEvent is one delivery. ID is stable across retries: de-duplicate by it. Data's shape depends on Type: DeskEventData for desk.*, JobFinishedData for job.finished, SupportSessionEventData for support.session.*.

func ParseWebhook

func ParseWebhook(secret, signature string, body []byte) (*WebhookEvent, error)

ParseWebhook verifies a delivery (VerifyWebhook, against now) and reads it.

func (*WebhookEvent) DecodeData

func (e *WebhookEvent) DecodeData(v any) error

DecodeData unmarshals the event's data into v (one of the *Data types).

type WebhookRequest

type WebhookRequest struct {
	// URL is an `https://` URL on the public internet.
	URL string
	// Events to deliver (at least one): EventDeskOnline, ….
	Events []string
	// Description, at most 200 characters.
	Description string
}

WebhookRequest subscribes an HTTPS endpoint to events.

Directories

Path Synopsis
examples
exec command
Run a command on a desk, then stream another one's output as it runs.
Run a command on a desk, then stream another one's output as it runs.
files command
Copy a file to a desk and back.
Copy a file to a desk and back.
jobs command
Start a background job, follow its log, and wait for it.
Start a background job, follow its log, and wait for it.
local command
On a desk: drive it through the GaiaDesk app's own API (no server, no internet), with the desk's local admin token found beside the socket.
On a desk: drive it through the GaiaDesk app's own API (no server, no internet), with the desk's local admin token found beside the socket.
webhook command
Receive GaiaDesk webhook deliveries, verifying each one.
Receive GaiaDesk webhook deliveries, verifying each one.
internal
e2e
Package e2e is the crypto of end-to-end encrypted desk operations, v1: the caller's side (seal a request, seal input, open events) and, for tests and mocks, the desk's side (open a request, seal events, open input).
Package e2e is the crypto of end-to-end encrypted desk operations, v1: the caller's side (seal a request, seal input, open events) and, for tests and mocks, the desk's side (open a request, seal events, open input).
mockapi
Package mockapi is a mock of GaiaDesk's /v1 API AND the desks behind it, for the SDK's tests: every route of the contract, plaintext and end-to-end encrypted desk operations (each desk holds an X25519 key, opens sealed requests, runs canned operations and seals its events back), held waits, SSE written in pieces, error envelopes, rate limits, and the desk-served variants (local: the admin token; lan: agent tokens only).
Package mockapi is a mock of GaiaDesk's /v1 API AND the desks behind it, for the SDK's tests: every route of the contract, plaintext and end-to-end encrypted desk operations (each desk holds an X25519 key, opens sealed requests, runs canned operations and seals its events back), held waits, SSE written in pieces, error envelopes, rate limits, and the desk-served variants (local: the admin token; lan: agent tokens only).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL