@gitterm/sdk 0.3.0 → 0.6.0

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
@@ -23,12 +23,12 @@ Create a workspace, run a prompt, and get the final result:
23
23
  import { createGittermClient } from "@gitterm/sdk";
24
24
 
25
25
  const client = createGittermClient({ token: process.env.GITTERM_API_TOKEN });
26
- const [github] = await client.integrations.github.list();
26
+ const [github] = await client.integrations.connections.list({ integration: "github" });
27
27
  if (!github) throw new Error("Connect GitHub in the Gitterm dashboard first");
28
28
 
29
29
  const { workspace } = await client.workspaces.create({
30
30
  repo: "https://github.com/acme/product",
31
- gitIntegrationId: github.id,
31
+ connections: [github.id],
32
32
  autoTerminateAfterMs: 2 * 60 * 60 * 1000,
33
33
  });
34
34
 
@@ -45,16 +45,17 @@ try {
45
45
  }
46
46
  ```
47
47
 
48
- Connected cloud identities are discoverable without returning credentials:
48
+ Every integration you can attach, personal or admin-provided, is a **connection** with one shape.
49
+ Attach any number by id (at most one per integration):
49
50
 
50
51
  ```ts
51
- const [github] = await client.integrations.github.list();
52
- const [googleCloud] = await client.integrations.googleCloud.list();
52
+ const connections = await client.integrations.connections.list();
53
+ const github = connections.find((c) => c.integration === "github");
54
+ const google = connections.find((c) => c.integration === "google");
53
55
 
54
56
  await client.workspaces.create({
55
57
  repo: "https://github.com/acme/product",
56
- gitIntegrationId: github.id,
57
- googleCloudIntegrationId: googleCloud.id,
58
+ connections: [github!.id, google!.id],
58
59
  });
59
60
  ```
60
61
 
@@ -160,8 +161,16 @@ client.catalog.cloudProviders();
160
161
  client.catalog.workspaceOptions();
161
162
  client.credentials.list(); // dashboard credential metadata, never secrets
162
163
  client.credentials.listProviders();
163
- client.models.list({ provider: "openai" }); // discover provider/model IDs
164
- client.workspaces.models(workspace); // resolved sources + known models; no secrets or runtime wake-up
164
+ client.integrations.catalog(); // integrations the admin enabled: key, category, personal/shared
165
+ client.integrations.connections.list(filter?); // what you can attach; personal + shared, one shape
166
+ client.integrations.connections.get(id);
167
+ client.integrations.connections.create(input); // integrations:write; may return a browser step
168
+ client.integrations.connections.remove(id);
169
+ client.integrations.connections.waitFor({ integration, since }); // after a `pending` create
170
+ client.integrations.github.repositories(connectionId);
171
+ client.integrations.github.branches(connectionId, owner, repo);
172
+ client.integrations.google.setup(); // issuer + attribute mapping to configure Google
173
+ client.workspaces.models(workspace); // resolved credential sources; no secrets or runtime wake-up
165
174
  ```
166
175
 
167
176
  Every poll-based wait (`ensureRunning`, `waitForSetup`, and `runs.create` with `waitForSetup`)
@@ -175,19 +184,77 @@ finishes tearing down resources after the call returns.
175
184
 
176
185
  ## Workspaces
177
186
 
178
- ### Managed private repositories
187
+ ### Integrations and connections
179
188
 
180
- For renewable, short-lived repository authentication, connect the GitHub App in the GitTerm
181
- dashboard and copy its **SDK integration ID** from the Integrations page:
189
+ `client.integrations` works the same way for every provider. `catalog()` tells you which
190
+ integrations the admin has enabled and whether each allows **personal** connections (you create
191
+ them, e.g. by installing the GitHub App) and/or a **shared** one (the admin provides it for the
192
+ whole deployment). `connections.list()` returns everything you can attach right now:
182
193
 
183
194
  ```ts
184
- const { workspace, runtime } = await client.workspaces.create({
185
- repo: "https://github.com/acme/private-repo",
186
- branch: "main",
187
- gitIntegrationId: "your-dashboard-integration-id",
195
+ for (const c of await client.integrations.connections.list()) {
196
+ console.log(c.id, c.integration, c.kind, c.name, c.status);
197
+ }
198
+ // 8c1f… github personal acme-bot (GitHub App) connected
199
+ // github:shared github shared acme-ci (shared PAT) connected
200
+ // 3f7d… google personal Production connected
201
+ ```
202
+
203
+ Personal connection ids are stable row ids. Shared connection ids are well-known:
204
+ `<integration>:shared`. Pass any mix to `workspaces.create({ connections: [...] })`; the server
205
+ rejects more than one connection per integration. Credentials are issued inside the workspace and
206
+ are never returned to the SDK caller.
207
+
208
+ #### Creating connections from the SDK
209
+
210
+ Creating a connection requires an API token with the `integrations:write` scope. Some providers
211
+ complete immediately; others need a browser step and return `pending`:
212
+
213
+ ```ts
214
+ // Google Cloud: completes immediately, then tell the user what to run in gcloud.
215
+ const setup = await client.integrations.google.setup(); // issuer to configure on the provider
216
+ const created = await client.integrations.connections.create({
217
+ integration: "google",
218
+ name: "Production",
219
+ projectId: "my-project",
220
+ workloadIdentityProvider:
221
+ "projects/123456789/locations/global/workloadIdentityPools/gitterm/providers/gitterm",
222
+ serviceAccountEmail: "agent@my-project.iam.gserviceaccount.com",
188
223
  });
224
+ if (created.status === "connected") {
225
+ for (const step of created.nextSteps) console.log(step.label, "\n", step.command);
226
+ }
227
+
228
+ // GitHub App: the user installs the App in a browser, then we wait for the connection.
229
+ const started = new Date();
230
+ const pending = await client.integrations.connections.create({ integration: "github" });
231
+ if (pending.status === "pending") {
232
+ console.log("Open", pending.authorizeUrl);
233
+ const github = await client.integrations.connections.waitFor({
234
+ integration: "github",
235
+ since: started,
236
+ });
237
+ console.log("Connected as", github.name);
238
+ }
189
239
  ```
190
240
 
241
+ `connections.remove(id)` disconnects a personal connection (for GitHub it asks GitHub to uninstall
242
+ the App; the removal completes when GitHub confirms). Shared connections are managed by the admin.
243
+
244
+ #### GitHub
245
+
246
+ A GitHub App connection can browse what it has access to:
247
+
248
+ ```ts
249
+ const repos = await client.integrations.github.repositories(github.id);
250
+ const branches = await client.integrations.github.branches(github.id, "acme", "product");
251
+ ```
252
+
253
+ The admin picks one repository mode for the deployment: either users install a GitHub App
254
+ (personal connections) or the admin provides a shared PAT (`github:shared`). The shared PAT
255
+ authenticates as the admin-chosen GitHub account, so its permissions apply to every workspace that
256
+ attaches it. Neither mode requires GitHub login.
257
+
191
258
  Managed workspaces can also use dashboard-managed model subscriptions while accepting an
192
259
  application-owned GitHub PAT inline:
193
260
 
@@ -206,11 +273,10 @@ const { workspace, runtime } = await client.workspaces.create({
206
273
  });
207
274
  ```
208
275
 
209
- The username defaults to `x-access-token`. Inline repository credentials take precedence over
210
- `gitIntegrationId` and authenticate repository validation, cloning, and runtime Git operations such
211
- as pull and push. Without inline credentials, `gitIntegrationId` continues to use the connected
212
- dashboard integration. Omitting `models` likewise continues to
213
- use dashboard-managed model credentials.
276
+ The username defaults to `x-access-token`. Use either a GitHub connection in `connections` or
277
+ `repositoryCredentials`, not both. Both authenticate repository validation, cloning, and runtime
278
+ Git operations such as pull and push. Omitting `models` continues to use dashboard-managed model
279
+ credentials.
214
280
 
215
281
  #### GitHub CLI authentication
216
282
 
@@ -218,12 +284,15 @@ For GitHub repositories, GitTerm configures both Git and `gh` from the same work
218
284
  credentials. Agents can run commands such as `gh pr list` or `gh pr create` without
219
285
  running `gh auth login`:
220
286
 
221
- - `repositoryCredentials.token` takes precedence over `gitIntegrationId`. The supplied
222
- token is retained in a permission-restricted file on the workspace machine and is
223
- not automatically renewed. This also works with the standalone SDK's direct providers.
224
- - `gitIntegrationId` uses the GitHub App installation token. Git and `gh` share a
287
+ - `repositoryCredentials.token` is retained in a permission-restricted file on the workspace
288
+ machine and is not automatically renewed. This also works with the standalone SDK's direct
289
+ providers.
290
+ - A personal GitHub connection uses the GitHub App installation token. Git and `gh` share a
225
291
  cached token and refresh it through GitTerm before expiry. Concurrent commands share
226
292
  the refresh, and a failed refresh stops the command rather than using an expired token.
293
+ - The shared `github:shared` connection uses the admin's deployment-wide PAT. Switching the
294
+ deployment's GitHub mode prevents future refreshes for existing workspaces using it; revoke
295
+ the PAT at GitHub to stop access in an already-running workspace.
227
296
  - Without repository credentials or an integration, GitTerm leaves CLI authentication
228
297
  to the environment or the CLI's existing configuration.
229
298
 
@@ -253,8 +322,9 @@ Updating an integration's token does not grant permissions absent from its insta
253
322
  GitTerm does not save inline PATs in its application database. Inline PATs must be delivered to the
254
323
  selected compute provider and retained on the workspace machine for runtime Git operations, so
255
324
  provider infrastructure and processes running in that workspace may be able to access them. Prefer
256
- `gitIntegrationId` for durable managed workspaces and use narrowly scoped, short-lived PATs when
257
- inline credentials are necessary.
325
+ a GitHub connection for durable managed workspaces and use narrowly scoped, short-lived PATs when
326
+ inline credentials are necessary. The standalone/direct SDK has no GitTerm account to look up:
327
+ it supports `repositoryCredentials` but not `connections`.
258
328
 
259
329
  The SDK deliberately exposes two clients. `createGittermClient()` uses a user API token and
260
330
  can manage the user's workspaces. `createGittermWorkspaceClient()` uses the scoped identity
@@ -266,8 +336,14 @@ import { createGittermWorkspaceClient } from "@gitterm/sdk";
266
336
  const workspace = createGittermWorkspaceClient();
267
337
  const self = await workspace.self.get();
268
338
  const preview = await workspace.ports.open(3000, { name: "app" });
339
+ const api = await workspace.ports.open(8080, { name: "api", visibility: "public" });
269
340
  ```
270
341
 
342
+ Ports are `private` by default: only the workspace owner's signed-in GitTerm browser session can
343
+ reach the URL. Make a port `public` when it needs to be reachable without a GitTerm login, for
344
+ example an API or webhook receiver. Use `workspace.ports.setVisibility(port, visibility)` to change
345
+ it later.
346
+
271
347
  The workspace client never reads the CLI's saved account login and has no create, list,
272
348
  pause, restart, or terminate operations.
273
349
 
@@ -450,7 +526,10 @@ Three things have to be true before a prompt can be delivered, and the SDK handl
450
526
  The shortest correct sequence is therefore:
451
527
 
452
528
  ```ts
453
- const { workspace } = await client.workspaces.create({ repo, setup: { afterAgent: ["npm ci"] } });
529
+ const { workspace } = await client.workspaces.create({
530
+ repo,
531
+ setup: { afterAgent: ["npm ci"] },
532
+ });
454
533
  const run = await client.runs.create({
455
534
  workspace,
456
535
  idempotencyKey: `review-${sha}`,
@@ -657,20 +736,21 @@ selection fails explicitly; give them distinct dashboard labels. Discovery requi
657
736
  **Inline credentials** use `{ source: "apiKey", apiKey }` for this workspace only. A missing or
658
737
  blank key fails validation; it never falls back to a saved credential. Keys are injected into
659
738
  the sandbox, not saved in the dashboard. `credentials.listProviders()` includes each authentication
660
- integration's `logicalProviderKey`. `models.list({ provider: "openai" })` discovers model IDs.
661
- Managed OAuth credentials must be connected through the dashboard.
739
+ integration's `logicalProviderKey`. Managed OAuth credentials must be connected through the
740
+ dashboard.
662
741
 
663
- `workspaces.models(workspace)` shows which sources were configured and the matching catalog models.
742
+ `workspaces.models(workspace)` shows which credential sources were configured.
664
743
  It reads control-plane metadata only: it does not verify a key with the model provider, discover
665
744
  custom runtime models, or resume a paused workspace. A saved credential's label/active status is
666
745
  its current dashboard metadata.
667
746
 
668
747
  Rules and errors:
669
748
 
670
- - Omitting `models` uses dashboard defaults. An explicit `models` block defaults to
749
+ - Omitting `models` ships every saved dashboard account, labelled as in the dashboard, with each
750
+ provider's default as the active OpenCode account. An explicit `models` block defaults to
671
751
  `inherit: "none"`; `models: {}` injects no dashboard credentials.
672
- - Use `inherit: "defaults"` to inherit every unlisted provider's dashboard default. Explicit
673
- sources override only their own provider. Each provider map key selects exactly one source.
752
+ - Use `inherit: "defaults"` to ship every unlisted provider's saved accounts the same way. Explicit
753
+ sources override only their own provider and ship exactly one account for it.
674
754
  - Unknown providers or inline keys for OAuth-only providers throw `MODEL_CREDENTIAL_INVALID`.
675
755
  A missing or ambiguous label throws `MODEL_CREDENTIAL_UNAVAILABLE`.
676
756
  - A run that requests a credential-backed `provider/model` not available in its workspace throws
@@ -680,12 +760,20 @@ Rules and errors:
680
760
  for that run. Credentials remain workspace-scoped: changing credentials for one concurrent run
681
761
  would otherwise change them for other runs on the same runtime.
682
762
 
683
- ## OpenCode API versions
763
+ ## OpenCode runtime
684
764
 
685
- Managed workspaces run OpenCode V2 from `@opencode/cli@2` and use `opencodeApi: "v2"`. V2 stores
686
- credentials in SQLite; GitTerm writes a short-lived V1-shaped `auth.json` seed before first startup,
687
- triggers V2's supported credential import, then deletes the seed. Runs, questions, permissions, and
688
- events use GitTerm's V2 runtime adapter.
765
+ Every workspace runs OpenCode 2 (the `@opencode/cli` npm package) and its `/api/*` HTTP API. Runs,
766
+ questions, permissions, and events use GitTerm's OpenCode 2 runtime adapter. Attach a local TUI with
767
+ `OPENCODE_PASSWORD=<password> opencode --server <workspace url>`.
768
+
769
+ OpenCode 2 keeps credentials in its SQLite store and has no headless import, so workspaces receive
770
+ `~/.gitterm/opencode/credentials.json` and a local OpenCode plugin at
771
+ `~/.config/opencode/plugins/gitterm-credentials.js`. The plugin imports each account through OpenCode's
772
+ integration API on first load under its dashboard label (inline keys use `Gitterm`), so a provider can
773
+ carry several accounts and `opencode auth switch` works inside the workspace. The dashboard default is
774
+ the account OpenCode selects. Accounts whose label already exists on the integration are skipped, so
775
+ restarts are idempotent. OAuth entries are stored with OpenCode's built-in method IDs and refreshed by
776
+ OpenCode itself.
689
777
 
690
778
  ## Errors
691
779
 
@@ -784,12 +872,17 @@ let workspace = await direct.workspaces.create({
784
872
  repo: "https://github.com/acme/project",
785
873
  lifecycle: "ephemeral",
786
874
  models: {
787
- providers: { anthropic: { source: "apiKey", apiKey: process.env.ANTHROPIC_API_KEY! } },
875
+ providers: {
876
+ anthropic: { source: "apiKey", apiKey: process.env.ANTHROPIC_API_KEY! },
877
+ },
788
878
  },
789
879
  });
790
880
 
791
881
  try {
792
- const run = await direct.runs.create({ workspace, prompt: "Review the open pull request" });
882
+ const run = await direct.runs.create({
883
+ workspace,
884
+ prompt: "Review the open pull request",
885
+ });
793
886
  const completed = await direct.runs.result(run);
794
887
  console.log(completed.finalText);
795
888
  } finally {
@@ -806,10 +899,9 @@ Direct mode does not provide managed durable run storage or submission idempoten
806
899
  Serialized workspaces/runs contain runtime credentials: encrypt them and never send them to an
807
900
  untrusted UI. Custom providers can implement `DirectProviderAdapter`; inspect `client.provider.capabilities`.
808
901
 
809
- Both modes default to OpenCode **v1** and share v1/v2 run, permission, question, and SSE adapters.
810
- For direct v2, supply a compatible provider image/template and `opencode: { api: "v2" }` at workspace
811
- creation. The flag chooses the protocol; it does not install or upgrade the runtime. Direct mode
812
- does not accept saved/dashboard credential sources or `inherit: "defaults"`.
902
+ Both modes run OpenCode 2 and share its run, permission, question, and SSE adapters. Provider
903
+ images/templates must ship OpenCode 2 (`@opencode/cli`). Direct mode does not accept saved/dashboard
904
+ credential sources or `inherit: "defaults"`.
813
905
 
814
906
  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.
815
907
 
@@ -852,9 +944,9 @@ await direct.workspaces.create({
852
944
 
853
945
  ### Provider authentication
854
946
 
855
- Direct **v2** workspaces can start OpenCode provider authentication without shell access. This
856
- connection-management API is v2-only; v1 supports inline API keys/OAuth bundles at creation and
857
- `auth.setCredential()`. Discover a headless/device-code method for remote authentication:
947
+ Direct workspaces can start OpenCode provider authentication without shell access. Inline API
948
+ keys/OAuth bundles are still accepted at creation. Discover a headless/device-code method for remote
949
+ authentication:
858
950
 
859
951
  ```ts
860
952
  const openai = await direct.auth.get(workspace, "openai");
@@ -901,14 +993,11 @@ const workspace = await direct.workspaces.create({
901
993
  },
902
994
  });
903
995
 
904
- // v1 credentials can also be added or rotated on an existing runtime.
905
- // On v2, setCredential supports API keys; use connectOAuth for OAuth rotation.
996
+ // API keys can also be added or rotated on an existing runtime; use connectOAuth for OAuth rotation.
906
997
  await direct.auth.setCredential(workspace, {
907
- source: "oauth",
998
+ source: "apiKey",
908
999
  providerName: "openai",
909
- refreshToken: credential.refreshToken,
910
- accessToken: credential.accessToken,
911
- expiresAt: credential.expiresAt,
1000
+ apiKey: credential.apiKey,
912
1001
  });
913
1002
  ```
914
1003
 
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { AgentType, AgentRun, AgentRunCreateInput, AgentRunListOptions, AgentRunListResult, AgentRunMessage, AgentRunReply, AuthStatus, CloudProvider, RunRef, RunWaitOptions, RunWatchOptions, WaitOptions, Workspace, WorkspaceCreateInput, WorkspaceCreateResult, WorkspaceEnsureRunningResult, WorkspaceListOptions, WorkspaceListResult, WorkspaceRef, WorkspaceRestartResult, WorkspaceRuntimeAccess, WorkspacePauseResult, WorkspaceTerminateResult, WorkspaceCatalog, WorkspaceSetupStatus, ModelCredential, ModelProviderInfo, GitHubIntegration, GoogleCloudIntegration } from "./types.js";
2
- import type { AgentRunEvent, AgentRunResult, RunResultOptions, ModelInfo, WorkspaceModelAccess } from "./types.js";
1
+ import type { AgentType, AgentRun, AgentRunCreateInput, AgentRunListOptions, AgentRunListResult, AgentRunMessage, AgentRunReply, AuthStatus, CloudProvider, RunRef, RunWaitOptions, RunWatchOptions, WaitOptions, Workspace, WorkspaceCreateInput, WorkspaceCreateResult, WorkspaceEnsureRunningResult, WorkspaceListOptions, WorkspaceListResult, WorkspaceRef, WorkspaceRestartResult, WorkspaceRuntimeAccess, WorkspacePauseResult, WorkspaceTerminateResult, WorkspaceCatalog, WorkspaceSetupStatus, ModelCredential, ModelProviderInfo, Integration, IntegrationKey, Connection, ConnectionKind, CreateConnectionInput, CreateConnectionResult, GitHubRepository, GitHubBranch, GoogleSetup } from "./types.js";
2
+ import type { AgentRunEvent, AgentRunResult, RunResultOptions, WorkspaceModelAccess } from "./types.js";
3
3
  export type GittermClientOptions = {
4
4
  serverUrl?: string;
5
5
  token?: string;
@@ -22,7 +22,7 @@ export type GittermClient = {
22
22
  terminate(workspace: WorkspaceRef): Promise<WorkspaceTerminateResult>;
23
23
  create(input: WorkspaceCreateInput): Promise<WorkspaceCreateResult>;
24
24
  setupStatus(workspace: WorkspaceRef): Promise<WorkspaceSetupStatus>;
25
- /** Credential-source metadata and known models; never wakes the runtime. */
25
+ /** Credential-source metadata; never wakes the runtime. */
26
26
  models(workspace: WorkspaceRef): Promise<WorkspaceModelAccess>;
27
27
  waitForSetup(workspace: WorkspaceRef, options?: WaitOptions): Promise<WorkspaceSetupStatus>;
28
28
  };
@@ -70,17 +70,39 @@ export type GittermClient = {
70
70
  listProviders(): Promise<ModelProviderInfo[]>;
71
71
  };
72
72
  integrations: {
73
+ /** Integrations the admin has enabled for this deployment. */
74
+ catalog(): Promise<Integration[]>;
75
+ connections: {
76
+ /** Personal and shared connections you can attach to a workspace. */
77
+ list(filter?: {
78
+ integration?: IntegrationKey;
79
+ kind?: ConnectionKind;
80
+ }): Promise<Connection[]>;
81
+ get(id: string): Promise<Connection>;
82
+ /** Requires the `integrations:write` scope. */
83
+ create(input: CreateConnectionInput): Promise<CreateConnectionResult>;
84
+ /** Removes a personal connection. Shared connections are managed by the admin. */
85
+ remove(id: string): Promise<void>;
86
+ /**
87
+ * Poll until a connection for `integration` connected after `since` appears; use after a
88
+ * `pending` create result once the browser step is done.
89
+ */
90
+ waitFor(options: {
91
+ integration: IntegrationKey;
92
+ since?: Date | string;
93
+ timeoutMs?: number;
94
+ intervalMs?: number;
95
+ }): Promise<Connection>;
96
+ };
73
97
  github: {
74
- list(): Promise<GitHubIntegration[]>;
98
+ /** Repositories reachable through a GitHub App connection. */
99
+ repositories(connectionId: string): Promise<GitHubRepository[]>;
100
+ branches(connectionId: string, owner: string, repo: string): Promise<GitHubBranch[]>;
75
101
  };
76
- googleCloud: {
77
- list(): Promise<GoogleCloudIntegration[]>;
102
+ google: {
103
+ /** Issuer and attribute mapping to configure the Google provider before connecting. */
104
+ setup(): Promise<GoogleSetup>;
78
105
  };
79
106
  };
80
- models: {
81
- list(options?: {
82
- provider?: string;
83
- }): Promise<ModelInfo[]>;
84
- };
85
107
  };
86
108
  export declare function createGittermClient(options?: GittermClientOptions): GittermClient;