@gitterm/sdk 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -72,13 +72,27 @@ self-hosted.
72
72
  import { createGittermClient } from "@gitterm/sdk";
73
73
 
74
74
  const client = createGittermClient({
75
- serverUrl: "https://api.gitterm.dev",
76
75
  token: process.env.GITTERM_API_TOKEN,
77
76
  });
78
77
 
79
78
  const { workspaces } = await client.workspaces.list();
80
79
  ```
81
80
 
81
+ The SDK deliberately exposes two clients. `createGittermClient()` uses a user API token and
82
+ can manage the user's workspaces. `createGittermWorkspaceClient()` uses the scoped identity
83
+ injected into a GitTerm workspace and can inspect only that workspace and its ports:
84
+
85
+ ```ts
86
+ import { createGittermWorkspaceClient } from "@gitterm/sdk";
87
+
88
+ const workspace = createGittermWorkspaceClient();
89
+ const self = await workspace.self.get();
90
+ const preview = await workspace.ports.open(3000, { name: "app" });
91
+ ```
92
+
93
+ The workspace client never reads the CLI's saved account login and has no create, list,
94
+ pause, restart, or terminate operations.
95
+
82
96
  ### With the CLI's saved login
83
97
 
84
98
  ```ts
@@ -98,18 +112,100 @@ client.workspaces.ensureRunning(workspaceId, options?);
98
112
  client.workspaces.pause(workspaceId);
99
113
  client.workspaces.restart(workspaceId);
100
114
  client.workspaces.terminate(workspaceId);
101
- client.workspaces.createSandbox({
102
- idempotencyKey, repo, branch, baseCommit, checkoutRef,
103
- agent, provider, persistent, // provider is optional
115
+ client.workspaces.create({
116
+ repo: "https://github.com/acme/product",
104
117
  });
105
118
  client.catalog.agentTypes();
106
119
  client.catalog.cloudProviders();
107
- client.catalog.resolveSandboxDefaults({ agent, provider });
120
+ client.catalog.workspaceOptions();
121
+ ```
122
+
123
+ The server defaults the agent to `opencode`, selects the user's preferred provider,
124
+ uses that provider's default machine profile, and applies the provider's persistence policy.
125
+ Override only the placement decisions your integration cares about:
126
+
127
+ ```ts
128
+ await client.workspaces.create({
129
+ repo: "https://github.com/acme/product",
130
+ agent: "opencode",
131
+ setupCommands: ["npm install", "npm run generate"],
132
+ opencode: {
133
+ skills: [
134
+ {
135
+ name: "release-demo",
136
+ content: `---
137
+ name: release-demo
138
+ description: Record and publish a product release demo.
139
+ ---
140
+
141
+ Follow the repository's release-demo workflow.`,
142
+ },
143
+ ],
144
+ plugins: ["@acme/opencode-browser@1.2.3"],
145
+ },
146
+ provider: {
147
+ type: "exedev",
148
+ machine: { type: "profile", key: "content-rendering" },
149
+ },
150
+ });
151
+ ```
152
+
153
+ Setup commands run in order from the checked-out repository after the agent server is
154
+ ready. They do not delay workspace creation or stop the agent if they fail. Provider and
155
+ agent defaults configured by an administrator run first. Use
156
+ `client.workspaces.setupStatus(workspaceId)` or `waitForSetup(workspaceId)` to inspect them.
157
+ GitTerm persists the reported state and bounded log; a recovery copy also lives in the
158
+ repository's git-excluded `.gitterm/setup/` directory.
159
+
160
+ `provider` is a discriminated union, so TypeScript only offers `region` for providers
161
+ where GitTerm supports caller-selected placement. Machine keys are configured by admins
162
+ and returned by `client.catalog.workspaceOptions()`; raw CPU, memory, credentials, and
163
+ provider account configuration are never supplied by SDK callers.
164
+
165
+ This makes release automation a normal workspace task: create an OpenCode workspace,
166
+ run UI review or browser capture tools in the sandbox, upload the resulting media, update
167
+ the changelog in the checked-out repository, then terminate the workspace. Use an
168
+ `idempotencyKey` based on the release SHA when the workflow may be retried.
169
+
170
+ ### Agent runs
171
+
172
+ Runs use durable GitTerm IDs backed by the workspace's native OpenCode session. Reusing an
173
+ idempotency key with the same input returns the original run, and terminal results remain
174
+ available after the workspace is paused. Completion means the native session became idle;
175
+ it does not claim that a pull request, upload, or other product outcome succeeded.
176
+
177
+ ```ts
178
+ const { workspace } = await client.workspaces.create({
179
+ repo: "https://github.com/acme/product",
180
+ setupCommands: ["npm install", "npm run db:seed"],
181
+ });
182
+
183
+ const run = await client.runs.create({
184
+ workspaceId: workspace.id,
185
+ idempotencyKey: "onboarding-v2",
186
+ waitForSetup: true,
187
+ prompt: "Record the new onboarding flow and open a pull request adding it to the changelog.",
188
+ });
189
+
190
+ const completed = await client.runs.wait(workspace.id, run.id);
191
+ const messages = await client.runs.messages(workspace.id, run.id);
192
+ ```
193
+
194
+ Runs are isolated by default and can execute in parallel. To preserve conversational context,
195
+ continue a terminal run; continued runs sharing context must remain sequential:
196
+
197
+ ```ts
198
+ const next = await client.runs.create({
199
+ workspaceId: workspace.id,
200
+ idempotencyKey: "onboarding-tests-v1",
201
+ prompt: "Now add tests for that change.",
202
+ context: { type: "continue", runId: completed.id },
203
+ });
108
204
  ```
109
205
 
110
- `agent` is the unique enabled agent name (for example `"OpenCode"`).
111
- `provider` is the unique enabled provider name (for example `"E2B"`).
112
- Omit `provider` to use the preferred default sandbox provider.
206
+ Use `client.runs.cancel(workspaceId, runId)` to abort the current run. GitTerm keeps the
207
+ underlying OpenCode session private. For native session control, use
208
+ `workspaces.getRuntimeAccess()` and connect with the official OpenCode SDK.
113
209
 
114
210
  ### Errors
115
211
 
@@ -130,7 +226,7 @@ try {
130
226
  Workspace lifecycle failures are also exposed as `WorkspaceLifecycleError`, with stable
131
227
  `WORKSPACE_TERMINATED`, `WORKSPACE_NON_RECOVERABLE`, `WORKSPACE_START_TIMEOUT`, and
132
228
  `WORKSPACE_RESTART_FAILED` codes. General codes are
133
- `NOT_LOGGED_IN`, `UNAUTHORIZED`, `NOT_FOUND`, `FORBIDDEN`, `BAD_REQUEST`,
229
+ `NOT_LOGGED_IN`, `UNAUTHORIZED`, `NOT_FOUND`, `FORBIDDEN`, `BAD_REQUEST`, `CONFLICT`,
134
230
  `SERVER_ERROR`, and `NETWORK`.
135
231
 
136
232
  The package ships self-contained declarations from `dist`; TypeScript consumers do not
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AgentType, AuthStatus, CloudProvider, Workspace, WorkspaceCreateInput, WorkspaceCreateResult, WorkspaceEnsureRunningResult, WorkspaceListOptions, WorkspaceListResult, WorkspaceRestartResult, WorkspaceRuntimeAccess, WorkspacePauseResult, WorkspaceTerminateResult, SandboxDefaults, SandboxDefaultsInput } from "./types.js";
1
+ import type { AgentType, AgentRun, AgentRunCreateInput, AgentRunMessage, AuthStatus, CloudProvider, Workspace, WorkspaceCreateInput, WorkspaceCreateResult, WorkspaceEnsureRunningResult, WorkspaceListOptions, WorkspaceListResult, WorkspaceRestartResult, WorkspaceRuntimeAccess, WorkspacePauseResult, WorkspaceTerminateResult, WorkspaceCatalog, WorkspaceSetupStatus } from "./types.js";
2
2
  export type GittermClientOptions = {
3
3
  serverUrl?: string;
4
4
  token?: string;
@@ -22,7 +22,23 @@ export type GittermClient = {
22
22
  restart(workspaceId: string): Promise<WorkspaceRestartResult>;
23
23
  terminate(workspaceId: string): Promise<WorkspaceTerminateResult>;
24
24
  create(input: WorkspaceCreateInput): Promise<WorkspaceCreateResult>;
25
- createSandbox(input: WorkspaceCreateInput): Promise<WorkspaceCreateResult>;
25
+ setupStatus(workspaceId: string): Promise<WorkspaceSetupStatus>;
26
+ waitForSetup(workspaceId: string, options?: {
27
+ timeoutMs?: number;
28
+ pollIntervalMs?: number;
29
+ }): Promise<WorkspaceSetupStatus>;
30
+ };
31
+ runs: {
32
+ create(input: AgentRunCreateInput): Promise<AgentRun>;
33
+ get(workspaceId: string, runId: string): Promise<AgentRun>;
34
+ messages(workspaceId: string, runId: string): Promise<AgentRunMessage[]>;
35
+ cancel(workspaceId: string, runId: string): Promise<{
36
+ cancelled: boolean;
37
+ }>;
38
+ wait(workspaceId: string, runId: string, options?: {
39
+ timeoutMs?: number;
40
+ pollIntervalMs?: number;
41
+ }): Promise<AgentRun>;
26
42
  };
27
43
  catalog: {
28
44
  agentTypes(input?: {
@@ -34,7 +50,7 @@ export type GittermClient = {
34
50
  sandboxOnly?: boolean;
35
51
  nonSandboxOnly?: boolean;
36
52
  }): Promise<CloudProvider[]>;
37
- resolveSandboxDefaults(input: SandboxDefaultsInput): Promise<SandboxDefaults>;
53
+ workspaceOptions(): Promise<WorkspaceCatalog>;
38
54
  };
39
55
  };
40
56
  export declare function createGittermClient(options?: GittermClientOptions): GittermClient;
package/dist/errors.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type GittermErrorCode = "NOT_LOGGED_IN" | "UNAUTHORIZED" | "NOT_FOUND" | "FORBIDDEN" | "BAD_REQUEST" | "SERVER_ERROR" | "NETWORK" | WorkspaceLifecycleErrorCode;
1
+ export type GittermErrorCode = "NOT_LOGGED_IN" | "UNAUTHORIZED" | "NOT_FOUND" | "FORBIDDEN" | "BAD_REQUEST" | "CONFLICT" | "SERVER_ERROR" | "NETWORK" | WorkspaceLifecycleErrorCode;
2
2
  export type WorkspaceLifecycleErrorCode = "WORKSPACE_TERMINATED" | "WORKSPACE_NON_RECOVERABLE" | "WORKSPACE_START_TIMEOUT" | "WORKSPACE_RESTART_FAILED";
3
3
  export declare class GittermError extends Error {
4
4
  readonly code: GittermErrorCode;
package/dist/index.d.ts CHANGED
@@ -4,5 +4,6 @@ export type { CliConfig } from "./config.js";
4
4
  export { loginWithDeviceCode } from "./device-login.js";
5
5
  export type { DeviceCodeInfo, LoginWithDeviceCodeOptions } from "./device-login.js";
6
6
  export { GittermError, WorkspaceLifecycleError } from "./errors.js";
7
+ export { createGittermWorkspaceClient, getWorkspaceEnvironment, type GittermWorkspaceClient, type WorkspaceClientOptions, type WorkspaceEnvironment, type WorkspacePort, type WorkspaceSelf, } from "./workspace-client.js";
7
8
  export type { GittermErrorCode, WorkspaceLifecycleErrorCode } from "./errors.js";
8
- export type { AgentType, AuthStatus, CloudProvider, SandboxDefaults, Workspace, WorkspaceCreateInput, WorkspaceCreateResult, WorkspaceEnsureRunningResult, WorkspaceHostingType, WorkspaceListOptions, WorkspaceListResult, WorkspaceRestartResult, WorkspaceRuntimeAccess, WorkspaceStatus, WorkspacePauseResult, WorkspaceTerminateResult, SandboxDefaultsInput, } from "./types.js";
9
+ export type { AgentRun, AgentRunCreateInput, AgentRunMessage, AgentRunStatus, AgentType, AgentKey, AuthStatus, BuiltInAgentKey, CloudProvider, ProviderKey, Workspace, WorkspaceCreateInput, WorkspaceCreateResult, WorkspaceCatalog, WorkspaceEnsureRunningResult, WorkspaceHostingType, WorkspaceListOptions, WorkspaceListResult, WorkspaceRestartResult, WorkspaceRuntimeAccess, WorkspaceStatus, WorkspaceSetupStatus, WorkspacePauseResult, WorkspaceTerminateResult, WorkspaceProviderSelection, } from "./types.js";
package/dist/index.js CHANGED
@@ -69,6 +69,36 @@ class WorkspaceLifecycleError extends GittermError {
69
69
  }
70
70
  }
71
71
 
72
+ // src/transport.ts
73
+ var LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
74
+ function normalizeServerUrl(value) {
75
+ let url;
76
+ try {
77
+ url = new URL(value);
78
+ } catch {
79
+ throw new Error(`Invalid GitTerm server URL: ${value}`);
80
+ }
81
+ if (url.username || url.password) {
82
+ throw new Error("GitTerm server URL must not contain credentials");
83
+ }
84
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname))) {
85
+ throw new Error("GitTerm server URL must use HTTPS (HTTP is allowed only for loopback)");
86
+ }
87
+ url.hash = "";
88
+ url.search = "";
89
+ return url.toString().replace(/\/$/, "");
90
+ }
91
+ function createNoRedirectFetch(fetchImpl = fetch) {
92
+ return async (input, init) => {
93
+ const response = await fetchImpl(input, { ...init, redirect: "manual" });
94
+ if (response.status >= 300 && response.status < 400) {
95
+ const location = response.headers.get("location");
96
+ throw new Error(location ? `GitTerm server redirects are not allowed: ${location}` : "GitTerm server redirects are not allowed");
97
+ }
98
+ return response;
99
+ };
100
+ }
101
+
72
102
  // src/client.ts
73
103
  function envValue(name) {
74
104
  const value = typeof process !== "undefined" ? process.env[name] : undefined;
@@ -76,12 +106,12 @@ function envValue(name) {
76
106
  }
77
107
  function resolveCredentials(options) {
78
108
  const config = !options.serverUrl || !options.token ? loadConfigSync(options.configPath) : null;
79
- const serverUrl = options.serverUrl ?? envValue("GITTERM_SERVER_URL") ?? config?.serverUrl;
109
+ const serverUrl = options.serverUrl ?? envValue("GITTERM_SERVER_URL") ?? config?.serverUrl ?? DEFAULT_GITTERM_SERVER_URL;
80
110
  const token = options.token ?? envValue("GITTERM_API_TOKEN") ?? config?.token;
81
111
  if (!serverUrl || !token) {
82
112
  throw new GittermError("NOT_LOGGED_IN", "Not logged in. Run: gitterm login");
83
113
  }
84
- return { serverUrl, token };
114
+ return { serverUrl: normalizeServerUrl(serverUrl), token };
85
115
  }
86
116
  function toTrpcUrl(serverUrl) {
87
117
  return new URL("/trpc", serverUrl).toString();
@@ -161,6 +191,8 @@ function mapTrpcCode(code) {
161
191
  return "FORBIDDEN";
162
192
  case "BAD_REQUEST":
163
193
  return "BAD_REQUEST";
194
+ case "CONFLICT":
195
+ return "CONFLICT";
164
196
  default:
165
197
  return "SERVER_ERROR";
166
198
  }
@@ -208,22 +240,13 @@ function createGittermClient(options = {}) {
208
240
  links: [
209
241
  httpBatchLink({
210
242
  url: toTrpcUrl(credentials.serverUrl),
211
- fetch: options.fetch,
243
+ fetch: createNoRedirectFetch(options.fetch),
212
244
  headers: () => ({ authorization: `Bearer ${credentials.token}` })
213
245
  })
214
246
  ]
215
247
  });
216
248
  const run = (operation) => runWithServer(credentials.serverUrl, operation);
217
- const createWorkspace = (input) => run(async () => {
218
- const { agent, provider, regionId, ...workspaceInput } = input;
219
- const defaults = await trpc.workspace.resolveSandboxDefaults.query({ agent, provider });
220
- const apiInput = {
221
- ...workspaceInput,
222
- agentTypeId: defaults.agentTypeId,
223
- cloudProviderId: defaults.cloudProviderId,
224
- regionId: regionId ?? defaults.regionId
225
- };
226
- const result = await trpc.workspace.createWorkspace.mutate(apiInput);
249
+ const normalizeCreateResult = (result) => {
227
250
  const workspace = normalizeWorkspace(result.workspace);
228
251
  if (!workspace)
229
252
  throw new GittermError("SERVER_ERROR", "Workspace creation failed");
@@ -241,7 +264,30 @@ function createGittermClient(options = {}) {
241
264
  providerKey: null
242
265
  };
243
266
  return { workspace, runtime };
267
+ };
268
+ const createWorkspace = (input) => run(async () => {
269
+ const result = await trpc.workspace.createWorkspace.mutate(input);
270
+ return normalizeCreateResult(result);
244
271
  });
272
+ const waitForWorkspaceSetup = async (workspaceId, waitOptions) => {
273
+ const timeoutMs = waitOptions?.timeoutMs ?? 10 * 60000;
274
+ const pollIntervalMs = waitOptions?.pollIntervalMs ?? 2000;
275
+ const deadline = Date.now() + timeoutMs;
276
+ while (true) {
277
+ const result = await trpc.workspace.getSetupStatus.query({ workspaceId });
278
+ if (result.status === "not_requested" || result.status === "succeeded")
279
+ return result;
280
+ if (result.status === "failed") {
281
+ const log = result.log?.trim();
282
+ throw new GittermError("BAD_REQUEST", `Workspace setup failed${result.exitCode === null ? "" : ` with exit code ${result.exitCode}`}${log ? `
283
+ ${log}` : ""}`);
284
+ }
285
+ if (Date.now() >= deadline) {
286
+ throw new GittermError("NETWORK", `Timed out waiting for workspace ${workspaceId} setup`);
287
+ }
288
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
289
+ }
290
+ };
245
291
  return {
246
292
  serverUrl: credentials.serverUrl,
247
293
  auth: {
@@ -306,7 +352,28 @@ function createGittermClient(options = {}) {
306
352
  };
307
353
  }),
308
354
  create: createWorkspace,
309
- createSandbox: createWorkspace
355
+ setupStatus: (workspaceId) => run(async () => trpc.workspace.getSetupStatus.query({ workspaceId })),
356
+ waitForSetup: (workspaceId, waitOptions) => run(() => waitForWorkspaceSetup(workspaceId, waitOptions))
357
+ },
358
+ runs: {
359
+ create: (input) => run(async () => trpc.run.create.mutate(input)),
360
+ get: (workspaceId, runId) => run(async () => trpc.run.get.query({ workspaceId, runId })),
361
+ messages: (workspaceId, runId) => run(async () => trpc.run.messages.query({ workspaceId, runId })),
362
+ cancel: (workspaceId, runId) => run(async () => trpc.run.cancel.mutate({ workspaceId, runId })),
363
+ wait: (workspaceId, runId, waitOptions) => run(async () => {
364
+ const timeoutMs = waitOptions?.timeoutMs ?? 30 * 60000;
365
+ const pollIntervalMs = waitOptions?.pollIntervalMs ?? 2000;
366
+ const deadline = Date.now() + timeoutMs;
367
+ while (true) {
368
+ const result = await trpc.run.get.query({ workspaceId, runId });
369
+ if (result.status !== "pending" && result.status !== "running" && result.status !== "retrying")
370
+ return result;
371
+ if (Date.now() >= deadline) {
372
+ throw new GittermError("NETWORK", `Timed out waiting for run ${runId}`);
373
+ }
374
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
375
+ }
376
+ })
310
377
  },
311
378
  catalog: {
312
379
  agentTypes: (input) => run(async () => {
@@ -317,7 +384,7 @@ function createGittermClient(options = {}) {
317
384
  const result = await trpc.workspace.listCloudProviders.query(input);
318
385
  return result.cloudProviders;
319
386
  }),
320
- resolveSandboxDefaults: (input) => run(async () => trpc.workspace.resolveSandboxDefaults.query(input))
387
+ workspaceOptions: () => run(async () => trpc.workspace.getWorkspaceCatalog.query())
321
388
  }
322
389
  };
323
390
  }
@@ -326,8 +393,9 @@ function sleep(ms) {
326
393
  return new Promise((resolve) => setTimeout(resolve, ms));
327
394
  }
328
395
  async function loginWithDeviceCode(serverUrl, options = {}) {
329
- const fetchImpl = options.fetch ?? fetch;
330
- const codeRes = await fetchImpl(new URL("/api/device/code", serverUrl), {
396
+ const normalizedServerUrl = normalizeServerUrl(serverUrl);
397
+ const fetchImpl = createNoRedirectFetch(options.fetch);
398
+ const codeRes = await fetchImpl(new URL("/api/device/code", normalizedServerUrl), {
331
399
  method: "POST",
332
400
  headers: { "content-type": "application/json" },
333
401
  body: JSON.stringify({ clientName: options.clientName ?? "gitterm" })
@@ -343,7 +411,7 @@ async function loginWithDeviceCode(serverUrl, options = {}) {
343
411
  });
344
412
  const deadline = Date.now() + codeJson.expiresInSeconds * 1000;
345
413
  while (Date.now() < deadline) {
346
- const tokenRes = await fetchImpl(new URL("/api/device/token", serverUrl), {
414
+ const tokenRes = await fetchImpl(new URL("/api/device/token", normalizedServerUrl), {
347
415
  method: "POST",
348
416
  headers: { "content-type": "application/json" },
349
417
  body: JSON.stringify({ deviceCode: codeJson.deviceCode })
@@ -360,13 +428,80 @@ async function loginWithDeviceCode(serverUrl, options = {}) {
360
428
  }
361
429
  throw new Error("Device code expired; try again.");
362
430
  }
431
+ // src/workspace-client.ts
432
+ import { TRPCClientError as TRPCClientError2, createTRPCClient as createTRPCClient2, httpBatchLink as httpBatchLink2 } from "@trpc/client";
433
+ function getWorkspaceEnvironment(environment) {
434
+ environment ??= typeof process === "undefined" ? {} : process.env;
435
+ const serverUrl = environment.WORKSPACE_API_URL;
436
+ const token = environment.WORKSPACE_AUTH_TOKEN;
437
+ const workspaceId = environment.WORKSPACE_ID;
438
+ const workspaceEnvironmentPresent = Boolean(serverUrl || token || workspaceId);
439
+ if (!workspaceEnvironmentPresent)
440
+ return null;
441
+ if (!serverUrl || !token || !workspaceId) {
442
+ throw new GittermError("UNAUTHORIZED", "Incomplete GitTerm workspace environment: WORKSPACE_API_URL, WORKSPACE_AUTH_TOKEN, and WORKSPACE_ID are required");
443
+ }
444
+ return { serverUrl: normalizeServerUrl(serverUrl), token, workspaceId };
445
+ }
446
+ function errorCode(code) {
447
+ if (code === "UNAUTHORIZED" || code === "NOT_FOUND" || code === "FORBIDDEN")
448
+ return code;
449
+ if (code === "BAD_REQUEST")
450
+ return code;
451
+ return "SERVER_ERROR";
452
+ }
453
+ function toWorkspaceTrpcUrl(serverUrl) {
454
+ const url = new URL(serverUrl);
455
+ return url.pathname.endsWith("/trpc") ? serverUrl : new URL("/trpc", url).toString();
456
+ }
457
+ function createGittermWorkspaceClient(options = {}) {
458
+ const detected = options.serverUrl && options.token && options.workspaceId ? null : getWorkspaceEnvironment();
459
+ const rawServerUrl = options.serverUrl ?? detected?.serverUrl;
460
+ const token = options.token ?? detected?.token;
461
+ const workspaceId = options.workspaceId ?? detected?.workspaceId;
462
+ if (!rawServerUrl || !token || !workspaceId) {
463
+ throw new GittermError("UNAUTHORIZED", "This command must run inside a GitTerm workspace");
464
+ }
465
+ const serverUrl = normalizeServerUrl(rawServerUrl);
466
+ const trpc = createTRPCClient2({
467
+ links: [
468
+ httpBatchLink2({
469
+ url: toWorkspaceTrpcUrl(serverUrl),
470
+ fetch: createNoRedirectFetch(options.fetch),
471
+ headers: () => ({ authorization: `Bearer ${token}` })
472
+ })
473
+ ]
474
+ });
475
+ async function run(operation) {
476
+ try {
477
+ return await operation();
478
+ } catch (error) {
479
+ if (error instanceof TRPCClientError2) {
480
+ throw new GittermError(errorCode(error.data?.code), error.message, { cause: error });
481
+ }
482
+ throw new GittermError("NETWORK", error instanceof Error ? error.message : "Network request failed", { cause: error });
483
+ }
484
+ }
485
+ return {
486
+ workspaceId,
487
+ serverUrl,
488
+ self: { get: () => run(() => trpc.workspaceOps.getSelf.query()) },
489
+ ports: {
490
+ list: () => run(() => trpc.workspaceOps.listPorts.query()),
491
+ open: (port, input) => run(() => trpc.workspaceOps.openPort.mutate({ port, ...input })),
492
+ close: (port) => run(() => trpc.workspaceOps.closePort.mutate({ port }))
493
+ }
494
+ };
495
+ }
363
496
  export {
364
497
  saveConfig,
365
498
  loginWithDeviceCode,
366
499
  loadConfigSync,
367
500
  loadConfig,
501
+ getWorkspaceEnvironment,
368
502
  getConfigPath,
369
503
  deleteConfig,
504
+ createGittermWorkspaceClient,
370
505
  createGittermClient,
371
506
  WorkspaceLifecycleError,
372
507
  GittermError,
@@ -0,0 +1,2 @@
1
+ export declare function normalizeServerUrl(value: string): string;
2
+ export declare function createNoRedirectFetch(fetchImpl?: typeof fetch): typeof fetch;
package/dist/types.d.ts CHANGED
@@ -72,23 +72,110 @@ export type WorkspaceListResult = {
72
72
  hasMore: boolean;
73
73
  };
74
74
  };
75
+ export type ProviderKey = "railway" | "aws" | "e2b" | "daytona" | "cloudflare" | "vercel" | "ascii" | "exedev";
76
+ type ProviderSelectionBase = {
77
+ /** Select a specific provider installation. Usually omitted. */
78
+ providerId?: string;
79
+ /** An admin-defined profile or flexible resources within the provider's allowed limits. */
80
+ machine?: {
81
+ type: "profile";
82
+ key: string;
83
+ };
84
+ };
85
+ type FlexibleMachine<T> = {
86
+ type: "profile";
87
+ key: string;
88
+ } | {
89
+ type: "custom";
90
+ resources: T;
91
+ };
92
+ type AwsResources = {
93
+ cpu?: number;
94
+ memory?: number;
95
+ ephemeralStorageGiB?: number;
96
+ architecture?: "X86_64" | "ARM64";
97
+ };
98
+ type DaytonaResources = {
99
+ resources?: {
100
+ cpu?: number;
101
+ memory?: number;
102
+ disk?: number;
103
+ };
104
+ editorResources?: {
105
+ cpu?: number;
106
+ memory?: number;
107
+ disk?: number;
108
+ };
109
+ };
110
+ type VercelResources = {
111
+ vcpus?: number;
112
+ };
113
+ type ExeDevResources = {
114
+ cpu?: number;
115
+ memory?: string;
116
+ disk?: string;
117
+ };
118
+ export type WorkspaceProviderSelection = {
119
+ type: "railway";
120
+ providerId?: string;
121
+ region?: string;
122
+ } | ({
123
+ type: "aws";
124
+ region?: string;
125
+ } & Omit<ProviderSelectionBase, "machine"> & {
126
+ machine?: FlexibleMachine<AwsResources>;
127
+ }) | ({
128
+ type: "daytona";
129
+ } & Omit<ProviderSelectionBase, "machine"> & {
130
+ machine?: FlexibleMachine<DaytonaResources>;
131
+ }) | ({
132
+ type: "vercel";
133
+ } & Omit<ProviderSelectionBase, "machine"> & {
134
+ machine?: FlexibleMachine<VercelResources>;
135
+ }) | ({
136
+ type: "exedev";
137
+ } & Omit<ProviderSelectionBase, "machine"> & {
138
+ machine?: FlexibleMachine<ExeDevResources>;
139
+ }) | ({
140
+ type: "e2b" | "ascii";
141
+ } & ProviderSelectionBase) | {
142
+ type: "cloudflare";
143
+ providerId?: string;
144
+ };
145
+ export type BuiltInAgentKey = "opencode-ttyd" | "opencode" | "t3code";
146
+ export type AgentKey = BuiltInAgentKey | (string & {});
75
147
  export type WorkspaceCreateInput = {
76
148
  idempotencyKey?: string;
77
149
  name?: string;
78
- repo?: string;
150
+ repo: string;
79
151
  branch?: string;
80
152
  baseCommit?: string;
81
153
  checkoutRef?: string;
82
154
  subdomain?: string;
83
- /** Unique enabled agent name. */
84
- agent: string;
85
- /** Unique enabled provider name. Optional when a preferred default exists. */
86
- provider?: string;
87
- regionId?: string;
155
+ /** Stable agent key. Defaults to `opencode`. */
156
+ agent?: AgentKey;
157
+ /** Provider intent. Defaults to the user's or deployment's preferred provider. */
158
+ provider?: WorkspaceProviderSelection;
88
159
  gitIntegrationId?: string;
89
- persistent: boolean;
160
+ /** Defaults from the selected provider. */
161
+ persistent?: boolean;
90
162
  workspaceProfile?: "standard" | "ssh-enabled";
91
163
  modelCredentialIds?: string[];
164
+ /**
165
+ * Ordered commands launched in the repository after the agent server starts.
166
+ * They do not block workspace readiness; inspect ~/.gitterm/setup for status
167
+ * and logs through workspaces.setupStatus()/waitForSetup().
168
+ */
169
+ setupCommands?: string[];
170
+ /** OpenCode capabilities materialized only in this workspace. */
171
+ opencode?: {
172
+ skills?: Array<{
173
+ name: string;
174
+ content: string;
175
+ }>;
176
+ /** NPM package specs or plugin paths accepted by OpenCode. Pin versions for repeatable runs. */
177
+ plugins?: string[];
178
+ };
92
179
  };
93
180
  export type WorkspaceRestartResult = {
94
181
  status: WorkspaceStatus;
@@ -104,8 +191,59 @@ export type WorkspaceEnsureRunningResult = {
104
191
  workspace: Workspace;
105
192
  runtime: WorkspaceRuntimeAccess;
106
193
  };
194
+ export type AgentRunStatus = "pending" | "running" | "retrying" | "completed" | "failed" | "cancelled";
195
+ export type AgentRun = {
196
+ id: string;
197
+ workspaceId: string;
198
+ title: string;
199
+ status: AgentRunStatus;
200
+ error: string | null;
201
+ finalText: string | null;
202
+ context: {
203
+ type: "isolated";
204
+ } | {
205
+ type: "continued";
206
+ runId: string;
207
+ };
208
+ };
209
+ export type AgentRunCreateInput = {
210
+ workspaceId: string;
211
+ /** Stable key used to return the same run when a request is retried. */
212
+ idempotencyKey: string;
213
+ prompt: string;
214
+ title?: string;
215
+ agent?: string;
216
+ /** OpenCode model in provider/model format. */
217
+ model?: string;
218
+ /** Start with fresh context (default), or continue a terminal run's context. */
219
+ context?: {
220
+ type: "isolated";
221
+ } | {
222
+ type: "continue";
223
+ runId: string;
224
+ };
225
+ /** Wait for workspace setup commands before submitting the prompt. */
226
+ waitForSetup?: boolean;
227
+ setupTimeoutMs?: number;
228
+ };
229
+ export type AgentRunMessage = {
230
+ id: string;
231
+ role: "user" | "assistant";
232
+ createdAt: string;
233
+ completedAt: string | null;
234
+ text: string;
235
+ error: string | null;
236
+ };
237
+ export type WorkspaceSetupStatus = {
238
+ status: "not_requested" | "waiting" | "running" | "succeeded" | "failed";
239
+ exitCode: number | null;
240
+ startedAt: string | null;
241
+ finishedAt: string | null;
242
+ log: string | null;
243
+ };
107
244
  export type AgentType = {
108
245
  id: string;
246
+ key: string;
109
247
  name: string;
110
248
  description: string | null;
111
249
  serverOnly: boolean;
@@ -116,18 +254,44 @@ export type AgentType = {
116
254
  export type CloudProvider = {
117
255
  id: string;
118
256
  name: string;
119
- providerKey: string;
120
- regions?: Array<Record<string, unknown>>;
121
- [key: string]: unknown;
122
- };
123
- export type SandboxDefaults = {
124
- agent: string;
125
- provider: string;
126
- agentTypeId: string;
127
- cloudProviderId: string;
128
- regionId?: string;
257
+ providerKey: ProviderKey | string;
258
+ regions?: Array<{
259
+ id: string;
260
+ name: string;
261
+ location: string;
262
+ externalRegionIdentifier: string;
263
+ }>;
129
264
  };
130
- export type SandboxDefaultsInput = {
131
- agent: string;
132
- provider?: string;
265
+ export type WorkspaceCatalog = {
266
+ agents: Array<{
267
+ id: string;
268
+ key: string;
269
+ name: string;
270
+ description: string | null;
271
+ serverOnly: boolean;
272
+ }>;
273
+ providers: Array<{
274
+ id: string;
275
+ type: ProviderKey;
276
+ name: string;
277
+ isDefault: boolean;
278
+ persistence: "required" | "optional" | "unsupported";
279
+ regionSelection: "none" | "user" | "admin";
280
+ regions: Array<{
281
+ id: string;
282
+ key: string;
283
+ name: string;
284
+ location: string;
285
+ }>;
286
+ machines: Array<{
287
+ id: string;
288
+ key: string;
289
+ name: string;
290
+ description: string | null;
291
+ isDefault: boolean;
292
+ }>;
293
+ agentKeys: string[];
294
+ ssh: boolean;
295
+ }>;
133
296
  };
297
+ export {};
@@ -0,0 +1,44 @@
1
+ export type WorkspaceEnvironment = {
2
+ serverUrl: string;
3
+ token: string;
4
+ workspaceId: string;
5
+ };
6
+ export type WorkspaceSelf = {
7
+ id: string;
8
+ name: string | null;
9
+ status: "pending" | "running" | "paused" | "terminated";
10
+ repositoryUrl: string | null;
11
+ repositoryBranch: string | null;
12
+ baseCommit: string | null;
13
+ checkoutRef: string | null;
14
+ providerKey: string | null;
15
+ url: string | null;
16
+ ports: WorkspacePort[];
17
+ };
18
+ export type WorkspacePort = {
19
+ port: number;
20
+ name: string | null;
21
+ url: string | null;
22
+ };
23
+ export type WorkspaceClientOptions = Partial<WorkspaceEnvironment> & {
24
+ fetch?: typeof globalThis.fetch;
25
+ };
26
+ export type GittermWorkspaceClient = {
27
+ workspaceId: string;
28
+ serverUrl: string;
29
+ self: {
30
+ get(): Promise<WorkspaceSelf>;
31
+ };
32
+ ports: {
33
+ list(): Promise<WorkspacePort[]>;
34
+ open(port: number, options?: {
35
+ name?: string;
36
+ }): Promise<WorkspacePort>;
37
+ close(port: number): Promise<{
38
+ port: number;
39
+ closed: boolean;
40
+ }>;
41
+ };
42
+ };
43
+ export declare function getWorkspaceEnvironment(environment?: Record<string, string | undefined>): WorkspaceEnvironment | null;
44
+ export declare function createGittermWorkspaceClient(options?: WorkspaceClientOptions): GittermWorkspaceClient;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitterm/sdk",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "files": [
5
5
  "dist"
6
6
  ],