Docs SDKs Reference

SDK reference

Same primitives in every language: register, resolve, start, stop, waitFor, and events. Each SDK is a thin client over the local daemon at http://127.0.0.1:27892 — there are no remote services, no API keys required for local operations.

As of v1.1.0 all SDKs also include the agent gateway — four methods that spin up, tear down, list, and tail logs for AI agent processes running on dedicated ports (8100–8200). See Agent gateway below.

Common shape

Every SDK exposes a single top-level gr object (or module) with the same surface:

// pseudo-typescript
interface Gretl {
  register(svc: ServiceSpec): Promise<Service>
  resolve(name: string): Promise<{ url: string, port: number }>
  url(name: string): string             // sync, throws if unknown
  start(name: string): Promise<Service>
  stop(name: string): Promise<void>
  ensure(name: string): Promise<Service>     // start if not running
  waitFor(name: string, opts?): Promise<Service>
  status(name?): Promise<Service[]>
}

Node — @gretl/sdk

# install
npm i @gretl/sdk

register

gr.register(spec: ServiceSpec) → Promise<Service>

Register a service with the daemon. Idempotent — calling register for an existing name updates its spec.

Field Type Required Notes
name string yes Globally unique within this machine.
port number yes Listening port. Gretl enforces uniqueness.
cmd string no Shell command to launch the service.
cwd string no Working directory for cmd. Defaults to process.cwd().
env Record<string,string> no Extra env vars merged into the child process.
group string no Group name (e.g. "@jobs") for batch ops.
ready string | (() ⇒ bool) no Health check URL or predicate. Used by waitFor.
dependsOn string[] no Service names that must be running first.
import { gr } from '@gretl/sdk';

await gr.register({
  name: 'jobs-server',
  port: 7401,
  cmd:  'npm run dev:server',
  group: '@jobs',
  ready: 'http://localhost:7401/health',
});

resolve / url

gr.resolve(name: string) → Promise<{ url, port }> gr.url(name: string) → string // sync

Look up a service's URL. Use resolve when the daemon may need to start it; use url in hot paths where you've already ensured it.

const { url } = await gr.resolve('jobs-server');
const res = await fetch(`${url}/health`);

ensure / waitFor

gr.ensure(name: string) → Promise<Service> gr.waitFor(name: string, opts?: { timeout }) → Promise<Service>

ensure is the workhorse — it starts the service if needed, waits for the ready-check, and returns once it's healthy. waitFor only waits; it doesn't start.

// in your test setup
beforeAll(async () => {
  await gr.ensure('postgres');
  await gr.ensure('jobs-server');
});

events

gr.events() → AsyncIterable<Event>

Subscribe to lifecycle events. Backed by the SSE stream at /v1/events.

for await (const ev of gr.events()) {
  if (ev.type === 'service.crashed') {
    console.error(`${ev.name} died:`, ev.exitCode);
  }
}

Kubernetes — Node

K8s methods talk to the Gretl cloud API (api.gretl.dev). Set GR_TOKEN env var or pass it as a constructor option.

import { gr } from '@gretl/sdk';

// List clusters and workloads
const clusters = await gr.k8s.clusters();
const workloads = await gr.k8s.workloads({ clusterId: clusters[0].id });

// Sleep / wake a deployment
await gr.k8s.sleep(workloads[0].id);
await gr.k8s.wake(workloads[0].id);

// Open a port-forward tether (returns localhost:PORT)
const tether = await gr.k8s.tether(workloads[0].id, { containerPort: 8000 });
console.log(`Access at localhost:${tether.local_port}`);

// Sync workloads from cluster
await gr.k8s.sync(clusters[0].id);

Python — gretl

# install
pip install gretl
from gretl import gr

# Register a port (creates or updates)
gr.add(9001, name="Worker", cmd="node worker.js", group="Backend")

# Start / stop / list
gr.start("Worker")
gr.stop("Worker")
ports = gr.ports()

# Wait for a port to come up (e.g. in tests)
gr.wait_for_active("Worker", timeout=30)

Kubernetes — Python

from gretl import gr

# List clusters and workloads
clusters = gr.k8s.clusters()
workloads = gr.k8s.workloads(cluster_id=clusters[0]["id"])

# Sleep / wake
gr.k8s.sleep(workloads[0]["id"])
gr.k8s.wake(workloads[0]["id"])

# Open a port-forward tether
tether = gr.k8s.tether(workloads[0]["id"], container_port=8000)
print(f"Access at localhost:{tether['local_port']}")

# Sync workloads from cluster
gr.k8s.sync(clusters[0]["id"])

Go — gretl-go

// install
go get github.com/slowdutch/gretl-sdks/go
import "github.com/slowdutch/gretl-sdks/go"

client := gretl.New()

// Register a port (creates or updates)
client.Add(9001, gretl.AddOptions{
    Name:  "Worker",
    Cmd:   "node worker.js",
    Group: "Backend",
})

// Start / stop / list
client.Start("Worker")
client.Stop("Worker")
ports, _ := client.Ports()

Kubernetes — Go

import gretl "github.com/slowdutch/gretl-sdks/go"

client := gretl.New(gretl.WithToken(os.Getenv("GR_TOKEN")))

// List clusters and workloads
clusters, _ := client.K8s.Clusters()
workloads, _ := client.K8s.Workloads(gretl.WorkloadOpts{ClusterID: clusters[0].ID})

// Sleep / wake
client.K8s.Sleep(workloads[0].ID)
client.K8s.Wake(workloads[0].ID)

// Open a port-forward tether
tether, _ := client.K8s.Tether(workloads[0].ID, 8000)
fmt.Printf("Access at localhost:%d\n", tether.LocalPort)

// Sync workloads
client.K8s.Sync(clusters[0].ID)

Ruby — gretl

# install
gem install gretl
require "gretl"

# Register a port (creates or updates)
Gretl::PA.add(9001, name: "Worker", cmd: "node worker.js", group: "Backend")

# Start / stop / list
Gretl::PA.start("Worker")
Gretl::PA.stop("Worker")
ports = Gretl::PA.ports

Kubernetes — Ruby

require "gretl"

client = Gretl::Client.new(token: ENV["GR_TOKEN"])

# List clusters and workloads
clusters = client.k8s.clusters
workloads = client.k8s.workloads(cluster_id: clusters.first[:id])

# Sleep / wake
client.k8s.sleep(workloads.first[:id])
client.k8s.wake(workloads.first[:id])

# Open a port-forward tether
tether = client.k8s.tether(workloads.first[:id], container_port: 8000)
puts "Access at localhost:#{tether[:local_port]}"

# Sync workloads from cluster
client.k8s.sync(clusters.first[:id])

Agent gateway

All four SDKs expose the same four operations. Agents are ports tagged Agents and auto-assigned ports in 8100–8200. The daemon wakes sleeping agents on demand — no manual polling required.

OperationDescription
spinupRegister and start an agent. Auto-assigns next free port in 8100–8200. Accepts optional cmd and explicit port.
spindownStop the agent process and remove it from Gretl, by name.
listAgentsReturn all ports in the Agents group with status and port number.
agentLogsFetch the last N stdout lines for a named agent (default 50).

Node — @gretl/sdk

import { gr } from '@gretl/sdk';

// Spin up — port auto-assigned (:8100)
const agent = await gr.spinup('coder-bot', 'python agent.py');
console.log(agent.port); // 8100

// List all agents
const agents = await gr.listAgents();

// Fetch the last 100 log lines
const lines = await gr.agentLogs('coder-bot', 100);

// Stop and remove
await gr.spindown('coder-bot');

Python — gretl

from gretl import gr

# Spin up — port auto-assigned (:8100)
agent = gr.agent_spinup("coder-bot", cmd="python agent.py")
print(agent.port)  # 8100

# List all agents
agents = gr.agent_list()

# Fetch the last 100 log lines
lines = gr.agent_logs("coder-bot", lines=100)

# Stop and remove
gr.agent_spindown("coder-bot")

Go — gretl-go

import gretl "github.com/slowdutch/gretl-sdks/go"

client := gretl.New()

// Spin up — port auto-assigned (:8100)
agent, _ := client.AgentSpinup("coder-bot", gretl.AgentSpinupOpts{
    Cmd: "python agent.py",
})
fmt.Println(agent.Port) // 8100

// List all agents
agents, _ := client.AgentList()

// Fetch the last 100 log lines
lines, _ := client.AgentLogs("coder-bot", 100)

// Stop and remove
client.AgentSpindown("coder-bot")

Ruby — gretl

require "gretl"

# Spin up — port auto-assigned (:8100)
agent = Gretl::PA.agent_spinup("coder-bot", cmd: "python agent.py")
puts agent.port # 8100

# List all agents
agents = Gretl::PA.agent_list

# Fetch the last 100 log lines
lines = Gretl::PA.agent_logs("coder-bot", lines: 100)

# Stop and remove
Gretl::PA.agent_spindown("coder-bot")