@gitterm/sdk 0.0.8 → 0.1.1

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
@@ -4,6 +4,152 @@ TypeScript SDK for the [GitTerm](https://gitterm.dev) API. Used by the `gitterm`
4
4
  the OpenCode plugin, and any integration that needs to manage GitTerm workspaces with
5
5
  a user API token.
6
6
 
7
+ ## Direct provider mode
8
+
9
+ Direct mode runs an agent using your cloud-provider account without a Gitterm server. It intentionally omits managed billing, proxying, policy, durable run history, and automatic cleanup; your application owns workspace state and lifecycle.
10
+
11
+ All built-in compute providers use the same provisioning plan and workspace/run API:
12
+
13
+ | Provider | Direct prerequisite | Persistent pause | Keep-alive |
14
+ | -------- | -------------------------------------------------------------- | ---------------- | ---------- |
15
+ | E2B | OpenCode-compatible template | Yes | Yes |
16
+ | Daytona | Public Gitterm OpenCode server image by default | Yes | Yes |
17
+ | Vercel | Vercel Sandbox project | Yes | Yes |
18
+ | Ascii | Box API key | Yes | Yes |
19
+ | exe.dev | Lifecycle token, or an existing VM with `ls,ssh,share,ssh-key` | Yes | No |
20
+ | Railway | Project/environment and public service domains | With a volume | No |
21
+
22
+ AWS remains available through `createGittermClient()` and the Gitterm control plane; it is intentionally not exposed in direct mode.
23
+
24
+ Cloudflare remains available through the Gitterm control plane. Direct Cloudflare support is deferred until the OpenCode v2 Workerd runtime is stable.
25
+
26
+ ```ts
27
+ import { createDirectGittermClient } from "@gitterm/sdk/direct";
28
+
29
+ const direct = createDirectGittermClient({
30
+ provider: {
31
+ type: "e2b",
32
+ apiKey: process.env.E2B_API_KEY!,
33
+ size: "standard",
34
+ },
35
+ });
36
+
37
+ let workspace = await direct.workspaces.create({
38
+ repo: "https://github.com/acme/project",
39
+ lifecycle: "ephemeral",
40
+ modelCredentials: [{ providerName: "anthropic", apiKey: process.env.ANTHROPIC_API_KEY! }],
41
+ });
42
+
43
+ try {
44
+ const run = await direct.runs.create({ workspace, prompt: "Review the open pull request" });
45
+ const completed = await direct.runs.wait(run, workspace);
46
+ console.log(completed.finalText);
47
+ } finally {
48
+ workspace = await direct.workspaces.terminate(workspace);
49
+ }
50
+ ```
51
+
52
+ `DirectWorkspace` is JSON-serializable. Persist it together with the returned `sessionId` to resume provider lifecycle and OpenCode conversation context after an application restart. The serialized workspace contains the OpenCode password and may contain provider routing tokens, so encrypt it as credential material. Custom providers can implement `DirectProviderAdapter`; use `client.provider.capabilities` rather than hard-coding lifecycle assumptions.
53
+
54
+ Every adapter receives the same normalized plan: repository/ref and optional Git credentials, agent files, model credentials, environment, setup commands, serve command, and port. Provider-specific configuration only describes how to allocate and expose compute.
55
+
56
+ Direct setup has explicit phases. `beforeAgent` blocks workspace creation, while
57
+ `afterAgent` runs in the background and can be observed with `setupStatus()` or
58
+ `waitForSetup()`:
59
+
60
+ ```ts
61
+ const workspace = await direct.workspaces.create({
62
+ repo: "https://github.com/acme/project",
63
+ setup: {
64
+ beforeAgent: ["npm install"],
65
+ afterAgent: ["npm run generate"],
66
+ },
67
+ secretFiles: [
68
+ {
69
+ path: "~/.config/gcloud/service-account.json",
70
+ content: process.env.GCP_SERVICE_ACCOUNT_JSON!,
71
+ mode: 0o600,
72
+ },
73
+ ],
74
+ });
75
+
76
+ await direct.workspaces.waitForSetup(workspace);
77
+ ```
78
+
79
+ To attach to an existing exe.dev VM without giving Gitterm ownership of that VM, pass
80
+ `exedev: { existingVmName: "acme-agent-machine" }` to `workspaces.create()`. Terminating
81
+ that workspace stops only its tracked agent process and does not remove the VM.
82
+
83
+ Trusted integration context can be appended to the generated global `AGENTS.md` without changing the model system prompt:
84
+
85
+ ```ts
86
+ await direct.workspaces.create({
87
+ repo: "https://github.com/acme/project",
88
+ additionalAgentInstructions:
89
+ "You are running as a Slack bot. Keep responses concise and suitable for a thread.",
90
+ });
91
+ ```
92
+
93
+ ### Provider authentication
94
+
95
+ Direct workspaces can start OpenCode provider authentication without shell access. Discover the provider's methods and select a headless or device-code OAuth method when OpenCode is running remotely:
96
+
97
+ ```ts
98
+ const openai = await direct.auth.get(workspace, "openai");
99
+ const method = openai.methods.find(
100
+ (item) => item.type === "oauth" && item.id === "chatgpt-headless",
101
+ );
102
+ if (!method || method.type !== "oauth") throw new Error("OpenAI device OAuth is unavailable");
103
+
104
+ const attempt = await direct.auth.connectOAuth({
105
+ workspace,
106
+ integrationId: "openai",
107
+ methodId: method.id,
108
+ label: "Slack bot",
109
+ });
110
+
111
+ // Present these through your application UI.
112
+ console.log(attempt.url, attempt.instructions);
113
+
114
+ if (attempt.mode === "auto") {
115
+ await direct.auth.wait(attempt, workspace);
116
+ } else {
117
+ await direct.auth.complete(attempt, workspace, await getCodeFromUser());
118
+ }
119
+ ```
120
+
121
+ OAuth started this way is stored and refreshed by OpenCode inside the workspace. Reusing a persistent workspace avoids repeated authentication; terminating an ephemeral workspace also destroys its credential store. OpenCode does not export OAuth tokens from this flow.
122
+
123
+ Applications that own OAuth separately can keep the token bundle in encrypted storage and inject it into every new workspace instead:
124
+
125
+ ```ts
126
+ const credential = await credentialStore.get(slackInstallationId);
127
+ const workspace = await direct.workspaces.create({
128
+ lifecycle: "ephemeral",
129
+ modelCredentials: [
130
+ {
131
+ type: "oauth",
132
+ providerName: "openai",
133
+ refreshToken: credential.refreshToken,
134
+ accessToken: credential.accessToken,
135
+ expiresAt: credential.expiresAt,
136
+ accountId: credential.accountId,
137
+ },
138
+ ],
139
+ });
140
+
141
+ // Credentials can also be added or rotated on an existing runtime.
142
+ await direct.auth.setCredential(workspace, {
143
+ type: "oauth",
144
+ providerName: "openai",
145
+ refreshToken: credential.refreshToken,
146
+ accessToken: credential.accessToken,
147
+ expiresAt: credential.expiresAt,
148
+ });
149
+ ```
150
+
151
+ In this mode the application owns encryption, tenant scoping, refresh, and persistence. OpenCode may refresh its workspace-local copy; the direct SDK does not copy rotated tokens back into application storage. Use the Gitterm control plane when those credential-management responsibilities should be managed centrally.
152
+
7
153
  ## Install
8
154
 
9
155
  ```sh
@@ -128,7 +274,10 @@ Override only the placement decisions your integration cares about:
128
274
  await client.workspaces.create({
129
275
  repo: "https://github.com/acme/product",
130
276
  agent: "opencode",
131
- setupCommands: ["npm install", "npm run generate"],
277
+ setup: {
278
+ beforeAgent: ["npm install"],
279
+ afterAgent: ["npm run generate"],
280
+ },
132
281
  opencode: {
133
282
  skills: [
134
283
  {
@@ -150,12 +299,38 @@ Follow the repository's release-demo workflow.`,
150
299
  });
151
300
  ```
152
301
 
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.
302
+ Setup commands run in order from the checked-out repository. `beforeAgent` blocks agent
303
+ startup; when it fails, `create()` rejects with the tail of its log. `afterAgent` starts
304
+ after the agent is reachable and reports status independently. Provider and agent defaults
305
+ configured by an administrator run first. Use `client.workspaces.setupStatus(workspaceId)`
306
+ or `waitForSetup(workspaceId)` to inspect the `afterAgent` phase. GitTerm persists bounded
307
+ logs and a recovery copy in the repository's git-excluded `.gitterm/setup/` directory.
308
+ Setup commands can reference the checkout with `$WORKSPACE_REPO_DIR`, which is the same on
309
+ every provider even though the underlying path differs.
310
+
311
+ Secret files are created relative to the repository with restrictive permissions and are
312
+ added to `.git/info/exclude` so the agent cannot commit them. GitTerm does not retain their
313
+ contents; to rotate a secret, recreate the workspace. Like model credentials, they are
314
+ delivered to the sandbox through its launch environment, so anyone who can read the
315
+ provider's task or container definition can read them:
316
+
317
+ ```ts
318
+ await client.workspaces.create({
319
+ repo: "https://github.com/acme/product",
320
+ secretFiles: [
321
+ {
322
+ path: ".secrets/gcp.json",
323
+ content: process.env.GCP_SERVICE_ACCOUNT_JSON!,
324
+ mode: "0600",
325
+ },
326
+ ],
327
+ setup: {
328
+ beforeAgent: [
329
+ 'gcloud auth activate-service-account --key-file "$WORKSPACE_REPO_DIR/.secrets/gcp.json"',
330
+ ],
331
+ },
332
+ });
333
+ ```
159
334
 
160
335
  `provider` is a discriminated union, so TypeScript only offers `region` for providers
161
336
  where GitTerm supports caller-selected placement. Machine keys are configured by admins
@@ -177,7 +352,7 @@ it does not claim that a pull request, upload, or other product outcome succeede
177
352
  ```ts
178
353
  const { workspace } = await client.workspaces.create({
179
354
  repo: "https://github.com/acme/product",
180
- setupCommands: ["npm install", "npm run db:seed"],
355
+ setup: { afterAgent: ["npm install", "npm run db:seed"] },
181
356
  });
182
357
 
183
358
  const run = await client.runs.create({
@@ -0,0 +1,2 @@
1
+ import type { AsciiDirectProviderConfig, DirectProviderAdapter } from "./types.js";
2
+ export declare function createAsciiDirectProvider(config: AsciiDirectProviderConfig): DirectProviderAdapter;
@@ -0,0 +1,60 @@
1
+ import type { DirectProviderAdapter, DirectProviderConfig, DirectAuthAttempt, DirectAuthAttemptStatus, DirectAuthIntegration, DirectAuthWaitOptions, DirectModelCredential, DirectRun, DirectRunCreateInput, DirectRunMessage, DirectRunWaitOptions, DirectWorkspace, DirectWorkspaceCreateInput, DirectWorkspaceSetupStatus, DirectWorkspaceSetupWaitOptions } from "./types.js";
2
+ export type DirectGittermClientOptions = {
3
+ provider: DirectProviderAdapter | DirectProviderConfig;
4
+ };
5
+ export declare function createDirectGittermClient(options: DirectGittermClientOptions): {
6
+ provider: {
7
+ name: string;
8
+ capabilities: import("./types.js").DirectProviderCapabilities;
9
+ };
10
+ auth: {
11
+ setCredential(workspace: DirectWorkspace, credential: DirectModelCredential): Promise<void>;
12
+ list(workspace: DirectWorkspace): Promise<DirectAuthIntegration[]>;
13
+ get(workspace: DirectWorkspace, integrationId: string): Promise<DirectAuthIntegration>;
14
+ connectKey(input: {
15
+ workspace: DirectWorkspace;
16
+ integrationId: string;
17
+ key: string;
18
+ label?: string;
19
+ }): Promise<void>;
20
+ connectOAuth(input: {
21
+ workspace: DirectWorkspace;
22
+ integrationId: string;
23
+ methodId: string;
24
+ inputs?: Record<string, string>;
25
+ label?: string;
26
+ }): Promise<DirectAuthAttempt>;
27
+ status(attempt: DirectAuthAttempt, workspace: DirectWorkspace): Promise<DirectAuthAttemptStatus>;
28
+ complete(attempt: DirectAuthAttempt, workspace: DirectWorkspace, code: string): Promise<void>;
29
+ wait(attempt: DirectAuthAttempt, workspace: DirectWorkspace, wait?: DirectAuthWaitOptions): Promise<DirectAuthAttemptStatus>;
30
+ cancel(attempt: DirectAuthAttempt, workspace: DirectWorkspace): Promise<void>;
31
+ };
32
+ workspaces: {
33
+ create(input?: DirectWorkspaceCreateInput): Promise<DirectWorkspace>;
34
+ status(workspace: DirectWorkspace): Promise<DirectWorkspace>;
35
+ pause(workspace: DirectWorkspace): Promise<DirectWorkspace>;
36
+ resume(workspace: DirectWorkspace): Promise<DirectWorkspace>;
37
+ terminate(workspace: DirectWorkspace): Promise<DirectWorkspace>;
38
+ keepAlive(workspace: DirectWorkspace, timeoutMs: number): Promise<void>;
39
+ setupStatus: (workspace: DirectWorkspace) => Promise<DirectWorkspaceSetupStatus>;
40
+ waitForSetup(workspace: DirectWorkspace, wait?: DirectWorkspaceSetupWaitOptions): Promise<DirectWorkspaceSetupStatus>;
41
+ };
42
+ runs: {
43
+ create(input: DirectRunCreateInput): Promise<DirectRun>;
44
+ get: (run: DirectRun, workspace: DirectWorkspace) => Promise<{
45
+ status: "running" | "completed" | "failed" | "cancelled" | "retrying";
46
+ error: string | null;
47
+ finalText: string | null;
48
+ id: string;
49
+ workspaceId: string;
50
+ sessionId: string;
51
+ messageId: string;
52
+ title: string;
53
+ submittedAt: string;
54
+ }>;
55
+ wait(run: DirectRun, workspace: DirectWorkspace, wait?: DirectRunWaitOptions): Promise<DirectRun>;
56
+ messages(run: DirectRun, workspace: DirectWorkspace): Promise<DirectRunMessage[]>;
57
+ cancel(run: DirectRun, workspace: DirectWorkspace): Promise<boolean>;
58
+ };
59
+ };
60
+ export type DirectGittermClient = ReturnType<typeof createDirectGittermClient>;
@@ -0,0 +1,2 @@
1
+ import type { DaytonaDirectProviderConfig, DirectProviderAdapter } from "./types.js";
2
+ export declare function createDaytonaDirectProvider(config: DaytonaDirectProviderConfig): DirectProviderAdapter;
@@ -0,0 +1,2 @@
1
+ import type { DirectProviderAdapter, E2BDirectProviderConfig } from "./types.js";
2
+ export declare function createE2BDirectProvider(config: E2BDirectProviderConfig): DirectProviderAdapter;
@@ -0,0 +1,2 @@
1
+ import type { DirectProviderAdapter, ExeDevDirectProviderConfig } from "./types.js";
2
+ export declare function createExeDevDirectProvider(config: ExeDevDirectProviderConfig): DirectProviderAdapter;
@@ -0,0 +1,8 @@
1
+ export { createDirectGittermClient, type DirectGittermClient, type DirectGittermClientOptions, } from "./client.js";
2
+ export { createE2BDirectProvider } from "./e2b.js";
3
+ export { createAsciiDirectProvider } from "./ascii.js";
4
+ export { createDaytonaDirectProvider } from "./daytona.js";
5
+ export { createExeDevDirectProvider } from "./exedev.js";
6
+ export { createRailwayDirectProvider } from "./railway.js";
7
+ export { createVercelDirectProvider } from "./vercel.js";
8
+ export type { AsciiDirectProviderConfig, DirectAuthAttempt, DirectAuthAttemptStatus, DirectAuthIntegration, DirectAuthMethod, DirectAuthPrompt, DirectAuthWaitOptions, DirectApiModelCredential, DaytonaDirectProviderConfig, DirectModelCredential, DirectOAuthModelCredential, DirectProviderAdapter, DirectProviderCapabilities, DirectProviderConfig, DirectProviderWorkspaceInput, DirectRun, DirectRunCreateInput, DirectRunMessage, DirectRunWaitOptions, DirectSecretFile, DirectWorkspace, DirectWorkspaceCreateInput, DirectWorkspaceLifecycle, DirectWorkspaceRuntime, DirectWorkspaceSetup, DirectWorkspaceSetupStatus, DirectWorkspaceSetupWaitOptions, E2BDirectProviderConfig, ExeDevDirectProviderConfig, RailwayDirectProviderConfig, VercelDirectProviderConfig, } from "./types.js";