@ninedeploy/sdk 0.3.4 → 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/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import type { ActiveSession, ActivityEntry, AlertRule, AnalyzeRepoInput, CreateAlertRuleInput, ApiToken, PasskeyCredential, Attachment, CreateServiceVolumeAttachmentInput, CreateVolumeBackupInput, ServiceVolumeAttachment, UpdateServiceVolumeAttachmentInput, Backup, BackupWithDb, CreateApiToken, CreateDatabaseInput, CreateDomainInput, CreateProjectInput, CreateServiceInput, CreateSourceInput, CreateTunnelInput, CreateWebhookInput, CreatedApiToken, CreatedWebhook, ConfigItem, ConfigListResponse, SetConfigInput, PluginListResponse, PluginInspectResponse, InstallPluginInput, MarketplaceCatalogResponse, MenuListResponse, DemoSeedResult, Deployment, DeployTemplateInput, DockerResources, Domain, DomainPatch, DomainEntry, EnvVar, Label, CreateLabelInput, LabelPatchInput, Login, ManagedDatabase, DatabaseDetail, DatabaseCredentials, PasswordChange, PasswordReset, MetricSeries, ProjectEntry, ProjectPatchInput, PublicUser, Refresh, Register, RepoInsights, Service, ServiceTags, SetServiceTagsInput, Session, SetLimitsInput, Source, StatsSnapshot, Template, TemplateSummary, TopologyGraph, TraefikCertificate, TraefikInfo, TraefikStatus, TunnelEntry, UserCreate, VolumeFileEntry, VolumeFileWriteInput, VolumePathCreateInput, TriggerDeploy, UpdateCheckResult, SelfUpdateStatus, UpdateServiceInput, UpsertEnvVarInput, VolumeEntry, Webhook, LogDrain, LogDrainCreateInput, LogDrainUpdateInput, LogDrainTestResult, AutoPruneConfigUpdateInput, AutoPruneStatus, AutoPruneRunResult, ServerSshTest, ServerSshTestResult, ServerSshBootstrap, ServerBootstrapResult, WorkspaceEntry, WorkspaceDetail, WorkspaceCreateInput, WorkspaceUpdateInput, WorkspaceMemberEntry, WorkspaceMemberInviteEntry, WorkspaceMemberAddInput, WorkspaceMemberRoleUpdateInput, OidcPublicProvider, WorkspaceInvitationEntry, WorkspaceInvitationPublic, WorkspaceRole, OidcProviderEntry, OidcProviderCreateInput, OidcProviderUpdateInput } from '@ninedeploy/schemas';
1
+ import type { ActiveSession, ActivityEntry, AlertRule, AnalyzeRepoInput, CreateAlertRuleInput, ApiToken, PasskeyCredential, Attachment, CreateServiceVolumeAttachmentInput, CreateVolumeBackupInput, ServiceVolumeAttachment, UpdateServiceVolumeAttachmentInput, Backup, BackupWithDb, CreateApiTokenInput, CreateDatabaseInput, CreateDomainInput, CreateProjectInput, CreateServiceInput, CreateSourceInput, CreateTunnelInput, CreateWebhookInput, CreatedApiToken, CreatedWebhook, ConfigItem, ConfigListResponse, SetConfigInput, DoctorFixRequestInput, DoctorFixResponse, DoctorReport, PluginListResponse, PluginInspectResponse, InstallPluginInput, MarketplaceCatalogResponse, MenuListResponse, DemoSeedResult, Deployment, QueueResponse, DeployTemplateInput, DockerResources, Domain, DomainPatch, DomainEntry, EnvVar, Label, CreateLabelInput, LabelPatchInput, Login, ManagedDatabase, DatabaseDetail, DatabaseCredentials, PasswordChange, PasswordReset, MetricSeries, ProjectEntry, ProjectPatchInput, PublicUser, Refresh, Register, RepoInsights, Service, ServiceTags, SetServiceTagsInput, Session, SetLimitsInput, Source, StatsSnapshot, Template, TemplateSummary, CommunityTemplateListResult, TopologyGraph, TraefikCertificate, CertificateInventoryEntry, CertificateInventoryReport, TraefikInfo, TraefikStatus, TunnelEntry, UserCreate, VolumeFileEntry, VolumeFileWriteInput, VolumePathCreateInput, TriggerDeploy, UpdateCheckResult, SelfUpdateStatus, UpdateServiceInput, UpsertEnvVarInput, VolumeEntry, Webhook, LogDrain, LogDrainCreateInput, LogDrainUpdateInput, LogDrainTestResult, AutoPruneConfigUpdateInput, AutoPruneStatus, AutoPruneRunResult, ServerSshTest, ServerSshTestResult, ServerSshBootstrap, ServerBootstrapResult, WorkspaceEntry, WorkspaceDetail, WorkspaceCreateInput, WorkspaceUpdateInput, WorkspaceMemberEntry, WorkspaceMemberInviteEntry, WorkspaceMemberAddInput, WorkspaceMemberRoleUpdateInput, OidcPublicProvider, NinedeployManifest, WorkspaceInvitationEntry, WorkspaceInvitationPublic, WorkspaceRole, OidcProviderEntry, OidcProviderCreateInput, OidcProviderUpdateInput } from '@ninedeploy/schemas';
2
2
  import { NineDeployError } from './errors.js';
3
3
  export { NineDeployError };
4
4
  export type * from '@ninedeploy/schemas';
5
- export { detectProjectKind, formatManifestYaml, ManifestParseError, ManifestValidationError, parseManifestYaml, starterManifest, } from './manifest.js';
6
- export type { ProjectKind } from './manifest.js';
5
+ export { detectProjectKind, formatManifestYaml, ManifestParseError, ManifestValidationError, parseManifestYaml, starterManifest, buildManifestFromTemplate, } from './manifest.js';
6
+ export type { ProjectKind, TemplateRegistryEntry } from './manifest.js';
7
7
  /**
8
8
  * Minimal structural fetch type so the SDK is isomorphic (browser + Node)
9
9
  * without pulling in DOM lib types. Both `globalThis.fetch` implementations
@@ -24,6 +24,25 @@ export interface HealthStatus {
24
24
  version?: string;
25
25
  time?: string;
26
26
  }
27
+ /**
28
+ * Result of `GET /v1/auth/token`. Tells the caller what
29
+ * the current bearer credential is and what it can do.
30
+ * `kind` is `'session'` for JWTs and `'api'` for opaque
31
+ * API tokens. `scopes` lists the resource-scoped
32
+ * authorities (or the legacy `read` / `write` / `operator`
33
+ * shorthand); an interactive session reports
34
+ * `['session']` which is implicit full authority.
35
+ */
36
+ export interface TokenIntrospection {
37
+ kind: 'session' | 'api';
38
+ userId: number;
39
+ scopes: string[];
40
+ expiresAt: string | null;
41
+ isOperator: boolean;
42
+ /** API tokens only. */
43
+ tokenId?: number;
44
+ name?: string;
45
+ }
27
46
  export interface TemplateDeployResult {
28
47
  serviceId: number;
29
48
  serviceName: string;
@@ -52,6 +71,222 @@ export interface TemplatePrepareResult {
52
71
  }>;
53
72
  stages: TemplateDeployResult['stages'];
54
73
  }
74
+ /**
75
+ * Response from `services.manifest.apply`. The server returns the
76
+ * subset of fields the manifest actually changed under each
77
+ * section; untouched fields stay in the panel-managed column
78
+ * and do not appear in `diff`.
79
+ */
80
+ export interface ApplyManifestResult {
81
+ ok: boolean;
82
+ serviceId: number;
83
+ /** Sections the server actually wrote: `service`, `build_config`. */
84
+ touched: string[];
85
+ diff: {
86
+ service: {
87
+ port?: number;
88
+ healthPath?: string;
89
+ publishedPort?: number | null;
90
+ };
91
+ build: {
92
+ installCmd?: string;
93
+ buildCmd?: string;
94
+ startCmd?: string;
95
+ baseDir?: string;
96
+ dockerfilePath?: string | null;
97
+ restartPolicy?: string;
98
+ stopGraceSeconds?: number;
99
+ };
100
+ };
101
+ }
102
+ /** Body for `services.manifest.apply`. The manifest shape itself
103
+ * comes from `@ninedeploy/schemas`'s `ninedeployManifest` parser. */
104
+ export interface ApplyManifestInput {
105
+ manifest: NinedeployManifest;
106
+ /** `merge` (default) preserves fields the manifest omits;
107
+ * `replace` is per-field (not per-row) and still leaves
108
+ * unmentioned sections alone. */
109
+ strategy?: 'merge' | 'replace';
110
+ }
111
+ /**
112
+ * Single row from `GET /v1/databases/:id/drills`. The drill
113
+ * is an engine-specific smoke check (pg_restore --list,
114
+ * redis-check-rdb, mysqldump header parse, ...) that proves
115
+ * a backup is at least parseable without spinning up a real
116
+ * database container. `details` is the engine-specific
117
+ * output (object counts, banner, ...).
118
+ */
119
+ export interface BackupDrillResult {
120
+ drillId: number;
121
+ ok: true;
122
+ status: 'passed' | 'failed';
123
+ durationMs: number;
124
+ details: Record<string, unknown> | null;
125
+ error: string | null;
126
+ }
127
+ /** History row from `GET /v1/databases/:id/drills`. */
128
+ export interface BackupDrillEntry {
129
+ id: number;
130
+ databaseId: number;
131
+ backupId: number;
132
+ status: 'pending' | 'running' | 'passed' | 'failed';
133
+ engine: string;
134
+ durationMs: number;
135
+ error: string | null;
136
+ details: Record<string, unknown> | null;
137
+ startedAt: number;
138
+ completedAt: number | null;
139
+ }
140
+ /**
141
+ * Body for `POST /v1/log-drains/search` (G-16). The
142
+ * search round-trips to the configured Loki drain.
143
+ */
144
+ export interface LogSearchInput {
145
+ /** Free-text search (case-insensitive substring). */
146
+ query: string;
147
+ /** Restrict to one service. */
148
+ serviceId?: number;
149
+ /** Window length in minutes (default 15). */
150
+ sinceMinutes?: number;
151
+ /** Hard cap on returned lines (default 200, max 1000). */
152
+ limit?: number;
153
+ /** Query a specific drain. */
154
+ drainId?: number;
155
+ }
156
+ export interface LogSearchLine {
157
+ ts: number;
158
+ line: string;
159
+ service: string | null;
160
+ }
161
+ export interface LogSearchResult {
162
+ drain: {
163
+ id: number;
164
+ name: string;
165
+ type: string;
166
+ };
167
+ serviceId: number | null;
168
+ window: {
169
+ since: string;
170
+ until: string;
171
+ };
172
+ lines: LogSearchLine[];
173
+ }
174
+ /**
175
+ * Built-in email template names (G-30). The full set
176
+ * lives in `apps/server/src/lib/emailTemplates.ts`; the
177
+ * union here is the closed set the SDK can name. The
178
+ * server validates on the wire (zod enum) — a string
179
+ * the SDK does not know about returns 400.
180
+ */
181
+ export type EmailTemplateName = 'password-reset' | 'workspace-invitation' | 'domain-transfer' | 'backup-drill-failed';
182
+ /** One row from `GET /v1/workspaces/:wid/email-templates`. */
183
+ export interface EmailTemplateEntry {
184
+ name: EmailTemplateName;
185
+ /** True when the workspace has an override for this name. */
186
+ overridden: boolean;
187
+ /** Override subject; null when the built-in default applies. */
188
+ subject: string | null;
189
+ /** Override text body; null when the built-in default applies. */
190
+ text: string | null;
191
+ }
192
+ /** Result of `POST /v1/workspaces/:wid/email-templates/preview`. */
193
+ export interface EmailTemplateRender {
194
+ subject: string;
195
+ text: string;
196
+ /** True when the rendered text came from a tenant override. */
197
+ overridden: boolean;
198
+ }
199
+ /**
200
+ * Result of `GET /v1/databases/:id/pgbouncer` (and the
201
+ * post-mutation body of the enable / disable routes).
202
+ * `pooledConnectionString` is non-null only when `running`
203
+ * is true — a `docker inspect` failure on the sidecar
204
+ * reports `enabled: true, running: false` so the panel
205
+ * can surface the discrepancy.
206
+ */
207
+ export interface PgbouncerStatus {
208
+ enabled: boolean;
209
+ containerName: string | null;
210
+ port: number;
211
+ running: boolean;
212
+ poolMode: string | null;
213
+ pooledConnectionString: string | null;
214
+ }
215
+ /**
216
+ * Single image row from `GET /v1/housekeeping/images`. Both
217
+ * the human-readable `size` ("1.2GB") and a parsed byte
218
+ * count are returned so the panel can sort without
219
+ * re-parsing. `inUse` is true when at least one container
220
+ * is currently using the image id.
221
+ */
222
+ export interface ImageInfo {
223
+ repository: string;
224
+ tag: string;
225
+ id: string;
226
+ size: string;
227
+ sizeBytes: number;
228
+ createdAt: string;
229
+ ageHours: number;
230
+ dangling: boolean;
231
+ inUse: boolean;
232
+ }
233
+ /** Body for `POST /v1/housekeeping/images/prune`. The server
234
+ * refuses the empty-options combination (it would delete
235
+ * every image not currently in use). */
236
+ export interface PruneImagesInput {
237
+ keepLast?: number;
238
+ olderThanHours?: number;
239
+ danglingOnly?: boolean;
240
+ dryRun?: boolean;
241
+ }
242
+ export interface PruneImagesResult {
243
+ freedBytes: number;
244
+ removed: string[];
245
+ removedLabels: string[];
246
+ dryRun: boolean;
247
+ output: string;
248
+ }
249
+ /**
250
+ * Result of `POST /v1/domains/:id/transfer` (start a
251
+ * transfer). The caller forwards `acceptUrl` to the target
252
+ * user out-of-band; the token is the only secret embedded
253
+ * in the URL.
254
+ */
255
+ export interface StartDomainTransferResult {
256
+ ok: boolean;
257
+ transferId: number;
258
+ acceptUrl: string;
259
+ expiresAt: number;
260
+ }
261
+ /**
262
+ * Result of `GET /v1/domain-transfers/:token` (preview).
263
+ * `effectivelyExpired` is true when the row is still
264
+ * `pending` server-side but `expiresAt` is in the past;
265
+ * the route reports it as `expired` and refuses accept.
266
+ */
267
+ export interface DomainTransferPreview {
268
+ id: number;
269
+ status: 'pending' | 'accepted' | 'cancelled' | 'expired';
270
+ hostname: string;
271
+ sourceEmail: string;
272
+ targetEmail: string;
273
+ expiresAt: number;
274
+ createdAt: number;
275
+ acceptedAt: number | null;
276
+ cancelledAt: number | null;
277
+ effectivelyExpired: boolean;
278
+ }
279
+ /** Body for `POST /v1/domain-transfers/:token/accept`. */
280
+ export interface AcceptDomainTransferInput {
281
+ targetServiceId: number;
282
+ }
283
+ export interface AcceptDomainTransferResult {
284
+ ok: boolean;
285
+ transferId: number;
286
+ domainId: number;
287
+ serviceId: number;
288
+ hostname: string;
289
+ }
55
290
  export interface NineDeployClientOptions {
56
291
  /** Base URL of the NineDeploy API, e.g. http://localhost:3000. */
57
292
  baseUrl: string;
@@ -65,6 +300,15 @@ export interface NineDeployClient {
65
300
  status: () => Promise<{
66
301
  initialized: boolean;
67
302
  }>;
303
+ /**
304
+ * Introspect the current bearer token. Returns the
305
+ * token's id (when opaque), name, scopes, expiry and
306
+ * the operator flag. Interactive sessions (JWT) report
307
+ * `scopes: ['session']`. The MCP server uses this to
308
+ * discover the token's authority before registering
309
+ * tools.
310
+ */
311
+ introspectToken: () => Promise<TokenIntrospection>;
68
312
  setup: (input: Register) => Promise<Session>;
69
313
  register: (input: Register) => Promise<Session>;
70
314
  login: (input: Login) => Promise<Session>;
@@ -107,7 +351,7 @@ export interface NineDeployClient {
107
351
  };
108
352
  me: () => Promise<PublicUser>;
109
353
  tokens: {
110
- create: (input?: CreateApiToken) => Promise<CreatedApiToken>;
354
+ create: (input?: CreateApiTokenInput) => Promise<CreatedApiToken>;
111
355
  list: () => Promise<ApiToken[]>;
112
356
  remove: (id: number) => Promise<void>;
113
357
  };
@@ -223,6 +467,23 @@ export interface NineDeployClient {
223
467
  slug: string;
224
468
  message: string;
225
469
  }>;
470
+ /**
471
+ * `.ninedeploy` manifest endpoints. The server applies
472
+ * build / run / network sections to the service + build
473
+ * config rows (operator > manifest > DB merge semantics);
474
+ * routes / alerts / database reconcile happens at deploy
475
+ * time via the deploy pipeline and is intentionally
476
+ * outside this endpoint's scope.
477
+ */
478
+ manifest: {
479
+ /**
480
+ * Push a parsed manifest to the panel and reconcile it
481
+ * into the service. Requires the `admin` role on the
482
+ * service. The response echoes the diff so the CLI can
483
+ * render a one-shot summary without a follow-up GET.
484
+ */
485
+ apply: (serviceId: number, input: ApplyManifestInput) => Promise<ApplyManifestResult>;
486
+ };
226
487
  };
227
488
  labels: {
228
489
  /** `query` is appended verbatim, e.g. `?workspaceId=1`. */
@@ -244,6 +505,13 @@ export interface NineDeployClient {
244
505
  deploymentId: number;
245
506
  }>;
246
507
  list: (serviceId: number) => Promise<Deployment[]>;
508
+ /**
509
+ * Global deploy queue view: every in-flight (queued / building /
510
+ * deploying) deployment the caller can see, with service + project
511
+ * metadata. The optional `status` filter is a comma-separated token
512
+ * list (e.g. `?status=queued,building`).
513
+ */
514
+ queue: (query?: string) => Promise<QueueResponse>;
247
515
  rollback: (serviceId: number, deploymentId: number) => Promise<{
248
516
  deploymentId: number;
249
517
  }>;
@@ -252,6 +520,15 @@ export interface NineDeployClient {
252
520
  ok: boolean;
253
521
  status: string;
254
522
  }>;
523
+ /**
524
+ * Remove a finished deployment from history, with its build log.
525
+ * Refused for an in-flight deployment (cancel it first) and for the one
526
+ * currently serving traffic. Requires the `admin` role on the service.
527
+ */
528
+ remove: (serviceId: number, deploymentId: number) => Promise<{
529
+ ok: boolean;
530
+ id: number;
531
+ }>;
255
532
  /** Build-config + env-key diff against the previous deployment. */
256
533
  configDiff: (serviceId: number, deploymentId: number) => Promise<{
257
534
  deploymentId: number;
@@ -271,6 +548,257 @@ export interface NineDeployClient {
271
548
  id: number;
272
549
  ssl: boolean;
273
550
  }>;
551
+ /** Toggle sticky-session routing for the service (G-28). */
552
+ setStickySession: (serviceId: number, enabled: boolean) => Promise<{
553
+ id: number;
554
+ enabled: boolean;
555
+ active: boolean;
556
+ }>;
557
+ /**
558
+ * Start a domain transfer (G-29). The source user (admin
559
+ * on the source service) names the target email; the
560
+ * server returns a one-time `acceptUrl` to forward. The
561
+ * target user accepts via `acceptDomainTransfer(token,
562
+ * { targetServiceId })`. The transfer expires after 7
563
+ * days; cancelling is one call.
564
+ */
565
+ transfer: (domainId: number, input: {
566
+ targetEmail: string;
567
+ }) => Promise<StartDomainTransferResult>;
568
+ /**
569
+ * Preview a transfer by token. Does NOT require auth
570
+ * (the token is the secret) — the panel uses this to
571
+ * render the accept page to a logged-out visitor.
572
+ */
573
+ previewTransfer: (token: string) => Promise<DomainTransferPreview>;
574
+ /**
575
+ * Accept a transfer. The caller must be authenticated
576
+ * and the caller's email must equal the transfer's
577
+ * target email; the panel / CLI surfaces the preview
578
+ * first so the user knows what they're signing in to.
579
+ */
580
+ acceptTransfer: (token: string, input: AcceptDomainTransferInput) => Promise<AcceptDomainTransferResult>;
581
+ /**
582
+ * Cancel a pending transfer. Only the source user
583
+ * (or an instance operator) can cancel.
584
+ */
585
+ cancelTransfer: (token: string) => Promise<{
586
+ transferId: number;
587
+ status: 'cancelled';
588
+ }>;
589
+ };
590
+ domainPresets: {
591
+ /** Every `IDomainProvider` the running kernel has registered. */
592
+ list: () => Promise<{
593
+ providers: string[];
594
+ }>;
595
+ /**
596
+ * Manually create the DNS record for `hostname` via the operator's
597
+ * active provider. `content` is optional: when omitted the route
598
+ * falls back to the panel's `dns_records_content` setting, then to
599
+ * `detectPublicIp()`. Returns the upstream recordId so the caller
600
+ * can correlate.
601
+ */
602
+ apply: (input: {
603
+ hostname: string;
604
+ content?: string;
605
+ }) => Promise<{
606
+ hostname: string;
607
+ provider: string;
608
+ zone: string;
609
+ recordId: string;
610
+ type: 'A' | 'CNAME';
611
+ content: string;
612
+ }>;
613
+ };
614
+ configPresets: {
615
+ /** Every preset the kernel has registered. */
616
+ list: () => Promise<{
617
+ presets: string[];
618
+ }>;
619
+ /** Fetch a single preset's values + description. */
620
+ get: (id: string) => Promise<{
621
+ id: string;
622
+ description: string | null;
623
+ values: Record<string, unknown>;
624
+ createdAt: string;
625
+ }>;
626
+ /** Register a new preset. Throws if `id` already exists. */
627
+ register: (input: {
628
+ id: string;
629
+ description?: string;
630
+ values: Record<string, unknown>;
631
+ }) => Promise<{
632
+ ok: boolean;
633
+ id: string;
634
+ keyCount: number;
635
+ }>;
636
+ /** Write every value in the preset to the live configCenter. */
637
+ apply: (id: string, opts?: {
638
+ override?: Record<string, unknown>;
639
+ }) => Promise<{
640
+ ok: boolean;
641
+ id: string;
642
+ keyCount: number;
643
+ failureCount?: number;
644
+ failures?: Array<{
645
+ key: string;
646
+ status: 'failed';
647
+ reason?: string;
648
+ }>;
649
+ }>;
650
+ /** Unregister a preset. Does NOT undo the apply. */
651
+ remove: (id: string) => Promise<{
652
+ ok: boolean;
653
+ id: string;
654
+ }>;
655
+ };
656
+ metricHistory: {
657
+ /** Current configuration snapshot (backend choice, enabled flag, retention, last-flush). */
658
+ get: () => Promise<{
659
+ enabled: boolean;
660
+ backend: 'builtin' | 'prometheus' | 'influxdb';
661
+ events: string[];
662
+ retentionDays: number;
663
+ lastFlush: {
664
+ ts: number;
665
+ backend: string;
666
+ count: number;
667
+ };
668
+ }>;
669
+ /** Run the built-in backend's retention sweep. Returns the row count trimmed. */
670
+ flush: () => Promise<{
671
+ ok: boolean;
672
+ backend: 'builtin';
673
+ deleted: number;
674
+ }>;
675
+ };
676
+ buildCache: {
677
+ /** Per-backend counters + merged totals. G-01 PR-A. */
678
+ stats: () => Promise<{
679
+ backends: Array<{
680
+ name: string;
681
+ entries: number;
682
+ totalBytes: number;
683
+ hits: number;
684
+ misses: number;
685
+ stores: number;
686
+ evictions: number;
687
+ }>;
688
+ totals: {
689
+ entries: number;
690
+ totalBytes: number;
691
+ hits: number;
692
+ misses: number;
693
+ stores: number;
694
+ evictions: number;
695
+ };
696
+ }>;
697
+ };
698
+ orchestrators: {
699
+ /** Every `IOrchestrator` the running kernel has registered. G-10 PR-A. */
700
+ list: () => Promise<{
701
+ orchestrators: Array<{
702
+ name: string;
703
+ stacks: Array<{
704
+ name: string;
705
+ serviceCount: number;
706
+ }>;
707
+ }>;
708
+ }>;
709
+ /** Stable snapshot of one stack. Returns `null` when the orchestrator has no record. */
710
+ stackStatus: (name: string) => Promise<{
711
+ name: string;
712
+ services: Array<{
713
+ name: string;
714
+ state: 'running' | 'stopped' | 'partial' | 'unknown';
715
+ replicas: number;
716
+ }>;
717
+ appliedAt: string;
718
+ } | null>;
719
+ };
720
+ branding: {
721
+ /** Read the four operator-overridable branding fields. G-30. */
722
+ get: () => Promise<{
723
+ logoUrl: string | null;
724
+ primaryColor: string | null;
725
+ supportEmail: string | null;
726
+ footerHtml: string | null;
727
+ }>;
728
+ /** Set one or more branding fields. Empty strings are stored as `null`. */
729
+ set: (input: {
730
+ logoUrl?: string | null;
731
+ primaryColor?: string | null;
732
+ supportEmail?: string | null;
733
+ footerHtml?: string | null;
734
+ }) => Promise<{
735
+ ok: boolean;
736
+ }>;
737
+ };
738
+ egress: {
739
+ /** Every egress IP rule across every registered driver. G-15. */
740
+ list: () => Promise<{
741
+ drivers: Array<{
742
+ name: string;
743
+ rules: Array<{
744
+ selector: {
745
+ projectId: number;
746
+ sourceCidr?: string;
747
+ };
748
+ ip: string;
749
+ createdAt: string;
750
+ }>;
751
+ }>;
752
+ }>;
753
+ /** Attach an egress IP to a project on the named (or first) driver. */
754
+ set: (input: {
755
+ projectId: number;
756
+ ip: string;
757
+ driver?: string;
758
+ }) => Promise<{
759
+ ok: boolean;
760
+ driver: string;
761
+ rule: {
762
+ selector: {
763
+ projectId: number;
764
+ sourceCidr?: string;
765
+ };
766
+ ip: string;
767
+ createdAt: string;
768
+ };
769
+ }>;
770
+ /** Detach the rule for a project. */
771
+ clear: (projectId: number) => Promise<{
772
+ ok: boolean;
773
+ driver: string;
774
+ }>;
775
+ };
776
+ sso: {
777
+ /** Every configured OIDC / SAML provider. G-22. */
778
+ listProviders: () => Promise<{
779
+ providers: Array<{
780
+ id: number;
781
+ type: 'oidc' | 'saml';
782
+ name: string;
783
+ createdAt: string;
784
+ }>;
785
+ }>;
786
+ /** Add a provider (OIDC issuer URL or SAML metadata URL lives in `config`). */
787
+ addProvider: (input: {
788
+ type: 'oidc' | 'saml';
789
+ name: string;
790
+ config: Record<string, unknown>;
791
+ }) => Promise<{
792
+ ok: boolean;
793
+ id?: number;
794
+ name?: string;
795
+ type?: 'oidc' | 'saml';
796
+ error?: string;
797
+ }>;
798
+ /** Remove a provider by id. */
799
+ removeProvider: (id: number) => Promise<{
800
+ ok: boolean;
801
+ }>;
274
802
  };
275
803
  volumes: {
276
804
  list: () => Promise<VolumeEntry[]>;
@@ -494,6 +1022,41 @@ export interface NineDeployClient {
494
1022
  error?: string;
495
1023
  }>;
496
1024
  };
1025
+ /** Namecheap DNS-record auto-provisioning for added domains (G-07 PR-A). */
1026
+ namecheap: {
1027
+ get: () => Promise<{
1028
+ configured: boolean;
1029
+ apiUser: string | null;
1030
+ clientIp: string | null;
1031
+ hasKey: boolean;
1032
+ }>;
1033
+ set: (input: {
1034
+ apiUser: string;
1035
+ apiKey: string;
1036
+ clientIp: string;
1037
+ }) => Promise<{
1038
+ ok: boolean;
1039
+ apiUser: string;
1040
+ }>;
1041
+ };
1042
+ /**
1043
+ * Master-key rotation. `rotate` re-encrypts every stored secret onto the
1044
+ * highest key version in `NINEDEPLOY_MASTER_KEYS`; it does NOT rewrite
1045
+ * backup envelopes, which is what `warning` reports.
1046
+ */
1047
+ masterKey: {
1048
+ get: () => Promise<{
1049
+ activeVersion: number;
1050
+ knownVersions: number[];
1051
+ rotatable: boolean;
1052
+ }>;
1053
+ rotate: () => Promise<{
1054
+ rotated: number;
1055
+ activeVersion: number;
1056
+ backupsNotRotated: number;
1057
+ warning: string | null;
1058
+ }>;
1059
+ };
497
1060
  };
498
1061
  firewall: {
499
1062
  status: () => Promise<{
@@ -556,6 +1119,19 @@ export interface NineDeployClient {
556
1119
  url: string;
557
1120
  expiresAt: string;
558
1121
  }>;
1122
+ /**
1123
+ * Operator-only: grant or revoke the INSTANCE-operator flag.
1124
+ *
1125
+ * Not a workspace role — this is the flag that gates operator-only routes
1126
+ * and the host-privilege boundary (PM2/compose deploys, lifecycle hooks,
1127
+ * docker-socket templates). Creating a workspace does NOT confer it. The
1128
+ * last remaining operator cannot be demoted.
1129
+ */
1130
+ setOperator: (id: number, isOperator: boolean) => Promise<{
1131
+ ok: boolean;
1132
+ id: number;
1133
+ isOperator: boolean;
1134
+ }>;
559
1135
  };
560
1136
  projects: {
561
1137
  /** `query` is appended verbatim, e.g. `?workspaceId=2` (workspace scoping). */
@@ -600,6 +1176,7 @@ export interface NineDeployClient {
600
1176
  type: string;
601
1177
  eventFilter: string;
602
1178
  active: boolean;
1179
+ configJson: string | null;
603
1180
  createdAt: string;
604
1181
  }>>;
605
1182
  createChannel: (input: {
@@ -607,6 +1184,7 @@ export interface NineDeployClient {
607
1184
  type: string;
608
1185
  target: string;
609
1186
  eventFilter?: string;
1187
+ configJson?: string | null;
610
1188
  }) => Promise<{
611
1189
  id: number;
612
1190
  name: string;
@@ -617,6 +1195,7 @@ export interface NineDeployClient {
617
1195
  target?: string;
618
1196
  eventFilter?: string;
619
1197
  active?: boolean;
1198
+ configJson?: string | null;
620
1199
  }) => Promise<{
621
1200
  id: number;
622
1201
  active: boolean;
@@ -708,6 +1287,38 @@ export interface NineDeployClient {
708
1287
  stopStudio: (id: number) => Promise<{
709
1288
  ok: boolean;
710
1289
  }>;
1290
+ /**
1291
+ * PgBouncer sidecar (G-32). Returns the current sidecar
1292
+ * state — `enabled` + `running` + the URL the services
1293
+ * should connect to. Use the same call after
1294
+ * `enablePgbouncer` / `disablePgbouncer` for a fresh
1295
+ * read (the route returns the post-mutation status).
1296
+ */
1297
+ pgbouncerStatus: (id: number) => Promise<PgbouncerStatus>;
1298
+ /**
1299
+ * Spin up the sidecar. `port` is optional — the row's
1300
+ * existing `pgbouncerPort` (default 6432) is used
1301
+ * when omitted. Only `engine='postgres'` is supported;
1302
+ * the server returns 422 for any other engine.
1303
+ */
1304
+ enablePgbouncer: (id: number, input?: {
1305
+ port?: number;
1306
+ }) => Promise<PgbouncerStatus>;
1307
+ /** Stop + remove the sidecar and clear the row's flags. */
1308
+ disablePgbouncer: (id: number) => Promise<PgbouncerStatus>;
1309
+ /**
1310
+ * Run an engine-specific smoke check against a backup
1311
+ * file (pg_restore --list, redis-check-rdb, mysqldump
1312
+ * header parse, ...). Returns the drill row with `status`
1313
+ * of `passed` or `failed`; a failed drill is a real
1314
+ * signal that the backup cannot be restored cleanly, not
1315
+ * a warning. Requires the `member` role on the database.
1316
+ */
1317
+ drillBackup: (id: number, input: {
1318
+ backupId: number;
1319
+ }) => Promise<BackupDrillResult>;
1320
+ /** List the most recent drills for a database, newest first. */
1321
+ drills: (id: number) => Promise<BackupDrillEntry[]>;
711
1322
  };
712
1323
  attachments: {
713
1324
  list: (serviceId: number) => Promise<Attachment[]>;
@@ -979,6 +1590,29 @@ export interface NineDeployClient {
979
1590
  get: (id: string) => Promise<Template>;
980
1591
  prepare: (id: string, input?: DeployTemplateInput) => Promise<TemplatePrepareResult>;
981
1592
  deploy: (id: string, input?: DeployTemplateInput) => Promise<TemplateDeployResult>;
1593
+ /**
1594
+ * Community contributions (G-13). The `list` call
1595
+ * already merges community entries with the curated
1596
+ * catalog; the dedicated `community` namespace lets
1597
+ * the panel surface errors and the operator import
1598
+ * a new template from a PR comment or a file.
1599
+ */
1600
+ community: {
1601
+ list: () => Promise<CommunityTemplateListResult>;
1602
+ import: (content: string, opts?: {
1603
+ replace?: boolean;
1604
+ }) => Promise<{
1605
+ ok: boolean;
1606
+ id: string;
1607
+ file: string;
1608
+ bytes: number;
1609
+ }>;
1610
+ remove: (id: string) => Promise<{
1611
+ ok: boolean;
1612
+ id: string;
1613
+ removed: boolean;
1614
+ }>;
1615
+ };
982
1616
  };
983
1617
  limits: {
984
1618
  setService: (serviceId: number, input: SetLimitsInput) => Promise<{
@@ -994,6 +1628,30 @@ export interface NineDeployClient {
994
1628
  get: () => Promise<TraefikInfo>;
995
1629
  status: () => Promise<TraefikStatus>;
996
1630
  certificates: () => Promise<TraefikCertificate[]>;
1631
+ /**
1632
+ * Richer certificate inventory (G-15). Same
1633
+ * underlying data as `certificates()` but adds a
1634
+ * `status` classification (valid / expiring-soon /
1635
+ * expired), a `summary` block, and the full set of
1636
+ * fields the Certificates page renders.
1637
+ * `threshold` is the days-out cutoff for the
1638
+ * `expiring-soon` bucket; default 30.
1639
+ */
1640
+ certificateInventory: (opts?: {
1641
+ threshold?: number;
1642
+ }) => Promise<CertificateInventoryReport>;
1643
+ /**
1644
+ * Focused "expiring within N days" filter. Default
1645
+ * 30. Used by the alert engine and by the panel's
1646
+ * "About to expire" widget.
1647
+ */
1648
+ expiringCertificates: (opts?: {
1649
+ days?: number;
1650
+ }) => Promise<{
1651
+ threshold: number;
1652
+ count: number;
1653
+ certificates: CertificateInventoryEntry[];
1654
+ }>;
997
1655
  logs: (lines?: number) => Promise<{
998
1656
  logs: string[];
999
1657
  }>;
@@ -1025,7 +1683,9 @@ export interface NineDeployClient {
1025
1683
  };
1026
1684
  plugins: {
1027
1685
  list: () => Promise<PluginListResponse>;
1028
- marketplace: () => Promise<MarketplaceCatalogResponse>;
1686
+ marketplace: (opts?: {
1687
+ refresh?: boolean;
1688
+ }) => Promise<MarketplaceCatalogResponse>;
1029
1689
  install: (input: InstallPluginInput) => Promise<{
1030
1690
  ok: boolean;
1031
1691
  id: string;
@@ -1071,11 +1731,62 @@ export interface NineDeployClient {
1071
1731
  ok: boolean;
1072
1732
  }>;
1073
1733
  test: (id: number) => Promise<LogDrainTestResult>;
1734
+ /**
1735
+ * Search the configured Loki drain for `query` over
1736
+ * the last `sinceMinutes` (default 15). The route
1737
+ * round-trips to the drain's `url` so the upstream
1738
+ * does the heavy lifting; this is the same shape
1739
+ * the operator sees in the Loki Grafana panel.
1740
+ */
1741
+ search: (input: LogSearchInput) => Promise<LogSearchResult>;
1742
+ };
1743
+ /**
1744
+ * Per-workspace email template overrides (G-30).
1745
+ * Read-side is open to members; write-side requires the
1746
+ * workspace's admin role. The list / preview are safe
1747
+ * to call from a CI script; `set` / `reset` mutate the
1748
+ * panel's outbound email and should be operator-only.
1749
+ */
1750
+ emailTemplates: {
1751
+ list: (workspaceId: number) => Promise<{
1752
+ workspaceId: number;
1753
+ templates: EmailTemplateEntry[];
1754
+ }>;
1755
+ preview: (workspaceId: number, name: EmailTemplateName, vars?: Record<string, string | number | null>) => Promise<EmailTemplateRender>;
1756
+ set: (workspaceId: number, name: EmailTemplateName, subject: string, text: string) => Promise<{
1757
+ ok: boolean;
1758
+ }>;
1759
+ reset: (workspaceId: number, name: EmailTemplateName) => Promise<{
1760
+ ok: boolean;
1761
+ }>;
1074
1762
  };
1075
1763
  housekeeping: {
1076
1764
  getAutoPrune: () => Promise<AutoPruneStatus>;
1077
1765
  updateAutoPrune: (input: AutoPruneConfigUpdateInput) => Promise<AutoPruneStatus>;
1078
1766
  runPrune: () => Promise<AutoPruneRunResult>;
1767
+ /**
1768
+ * List every image on the host with repo / tag / size /
1769
+ * created / dangling / inUse metadata. The companion to
1770
+ * the auto-prune cron: this is what the operator
1771
+ * inspects before `pruneImages({ keepLast: 5 })`.
1772
+ */
1773
+ listImages: () => Promise<{
1774
+ images: ImageInfo[];
1775
+ totalCount: number;
1776
+ totalBytes: number;
1777
+ }>;
1778
+ /**
1779
+ * Prune images with operator-supplied filters. `dryRun`
1780
+ * returns the candidate set without deleting. Refused
1781
+ * when no filter is supplied (a naked prune would
1782
+ * delete every unused image).
1783
+ */
1784
+ pruneImages: (input: PruneImagesInput) => Promise<PruneImagesResult>;
1785
+ };
1786
+ /** Host-wide analysis + guarded cleanup. */
1787
+ doctor: {
1788
+ scan: () => Promise<DoctorReport>;
1789
+ fix: (input: DoctorFixRequestInput) => Promise<DoctorFixResponse>;
1079
1790
  };
1080
1791
  health: () => Promise<HealthStatus>;
1081
1792
  }