@hoststack.dev/sdk 0.3.0 → 0.5.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
@@ -53,17 +53,21 @@ Generate an API key from your [HostStack dashboard → Settings → API Keys](ht
53
53
 
54
54
  ## Resources
55
55
 
56
- | Resource | Methods |
57
- | --- | --- |
58
- | `client.projects` | `list`, `get`, `create`, `update`, `delete` |
59
- | `client.services` | `list`, `get`, `create`, `update`, `delete`, `suspend`, `resume`, `getMetrics`, `getConfig`, `updateConfig`, `getRuntimeLogs`, `streamLogs` |
60
- | `client.deploys` | `list`, `get`, `trigger`, `cancel`, `rollback`, `getLogs` |
61
- | `client.databases` | `list`, `get`, `create`, `update`, `delete`, `suspend`, `resume`, `getCredentials`, `resetPassword` |
62
- | `client.domains` | `list`, `add`, `update`, `remove`, `verify` |
63
- | `client.envVars` | `list`, `create`, `update`, `delete`, `bulkSet` |
64
- | `client.cron` | `list`, `get`, `trigger` |
65
-
66
- Every method's first argument is `teamId: number`. Full API reference: **[hoststack.dev/docs/sdk](https://hoststack.dev/docs/sdk)**.
56
+ | Resource | Methods |
57
+ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
58
+ | `client.projects` | `list`, `get`, `create`, `update`, `delete` |
59
+ | `client.services` | `list`, `get`, `create`, `update`, `delete`, `suspend`, `resume`, `getMetrics`, `getConfig`, `updateConfig`, `getRuntimeLogs`, `streamLogs` |
60
+ | `client.deploys` | `list`, `get`, `trigger`, `cancel`, `rollback`, `promote`, `getLogs` |
61
+ | `client.databases` | `list`, `get`, `create`, `update`, `delete`, `suspend`, `resume`, `getCredentials`, `resetPassword` |
62
+ | `client.domains` | `list`, `add`, `update`, `remove`, `verify` |
63
+ | `client.envVars` | `list`, `create`, `update`, `delete`, `bulkSet` |
64
+ | `client.volumes` | `list`, `create`, `update`, `delete` |
65
+ | `client.environments` | `list`, `get`, `create`, `update`, `delete` |
66
+ | `client.cron` | `list`, `get`, `trigger` |
67
+
68
+ Every method's first argument is the team id — accepts either the numeric id or the `team_…` publicId (the SDK resolves publicIds to numeric ids internally and caches the lookup). Full API reference: **[hoststack.dev/docs/sdk](https://hoststack.dev/docs/sdk)**.
69
+
70
+ `client.deploys.promote(teamId, serviceId, deployId, targetEnvironmentId)` performs image-based promotion: it pins the same built image into a sibling environment without rebuilding.
67
71
 
68
72
  ## Error handling
69
73
 
@@ -94,7 +98,8 @@ try {
94
98
  ## Related packages
95
99
 
96
100
  - **[@hoststack.dev/cli](https://www.npmjs.com/package/@hoststack.dev/cli)** — command-line interface for HostStack
97
- - **[Terraform provider](https://github.com/gethoststack/terraform-provider-hoststack)** — manage HostStack resources as IaC
101
+ - **[@hoststack.dev/mcp](https://www.npmjs.com/package/@hoststack.dev/mcp)** — MCP server for Claude, Cursor, and other AI agents
102
+ - **Terraform provider** — see [hoststack.dev/docs](https://hoststack.dev/docs) for installation and resource reference
98
103
 
99
104
  ## Support
100
105
 
package/dist/index.cjs CHANGED
@@ -184,6 +184,24 @@ var DeploysResource = class {
184
184
  });
185
185
  return this.client.request("POST", `/api/services/${tid}/${sid}/deploys/${did}/rollback`);
186
186
  }
187
+ /**
188
+ * v66 P5: promote a built deploy to a sibling service in another
189
+ * environment. Reuses the source deploy's docker image (no rebuild).
190
+ * If no sibling service exists in the target env yet, the API auto-
191
+ * creates one by cloning the source service's config.
192
+ */
193
+ async promote(teamId, serviceId, deployId, targetEnvironmentId) {
194
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
195
+ const sid = await this.client.resolveId(serviceId, { kind: "service", teamId: tid });
196
+ const did = await this.client.resolveId(deployId, {
197
+ kind: "deploy",
198
+ teamId: tid,
199
+ serviceId: sid
200
+ });
201
+ return this.client.request("POST", `/api/services/${tid}/${sid}/deploys/${did}/promote`, {
202
+ targetEnvironmentId
203
+ });
204
+ }
187
205
  /**
188
206
  * Get build logs for a deploy.
189
207
  *
@@ -245,6 +263,50 @@ var DomainsResource = class {
245
263
  }
246
264
  };
247
265
 
266
+ // src/resources/environments.ts
267
+ var EnvironmentsResource = class {
268
+ constructor(client) {
269
+ this.client = client;
270
+ }
271
+ /** List all environments for a project. */
272
+ async list(teamId, projectId) {
273
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
274
+ const pid = await this.client.resolveId(projectId, { kind: "project", teamId: tid });
275
+ return this.client.request("GET", `/api/environments/${tid}/${pid}`);
276
+ }
277
+ /** Get a single environment by id. */
278
+ async get(teamId, projectId, envId) {
279
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
280
+ const pid = await this.client.resolveId(projectId, { kind: "project", teamId: tid });
281
+ const eid = await this.client.resolveId(envId, { kind: "environment", teamId: tid });
282
+ return this.client.request("GET", `/api/environments/${tid}/${pid}/${eid}`);
283
+ }
284
+ /** Create a new environment in the given project. */
285
+ async create(teamId, projectId, data) {
286
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
287
+ const pid = await this.client.resolveId(projectId, { kind: "project", teamId: tid });
288
+ return this.client.request("POST", `/api/environments/${tid}/${pid}`, data);
289
+ }
290
+ /** Update environment metadata (name, default flag, protected flag). */
291
+ async update(teamId, projectId, envId, data) {
292
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
293
+ const pid = await this.client.resolveId(projectId, { kind: "project", teamId: tid });
294
+ const eid = await this.client.resolveId(envId, { kind: "environment", teamId: tid });
295
+ return this.client.request("PATCH", `/api/environments/${tid}/${pid}/${eid}`, data);
296
+ }
297
+ /**
298
+ * Delete an environment. The API blocks delete when the env still
299
+ * has services or databases attached — destroy them first or move
300
+ * them to another env. Cannot delete the project's default env.
301
+ */
302
+ async delete(teamId, projectId, envId) {
303
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
304
+ const pid = await this.client.resolveId(projectId, { kind: "project", teamId: tid });
305
+ const eid = await this.client.resolveId(envId, { kind: "environment", teamId: tid });
306
+ return this.client.request("DELETE", `/api/environments/${tid}/${pid}/${eid}`);
307
+ }
308
+ };
309
+
248
310
  // src/resources/env-vars.ts
249
311
  var EnvVarsResource = class {
250
312
  constructor(client) {
@@ -292,6 +354,65 @@ var EnvVarsResource = class {
292
354
  }
293
355
  };
294
356
 
357
+ // src/resources/notifications.ts
358
+ var NotificationsResource = class {
359
+ constructor(client) {
360
+ this.client = client;
361
+ }
362
+ /**
363
+ * List notification channels for the team. Webhook URLs are
364
+ * server-side masked in the response so this is safe to log.
365
+ */
366
+ async listChannels(teamId) {
367
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
368
+ return this.client.request("GET", `/api/notifications/${tid}/channels`);
369
+ }
370
+ /**
371
+ * Create a Slack/Discord/email notification channel.
372
+ *
373
+ * For type=email, `webhookUrl` is the recipient email address; for
374
+ * type=slack/discord it's the incoming webhook URL.
375
+ *
376
+ * `events` is the explicit subscription list — an empty array means
377
+ * "receive nothing". The platform pre-selects the critical-event
378
+ * set on the dashboard, but SDK callers must pass the list
379
+ * explicitly so behaviour is deterministic.
380
+ */
381
+ async createChannel(teamId, data) {
382
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
383
+ return this.client.request("POST", `/api/notifications/${tid}/channels`, data);
384
+ }
385
+ /**
386
+ * Update a channel's name / active state / event subscriptions. The
387
+ * webhook URL and type are immutable — create a new channel if
388
+ * those need to change.
389
+ */
390
+ async updateChannel(teamId, channelId, data) {
391
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
392
+ return this.client.request(
393
+ "PATCH",
394
+ `/api/notifications/${tid}/channels/${channelId}`,
395
+ data
396
+ );
397
+ }
398
+ /** Delete a notification channel. */
399
+ async deleteChannel(teamId, channelId) {
400
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
401
+ return this.client.request("DELETE", `/api/notifications/${tid}/channels/${channelId}`);
402
+ }
403
+ /**
404
+ * Fire a test event to the channel so the user can confirm the
405
+ * webhook is wired correctly. Returns the dispatch outcome.
406
+ */
407
+ async testChannel(teamId, channelId) {
408
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
409
+ return this.client.request(
410
+ "POST",
411
+ `/api/notifications/${tid}/channels/${channelId}/test`
412
+ );
413
+ }
414
+ };
415
+
295
416
  // src/resources/projects.ts
296
417
  var ProjectsResource = class {
297
418
  constructor(client) {
@@ -335,7 +456,7 @@ async function* streamLogsViaPolling(fetch2, basePath, options = {}) {
335
456
  const buildPath = (lines, since) => {
336
457
  const params = new URLSearchParams();
337
458
  if (options.stream) params.set("stream", options.stream);
338
- if (lines !== void 0) params.set("lines", String(lines));
459
+ if (lines !== void 0) params.set("limit", String(lines));
339
460
  if (since) params.set("since", since);
340
461
  const qs = params.toString();
341
462
  return `${basePath}${qs ? `?${qs}` : ""}`;
@@ -389,10 +510,23 @@ var ServicesResource = class {
389
510
  constructor(client) {
390
511
  this.client = client;
391
512
  }
392
- /** List all services for the active team. */
393
- async list(teamId) {
513
+ /**
514
+ * List services for the active team.
515
+ *
516
+ * Optional filters narrow by project, environment, status, or type.
517
+ * The server treats unknown enum values as no match (returns empty)
518
+ * rather than 400-ing.
519
+ */
520
+ async list(teamId, filters) {
394
521
  const tid = await this.client.resolveId(teamId, { kind: "team" });
395
- return this.client.request("GET", `/api/services/${tid}`);
522
+ const params = new URLSearchParams();
523
+ if (filters?.projectId !== void 0) params.set("projectId", String(filters.projectId));
524
+ if (filters?.environmentId !== void 0)
525
+ params.set("environmentId", String(filters.environmentId));
526
+ if (filters?.status) params.set("status", filters.status);
527
+ if (filters?.type) params.set("type", filters.type);
528
+ const qs = params.toString();
529
+ return this.client.request("GET", `/api/services/${tid}${qs ? `?${qs}` : ""}`);
396
530
  }
397
531
  /** Get a single service by ID. */
398
532
  async get(teamId, serviceId) {
@@ -435,6 +569,25 @@ var ServicesResource = class {
435
569
  const sid = await this.client.resolveId(serviceId, { kind: "service", teamId: tid });
436
570
  return this.client.request("GET", `/api/services/${tid}/${sid}/metrics`);
437
571
  }
572
+ /**
573
+ * Get a metrics time series for a service.
574
+ *
575
+ * `from`/`to` accept ISO-8601 timestamps. Omit both for the trailing
576
+ * hour. Server picks the resolution: raw samples ≤7d, hourly pre-
577
+ * aggregates ≤30d, daily beyond that. Up to ~500 points returned.
578
+ */
579
+ async getMetricsHistory(teamId, serviceId, options) {
580
+ const tid = await this.client.resolveId(teamId, { kind: "team" });
581
+ const sid = await this.client.resolveId(serviceId, { kind: "service", teamId: tid });
582
+ const params = new URLSearchParams();
583
+ if (options?.from) params.set("from", options.from);
584
+ if (options?.to) params.set("to", options.to);
585
+ const qs = params.toString();
586
+ return this.client.request(
587
+ "GET",
588
+ `/api/services/${tid}/${sid}/metrics/history${qs ? `?${qs}` : ""}`
589
+ );
590
+ }
438
591
  /** Get service configuration. */
439
592
  async getConfig(teamId, serviceId) {
440
593
  const tid = await this.client.resolveId(teamId, { kind: "team" });
@@ -447,14 +600,30 @@ var ServicesResource = class {
447
600
  const sid = await this.client.resolveId(serviceId, { kind: "service", teamId: tid });
448
601
  return this.client.request("PATCH", `/api/services/${tid}/${sid}/config`, data);
449
602
  }
450
- /** Get runtime logs for a service. */
603
+ /**
604
+ * Get runtime logs for a service.
605
+ *
606
+ * `since`/`until` accept either an ISO-8601 timestamp or a short
607
+ * relative offset like `-5m`, `-1h`, `-2d`.
608
+ *
609
+ * `search` does case-insensitive substring filtering server-side
610
+ * (≤100 chars). `countOnly` returns just `{ count: N }` for cheap
611
+ * polling — useful when you want to know "how many error lines in the
612
+ * last 5 minutes" without paying the bytes.
613
+ */
451
614
  async getRuntimeLogs(teamId, serviceId, options) {
452
615
  const tid = await this.client.resolveId(teamId, { kind: "team" });
453
616
  const sid = await this.client.resolveId(serviceId, { kind: "service", teamId: tid });
454
617
  const params = new URLSearchParams();
455
- if (options?.lines) params.set("lines", String(options.lines));
618
+ const lim = options?.limit ?? options?.lines;
619
+ if (lim != null) params.set("limit", String(lim));
456
620
  if (options?.since) params.set("since", options.since);
621
+ if (options?.until) params.set("until", options.until);
457
622
  if (options?.stream) params.set("stream", options.stream);
623
+ if (options?.level) params.set("level", options.level);
624
+ const grep = options?.grep ?? options?.search;
625
+ if (grep) params.set("search", grep);
626
+ if (options?.countOnly) params.set("count_only", "1");
458
627
  const qs = params.toString();
459
628
  return this.client.request(
460
629
  "GET",
@@ -477,11 +646,7 @@ var ServicesResource = class {
477
646
  const tid = await this.client.resolveId(teamId, { kind: "team" });
478
647
  const sid = await this.client.resolveId(serviceId, { kind: "service", teamId: tid });
479
648
  const basePath = `/api/services/${tid}/${sid}/runtime-logs`;
480
- yield* streamLogsViaPolling(
481
- (path) => this.client.request("GET", path),
482
- basePath,
483
- options
484
- );
649
+ yield* streamLogsViaPolling((path) => this.client.request("GET", path), basePath, options);
485
650
  }
486
651
  };
487
652
 
@@ -543,6 +708,7 @@ var PREFIX = {
543
708
  domain: "dom_",
544
709
  volume: "vol_",
545
710
  envVar: "env_",
711
+ environment: "environment_",
546
712
  cronExecution: "cjob_"
547
713
  };
548
714
  var HostStack = class {
@@ -562,10 +728,18 @@ var HostStack = class {
562
728
  domains;
563
729
  /** Manage service environment variables. */
564
730
  envVars;
731
+ /** v66: manage environments (production/staging/development/preview) per project. */
732
+ environments;
565
733
  /** Manage cron job executions. */
566
734
  cron;
567
735
  /** Manage persistent disks attached to services. */
568
736
  volumes;
737
+ /**
738
+ * Manage notification channels — Slack/Discord webhooks + email
739
+ * recipients with per-channel event filters. Used for deploy
740
+ * failures, restart loops, ACME failures, git auth losses, etc.
741
+ */
742
+ notifications;
569
743
  constructor(options) {
570
744
  if (!options.apiKey) {
571
745
  throw new Error("apiKey is required");
@@ -578,8 +752,10 @@ var HostStack = class {
578
752
  this.databases = new DatabasesResource(this);
579
753
  this.domains = new DomainsResource(this);
580
754
  this.envVars = new EnvVarsResource(this);
755
+ this.environments = new EnvironmentsResource(this);
581
756
  this.cron = new CronResource(this);
582
757
  this.volumes = new VolumesResource(this);
758
+ this.notifications = new NotificationsResource(this);
583
759
  }
584
760
  /**
585
761
  * Make an authenticated request to the HostStack API.
@@ -671,17 +847,11 @@ var HostStack = class {
671
847
  return r.services ?? [];
672
848
  }
673
849
  case "deploy": {
674
- const r = await this.request(
675
- "GET",
676
- `/api/services/${scope.teamId}/${scope.serviceId}/deploys?perPage=100`
677
- );
850
+ const r = await this.request("GET", `/api/services/${scope.teamId}/${scope.serviceId}/deploys?perPage=100`);
678
851
  return r.data ?? [];
679
852
  }
680
853
  case "database": {
681
- const r = await this.request(
682
- "GET",
683
- `/api/databases/${scope.teamId}`
684
- );
854
+ const r = await this.request("GET", `/api/databases/${scope.teamId}`);
685
855
  return r.databases ?? [];
686
856
  }
687
857
  case "domain": {
@@ -706,6 +876,15 @@ var HostStack = class {
706
876
  const r = await this.request("GET", `/api/services/${scope.teamId}/${scope.serviceId}/cron-executions`);
707
877
  return r.executions ?? [];
708
878
  }
879
+ case "environment": {
880
+ const projectsRes = await this.request("GET", `/api/projects/${scope.teamId}`);
881
+ const envs = [];
882
+ for (const p of projectsRes.projects ?? []) {
883
+ const r = await this.request("GET", `/api/environments/${scope.teamId}/${p.id}`);
884
+ envs.push(...r.environments ?? []);
885
+ }
886
+ return envs;
887
+ }
709
888
  }
710
889
  }
711
890
  };
@@ -717,6 +896,7 @@ function cacheScope(scope) {
717
896
  case "service":
718
897
  case "database":
719
898
  case "domain":
899
+ case "environment":
720
900
  return `${scope.kind}:${scope.teamId}`;
721
901
  case "deploy":
722
902
  case "volume":