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.
| Operation | Description |
|---|---|
| spinup | Register and start an agent. Auto-assigns next free port in 8100–8200. Accepts optional cmd and explicit port. |
| spindown | Stop the agent process and remove it from Gretl, by name. |
| listAgents | Return all ports in the Agents group with status and port number. |
| agentLogs | Fetch 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")