ninepm 0.68.0 → 0.72.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
@@ -62,6 +62,28 @@ Deploy a static directory:
62
62
  9pm deploy . --name my-app --slug my-app
63
63
  ```
64
64
 
65
+ ### How long an app's address stays the same
66
+
67
+ Check this before printing a link or QR code. The live URL is `<slug>-<suffix>.9pm.ai`, and the suffix comes from the app's internal id, not from the slug:
68
+
69
+ - Redeploying under the same slug keeps the address.
70
+ - Deleting the app and creating it again under the same slug gives it a **new** address, because the new app gets a new id. Anything printed with the old one stops working.
71
+ - There is no rename: a different address means a different app.
72
+ - A custom domain you own, added on the dashboard's Domains page, gives the app an address of your own. Not every plan includes custom domains.
73
+
74
+ ### Pages that do not exist
75
+
76
+ A static site or `--entry` worker app answers a path that has no file in one of two ways. Every deploy prints which, as a `Missing pages:` line.
77
+
78
+ - `--no-spa` (the default for new apps): the path returns status 404, with the deploy's own `404.html` when it has one. `/` still serves `index.html`, and extensionless links work: `/menu` serves `menu.html`, or redirects to `/menu/` when only `menu/index.html` exists, and `/menu/` serves `menu/index.html`. Right for a multi-page site, where a removed or mistyped page should say "not found" rather than show the homepage.
79
+ - `--spa`: every path with no file serves `index.html`, so a single-page app that routes in the browser loads on a deep link or a refresh. That includes dotted paths such as `/users/jane.smith` and missing files such as `/missing.json`, which get the app's shell rather than a 404.
80
+
81
+ ```sh
82
+ 9pm deploy ./dist --name my-spa --spa
83
+ ```
84
+
85
+ The app remembers the choice: a deploy without either flag keeps it, and a deploy that fails to publish leaves the previous choice in place. Apps created before this choice existed keep serving `index.html` for every unmatched path until a deploy passes one of the flags. A worker app's own `Response`, a 404 included, is always returned as it is. The flags do not apply to `--bundle` or container apps.
86
+
65
87
  Deploy a built framework output directory:
66
88
 
67
89
  ```sh
@@ -137,16 +159,20 @@ Delete an app, its managed database, managed container app, and retained image:
137
159
  9pm delete my-app --confirm my-app
138
160
  ```
139
161
 
162
+ The app's address goes with it: creating an app again under the same slug gives it a new address, so printed links and QR codes stop working. `9pm delete` prints this warning before it deletes anything.
163
+
140
164
  ## Access Modes
141
165
 
142
166
  Apps are public by default.
143
167
 
168
+ Changing an app's access mode needs `--switch-mode`. Without it the command changes nothing and prints what the change would do (who loses access, what happens to the app's custom domains) and the command to re-run. Service-token mode locks every person out and removes the app's custom domains; leaving it sets them up again with new DNS records to publish. Creating another service token on an app already in service-token mode needs no flag.
169
+
144
170
  One-time PIN with email/domain allowlist:
145
171
 
146
172
  ```sh
147
173
  9pm access allow email my-app dev@example.com
148
174
  9pm access allow domain my-app example.com
149
- 9pm access otp my-app
175
+ 9pm access otp my-app --switch-mode
150
176
  9pm access revoke email my-app dev@example.com
151
177
  9pm access revoke domain my-app example.com
152
178
  ```
@@ -154,19 +180,19 @@ One-time PIN with email/domain allowlist:
154
180
  Identity provider access:
155
181
 
156
182
  ```sh
157
- 9pm access idp my-app --provider google
183
+ 9pm access idp my-app --provider google --switch-mode
158
184
  ```
159
185
 
160
186
  Machine access:
161
187
 
162
188
  ```sh
163
- 9pm access service create my-app --name backend
189
+ 9pm access service create my-app --name backend --switch-mode
164
190
  ```
165
191
 
166
192
  Return to public:
167
193
 
168
194
  ```sh
169
- 9pm access public my-app
195
+ 9pm access public my-app --switch-mode
170
196
  ```
171
197
 
172
198
  Create an app-scoped runtime token for server-side allowlist management:
@@ -178,11 +204,40 @@ Create an app-scoped runtime token for server-side allowlist management:
178
204
  Use `9pm_app_...` runtime tokens only from server-side code. They can list, add, and remove email/domain grants for one app. They cannot deploy, create databases, create API keys, or manage another app.
179
205
  Service token secrets returned by `access service create` are also server-side secrets.
180
206
 
207
+ ## Custom Domains
208
+
209
+ Connect a domain you own to an app:
210
+
211
+ ```sh
212
+ 9pm domains add my-app www.example.com
213
+ ```
214
+
215
+ This prints the DNS records to publish at your DNS provider, with names relative to your
216
+ domain (`www`, `_acme-challenge.www`) and what DNS shows for each right now. When a TXT value
217
+ is missing, add it as its own TXT record; other TXT values on that name can stay. The root of
218
+ your domain (`@`) cannot hold a CNAME, so a root domain is shown as an `ALIAS` record: add it as
219
+ ALIAS or ANAME (or a CNAME your provider flattens). A correct ALIAS answers with addresses, which
220
+ cannot be compared from outside, so that row follows the platform's own routing check. Then:
221
+
222
+ ```sh
223
+ 9pm domains wait my-app www.example.com
224
+ ```
225
+
226
+ `wait` exits `0` once the domain is live, `10` if it timed out while a record is missing or could
227
+ not be checked, `11` if it timed out with every record checked and found (the platform is still
228
+ finishing; wait again), `20` if the domain failed or expired, and `1` on a usage, sign-in or API
229
+ error. `9pm domains status <app> <hostname> --json` prints `{hostname, app, state, routing, tls,
230
+ provider, nameservers, records, error, expiresAt, checkedAt}`, where `state` is `live`,
231
+ `waiting_for_dns`, `dns_unverified` (routing is active but the records could not be checked),
232
+ `issuing_certificate`, `failed` or `expired`. Each record's `ok` is `null` when the check cannot
233
+ say, and its `check` says why (`not_checked`, or `apex_addresses`). `list`, `status` and `wait`
234
+ only read; `add`, `check`, `restart` and `remove` need permission to configure the app.
235
+
181
236
  ## Commands
182
237
 
183
238
  ```sh
184
239
  9pm --version
185
- 9pm deploy [directory] --name <name> [--slug <slug>] [--with-db]
240
+ 9pm deploy [directory] --name <name> [--slug <slug>] [--spa | --no-spa] [--with-db]
186
241
  9pm deploy [directory] --check [--runtime worker --entry <path> (--assets <dir> | --no-assets)]
187
242
  9pm deploy [directory] --runtime container --port 8080 [--health-path /health] [--region auto] [--with-db]
188
243
  9pm signup
@@ -197,17 +252,24 @@ Service token secrets returned by `access service create` are also server-side s
197
252
  9pm files get <file-id> [--output <path>] [--project <site>]
198
253
  9pm files share <file-id> [--expires 24h] [--public] [--project <site>]
199
254
  9pm files delete <file-id> [--project <site>]
200
- 9pm deploy [directory] --runtime worker --entry <path> (--assets <dir> | --no-assets) [--with-db]
255
+ 9pm deploy [directory] --runtime worker --entry <path> (--assets <dir> | --no-assets) [--spa | --no-spa] [--with-db]
201
256
  9pm delete <site> --confirm <slug>
257
+ 9pm domains list [<app>] [--owner <organisation>] [--json]
258
+ 9pm domains add <app> <hostname> [--json]
259
+ 9pm domains status <app> <hostname> [--json]
260
+ 9pm domains check <app> <hostname> [--json]
261
+ 9pm domains wait <app> <hostname> [--timeout 30m] [--interval 20s] [--json]
262
+ 9pm domains restart <app> <hostname>
263
+ 9pm domains remove <app> <hostname> --confirm <hostname>
202
264
  9pm access show <site>
203
- 9pm access public <site>
204
- 9pm access otp <site>
205
- 9pm access idp <site> [--provider google|github|entra]
265
+ 9pm access public <site> [--switch-mode]
266
+ 9pm access otp <site> [--switch-mode]
267
+ 9pm access idp <site> [--provider google|github|entra] [--switch-mode]
206
268
  9pm access allow email <site> <email>
207
269
  9pm access allow domain <site> <domain>
208
270
  9pm access revoke email <site> <email>
209
271
  9pm access revoke domain <site> <domain>
210
- 9pm access service create <site> [--name <name>] [--duration 8760h]
272
+ 9pm access service create <site> [--name <name>] [--duration 8760h] [--switch-mode]
211
273
  9pm access service revoke <site> <service-token-id>
212
274
  9pm access token create <site> [--name <name>]
213
275
  9pm access token revoke <site> <token-id>
@@ -229,6 +291,8 @@ Options:
229
291
  - `--entry <path>` selects the Worker runtime server entry file
230
292
  - `--assets <dir>` makes only that directory of a worker deploy public (remembered for later deploys)
231
293
  - `--no-assets` makes no file of a worker deploy public (remembered for later deploys)
294
+ - `--spa` serves `index.html` for every path that has no file, for single-page apps (remembered for later deploys)
295
+ - `--no-spa` returns 404 for a path that has no file, with the deploy's `404.html` when present; the default for new apps (remembered for later deploys)
232
296
  - `--port <port>` selects the HTTP port exposed by a container app
233
297
  - `--health-path <path>` optionally lets 9pm.ai check container health before marking it healthy
234
298
  - `--region <region>` optionally selects provider-neutral container placement
@@ -8,6 +8,7 @@ export type AppRuntime = "static" | "worker" | "container";
8
8
  export type ContainerRegion = "auto" | "us-east" | "us-west" | "europe" | "asia" | "south-america" | "middle-east" | "oceania" | "africa";
9
9
  export type ManagedFileScope = "user" | "project";
10
10
  export type ManagedFileShareMode = "public" | "login_required";
11
+ export type MissingPagesMode = "legacy" | "spa" | "404";
11
12
  export interface Project {
12
13
  id: string;
13
14
  name: string;
@@ -16,6 +17,7 @@ export interface Project {
16
17
  createdAt: string;
17
18
  updatedAt: string;
18
19
  workerPublicAssets?: string | null;
20
+ missingPages?: MissingPagesMode;
19
21
  }
20
22
  export interface AppUserFilesUsage {
21
23
  usedBytes: number;
@@ -0,0 +1,7 @@
1
+ /** A best guess at the zone a custom hostname lives in: `www.tmrwtitans.com` -> `tmrwtitans.com`. */
2
+ export declare function guessZoneApex(hostname: string): string;
3
+ /**
4
+ * The record name the way most registrars' Host field wants it, relative to the zone:
5
+ * `www.tmrwtitans.com` -> `www`, the apex itself -> `@`. A name outside the zone stays whole.
6
+ */
7
+ export declare function relativeRecordName(recordHost: string, apex: string): string;
@@ -0,0 +1,27 @@
1
+ // AUTO-GENERATED by scripts/generate-cli-package.mjs from the in-repo CLI source -- do not edit.
2
+ // Edit src/cli or src/shared instead; this is regenerated on typecheck/test/build/pack.
3
+ // Record names for a custom domain's DNS instructions, written the way a registrar's Host
4
+ // field wants them. Read by the dashboard's DNS table and by `9pm domains`, and copied into
5
+ // the CLI tarball by scripts/generate-cli-package.mjs, so it must import nothing.
6
+ // Second-level labels that sit under a two-letter country code as part of the public suffix
7
+ // (example.co.uk, example.com.au). Not a public suffix list: it covers the common shapes, and
8
+ // the full name is always shown beside the short one for the cases it misses.
9
+ const SECOND_LEVEL = new Set(["co", "com", "net", "org", "gov", "ac", "edu", "ne", "or", "go"]);
10
+ /** A best guess at the zone a custom hostname lives in: `www.tmrwtitans.com` -> `tmrwtitans.com`. */
11
+ export function guessZoneApex(hostname) {
12
+ const labels = hostname.toLowerCase().replace(/\.$/, "").split(".");
13
+ if (labels.length >= 3 && SECOND_LEVEL.has(labels[labels.length - 2]) && labels[labels.length - 1].length === 2) {
14
+ return labels.slice(-3).join(".");
15
+ }
16
+ return labels.slice(-2).join(".");
17
+ }
18
+ /**
19
+ * The record name the way most registrars' Host field wants it, relative to the zone:
20
+ * `www.tmrwtitans.com` -> `www`, the apex itself -> `@`. A name outside the zone stays whole.
21
+ */
22
+ export function relativeRecordName(recordHost, apex) {
23
+ const host = recordHost.toLowerCase().replace(/\.$/, "");
24
+ if (host === apex)
25
+ return "@";
26
+ return host.endsWith(`.${apex}`) ? host.slice(0, -(apex.length + 1)) : host;
27
+ }
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export interface DeployOptions {
18
18
  runtime?: AppRuntime;
19
19
  entry?: string;
20
20
  publicAssets?: string;
21
+ missingPages?: "spa" | "404";
21
22
  port?: number;
22
23
  healthPath?: string;
23
24
  region?: ContainerRegion;
@@ -124,7 +125,7 @@ export interface CreatedServiceToken {
124
125
  clientSecret: string;
125
126
  }
126
127
  export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
127
- export declare const CLI_VERSION = "0.68.0";
128
+ export declare const CLI_VERSION = "0.72.0";
128
129
  type CredentialBackend = "macos-keychain" | "windows-dpapi" | "linux-secret-service" | "file-plaintext" | "none";
129
130
  export interface StoredCredentialEntry {
130
131
  token: string;
@@ -225,7 +226,8 @@ export declare class DeployError extends Error {
225
226
  * prevent (#1061).
226
227
  */
227
228
  export declare class DeliberateRefusalError extends Error {
228
- constructor(message: string);
229
+ readonly code: string | undefined;
230
+ constructor(message: string, code?: string);
229
231
  }
230
232
  export declare function isDeliberateRefusal(error: unknown): boolean;
231
233
  export declare function deployDirectory(initialOptions: DeployOptions): Promise<DeployResult>;
@@ -271,12 +273,16 @@ export type LogoutSummary = {
271
273
  };
272
274
  export declare function summarizeRevocations(attempts: RevocationAttempt[]): LogoutSummary;
273
275
  export declare function getSiteAccess(options: SiteAccessOptions): Promise<AccessState>;
274
- export declare function setSiteAccessPublic(options: SiteAccessOptions): Promise<AccessState["access"]>;
275
- export declare function setSiteAccessOtp(options: SiteAccessOptions): Promise<AccessState["access"]>;
276
- export declare function setSiteAccessIdp(options: SiteAccessOptions & {
276
+ export declare const ACCESS_MODE_CHANGE_UNCONFIRMED = "access_mode_change_unconfirmed";
277
+ export interface AccessModeChangeOptions extends SiteAccessOptions {
278
+ switchMode?: boolean;
279
+ }
280
+ export declare function setSiteAccessPublic(options: AccessModeChangeOptions): Promise<AccessState["access"]>;
281
+ export declare function setSiteAccessOtp(options: AccessModeChangeOptions): Promise<AccessState["access"]>;
282
+ export declare function setSiteAccessIdp(options: AccessModeChangeOptions & {
277
283
  providers?: AccessProviderKey[];
278
284
  }): Promise<AccessState["access"]>;
279
- export declare function createAccessServiceToken(options: SiteAccessOptions & {
285
+ export declare function createAccessServiceToken(options: AccessModeChangeOptions & {
280
286
  name?: string;
281
287
  duration?: string;
282
288
  }): Promise<CreatedServiceToken>;
@@ -343,6 +349,7 @@ export interface ListSitesResult {
343
349
  export declare function listSites(options: ApiOptions & {
344
350
  owner?: string;
345
351
  }): Promise<ListSitesResult>;
352
+ export declare function deleteWarningLines(slug: string): string[];
346
353
  export declare function deleteSite(options: DeleteSiteOptions): Promise<DeletedSiteResult>;
347
354
  export interface DeviceAuthFeedbackOptions {
348
355
  progressStream?: NodeJS.WriteStream;
@@ -611,6 +618,36 @@ type CliCommand = {
611
618
  };
612
619
  apiUrl: string;
613
620
  token: string;
621
+ } | {
622
+ kind: "domains";
623
+ owner?: string;
624
+ action: {
625
+ kind: "list";
626
+ site?: string;
627
+ json: boolean;
628
+ } | {
629
+ kind: "add" | "status" | "check";
630
+ site: string;
631
+ hostname: string;
632
+ json: boolean;
633
+ } | {
634
+ kind: "wait";
635
+ site: string;
636
+ hostname: string;
637
+ json: boolean;
638
+ timeoutSeconds: number;
639
+ intervalSeconds: number;
640
+ } | {
641
+ kind: "restart";
642
+ site: string;
643
+ hostname: string;
644
+ } | {
645
+ kind: "remove";
646
+ site: string;
647
+ hostname: string;
648
+ };
649
+ apiUrl: string;
650
+ token: string;
614
651
  } | {
615
652
  kind: "deployments";
616
653
  action: {
@@ -631,13 +668,16 @@ type CliCommand = {
631
668
  } | {
632
669
  kind: "public";
633
670
  site: string;
671
+ switchMode?: true;
634
672
  } | {
635
673
  kind: "otp";
636
674
  site: string;
675
+ switchMode?: true;
637
676
  } | {
638
677
  kind: "idp";
639
678
  site: string;
640
679
  providers?: AccessProviderKey[];
680
+ switchMode?: true;
641
681
  } | {
642
682
  kind: "allow";
643
683
  site: string;
@@ -653,6 +693,7 @@ type CliCommand = {
653
693
  site: string;
654
694
  name?: string;
655
695
  duration?: string;
696
+ switchMode?: true;
656
697
  } | {
657
698
  kind: "service-revoke";
658
699
  site: string;
@@ -668,6 +709,7 @@ type CliCommand = {
668
709
  tokenId: string;
669
710
  };
670
711
  apiUrl: string;
712
+ apiUrlFrom: ApiUrlSource;
671
713
  token: string;
672
714
  };
673
715
  export declare function parseArgs(argv: string[]): CliCommand;
@@ -676,7 +718,142 @@ export declare function parseEnvFileEntries(content: string, kind: "secret" | "v
676
718
  value: string;
677
719
  kind: "secret" | "var";
678
720
  }>;
721
+ export type ListedDeployment = Pick<Deployment, "id" | "version" | "status" | "createdAt"> & {
722
+ active: boolean;
723
+ };
724
+ export declare function deploymentStatusLabel(deployment: ListedDeployment): string;
725
+ export declare function formatDeploymentsList(deployments: ListedDeployment[]): string[];
726
+ export declare function deploymentsListJson<T extends ListedDeployment>(deployments: T[]): {
727
+ servingDeploymentId: string | null;
728
+ deployments: Array<T & {
729
+ serving: boolean;
730
+ }>;
731
+ };
732
+ export interface CliDnsInstruction {
733
+ type: string;
734
+ host: string;
735
+ value: string;
736
+ optional?: boolean;
737
+ }
738
+ export interface CliDomainDnsCheck {
739
+ provider: string | null;
740
+ nameservers: string[];
741
+ records: Array<{
742
+ type: string;
743
+ host: string;
744
+ expected: string;
745
+ current: string[];
746
+ matches: boolean;
747
+ }>;
748
+ }
749
+ export interface CliDomain {
750
+ hostname: string;
751
+ routingStatus: string;
752
+ tlsStatus: string;
753
+ dnsInstructions: CliDnsInstruction[];
754
+ provisioningError: string | null;
755
+ expireAfter?: string | null;
756
+ expiresAt?: string | null;
757
+ dnsCheck?: CliDomainDnsCheck;
758
+ app?: {
759
+ id: string;
760
+ slug: string;
761
+ name: string;
762
+ };
763
+ }
764
+ export type DomainState = "live" | "waiting_for_dns" | "dns_unverified" | "issuing_certificate" | "failed" | "expired";
765
+ export declare const CUSTOM_DOMAIN_EXPIRED_TEXT = "This domain was removed because its DNS verification was never completed. Add the domain again to retry.";
766
+ export interface DomainRecordStatus {
767
+ type: string;
768
+ name: string;
769
+ fqdn: string;
770
+ expected: string;
771
+ current: string[] | null;
772
+ ok: boolean | null;
773
+ check: DomainRecordCheck;
774
+ apex: boolean;
775
+ optional: boolean;
776
+ }
777
+ export type DomainRecordCheck = "found" | "missing" | "different" | "not_checked" | "apex_addresses";
778
+ export interface DomainStatusJson {
779
+ hostname: string;
780
+ app: string;
781
+ state: DomainState;
782
+ routing: string;
783
+ tls: string;
784
+ provider: string | null;
785
+ nameservers: string[];
786
+ records: DomainRecordStatus[];
787
+ error: string | null;
788
+ expiresAt: string | null;
789
+ checkedAt: string;
790
+ }
791
+ export declare const DOMAIN_WAIT_EXIT: {
792
+ readonly live: 0;
793
+ readonly ownerAction: 10;
794
+ readonly platform: 11;
795
+ readonly failed: 20;
796
+ };
797
+ export declare function domainRecordStatuses(domain: CliDomain): DomainRecordStatus[];
798
+ export declare function deriveDomainState(domain: CliDomain, records?: DomainRecordStatus[]): DomainState;
799
+ export declare function domainStatusJson(domain: CliDomain, app: string, checkedAt: string): DomainStatusJson;
800
+ export declare function domainStateLabel(state: DomainState): string;
801
+ export declare function formatDomainStatus(status: DomainStatusJson, options?: {
802
+ owner?: string;
803
+ }): string[];
804
+ export declare function domainWaitTimeoutMessage(status: DomainStatusJson): string;
805
+ export declare function formatDomainsList(statuses: DomainStatusJson[]): string[];
806
+ export declare class DomainsApiError extends Error {
807
+ readonly status: number;
808
+ readonly code: string | null;
809
+ constructor(message: string, status: number, code: string | null);
810
+ }
811
+ export declare function domainsApiError(status: number, text: string): DomainsApiError;
812
+ export interface DomainCommandOptions extends ApiOptions {
813
+ site: string;
814
+ owner?: string;
815
+ }
816
+ export declare function normalizeDomainArg(value: string): string;
817
+ export declare function listDomainStatuses(options: ApiOptions & {
818
+ site?: string;
819
+ owner?: string;
820
+ now?: () => Date;
821
+ }): Promise<DomainStatusJson[]>;
822
+ export declare function getDomainStatus(options: DomainCommandOptions & {
823
+ hostname: string;
824
+ now?: () => Date;
825
+ }): Promise<DomainStatusJson>;
826
+ export declare function addDomain(options: DomainCommandOptions & {
827
+ hostname: string;
828
+ }): Promise<CliDomain | null>;
829
+ export declare function recheckDomain(options: DomainCommandOptions & {
830
+ hostname: string;
831
+ }): Promise<void>;
832
+ export declare function restartDomain(options: DomainCommandOptions & {
833
+ hostname: string;
834
+ }): Promise<{
835
+ domain: CliDomain;
836
+ replacedInstructions: CliDnsInstruction[];
837
+ }>;
838
+ export declare function removeDomain(options: DomainCommandOptions & {
839
+ hostname: string;
840
+ }): Promise<CliDomain | null>;
841
+ export interface DomainWaitResult {
842
+ status: DomainStatusJson;
843
+ exitCode: number;
844
+ timedOut: boolean;
845
+ }
846
+ export declare function waitForDomain(options: DomainCommandOptions & {
847
+ hostname: string;
848
+ timeoutMs: number;
849
+ intervalMs: number;
850
+ nowMs?: () => number;
851
+ sleepFn?: (ms: number) => Promise<void>;
852
+ onStatus?: (status: DomainStatusJson) => void;
853
+ onRetry?: (error: unknown) => void;
854
+ }): Promise<DomainWaitResult>;
679
855
  export declare function healthWithReason(status: string | null | undefined, reason: string | null | undefined): string;
856
+ export type ApiUrlSource = "flag" | "env" | "default";
680
857
  export declare function workerPublicFiles(paths: readonly string[], serverEntry: string, selection: string | null): Array<{
681
858
  path: string;
682
859
  url: string;
@@ -711,4 +888,15 @@ export declare function hasNoGraphicalDisplay(platform: string, env: Record<stri
711
888
  export declare function formatAppUserFilesUsageLine(slug: string, usage: AppUserFilesUsage): string;
712
889
  export declare class UsageError extends Error {
713
890
  }
891
+ export declare function missingPagesLine(deployment: Deployment): string | null;
892
+ type AccessModeChangeAction = Extract<Extract<CliCommand, {
893
+ kind: "access";
894
+ }>["action"], {
895
+ kind: "public" | "otp" | "idp" | "service-create";
896
+ }>;
897
+ export interface AccessApiTarget {
898
+ apiUrl: string;
899
+ apiUrlFrom: ApiUrlSource;
900
+ }
901
+ export declare function accessSwitchModeCommand(action: AccessModeChangeAction, target?: AccessApiTarget): string;
714
902
  export {};