nylorun 0.3.0-beta → 0.5.0-beta

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +154 -68
  3. package/dist/cli.js +29 -25
  4. package/dist/doctor.d.ts +7 -5
  5. package/dist/doctor.js +58 -29
  6. package/dist/home.d.ts +1 -1
  7. package/dist/home.js +1 -1
  8. package/dist/project/derived-key.d.ts +8 -0
  9. package/dist/project/derived-key.js +18 -0
  10. package/dist/project/link.d.ts +32 -4
  11. package/dist/project/link.js +83 -14
  12. package/dist/project/seed.d.ts +27 -0
  13. package/dist/project/seed.js +111 -0
  14. package/dist/stack/commands.d.ts +42 -19
  15. package/dist/stack/commands.js +465 -152
  16. package/dist/stack/compose-file.d.ts +30 -11
  17. package/dist/stack/compose-file.js +88 -18
  18. package/dist/stack/docker.js +1 -1
  19. package/dist/stack/env-file.d.ts +24 -3
  20. package/dist/stack/env-file.js +49 -4
  21. package/dist/stack/host-files.d.ts +12 -3
  22. package/dist/stack/host-files.js +26 -3
  23. package/dist/stack/images.d.ts +1 -1
  24. package/dist/stack/images.js +1 -1
  25. package/dist/stack/index.d.ts +3 -3
  26. package/dist/stack/index.js +12 -3
  27. package/dist/stack/paths.d.ts +12 -4
  28. package/dist/stack/paths.js +9 -6
  29. package/dist/stack/ports.d.ts +2 -2
  30. package/dist/stack/ports.js +1 -1
  31. package/dist/stack/prepare.d.ts +13 -4
  32. package/dist/stack/prepare.js +25 -13
  33. package/dist/stack/restate-identity.d.ts +1 -1
  34. package/dist/stack/restate-identity.js +1 -1
  35. package/dist/stack/stacks.d.ts +50 -0
  36. package/dist/stack/stacks.js +150 -0
  37. package/dist/stack/versions.d.ts +1 -1
  38. package/dist/stack/versions.js +1 -1
  39. package/dist/telemetry.d.ts +31 -0
  40. package/dist/telemetry.js +79 -0
  41. package/package.json +5 -5
@@ -0,0 +1,8 @@
1
+ /** The derived principal a Project uses; the local Runtime registers it on its Tenant. */
2
+ export declare const PROJECT_PRINCIPAL_ID = "project";
3
+ /**
4
+ * Key of a derived principal on the local Tenant, from the admin key. A copy of
5
+ * `deriveTenantKey` in `@nylorun/admin` (nylorun depends on `@nylorun/core` only); both are
6
+ * tested against the same fixed vector.
7
+ */
8
+ export declare function deriveTenantKey(adminKey: string, tenantId: string, principalId: string): string;
@@ -0,0 +1,18 @@
1
+ import { createHmac } from "node:crypto";
2
+ /** The derived principal a Project uses; the local Runtime registers it on its Tenant. */
3
+ export const PROJECT_PRINCIPAL_ID = "project";
4
+ /**
5
+ * Key of a derived principal on the local Tenant, from the admin key. A copy of
6
+ * `deriveTenantKey` in `@nylorun/admin` (nylorun depends on `@nylorun/core` only); both are
7
+ * tested against the same fixed vector.
8
+ */
9
+ export function deriveTenantKey(adminKey, tenantId, principalId) {
10
+ const message = Buffer.concat([
11
+ Buffer.from("nylorun/principal/v1", "utf8"),
12
+ Buffer.from([0]),
13
+ Buffer.from(principalId, "utf8"),
14
+ Buffer.from([0]),
15
+ Buffer.from(tenantId, "utf8"),
16
+ ]);
17
+ return createHmac("sha256", Buffer.from(adminKey, "utf8")).update(message).digest("hex");
18
+ }
@@ -1,6 +1,34 @@
1
1
  /**
2
- * The linked Project's Tenant id, read from `.nylorun/link.json`, or undefined
3
- * outside a linked Project. nylorun only reads the link: `@nylorun/cli` writes
4
- * it when it creates or chooses a Tenant.
2
+ * The Project link (`.nylorun/link.json`) and credentials (`.nylorun/credentials.json`).
3
+ * `nylorun start` writes both, format 3: the local Tenant's name, its URL and Host id, and
4
+ * the Tenant id as information (nothing in a request selects a Tenant). `start` replaces a link
5
+ * of an older format.
5
6
  */
6
- export declare function linkedTenantId(cwd?: string): Promise<string | undefined>;
7
+ export interface ProjectLink {
8
+ format: 0 | 1 | 2 | 3;
9
+ /** The local Tenant's name (format 3). */
10
+ tenant?: string;
11
+ hostUrl: string;
12
+ hostId: string;
13
+ tenantId?: string;
14
+ }
15
+ export declare function linkPath(projectRoot: string): string;
16
+ export declare function credentialsPath(projectRoot: string): string;
17
+ /** The Project's link, or undefined when it has none. An unreadable link is an error. */
18
+ export declare function readProjectLink(projectRoot: string): Promise<ProjectLink | undefined>;
19
+ export declare function writeProjectLink(projectRoot: string, link: {
20
+ tenant: string;
21
+ hostUrl: string;
22
+ hostId: string;
23
+ tenantId: string;
24
+ }): Promise<void>;
25
+ /** The Project's application key and principal, or undefined when absent or unreadable. */
26
+ export declare function readProjectCredentials(projectRoot: string): Promise<{
27
+ applicationKey: string;
28
+ principalId: string;
29
+ } | undefined>;
30
+ /** `.nylorun/credentials.json` (format 1, mode 0600). */
31
+ export declare function writeProjectCredentials(projectRoot: string, credentials: {
32
+ applicationKey: string;
33
+ principalId: string;
34
+ }): Promise<void>;
@@ -1,21 +1,90 @@
1
- import { readFile } from "node:fs/promises";
1
+ import { randomBytes } from "node:crypto";
2
+ import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
2
3
  import { join } from "node:path";
3
- import { isTenantId } from "@nylorun/core/compatibility";
4
- import { findProjectRoot } from "./root.js";
5
- /**
6
- * The linked Project's Tenant id, read from `.nylorun/link.json`, or undefined
7
- * outside a linked Project. nylorun only reads the link: `@nylorun/cli` writes
8
- * it when it creates or chooses a Tenant.
9
- */
10
- export async function linkedTenantId(cwd = process.cwd()) {
11
- const root = findProjectRoot(cwd);
12
- if (!root)
13
- return undefined;
4
+ import { ProjectLinkFileSchema } from "@nylorun/core/contracts";
5
+ import { CliError } from "../errors.js";
6
+ export function linkPath(projectRoot) {
7
+ return join(projectRoot, ".nylorun", "link.json");
8
+ }
9
+ export function credentialsPath(projectRoot) {
10
+ return join(projectRoot, ".nylorun", "credentials.json");
11
+ }
12
+ /** The Project's link, or undefined when it has none. An unreadable link is an error. */
13
+ export async function readProjectLink(projectRoot) {
14
+ let text;
15
+ try {
16
+ text = await readFile(linkPath(projectRoot), "utf8");
17
+ }
18
+ catch (error) {
19
+ if (error.code === "ENOENT")
20
+ return undefined;
21
+ throw error;
22
+ }
23
+ let value;
24
+ try {
25
+ value = JSON.parse(text);
26
+ }
27
+ catch {
28
+ value = undefined;
29
+ }
30
+ const parsed = ProjectLinkFileSchema.safeParse(value);
31
+ if (!parsed.success)
32
+ throw new CliError(`Invalid or newer Project link at ${linkPath(projectRoot)}. Upgrade nylorun, or remove the file and run "npx nylorun start" again.`, 1);
33
+ const link = parsed.data;
34
+ return {
35
+ format: link.format,
36
+ ...(link.format === 3 && link.tenant ? { tenant: link.tenant } : {}),
37
+ hostUrl: link.hostUrl.replace(/\/$/, ""),
38
+ hostId: link.hostId,
39
+ ...(link.tenantId ? { tenantId: link.tenantId } : {}),
40
+ };
41
+ }
42
+ async function writePrivate(path, value) {
43
+ const temporary = `${path}.${randomBytes(8).toString("hex")}.tmp`;
14
44
  try {
15
- const link = JSON.parse(await readFile(join(root, ".nylorun", "link.json"), "utf8"));
16
- return isTenantId(link.tenantId) ? link.tenantId : undefined;
45
+ await writeFile(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
46
+ await rename(temporary, path);
47
+ await chmod(path, 0o600);
48
+ }
49
+ finally {
50
+ await rm(temporary, { force: true });
51
+ }
52
+ }
53
+ /** `.nylorun/` mode 0700 with its own `.gitignore` of `*`; the Project's is never edited. */
54
+ async function ensureProjectDir(projectRoot) {
55
+ const dir = join(projectRoot, ".nylorun");
56
+ await mkdir(dir, { recursive: true, mode: 0o700 });
57
+ await chmod(dir, 0o700);
58
+ await writeFile(join(dir, ".gitignore"), "*\n", { mode: 0o600 });
59
+ }
60
+ export async function writeProjectLink(projectRoot, link) {
61
+ await ensureProjectDir(projectRoot);
62
+ await writePrivate(linkPath(projectRoot), {
63
+ format: 3,
64
+ tenant: link.tenant,
65
+ hostUrl: link.hostUrl.replace(/\/$/, ""),
66
+ hostId: link.hostId,
67
+ tenantId: link.tenantId,
68
+ });
69
+ }
70
+ /** The Project's application key and principal, or undefined when absent or unreadable. */
71
+ export async function readProjectCredentials(projectRoot) {
72
+ try {
73
+ const value = JSON.parse(await readFile(credentialsPath(projectRoot), "utf8"));
74
+ return typeof value.applicationKey === "string" && typeof value.principalId === "string"
75
+ ? { applicationKey: value.applicationKey, principalId: value.principalId }
76
+ : undefined;
17
77
  }
18
78
  catch {
19
79
  return undefined;
20
80
  }
21
81
  }
82
+ /** `.nylorun/credentials.json` (format 1, mode 0600). */
83
+ export async function writeProjectCredentials(projectRoot, credentials) {
84
+ await ensureProjectDir(projectRoot);
85
+ await writePrivate(credentialsPath(projectRoot), {
86
+ format: 1,
87
+ applicationKey: credentials.applicationKey,
88
+ principalId: credentials.principalId,
89
+ });
90
+ }
@@ -0,0 +1,27 @@
1
+ type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
2
+ export interface SeedResult {
3
+ /** Settings the seed set, and the ones the Tenant already had. */
4
+ applied: string[];
5
+ kept: string[];
6
+ /** `provider/model` stored in the Tenant's vault. */
7
+ model?: string;
8
+ }
9
+ /**
10
+ * The Project's `.env` as a value map. It is never applied to this process or written back:
11
+ * a Project's `.env` never configures the Host.
12
+ */
13
+ export declare function readProjectEnv(projectRoot: string): Record<string, string>;
14
+ /**
15
+ * Seed a new Project's Tenant from the Project's `.env`: the sandbox backend
16
+ * (`NYLORUN_SANDBOX=auto|virtual`, `PUT /v1/tenant/config/seed`, which keeps settings the
17
+ * Tenant has) and the model credential (`MODEL_PROVIDER`, `MODEL`, `MODEL_PROVIDER_API_KEY`,
18
+ * `MODEL_PROVIDER_BASE_URL`, or `.nylorun/auth.json`), stored in the Tenant's vault unless the
19
+ * Tenant has a model or `NYLORUN_DEV_MODEL=fixture`.
20
+ */
21
+ export declare function seedTenant(input: {
22
+ fetch: FetchLike;
23
+ runtimeUrl: string;
24
+ applicationKey: string;
25
+ projectRoot: string;
26
+ }): Promise<SeedResult>;
27
+ export {};
@@ -0,0 +1,111 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { readFileSync, statSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { parseEnv } from "node:util";
5
+ import { PROTOCOL_HEADER, PROTOCOL_VERSION } from "@nylorun/core/compatibility";
6
+ /**
7
+ * The Project's `.env` as a value map. It is never applied to this process or written back:
8
+ * a Project's `.env` never configures the Host.
9
+ */
10
+ export function readProjectEnv(projectRoot) {
11
+ const file = join(projectRoot, ".env");
12
+ try {
13
+ if (!statSync(file).isFile())
14
+ return {};
15
+ const result = {};
16
+ for (const [key, value] of Object.entries(parseEnv(readFileSync(file, "utf8"))))
17
+ if (value !== undefined)
18
+ result[key] = value;
19
+ return result;
20
+ }
21
+ catch {
22
+ return {};
23
+ }
24
+ }
25
+ function modelFromEnv(env, projectRoot) {
26
+ const provider = env.MODEL_PROVIDER?.trim();
27
+ const model = env.MODEL?.trim();
28
+ if (!provider || !model)
29
+ return undefined;
30
+ const baseUrl = env.MODEL_PROVIDER_BASE_URL?.trim();
31
+ const base = { provider, model, ...(baseUrl ? { baseUrl } : {}) };
32
+ const key = env.MODEL_PROVIDER_API_KEY;
33
+ if (key)
34
+ return { ...base, auth: { type: "api_key", key } };
35
+ for (const relative of [".nylorun/auth.json", ".env/auth.json"]) {
36
+ try {
37
+ const all = JSON.parse(readFileSync(join(projectRoot, relative), "utf8"));
38
+ const credential = all[provider];
39
+ if (credential?.type === "api_key" || credential?.type === "oauth")
40
+ return { ...base, auth: credential };
41
+ }
42
+ catch {
43
+ /* absent */
44
+ }
45
+ }
46
+ return undefined;
47
+ }
48
+ async function call(fetch, url, applicationKey, init = {}) {
49
+ const response = await fetch(url, {
50
+ ...(init.method ? { method: init.method } : {}),
51
+ headers: {
52
+ authorization: `Bearer ${applicationKey}`,
53
+ [PROTOCOL_HEADER]: String(PROTOCOL_VERSION),
54
+ accept: "application/json",
55
+ ...(init.body === undefined ? {} : { "content-type": "application/json" }),
56
+ },
57
+ ...(init.body === undefined ? {} : { body: JSON.stringify(init.body) }),
58
+ redirect: "error",
59
+ signal: AbortSignal.timeout(15_000),
60
+ });
61
+ const body = (await response.json().catch(() => ({})));
62
+ if (!response.ok)
63
+ throw new Error(`${init.method ?? "GET"} ${new URL(url).pathname} returned ${response.status}${typeof body.message === "string" ? `: ${body.message}` : ""}`);
64
+ return body;
65
+ }
66
+ /**
67
+ * Seed a new Project's Tenant from the Project's `.env`: the sandbox backend
68
+ * (`NYLORUN_SANDBOX=auto|virtual`, `PUT /v1/tenant/config/seed`, which keeps settings the
69
+ * Tenant has) and the model credential (`MODEL_PROVIDER`, `MODEL`, `MODEL_PROVIDER_API_KEY`,
70
+ * `MODEL_PROVIDER_BASE_URL`, or `.nylorun/auth.json`), stored in the Tenant's vault unless the
71
+ * Tenant has a model or `NYLORUN_DEV_MODEL=fixture`.
72
+ */
73
+ export async function seedTenant(input) {
74
+ const env = readProjectEnv(input.projectRoot);
75
+ const base = input.runtimeUrl.replace(/\/$/, "");
76
+ const result = { applied: [], kept: [] };
77
+ const sandbox = env.NYLORUN_SANDBOX?.trim();
78
+ if (sandbox === "auto" || sandbox === "virtual") {
79
+ const body = (await call(input.fetch, `${base}/v1/tenant/config/seed`, input.applicationKey, {
80
+ method: "PUT",
81
+ body: { requestId: randomUUID(), sandbox: { backend: sandbox } },
82
+ }));
83
+ if (Array.isArray(body.applied))
84
+ result.applied = body.applied.map(String);
85
+ if (Array.isArray(body.kept))
86
+ result.kept = body.kept.map(String);
87
+ }
88
+ if (env.NYLORUN_DEV_MODEL?.trim() === "fixture")
89
+ return result;
90
+ const seeded = modelFromEnv(env, input.projectRoot);
91
+ if (!seeded)
92
+ return result;
93
+ const current = (await call(input.fetch, `${base}/v1/tenant/model`, input.applicationKey));
94
+ if (current.configured === true) {
95
+ result.kept.push("model");
96
+ return result;
97
+ }
98
+ await call(input.fetch, `${base}/v1/tenant/model`, input.applicationKey, {
99
+ method: "PUT",
100
+ body: {
101
+ requestId: randomUUID(),
102
+ idempotencyKey: randomUUID(),
103
+ provider: seeded.provider,
104
+ model: seeded.model,
105
+ ...(seeded.baseUrl ? { baseUrl: seeded.baseUrl } : {}),
106
+ auth: seeded.auth,
107
+ },
108
+ });
109
+ result.model = `${seeded.provider}/${seeded.model}`;
110
+ return result;
111
+ }
@@ -1,10 +1,11 @@
1
+ import type { HostTenant } from "@nylorun/core/contracts";
1
2
  import { type ComposeService, type DockerRunner } from "./docker.js";
2
3
  import type { PortProbe } from "./ports.js";
3
4
  import { type FetchLike } from "./studio-login.js";
4
- export declare const STACK_SERVICES: readonly ["postgres", "restate", "s2", "runtime", "studio"];
5
+ export declare const STACK_SERVICES: readonly ["postgres", "restate", "s2", "gateway", "runtime", "studio"];
5
6
  export declare const stackUsage: string;
6
7
  export interface StackDeps {
7
- /** Environment snapshot (NYLORUN_HOME, image overrides, NYLORUN_STACK_PROJECT). */
8
+ /** Environment snapshot (NYLORUN_HOME, NYLORUN_TENANT, image overrides, NYLORUN_COMPOSE_PROJECT). */
8
9
  env: Readonly<Record<string, string | undefined>>;
9
10
  docker: DockerRunner;
10
11
  fetch: FetchLike;
@@ -23,14 +24,19 @@ export interface StackDeps {
23
24
  openBrowser(url: string): Promise<boolean>;
24
25
  /** Is this process id alive? (launcher-managed Runtime detection) */
25
26
  pidAlive(pid: number): boolean;
27
+ /** Where the project is looked for (default: the process's working directory). */
28
+ cwd?: string;
29
+ /** `~/.nylorun`: the Tenants (tests use a temporary directory). */
30
+ nylorunRoot?: string;
26
31
  /** Delay between health polls; tests shorten it. */
27
32
  pollMs?: number;
28
- /** How long `start` waits for the Runtime's /health after Compose. */
33
+ /** How long `start` waits for the Runtime's /health, and then its Tenant, after Compose. */
29
34
  healthTimeoutMs?: number;
30
35
  /** How long a Studio login is retried before warning. */
31
36
  loginTimeoutMs?: number;
32
37
  }
33
- export declare function stackProject(env: StackDeps["env"]): string;
38
+ /** The Compose project of Tenant `name`: `nylorun-<name>`, or `NYLORUN_COMPOSE_PROJECT`. */
39
+ export declare function stackProject(env: StackDeps["env"], name: string): string;
34
40
  interface Flags {
35
41
  rest: string[];
36
42
  booleans: Set<string>;
@@ -44,23 +50,28 @@ export declare function parseStackFlags(args: readonly string[], allowed: {
44
50
  lists?: readonly string[];
45
51
  aliases?: Record<string, string>;
46
52
  }, usage: string): Flags;
53
+ /** The Host's one Tenant as `/v1/admin/status` reports it (no envelope). */
54
+ export type StackTenant = Pick<HostTenant, "id" | "name" | "state" | "cause">;
47
55
  /** Printed by `start` when it does not open Studio itself. */
48
56
  export declare const STUDIO_SIGN_IN_HINT = "To sign a browser in to Studio, run \"npx nylorun studio\".";
49
- /** Printed by `start` while the Host has no Tenant: nylorun never creates one. */
50
- export declare const TENANT_HINT = "No Tenant yet. Create one in Studio, or run `npx @nylorun/cli tenant create` in your project.";
51
57
  export interface StackStatus {
58
+ /** The Tenant's name. */
59
+ name: string;
60
+ /** The Compose project. */
52
61
  project: string;
62
+ /** The Host root. */
53
63
  home: string;
54
64
  state: "running" | "stopped" | "absent";
55
65
  runtime: {
56
66
  url?: string;
57
- /** The Admin API (operator listener), when the stack publishes one. */
67
+ /** The Admin API (operator listener), when the Tenant publishes one. */
58
68
  adminUrl?: string;
59
69
  healthy: boolean;
60
70
  version?: string;
61
71
  hostId?: string;
62
- tenants?: number;
63
72
  };
73
+ /** The Tenant as the Runtime reports it, while it answers. */
74
+ tenant?: StackTenant;
64
75
  studio: {
65
76
  url?: string;
66
77
  state: string;
@@ -70,16 +81,27 @@ export interface StackStatus {
70
81
  restate: {
71
82
  url?: string;
72
83
  };
84
+ /** The gateway container (the Model Gate), in the combined packing. */
85
+ gateway: {
86
+ state: string;
87
+ healthy: boolean;
88
+ };
73
89
  services: ComposeService[];
74
90
  }
75
91
  /** What `nylorun status` reports, as data (`nylorun doctor`). */
76
- export declare function readStackStatus(deps: StackDeps): Promise<StackStatus>;
77
- /** The running stack as clients reach it. */
92
+ export declare function readStackStatus(deps: StackDeps, options?: {
93
+ name?: string;
94
+ }): Promise<StackStatus>;
95
+ /** The running Tenant as clients reach it. */
78
96
  export interface StackEndpoints {
79
- /** The Host root (`NYLORUN_HOME` or `~/.nylorun`). */
97
+ /** The Tenant's name. */
98
+ name: string;
99
+ /** The Host root. */
80
100
  home: string;
81
101
  /** `http://localhost:<port>` */
82
102
  runtimeUrl: string;
103
+ /** The operator listener (Admin API). */
104
+ adminUrl: string;
83
105
  hostId: string;
84
106
  adminKey: string;
85
107
  studioPort: number;
@@ -89,27 +111,28 @@ export interface StackEndpoints {
89
111
  started: boolean;
90
112
  }
91
113
  /**
92
- * Start the stack unless it is already running (`nylorun studio`): the
93
- * `start` code path without its own output. Compose progress
94
- * still streams, since the first run pulls images.
114
+ * Start the selected Tenant unless it is already running (`nylorun studio`): the
115
+ * `start` code path without its output, its Tenant wait or the Project link. Compose
116
+ * progress still streams, since the first run pulls images.
95
117
  */
96
118
  export declare function ensureStack(deps: StackDeps, options: {
97
119
  studio: boolean;
120
+ name?: string;
98
121
  }): Promise<StackEndpoints>;
99
122
  /** Add Studio's `next` path (e.g. `/tenants/<id>`) to a login URL. */
100
123
  export declare function withNext(loginUrl: string, next: string | undefined): string;
101
- /** The Studio page for one Tenant, as a login `next` path. */
124
+ /** The Tenant's Studio page, as a login `next` path. */
102
125
  export declare function tenantStudioPath(tenantId: string): string;
103
126
  /**
104
- * Mint a fresh Studio login URL on a running stack, landing on `next`.
127
+ * Mint a fresh Studio login URL on a running Tenant, landing on `next`.
105
128
  * Undefined (after a warning) when Studio does not answer.
106
129
  */
107
- export declare function studioLoginUrl(deps: StackDeps, stack: Pick<StackEndpoints, "studioPort" | "adminKey">, next?: string): Promise<string | undefined>;
108
- /** `nylorun studio`, landing on `next` (the linked Project's Tenant) when given. */
130
+ export declare function studioLoginUrl(deps: StackDeps, running: Pick<StackEndpoints, "studioPort" | "adminKey">, next?: string): Promise<string | undefined>;
131
+ /** `nylorun studio`, landing on `next` when given, else on the Tenant's page. */
109
132
  export declare function runStudioCommand(args: readonly string[], deps: StackDeps, options?: {
110
133
  next?: string;
111
134
  }): Promise<number>;
112
135
  export declare function isStackCommand(name: string | undefined): boolean;
113
- /** Run one stack command (`up`/`start`, `down`/`stop`, `status`, `logs`, `reset`, `studio`). */
136
+ /** Run one Tenant command (`up`/`start`, `down`/`stop`, `status`, `logs`, `reset`, `studio`, `ls`, `delete`). */
114
137
  export declare function runStackCommand(name: string, args: readonly string[], deps: StackDeps): Promise<number>;
115
138
  export {};