@miosa/sdk 1.1.0 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miosa/sdk",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "TypeScript SDK for the MIOSA API — cloud VM desktop infrastructure for AI agents",
5
5
  "license": "MIT",
6
6
  "author": "MIOSA <hello@miosa.ai>",
package/src/client.ts CHANGED
@@ -44,7 +44,6 @@ import { Volumes } from "./resources/volumes.js";
44
44
  import { Webhooks } from "./resources/webhooks.js";
45
45
  import { WorkspaceInvites } from "./resources/workspace-invites.js";
46
46
  import { WorkspaceMembers } from "./resources/workspace-members.js";
47
- import { Workspaces } from "./resources/workspaces.js";
48
47
  import type { MiosaClientConfig } from "./types.js";
49
48
 
50
49
  const DEFAULT_BASE_URL = "https://api.miosa.ai/api/v1";
@@ -65,9 +64,6 @@ const DEFAULT_MAX_RETRIES = 3;
65
64
  * ```
66
65
  */
67
66
  export class Miosa {
68
- /** Workspace CRUD — create, list, get, update, delete, and sub-resource queries. */
69
- readonly workspaces: Workspaces;
70
-
71
67
  /** Per-workspace user roster — list, add, update role, remove. */
72
68
  readonly workspaceMembers: WorkspaceMembers;
73
69
 
@@ -242,7 +238,6 @@ export class Miosa {
242
238
  maxRetries: config.maxRetries ?? DEFAULT_MAX_RETRIES,
243
239
  });
244
240
 
245
- this.workspaces = new Workspaces(this.http);
246
241
  this.workspaceMembers = new WorkspaceMembers(this.http);
247
242
  this.workspaceInvites = new WorkspaceInvites(this.http);
248
243
  this.orgInvites = new OrgInvites(this.http);
package/src/index.ts CHANGED
@@ -245,16 +245,6 @@ export type {
245
245
  ApiKeyCreateParams,
246
246
  } from "./resources/api-keys.js";
247
247
 
248
- // Workspace CRUD
249
- export { Workspaces } from "./resources/workspaces.js";
250
- export type {
251
- WorkspaceId as TenantWorkspaceId,
252
- WorkspaceData,
253
- WorkspaceCreateParams,
254
- WorkspaceUpdateParams,
255
- WorkspaceComputerTemplateCreateParams,
256
- } from "./resources/workspaces.js";
257
-
258
248
  // Members & Invites
259
249
  export { WorkspaceMembers } from "./resources/workspace-members.js";
260
250
  export type {
@@ -47,9 +47,6 @@ export interface ApiKeyCreateParams {
47
47
  expires_at?: string;
48
48
  expiresAt?: string;
49
49
  idempotencyKey?: string;
50
- /** Scope this key to a single workspace. Omit for a tenant-wide key. */
51
- workspaceId?: string;
52
- workspace_id?: string;
53
50
  [key: string]: unknown;
54
51
  }
55
52
 
@@ -103,11 +100,10 @@ export class ApiKeys {
103
100
  }
104
101
 
105
102
  async create(params: ApiKeyCreateParams): Promise<ApiKeyCreateResult> {
106
- const { idempotencyKey: ikey, expiresAt, workspaceId, ...rest } = params;
103
+ const { idempotencyKey: ikey, expiresAt, ...rest } = params;
107
104
  const body = stripUndefined({
108
105
  ...rest,
109
106
  expires_at: expiresAt ?? rest.expires_at,
110
- workspace_id: workspaceId ?? rest.workspace_id,
111
107
  });
112
108
  const data = await this.http.request<unknown>("/api-keys", {
113
109
  method: "POST",
@@ -176,21 +176,26 @@ export class Computer {
176
176
  * ```ts
177
177
  * await computer.exec.bash("npm run dev &");
178
178
  * const url = computer.previewUrl(3000);
179
- * // => https://3000-<slug>.sandbox.miosa.ai
179
+ * // => https://3000-<slug>.sandbox.<tenant-domain>
180
180
  * ```
181
181
  *
182
182
  * Works for any TCP HTTP listener. Public (no auth required); anyone with
183
183
  * the URL can see it. Served over the ingress proxy so it inherits the
184
- * wildcard TLS cert — no per-sandbox certs to manage.
184
+ * tenant's white-label preview domain — no per-sandbox certs to manage.
185
185
  */
186
186
  previewUrl(port: number, path: string = "/"): string {
187
187
  const p = path.startsWith("/") ? path : `/${path}`;
188
- return `https://${port}-${this.slug}.sandbox.miosa.ai${p}`;
188
+ return `https://${port}-${this.slug}.sandbox.${this.previewDomain}${p}`;
189
189
  }
190
190
 
191
191
  /** Root preview URL — serves whatever is on the default app port. */
192
192
  get publicUrl(): string {
193
- return `https://${this.slug}.sandbox.miosa.ai`;
193
+ return `https://${this.slug}.sandbox.${this.previewDomain}`;
194
+ }
195
+
196
+ /** Tenant's preview/base domain, white-label aware. */
197
+ get previewDomain(): string {
198
+ return this.data.preview_domain || "miosa.app";
194
199
  }
195
200
 
196
201
  // ─── Lifecycle ─────────────────────────────────────────────────────────────
@@ -55,13 +55,6 @@ export interface CronJobCreateParams {
55
55
  name: string;
56
56
  schedule: string;
57
57
  idempotencyKey?: string;
58
- // White-label attribution
59
- externalWorkspaceId?: string;
60
- external_workspace_id?: string;
61
- externalUserId?: string;
62
- external_user_id?: string;
63
- externalProjectId?: string;
64
- external_project_id?: string;
65
58
  [key: string]: unknown;
66
59
  }
67
60
 
@@ -27,13 +27,6 @@ export interface CustomDomainData {
27
27
 
28
28
  export interface CustomDomainRegisterParams {
29
29
  fqdn: string;
30
- // White-label attribution
31
- externalWorkspaceId?: string;
32
- external_workspace_id?: string;
33
- externalUserId?: string;
34
- external_user_id?: string;
35
- externalProjectId?: string;
36
- external_project_id?: string;
37
30
  }
38
31
 
39
32
  // ─── CustomDomains resource ──────────────────────────────────────────────────
@@ -48,7 +41,7 @@ export interface CustomDomainRegisterParams {
48
41
  * // 1. Register the domain
49
42
  * const domain = await computer.domains.register("app.example.com");
50
43
  * console.log(domain.instructions);
51
- * // => "Add a CNAME record: app.example.com → <slug>.sandbox.miosa.ai"
44
+ * // => "Add a CNAME record: app.example.com → <slug>.sandbox.<tenant-domain>"
52
45
  *
53
46
  * // 2. Add the CNAME in your DNS registrar, then...
54
47
  *
@@ -95,7 +95,7 @@ export interface DeploymentData {
95
95
  * with `source_sandbox_id` on the version row. Will become nullable.
96
96
  */
97
97
  repo_url?: string;
98
- repo_provider?: "github" | "gitlab" | "bitbucket";
98
+ repo_provider?: "github";
99
99
  branch?: string;
100
100
  build_command?: string | null;
101
101
  run_command?: string | null;
@@ -278,9 +278,6 @@ export interface DeploymentCreateParams extends ExternalAttribution {
278
278
  autoDeploy?: boolean;
279
279
  database?: DeploymentDatabaseRequest;
280
280
  metadata?: Record<string, unknown>;
281
- /** Pin build and runtime VMs to a specific DC. One of: "us-west", "us-east", "us-mia". */
282
- target_region?: string;
283
- targetRegion?: string;
284
281
  idempotencyKey?: string;
285
282
  }
286
283
 
@@ -620,7 +617,6 @@ export class Deployments {
620
617
  auto_deploy: params.autoDeploy ?? params.auto_deploy,
621
618
  database: params.database,
622
619
  metadata: params.metadata,
623
- target_region: params.targetRegion ?? params.target_region,
624
620
  ...attributionBody(params),
625
621
  });
626
622
  const data = await this.http.request<unknown>("/deployments", {
@@ -739,16 +735,9 @@ export class Deployments {
739
735
  return unwrap(data) as DeploymentBuildData;
740
736
  }
741
737
 
742
- async listEnv(
743
- deploymentId: string,
744
- opts: { environment?: string } = {},
745
- ): Promise<Record<string, unknown>[]> {
746
- const query = stripUndefined({ environment: opts.environment });
738
+ async listEnv(deploymentId: string): Promise<Record<string, unknown>[]> {
747
739
  const data = await this.http.get<unknown>(
748
740
  `/deployments/${deploymentId}/env`,
749
- Object.keys(query).length
750
- ? (query as Record<string, string | number | boolean | undefined>)
751
- : undefined,
752
741
  );
753
742
  return listItems<Record<string, unknown>>(data);
754
743
  }
@@ -47,13 +47,6 @@ export interface CustomDomainCreateParams {
47
47
  redirect_policy?: "none" | "www_to_apex" | "apex_to_www";
48
48
  redirectPolicy?: "none" | "www_to_apex" | "apex_to_www";
49
49
  idempotencyKey?: string;
50
- // White-label attribution
51
- externalWorkspaceId?: string;
52
- external_workspace_id?: string;
53
- externalUserId?: string;
54
- external_user_id?: string;
55
- externalProjectId?: string;
56
- external_project_id?: string;
57
50
  [key: string]: unknown;
58
51
  }
59
52
 
@@ -46,13 +46,6 @@ export interface FunctionCreateParams {
46
46
  timeoutSec?: number;
47
47
  env?: Record<string, string>;
48
48
  idempotencyKey?: string;
49
- // White-label attribution
50
- externalWorkspaceId?: string;
51
- external_workspace_id?: string;
52
- externalUserId?: string;
53
- external_user_id?: string;
54
- externalProjectId?: string;
55
- external_project_id?: string;
56
49
  [key: string]: unknown;
57
50
  }
58
51
 
@@ -393,10 +393,10 @@ describe("Sandbox handle", () => {
393
393
  it("pause/resume/deploy use native sandbox lifecycle endpoints", async () => {
394
394
  mockPost
395
395
  .mockResolvedValueOnce({ data: sandboxData({ state: "paused" }) })
396
- .mockResolvedValueOnce({ data: sandboxData({ state: "running" }) })
397
- .mockResolvedValueOnce({
396
+ .mockResolvedValueOnce({ data: sandboxData({ state: "running" }) });
397
+ mockRequest.mockResolvedValueOnce({
398
398
  data: { deployment_id: "dep_1", url: "https://app.miosa.app" },
399
- });
399
+ });
400
400
 
401
401
  const sandbox = new Sandbox(makeHttp(), sandboxData());
402
402
  await sandbox.pause();
@@ -413,10 +413,13 @@ describe("Sandbox handle", () => {
413
413
  "/sandboxes/sbx_123/resume",
414
414
  {},
415
415
  );
416
- expect(mockPost).toHaveBeenNthCalledWith(3, "/sandboxes/sbx_123/deploy", {
417
- name: "site",
418
- path: "/workspace/dist",
419
- custom_domain: "example.com",
416
+ expect(mockRequest).toHaveBeenCalledWith("/sandboxes/sbx_123/deploy", {
417
+ method: "POST",
418
+ body: {
419
+ name: "site",
420
+ output_path: "/workspace/dist",
421
+ custom_domain: "example.com",
422
+ },
420
423
  });
421
424
  expect(deployment.deployment_id).toBe("dep_1");
422
425
  });
@@ -49,13 +49,6 @@ export interface BucketCreateParams {
49
49
  visibility?: "private" | "public";
50
50
  quota_bytes?: number;
51
51
  public?: boolean;
52
- // White-label attribution
53
- externalWorkspaceId?: string;
54
- external_workspace_id?: string;
55
- externalUserId?: string;
56
- external_user_id?: string;
57
- externalProjectId?: string;
58
- external_project_id?: string;
59
52
  [key: string]: unknown;
60
53
  }
61
54
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Tenant — current tenant info, preview domain, and branding.
2
+ * Tenant — current tenant info and plan/usage.
3
3
  */
4
4
 
5
5
  import type { HttpClient } from "../http.js";
@@ -14,101 +14,22 @@ export interface TenantPlan {
14
14
  [key: string]: unknown;
15
15
  }
16
16
 
17
- export interface PreviewDomainData {
18
- domain: string;
19
- verified_at?: string | null;
20
- cname_target?: string;
21
- [key: string]: unknown;
22
- }
23
-
24
- export interface PreviewDomainVerifyResult {
25
- verified: boolean;
26
- target?: string;
27
- records?: unknown[];
28
- [key: string]: unknown;
29
- }
30
-
31
- export interface BrandingData {
32
- product_name?: string;
33
- logo_url?: string;
34
- support_url?: string;
35
- support_email?: string;
36
- primary_color?: string;
37
- background_color?: string;
38
- [key: string]: unknown;
39
- }
40
-
41
17
  // ── Helpers ───────────────────────────────────────────────────────────────────
42
18
 
43
19
  function unwrap<T>(payload: unknown): T {
44
20
  if (payload && typeof payload === "object") {
45
21
  const p = payload as Record<string, unknown>;
46
- for (const k of ["data", "tenant", "branding", "preview_domain", "items"]) {
22
+ for (const k of ["data", "tenant", "items"]) {
47
23
  if (k in p) return p[k] as T;
48
24
  }
49
25
  }
50
26
  return payload as T;
51
27
  }
52
28
 
53
- // ── Sub-resources ─────────────────────────────────────────────────────────────
54
-
55
- export class PreviewDomain {
56
- constructor(private readonly http: HttpClient) {}
57
-
58
- /** GET /api/v1/tenant/preview-domain → {domain, verified_at, cname_target} */
59
- async get(): Promise<PreviewDomainData> {
60
- return unwrap(await this.http.get<unknown>("/tenant/preview-domain"));
61
- }
62
-
63
- /** PUT /api/v1/tenant/preview-domain — set the preview domain. */
64
- async set(domain: string): Promise<PreviewDomainData> {
65
- return unwrap(
66
- await this.http.put<unknown>("/tenant/preview-domain", { domain }),
67
- );
68
- }
69
-
70
- /** POST /api/v1/tenant/preview-domain/verify → {verified, target, records} */
71
- async verify(): Promise<PreviewDomainVerifyResult> {
72
- return unwrap(
73
- await this.http.post<unknown>("/tenant/preview-domain/verify", {}),
74
- );
75
- }
76
-
77
- /** DELETE /api/v1/tenant/preview-domain */
78
- async delete(): Promise<void> {
79
- await this.http.delete<unknown>("/tenant/preview-domain");
80
- }
81
- }
82
-
83
- export class Branding {
84
- constructor(private readonly http: HttpClient) {}
85
-
86
- /** GET /api/v1/tenant/branding */
87
- async get(): Promise<BrandingData> {
88
- return unwrap(await this.http.get<unknown>("/tenant/branding"));
89
- }
90
-
91
- /** PUT /api/v1/tenant/branding — keys: product_name, logo_url, support_url, support_email, primary_color, background_color */
92
- async set(branding: BrandingData): Promise<BrandingData> {
93
- return unwrap(await this.http.put<unknown>("/tenant/branding", branding));
94
- }
95
-
96
- /** DELETE /api/v1/tenant/branding */
97
- async delete(): Promise<void> {
98
- await this.http.delete<unknown>("/tenant/branding");
99
- }
100
- }
101
-
102
29
  // ── Main resource ─────────────────────────────────────────────────────────────
103
30
 
104
31
  export class Tenant {
105
- readonly preview_domain: PreviewDomain;
106
- readonly branding: Branding;
107
-
108
- constructor(private readonly http: HttpClient) {
109
- this.preview_domain = new PreviewDomain(http);
110
- this.branding = new Branding(http);
111
- }
32
+ constructor(private readonly http: HttpClient) {}
112
33
 
113
34
  /** Get the current tenant's plan, limits, and live usage counters. */
114
35
  async current(): Promise<TenantPlan> {
@@ -40,13 +40,6 @@ export interface VolumeCreateParams {
40
40
  sizeGb?: number;
41
41
  region?: string;
42
42
  idempotencyKey?: string;
43
- // White-label attribution
44
- externalWorkspaceId?: string;
45
- external_workspace_id?: string;
46
- externalUserId?: string;
47
- external_user_id?: string;
48
- externalProjectId?: string;
49
- external_project_id?: string;
50
43
  [key: string]: unknown;
51
44
  }
52
45
 
@@ -2,48 +2,10 @@
2
2
  * Webhooks resource — tenant outgoing event delivery.
3
3
  */
4
4
 
5
- import { createHmac, timingSafeEqual } from "node:crypto";
6
5
  import { randomUUID } from "node:crypto";
7
6
 
8
7
  import type { HttpClient } from "../http.js";
9
8
 
10
- const MAX_TIMESTAMP_AGE_MS = 5 * 60 * 1000;
11
-
12
- /**
13
- * Verify an incoming ``X-Miosa-Signature`` header.
14
- * Header format: ``t=<unix_ts>,v1=<hex_hmac>``
15
- */
16
- export function verifySignature(
17
- payload: string | Uint8Array,
18
- signatureHeader: string,
19
- secret: string,
20
- ): boolean {
21
- const parts: Record<string, string> = {};
22
- for (const part of signatureHeader.split(",")) {
23
- const idx = part.indexOf("=");
24
- if (idx > 0) parts[part.slice(0, idx).trim()] = part.slice(idx + 1).trim();
25
- }
26
- const tsStr = parts["t"];
27
- const sig = parts["v1"];
28
- if (!tsStr || !sig) return false;
29
-
30
- const ts = parseInt(tsStr, 10);
31
- if (isNaN(ts)) return false;
32
- if (Date.now() - ts * 1000 > MAX_TIMESTAMP_AGE_MS) {
33
- throw new Error(`Webhook timestamp is too old: ${ts}`);
34
- }
35
-
36
- const body =
37
- payload instanceof Uint8Array ? payload : Buffer.from(payload, "utf-8");
38
- const signed = Buffer.concat([Buffer.from(`${tsStr}.`, "utf-8"), body]);
39
- const expected = createHmac("sha256", secret).update(signed).digest("hex");
40
-
41
- return timingSafeEqual(
42
- Buffer.from(expected, "utf-8"),
43
- Buffer.from(sig, "utf-8"),
44
- );
45
- }
46
-
47
9
  // ── Branded IDs ──────────────────────────────────────────────────────────────
48
10
 
49
11
  export type WebhookId = string & { readonly __brand: "WebhookId" };
@@ -206,21 +168,4 @@ export class Webhooks {
206
168
  "items",
207
169
  ]);
208
170
  }
209
-
210
- /**
211
- * Verify an incoming ``X-Miosa-Signature`` header.
212
- *
213
- * Header format: ``t=<unix_ts>,v1=<hex_hmac>``
214
- * HMAC body: ``<t>.<raw_payload>``
215
- *
216
- * Throws if timestamp is older than 5 minutes.
217
- * Returns true if signature matches.
218
- */
219
- static verifySignature(
220
- payload: string | Uint8Array,
221
- signatureHeader: string,
222
- secret: string,
223
- ): boolean {
224
- return verifySignature(payload, signatureHeader, secret);
225
- }
226
171
  }
package/src/types.ts CHANGED
@@ -66,13 +66,6 @@ export interface ComputerCreateParams {
66
66
  size?: ComputerSize;
67
67
  visibility?: ComputerVisibility;
68
68
  metadata?: Record<string, string>;
69
- // White-label attribution
70
- externalWorkspaceId?: string;
71
- external_workspace_id?: string;
72
- externalUserId?: string;
73
- external_user_id?: string;
74
- externalProjectId?: string;
75
- external_project_id?: string;
76
69
  }
77
70
 
78
71
  export interface ComputerUpdateParams {
@@ -85,7 +78,8 @@ export interface ComputerData {
85
78
  id: ComputerId;
86
79
  name: string;
87
80
  /**
88
- * URL-safe identifier used in preview URLs: `https://{port}-{slug}.sandbox.miosa.ai`.
81
+ * URL-safe identifier used in preview URLs:
82
+ * `https://{port}-{slug}.sandbox.{preview_domain}`.
89
83
  * Falls back to the computer id when no slug is assigned.
90
84
  */
91
85
  slug: string;
@@ -97,8 +91,10 @@ export interface ComputerData {
97
91
  metadata: Record<string, string>;
98
92
  /** Controls who can access the HTTP preview URL. Defaults to `"public"`. */
99
93
  visibility: ComputerVisibility;
100
- /** Public ingress root, e.g. `https://<slug>.sandbox.miosa.ai`. */
94
+ /** Public ingress root, e.g. `https://<slug>.sandbox.<preview_domain>`. */
101
95
  sandbox_url?: string;
96
+ /** Tenant's white-label preview/base domain, e.g. `cliniciq.com`. */
97
+ preview_domain?: string;
102
98
  /** KasmVNC URL for desktop templates. */
103
99
  desktop_url?: string;
104
100
  created_at: string;