@sealant/sdk 0.24.1 → 0.25.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/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AccessTokensNamespace, ConnectedAccountsNamespace, CreateOptions, InferenceNamespace, ListOptions, Run, Workspace, SealantConfig, UsersNamespace } from "./types.js";
1
+ import type { AccessTokensNamespace, ConnectedAccountsNamespace, CreateOptions, InferenceNamespace, ListOptions, Run, SshKeysNamespace, Workspace, WorkspaceSshNamespace, SealantConfig, UsersNamespace } from "./types.js";
2
2
  export declare class Sealant {
3
3
  #private;
4
4
  constructor(config: SealantConfig);
@@ -25,6 +25,17 @@ export declare class Sealant {
25
25
  readonly users: UsersNamespace;
26
26
  /** This client's owner's Claude / Codex / GitHub accounts. Secrets go in; none come out. */
27
27
  readonly connectedAccounts: ConnectedAccountsNamespace;
28
+ /**
29
+ * Where workspace SSH connects for this deployment — how an editor (VS Code Remote-SSH) or
30
+ * plain `ssh` reaches a workspace. Destination: `<usernamePrefix>-<workspaceId>@<host>:<port>`;
31
+ * the gateway authorizes each connection from the offered key's owning account.
32
+ */
33
+ readonly workspaceSsh: WorkspaceSshNamespace;
34
+ /**
35
+ * The owner's SSH public keys — what the workspace SSH gateway resolves a connection to.
36
+ * `ensure` is idempotent: re-offering the same key returns the existing row.
37
+ */
38
+ readonly sshKeys: SshKeysNamespace;
28
39
  readonly accessTokens: AccessTokensNamespace;
29
40
  /** Runs by id — so a record can be replayed long after its workspace is gone. */
30
41
  readonly runs: {
package/dist/client.js CHANGED
@@ -1,4 +1,4 @@
1
- import { archiveConnectedAccountOp, createAccessTokenOp, createConnectedAccountOp, createWorkspaceOp, ensureUserOp, getRunOp, getUserOp, getWorkspaceOp, inferenceRespondOp, listConnectedAccountsOp, listWorkspacesOp, } from "./effect/operations.js";
1
+ import { archiveConnectedAccountOp, archiveSshKeyOp, createAccessTokenOp, createConnectedAccountOp, createSshKeyOp, createWorkspaceOp, ensureUserOp, getRunOp, getSetupStateOp, getUserOp, getWorkspaceOp, inferenceRespondOp, listConnectedAccountsOp, listSshKeysOp, listWorkspacesOp, } from "./effect/operations.js";
2
2
  import { runHarness, startHarness } from "./effect/run-harness.js";
3
3
  import { makeSdkRuntime } from "./effect/runtime.js";
4
4
  import { SealantError } from "./errors.js";
@@ -8,6 +8,7 @@ import { buildCreateWorkspaceRequest } from "./internal/blueprint.js";
8
8
  import { resolveInternalConfig } from "./internal/config.js";
9
9
  import { parseTtlSeconds } from "./internal/duration.js";
10
10
  import { buildInferenceRespondRequest, mapInferenceResponse } from "./internal/inference.js";
11
+ import { mapSshKey, mapWorkspaceSshInfo } from "./internal/ssh.js";
11
12
  const mapConnectedAccount = (wire) => ({
12
13
  connectedAccountId: wire.connectedAccountId,
13
14
  ownerUserId: wire.ownerUserId,
@@ -151,6 +152,33 @@ export class Sealant {
151
152
  return mapConnectedAccount(wire);
152
153
  },
153
154
  };
155
+ /**
156
+ * Where workspace SSH connects for this deployment — how an editor (VS Code Remote-SSH) or
157
+ * plain `ssh` reaches a workspace. Destination: `<usernamePrefix>-<workspaceId>@<host>:<port>`;
158
+ * the gateway authorizes each connection from the offered key's owning account.
159
+ */
160
+ workspaceSsh = {
161
+ info: async () => {
162
+ const wire = await this.#runtime.run(getSetupStateOp());
163
+ return mapWorkspaceSshInfo(wire);
164
+ },
165
+ };
166
+ /**
167
+ * The owner's SSH public keys — what the workspace SSH gateway resolves a connection to.
168
+ * `ensure` is idempotent: re-offering the same key returns the existing row.
169
+ */
170
+ sshKeys = {
171
+ ensure: async (options) => mapSshKey(await this.#runtime.run(createSshKeyOp({
172
+ ownerUserId: this.#ctx.config.hostLocal.ownerUserId,
173
+ publicKey: options.publicKey,
174
+ ...(options.name === undefined ? {} : { name: options.name }),
175
+ }))),
176
+ list: async () => {
177
+ const wire = await this.#runtime.run(listSshKeysOp(this.#ctx.config.hostLocal.ownerUserId));
178
+ return wire.items.map(mapSshKey);
179
+ },
180
+ remove: async (sshKeyId) => mapSshKey(await this.#runtime.run(archiveSshKeyOp(sshKeyId, this.#ctx.config.hostLocal.ownerUserId))),
181
+ };
154
182
  accessTokens = {
155
183
  create: async (options) => {
156
184
  const wire = await this.#runtime.run(createAccessTokenOp({
@@ -607,3 +607,41 @@ export declare const inferenceRespondOp: (payload: {
607
607
  readonly outputTokens: number;
608
608
  } | undefined;
609
609
  }, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").InferenceBadRequestError | import("@sealant/api-contracts").InferenceConflictError | import("@sealant/api-contracts").InferenceInternalServerError | import("@sealant/api-contracts").InferenceNotFoundError | import("@sealant/api-contracts").InferenceUnavailableError | import("effect/Schema").SchemaError, SealantApiClient>;
610
+ export declare const getSetupStateOp: () => Effect.Effect<{
611
+ readonly needsSetup: boolean;
612
+ readonly sshGateway: {
613
+ readonly host: string;
614
+ readonly port: number;
615
+ readonly usernamePrefix: string;
616
+ } | null;
617
+ }, import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError | import("@sealant/api-contracts").SystemInternalServerError, SealantApiClient>;
618
+ export declare const createSshKeyOp: (payload: {
619
+ readonly ownerUserId: string;
620
+ readonly name?: string | undefined;
621
+ readonly publicKey: string;
622
+ }) => Effect.Effect<{
623
+ readonly sshKeyId: string;
624
+ readonly ownerUserId: string;
625
+ readonly name: string;
626
+ readonly algorithm: string;
627
+ readonly fingerprint: string;
628
+ readonly createdAt: string;
629
+ }, import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError | import("@sealant/api-contracts").SshKeyBadRequestError | import("@sealant/api-contracts").SshKeyConflictError | import("@sealant/api-contracts").SshKeyInternalServerError | import("@sealant/api-contracts").SshKeyNotFoundError, SealantApiClient>;
630
+ export declare const listSshKeysOp: (ownerUserId: string) => Effect.Effect<{
631
+ readonly items: readonly {
632
+ readonly sshKeyId: string;
633
+ readonly ownerUserId: string;
634
+ readonly name: string;
635
+ readonly algorithm: string;
636
+ readonly fingerprint: string;
637
+ readonly createdAt: string;
638
+ }[];
639
+ }, import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError | import("@sealant/api-contracts").SshKeyInternalServerError, SealantApiClient>;
640
+ export declare const archiveSshKeyOp: (sshKeyId: string, ownerUserId: string) => Effect.Effect<{
641
+ readonly sshKeyId: string;
642
+ readonly ownerUserId: string;
643
+ readonly name: string;
644
+ readonly algorithm: string;
645
+ readonly fingerprint: string;
646
+ readonly createdAt: string;
647
+ }, import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError | import("@sealant/api-contracts").SshKeyInternalServerError | import("@sealant/api-contracts").SshKeyNotFoundError, SealantApiClient>;
@@ -51,3 +51,9 @@ export const archiveConnectedAccountOp = (connectedAccountId, ownerUserId) => Ef
51
51
  }));
52
52
  // ---- inference ----
53
53
  export const inferenceRespondOp = (payload) => Effect.flatMap(SealantApiClient, (client) => client.inference.respond({ payload }));
54
+ // ---- system ----
55
+ export const getSetupStateOp = () => Effect.flatMap(SealantApiClient, (client) => client.system.getSetupState({}));
56
+ // ---- ssh keys ----
57
+ export const createSshKeyOp = (payload) => Effect.flatMap(SealantApiClient, (client) => client.sshKeys.createSshKey({ payload }));
58
+ export const listSshKeysOp = (ownerUserId) => Effect.flatMap(SealantApiClient, (client) => client.sshKeys.listSshKeys({ query: { ownerUserId } }));
59
+ export const archiveSshKeyOp = (sshKeyId, ownerUserId) => Effect.flatMap(SealantApiClient, (client) => client.sshKeys.archiveSshKey({ params: { sshKeyId }, query: { ownerUserId } }));
@@ -0,0 +1,17 @@
1
+ import type { SshKey, WorkspaceSshInfo } from "../types.js";
2
+ export declare const mapWorkspaceSshInfo: (wire: {
3
+ readonly needsSetup: boolean;
4
+ readonly sshGateway: {
5
+ readonly host: string;
6
+ readonly port: number;
7
+ readonly usernamePrefix: string;
8
+ } | null;
9
+ }) => WorkspaceSshInfo | null;
10
+ export declare const mapSshKey: (wire: {
11
+ readonly sshKeyId: string;
12
+ readonly ownerUserId: string;
13
+ readonly name: string;
14
+ readonly algorithm: string;
15
+ readonly fingerprint: string;
16
+ readonly createdAt: string;
17
+ }) => SshKey;
@@ -0,0 +1,15 @@
1
+ export const mapWorkspaceSshInfo = (wire) => wire.sshGateway === null
2
+ ? null
3
+ : {
4
+ host: wire.sshGateway.host,
5
+ port: wire.sshGateway.port,
6
+ usernamePrefix: wire.sshGateway.usernamePrefix,
7
+ };
8
+ export const mapSshKey = (wire) => ({
9
+ sshKeyId: wire.sshKeyId,
10
+ ownerUserId: wire.ownerUserId,
11
+ name: wire.name,
12
+ algorithm: wire.algorithm,
13
+ fingerprint: wire.fingerprint,
14
+ createdAt: wire.createdAt,
15
+ });
package/dist/types.d.ts CHANGED
@@ -996,3 +996,45 @@ export interface ConnectedAccountsNamespace {
996
996
  /** Soft-archives the account; uniform not-found for "does not exist" and "not yours". */
997
997
  disconnect(connectedAccountId: string): Promise<ConnectedAccount>;
998
998
  }
999
+ /** Connect coordinates for the deployment's workspace SSH gateway. */
1000
+ export interface WorkspaceSshInfo {
1001
+ readonly host: string;
1002
+ readonly port: number;
1003
+ /** The SSH username is `<usernamePrefix>-<workspaceId>`; the key names the account. */
1004
+ readonly usernamePrefix: string;
1005
+ }
1006
+ /**
1007
+ * Where workspace SSH connects for this deployment. Consumers build the destination
1008
+ * `<usernamePrefix>-<workspaceId>@<host>:<port>` themselves — nothing here is secret; the
1009
+ * gateway authorizes each connection from the offered key's owning account.
1010
+ */
1011
+ export interface WorkspaceSshNamespace {
1012
+ /** Gateway connect coordinates, or null when the deployment exposes no workspace SSH gateway. */
1013
+ info(): Promise<WorkspaceSshInfo | null>;
1014
+ }
1015
+ /** A registered SSH public key as every surface sees it — never carries the key material back. */
1016
+ export interface SshKey {
1017
+ readonly sshKeyId: string;
1018
+ readonly ownerUserId: string;
1019
+ readonly name: string;
1020
+ readonly algorithm: string;
1021
+ readonly fingerprint: string;
1022
+ readonly createdAt: string;
1023
+ }
1024
+ export interface EnsureSshKeyOptions {
1025
+ /** Raw `<algorithm> <base64> [comment]` line; normalized and fingerprinted server-side. */
1026
+ readonly publicKey: string;
1027
+ /** Display name; defaults to the key comment, else `<algorithm> <fingerprint prefix>`. */
1028
+ readonly name?: string;
1029
+ }
1030
+ /**
1031
+ * The owner's SSH public keys — what the workspace SSH gateway resolves a connection to.
1032
+ * `ensure` is idempotent per owner: re-offering the same key returns the existing row; a key
1033
+ * active on another account is refused (active fingerprints are globally unique).
1034
+ */
1035
+ export interface SshKeysNamespace {
1036
+ ensure(options: EnsureSshKeyOptions): Promise<SshKey>;
1037
+ list(): Promise<readonly SshKey[]>;
1038
+ /** Archives the key; the gateway stops resolving it. */
1039
+ remove(sshKeyId: string): Promise<SshKey>;
1040
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealant/sdk",
3
- "version": "0.24.1",
3
+ "version": "0.25.1",
4
4
  "description": "The fluent public SDK for Sealant — create a workspace, run a harness, replay the record.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -26,7 +26,7 @@
26
26
  "access": "public"
27
27
  },
28
28
  "dependencies": {
29
- "@sealant/api-contracts": "^0.24.1"
29
+ "@sealant/api-contracts": "^0.25.1"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@effect/vitest": "4.0.0-beta.85",