nylorun 0.13.0-beta → 0.15.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 (76) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/README.md +255 -166
  3. package/dist/cli.js +141 -47
  4. package/dist/client/agents.d.ts +6 -0
  5. package/dist/client/agents.js +148 -0
  6. package/dist/client/artifacts.d.ts +4 -0
  7. package/dist/client/artifacts.js +210 -0
  8. package/dist/client/doctor.d.ts +10 -4
  9. package/dist/client/doctor.js +28 -20
  10. package/dist/client/environment-manifests.d.ts +10 -0
  11. package/dist/client/environment-manifests.js +173 -0
  12. package/dist/client/environments.d.ts +4 -0
  13. package/dist/client/environments.js +295 -0
  14. package/dist/client/io.d.ts +31 -0
  15. package/dist/client/io.js +72 -0
  16. package/dist/client/mcp.d.ts +11 -0
  17. package/dist/{stack → client}/mcp.js +40 -26
  18. package/dist/client/model/pi-ai.d.ts +3 -3
  19. package/dist/client/model/pi-ai.js +4 -4
  20. package/dist/client/models.d.ts +13 -0
  21. package/dist/client/models.js +203 -0
  22. package/dist/client/runtime.d.ts +40 -0
  23. package/dist/client/runtime.js +134 -0
  24. package/dist/client/sessions.d.ts +4 -0
  25. package/dist/client/sessions.js +295 -0
  26. package/dist/client/settings.d.ts +12 -0
  27. package/dist/client/settings.js +103 -0
  28. package/dist/client/signing-keys.d.ts +3 -0
  29. package/dist/client/signing-keys.js +32 -0
  30. package/dist/client/target.d.ts +33 -0
  31. package/dist/client/target.js +65 -0
  32. package/dist/client/tenant.d.ts +22 -0
  33. package/dist/client/tenant.js +176 -0
  34. package/dist/doctor.js +3 -3
  35. package/dist/errors.d.ts +1 -1
  36. package/dist/errors.js +1 -1
  37. package/dist/nylo.js +20 -4
  38. package/dist/sandbox/cluster-file.d.ts +1 -1
  39. package/dist/sandbox/commands.d.ts +1 -1
  40. package/dist/sandbox/commands.js +7 -7
  41. package/dist/sandbox/manifests.d.ts +1 -1
  42. package/dist/sandbox/manifests.js +1 -1
  43. package/dist/sandbox/probe.d.ts +1 -1
  44. package/dist/stack/commands.d.ts +9 -4
  45. package/dist/stack/commands.js +22 -26
  46. package/dist/stack/compose-file.d.ts +1 -1
  47. package/dist/stack/compose-file.js +22 -13
  48. package/dist/stack/env-file.d.ts +2 -2
  49. package/dist/stack/env-file.js +2 -2
  50. package/dist/stack/images.d.ts +1 -1
  51. package/dist/stack/images.js +1 -1
  52. package/dist/stack/index.d.ts +1 -10
  53. package/dist/stack/index.js +3 -19
  54. package/dist/stack/keys.d.ts +2 -2
  55. package/dist/stack/keys.js +14 -11
  56. package/dist/stack/paths.d.ts +1 -1
  57. package/dist/stack/prepare.d.ts +1 -1
  58. package/dist/stack/stacks.js +1 -1
  59. package/dist/stack/versions.d.ts +2 -2
  60. package/dist/stack/versions.js +3 -3
  61. package/dist/telemetry.d.ts +2 -2
  62. package/dist/telemetry.js +4 -4
  63. package/package.json +10 -9
  64. package/dist/client/access/commands.d.ts +0 -8
  65. package/dist/client/access/commands.js +0 -33
  66. package/dist/client/cli.d.ts +0 -2
  67. package/dist/client/cli.js +0 -106
  68. package/dist/client/installation/commands.d.ts +0 -7
  69. package/dist/client/installation/commands.js +0 -80
  70. package/dist/client/project/connection.d.ts +0 -36
  71. package/dist/client/project/connection.js +0 -80
  72. package/dist/client/project/env.d.ts +0 -3
  73. package/dist/client/project/env.js +0 -19
  74. package/dist/stack/mcp.d.ts +0 -9
  75. package/dist/stack/sandbox.d.ts +0 -4
  76. package/dist/stack/sandbox.js +0 -106
@@ -38,13 +38,8 @@ export const stackUsage = ` up|start [--tenant <name>] [--no-link] [--no-studio
38
38
  --restate-ui (or NYLORUN_RESTATE_UI=1) publishes Restate's UI on loopback for debugging
39
39
  down|stop [--tenant <name> | --all] stop the Tenant's containers (--all: every Tenant's); keep volumes
40
40
  status [--tenant <name>] [--json] the Tenant, its id, services, endpoints and Runtime health
41
- logs [service] [--tenant <name>] [-f] [--tail <n>]
42
- container logs (${STACK_SERVICES.join(", ")})
43
41
  studio [--tenant <name>] [--no-open]
44
- sign a browser in to Studio on the Tenant; --no-open prints the login URL; start the Tenant if it is stopped
45
- reset [--tenant <name>] [--yes] delete the Tenant's data (volumes, tenant/ and vault key); the next start creates it anew
46
- ls [--json] the Tenants on this machine, with their memory in use
47
- delete <tenant> --yes remove a Tenant: containers, volumes and its Host root, with the vault key (KEK)`;
42
+ sign a browser in to Studio on the Tenant; --no-open prints the login URL; start the Tenant if it is stopped`;
48
43
  const usageError = (message) => new CliError(message, 2);
49
44
  /** The Compose project of Tenant `name`: `nylorun-<name>`, or `NYLORUN_COMPOSE_PROJECT`. */
50
45
  export function stackProject(env, name) {
@@ -166,7 +161,8 @@ export function parseStackFlags(args, allowed, usage) {
166
161
  for (let index = 0; index < args.length; index += 1) {
167
162
  const raw = args[index];
168
163
  const arg = allowed.aliases?.[raw] ?? raw;
169
- if (!arg.startsWith("-")) {
164
+ // `-` alone is an argument: standard input.
165
+ if (arg === "-" || !arg.startsWith("-")) {
170
166
  rest.push(arg);
171
167
  continue;
172
168
  }
@@ -224,7 +220,7 @@ async function waitForHealth(ctx, runtimeUrl, hostId) {
224
220
  return health;
225
221
  }
226
222
  if (Date.now() > deadline)
227
- throw new CliError(`The Runtime did not answer ${runtimeUrl}/health. See "nylorun logs runtime".`, 7);
223
+ throw new CliError(`The Runtime did not answer ${runtimeUrl}/health. See "nylorun local logs runtime".`, 7);
228
224
  await sleep(ctx.deps.pollMs ?? 500);
229
225
  }
230
226
  }
@@ -284,7 +280,7 @@ function tenantCauseMessage(ctx, tenant) {
284
280
  ? ` This nylorun pins Runtime ${ctx.deps.runtimeVersion}, older than the Tenant's database: update nylorun (npx nylorun@latest start).`
285
281
  : "";
286
282
  const message = tenant.message ? `: ${tenant.message.replace(/\.?$/, ".")}` : ".";
287
- return `Tenant ${ctx.name} is unavailable (${tenant.cause})${message}${hint} See "nylorun logs runtime".`;
283
+ return `Tenant ${ctx.name} is unavailable (${tenant.cause})${message}${hint} See "nylorun local logs runtime".`;
288
284
  }
289
285
  /**
290
286
  * Wait until the Tenant is open (`/ready`'s `tenant` check): the Runtime creates it on the
@@ -304,7 +300,7 @@ async function waitForTenant(ctx, runtimeUrl) {
304
300
  if (!open && tenant?.cause)
305
301
  throw new CliError(tenantCauseMessage(ctx, tenant), 7);
306
302
  if (late)
307
- throw new CliError(`Tenant ${ctx.name} did not open (${runtimeUrl}/ready: ${open ? "open, but nylorun-operate status did not answer" : "not open"})${tenant?.message ? `: ${tenant.message.replace(/\.$/, "")}` : ""}. See "nylorun status" and "nylorun logs runtime".`, 7);
303
+ throw new CliError(`Tenant ${ctx.name} did not open (${runtimeUrl}/ready: ${open ? "open, but nylorun-operate status did not answer" : "not open"})${tenant?.message ? `: ${tenant.message.replace(/\.$/, "")}` : ""}. See "nylorun status" and "nylorun local logs runtime".`, 7);
308
304
  }
309
305
  await sleep(ctx.deps.pollMs ?? 500);
310
306
  }
@@ -394,7 +390,7 @@ async function bringUp(ctx, options) {
394
390
  await refuseDowngrade(ctx, options.allowDowngrade ?? false);
395
391
  const legacy = await oldVolumes(deps.docker, ctx.project);
396
392
  if (legacy.old.length && !legacy.current)
397
- throw new CliError(`Tenant ${ctx.name} was created by nylorun 0.5; its data is in the old volumes ${legacy.old.join(", ")}. Run "nylorun reset --tenant ${ctx.name}" to start it fresh; that also removes the old volumes (or drop them yourself with "docker volume rm ${legacy.old.join(" ")}").`, 3);
393
+ throw new CliError(`Tenant ${ctx.name} was created by nylorun 0.5; its data is in the old volumes ${legacy.old.join(", ")}. Run "nylorun local reset --tenant ${ctx.name}" to start it fresh; that also removes the old volumes (or drop them yourself with "docker volume rm ${legacy.old.join(" ")}").`, 3);
398
394
  const base = nylorunRoot(deps);
399
395
  const telemetry = await readTelemetry(base);
400
396
  const analytics = telemetryDecision(deps.env, telemetry).enabled;
@@ -437,21 +433,21 @@ async function bringUp(ctx, options) {
437
433
  const tenant = await operateStatus(ctx);
438
434
  if (tenant?.cause)
439
435
  throw new CliError(tenantCauseMessage(ctx, tenant), 7);
440
- throw new CliError(`docker compose up failed (exit ${up}). See "nylorun logs runtime", "nylorun logs gateway", "nylorun logs harness" and "nylorun status".`, 7);
436
+ throw new CliError(`docker compose up failed (exit ${up}). See "nylorun local logs runtime", "nylorun local logs gateway", "nylorun local logs harness" and "nylorun status".`, 7);
441
437
  }
442
438
  await waitForHealth(ctx, runtimeUrl, prepared.host.hostId);
443
439
  // Without --wait: a cluster that is down must not fail the start; sessions refuse pods.
444
440
  if (prepared.env.sandboxes) {
445
441
  const sandboxes = await deps.docker.stream(composeArgs(ctx, "up", "--detach", "sandboxes"));
446
442
  if (sandboxes !== 0)
447
- deps.err(`Warning: the sandboxes service did not start. See "nylorun logs sandboxes".`);
443
+ deps.err(`Warning: the sandboxes service did not start. See "nylorun local logs sandboxes".`);
448
444
  }
449
445
  let studioStarted = false;
450
446
  if (options.studio) {
451
447
  const studio = await deps.docker.stream(composeArgs(ctx, "up", "--detach", "--wait", "--wait-timeout", "120", "studio"));
452
448
  studioStarted = studio === 0;
453
449
  if (!studioStarted)
454
- deps.err(`Warning: Studio did not start (image ${prepared.env.studioImage}). The Runtime is up; see "nylorun logs studio".`);
450
+ deps.err(`Warning: Studio did not start (image ${prepared.env.studioImage}). The Runtime is up; see "nylorun local logs studio".`);
455
451
  }
456
452
  return {
457
453
  runtimeUrl,
@@ -561,7 +557,7 @@ async function linkProject(ctx, started, tenantId) {
561
557
  deps.err(`Seeded the Tenant from .env: ${set.join(", ")}.`);
562
558
  }
563
559
  catch (error) {
564
- deps.err(`Warning: could not seed the Tenant from .env (${error instanceof Error ? error.message : String(error)}). Set the model in Studio or with "npx -p nylorun nylo configure".`);
560
+ deps.err(`Warning: could not seed the Tenant from .env (${error instanceof Error ? error.message : String(error)}). Set the model in Studio or with "npx nylorun tenant models set".`);
565
561
  }
566
562
  }
567
563
  const START_USAGE = "nylorun start [--tenant <name>] [--no-link] [--no-studio] [--allow-downgrade] [--studio-embed-origin <origin>]... [--studio-embed-origin-reset] [--restate-ui]";
@@ -777,14 +773,14 @@ async function status(deps, args) {
777
773
  out(`Embeds ${result.studio.embedOrigins.join(" ")} (may show Studio in a frame)`);
778
774
  out(`Gateway ${result.gateway.state} (the Model Gate; model calls fail while it is down)`);
779
775
  out(result.harness.mode === "remote"
780
- ? `Harness ${result.harness.state}, remote${result.harness.connected === undefined ? "" : `, ${result.harness.connected} connected`} (agent turns, MCP servers and workspaces; "nylorun logs harness")`
776
+ ? `Harness ${result.harness.state}, remote${result.harness.connected === undefined ? "" : `, ${result.harness.connected} connected`} (agent turns, MCP servers and workspaces; "nylorun local logs harness")`
781
777
  : `Harness in-process (NYLORUN_HARNESS=in-process in .env: turns run in the runtime container)`);
782
778
  out(result.restate.url
783
779
  ? `Restate UI ${result.restate.url} (unauthenticated; published by --restate-ui)`
784
780
  : `Restate UI not published (nylorun start --restate-ui)`);
785
781
  const sandboxes = result.services.find((s) => s.service === "sandboxes");
786
782
  if (sandboxes)
787
- out(`Sandboxes ${[sandboxes.state, sandboxes.health].filter(Boolean).join(", ")} (see "nylorun sandbox status")`);
783
+ out(`Sandboxes ${[sandboxes.state, sandboxes.health].filter(Boolean).join(", ")} (see "nylorun local sandbox status")`);
788
784
  out(`Services ${STACK_SERVICES.map((name) => {
789
785
  const service = result.services.find((s) => s.service === name);
790
786
  const state = service
@@ -796,7 +792,7 @@ async function status(deps, args) {
796
792
  return result.runtime.healthy ? 0 : 3;
797
793
  }
798
794
  async function logs(deps, args) {
799
- const usage = `nylorun logs [${STACK_SERVICES.join("|")}] [--tenant <name>] [-f] [--tail <n>]`;
795
+ const usage = `nylorun local logs [${STACK_SERVICES.join("|")}] [--tenant <name>] [-f] [--tail <n>]`;
800
796
  const flags = parseStackFlags(args, { booleans: ["--follow"], values: ["--tail", "--tenant"], aliases: { "-f": "--follow" } }, usage);
801
797
  if (flags.rest.length > 1)
802
798
  throw usageError(`Usage: ${usage}`);
@@ -812,7 +808,7 @@ async function logs(deps, args) {
812
808
  return await deps.docker.stream(composeArgs(ctx, "logs", ...(flags.booleans.has("--follow") ? ["--follow"] : []), ...(tail !== undefined ? ["--tail", tail] : []), ...(service ? [service] : [])));
813
809
  }
814
810
  async function reset(deps, args) {
815
- const usage = "nylorun reset [--tenant <name>] [--yes]";
811
+ const usage = "nylorun local reset [--tenant <name>] [--yes]";
816
812
  const flags = parseStackFlags(args, { booleans: ["--yes"], values: ["--tenant"], aliases: { "-y": "--yes" } }, usage);
817
813
  if (flags.rest.length)
818
814
  throw usageError(`Usage: ${usage}`);
@@ -821,7 +817,7 @@ async function reset(deps, args) {
821
817
  if (!flags.booleans.has("--yes")) {
822
818
  const question = `Delete Tenant ${ctx.name}'s volumes (Compose project ${ctx.project}), its Tenant directory ${paths.tenant} and its vault key ${paths.vaultKey}? Its data is lost, and the next start creates the Tenant anew, with a new id. [y/N] `;
823
819
  if (!deps.confirm)
824
- throw usageError(`nylorun reset deletes all of Tenant ${ctx.name}'s data; pass --yes to confirm when not in a terminal.`);
820
+ throw usageError(`nylorun local reset deletes all of Tenant ${ctx.name}'s data; pass --yes to confirm when not in a terminal.`);
825
821
  if (!(await deps.confirm(question))) {
826
822
  deps.err("Reset cancelled.");
827
823
  return 1;
@@ -856,7 +852,7 @@ async function composeProjects(docker) {
856
852
  }
857
853
  }
858
854
  async function ls(deps, args) {
859
- const usage = "nylorun ls [--json]";
855
+ const usage = "nylorun local ls [--json]";
860
856
  const flags = parseStackFlags(args, { booleans: ["--json"] }, usage);
861
857
  if (flags.rest.length)
862
858
  throw usageError(`Usage: ${usage}`);
@@ -912,7 +908,7 @@ async function ls(deps, args) {
912
908
  return 0;
913
909
  }
914
910
  async function deleteStack(deps, args) {
915
- const usage = "nylorun delete <tenant> --yes";
911
+ const usage = "nylorun local delete <tenant> --yes";
916
912
  const flags = parseStackFlags(args, { booleans: ["--yes"], aliases: { "-y": "--yes" } }, usage);
917
913
  const [name, ...extra] = flags.rest;
918
914
  if (name === undefined || extra.length)
@@ -921,9 +917,9 @@ async function deleteStack(deps, args) {
921
917
  const base = nylorunRoot(deps);
922
918
  const ctx = { deps, project: stackProject(deps.env, name), paths: stackPaths(tenantRoot(base, name)) };
923
919
  if (!existsSync(ctx.paths.root))
924
- throw new CliError(`No Tenant ${name} under ${tenantsDir(base)}. See "nylorun ls".`, 3);
920
+ throw new CliError(`No Tenant ${name} under ${tenantsDir(base)}. See "nylorun local ls".`, 3);
925
921
  if (!flags.booleans.has("--yes"))
926
- throw usageError(`nylorun delete removes Tenant ${name}: its containers, volumes and Host root ${ctx.paths.root}, with its vault key (KEK) and all its data. This cannot be undone; pass --yes to confirm.`);
922
+ throw usageError(`nylorun local delete removes Tenant ${name}: its containers, volumes and Host root ${ctx.paths.root}, with its vault key (KEK) and all its data. This cannot be undone; pass --yes to confirm.`);
927
923
  await dockerPreflight(deps.docker);
928
924
  const files = existsSync(ctx.paths.compose) && existsSync(ctx.paths.env);
929
925
  await composeDown(ctx, files
@@ -1051,7 +1047,7 @@ export async function runningTenantApi(deps, options = {}) {
1051
1047
  export async function ensureStack(deps, options) {
1052
1048
  return await ensureSelected(await selectStack(deps, options.name === undefined ? {} : { name: options.name }), options);
1053
1049
  }
1054
- /** The Tenant `nylorun sandbox` acts on; it must have been started once. */
1050
+ /** The Tenant `nylorun local sandbox` acts on; it must have been started once. */
1055
1051
  export async function selectTenant(deps, options = {}) {
1056
1052
  await tidyMachine(deps);
1057
1053
  const ctx = await selectStack(deps, options);
@@ -1115,7 +1111,7 @@ async function studio(deps, args, options = {}) {
1115
1111
  if (running.started)
1116
1112
  deps.out(`Runtime ${running.runtimeUrl}`);
1117
1113
  if (!running.studioUp)
1118
- throw new CliError(`Studio did not start. See "nylorun logs studio".`, 7);
1114
+ throw new CliError(`Studio did not start. See "nylorun local logs studio".`, 7);
1119
1115
  // Studio serves the one Tenant; the link's Tenant id stands in while it is opening.
1120
1116
  let next = options.next;
1121
1117
  if (next === undefined) {
@@ -41,7 +41,7 @@
41
41
  * gateway receive it (NYLORUN_OBJECT_STORE_*). The runtime creates the bucket at boot and
42
42
  * reaches the store through its `s3` BlobStore.
43
43
  *
44
- * With `sandboxes` (after `nylorun sandbox enable`, F7.2) the `sandboxes` service runs the
44
+ * With `sandboxes` (after `nylorun local sandbox enable`, F7.2) the `sandboxes` service runs the
45
45
  * agent-sandbox driver: the only container holding the cluster credentials
46
46
  * (`<Host root>/sandboxes`, read-only), not published, reached by the runtime alone with
47
47
  * NYLORUN_SANDBOXES_TOKEN. The runtime gets an empty read-only mount over that directory.
@@ -42,7 +42,7 @@ import { PINNED_IMAGES } from "./images.js";
42
42
  * gateway receive it (NYLORUN_OBJECT_STORE_*). The runtime creates the bucket at boot and
43
43
  * reaches the store through its `s3` BlobStore.
44
44
  *
45
- * With `sandboxes` (after `nylorun sandbox enable`, F7.2) the `sandboxes` service runs the
45
+ * With `sandboxes` (after `nylorun local sandbox enable`, F7.2) the `sandboxes` service runs the
46
46
  * agent-sandbox driver: the only container holding the cluster credentials
47
47
  * (`<Host root>/sandboxes`, read-only), not published, reached by the runtime alone with
48
48
  * NYLORUN_SANDBOXES_TOKEN. The runtime gets an empty read-only mount over that directory.
@@ -143,7 +143,7 @@ ${restateUi ? RESTATE_UI : RESTATE_CLOSED} healthcheck:
143
143
  RUSTFS_ACCESS_KEY: nylorun
144
144
  RUSTFS_SECRET_KEY: \${NYLORUN_OBJECT_STORE_SECRET_KEY:?run nylorun start}
145
145
  RUSTFS_CONSOLE_ENABLE: "false" # no web console: clients go through the Runtime
146
- RUSTFS_OBS_LOG_DIRECTORY: "" # log to stdout (nylorun logs rustfs)
146
+ RUSTFS_OBS_LOG_DIRECTORY: "" # log to stdout (nylorun local logs rustfs)
147
147
  # The image runs as uid 10001 and owns /data, which Docker copies into the new volume.
148
148
  volumes:
149
149
  - rustfs:/data
@@ -238,7 +238,16 @@ ${sandboxes ? SANDBOXES_GATES_PORT : ""} # Egress and the stores; the harness
238
238
  NYLORUN_HARNESS: \${NYLORUN_HARNESS:-remote}
239
239
  NYLORUN_HARNESS_LISTEN_PORT: "4200"
240
240
  NYLORUN_HARNESS_ALLOWED_HOSTS: runtime:4200${sandboxes ? SANDBOXES_HARNESS_HOST : ""}
241
- NYLORUN_HARNESS_TOKEN: \${NYLORUN_HARNESS_TOKEN:?run nylorun start}${sandboxes ? SANDBOXES_RUNTIME_ENV : ""}${identity ? IDENTITY_RUNTIME_ENV : ""}
241
+ NYLORUN_HARNESS_TOKEN: \${NYLORUN_HARNESS_TOKEN:?run nylorun start}
242
+ # Traces (R7.2): from the shell that runs \`nylorun start\`. Unset, they are
243
+ # empty and the Runtime exports nothing. A collector on this machine is host.docker.internal.
244
+ OTEL_EXPORTER_OTLP_ENDPOINT: \${OTEL_EXPORTER_OTLP_ENDPOINT:-}
245
+ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: \${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT:-}
246
+ OTEL_EXPORTER_OTLP_HEADERS: \${OTEL_EXPORTER_OTLP_HEADERS:-}
247
+ OTEL_SERVICE_NAME: \${OTEL_SERVICE_NAME:-}
248
+ OTEL_RESOURCE_ATTRIBUTES: \${OTEL_RESOURCE_ATTRIBUTES:-}
249
+ OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT: \${OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT:-}
250
+ NYLORUN_TRACE_CONTENT: \${NYLORUN_TRACE_CONTENT:-}${sandboxes ? SANDBOXES_RUNTIME_ENV : ""}${identity ? IDENTITY_RUNTIME_ENV : ""}
242
251
  extra_hosts:
243
252
  host.docker.internal: host-gateway # the Docker host, also on Linux Docker Engine
244
253
  volumes:
@@ -369,21 +378,21 @@ const IDENTITY_RUNTIME_ENV = `
369
378
  # Trusted issuers (<Host root>/identity.yaml): JWTs from the operator's identity provider.
370
379
  NYLORUN_IDENTITY_FILE: /nylorun/identity.yaml`;
371
380
  const SANDBOXES_RUNTIME_ENV = `
372
- # The sandboxes service (nylorun sandbox enable): pods on the Tenant's cluster.
381
+ # The sandboxes service (nylorun local sandbox enable): pods on the Tenant's cluster.
373
382
  NYLORUN_SANDBOXES_URL: http://sandboxes:4300
374
- NYLORUN_SANDBOXES_TOKEN: \${NYLORUN_SANDBOXES_TOKEN:?run nylorun sandbox enable}
383
+ NYLORUN_SANDBOXES_TOKEN: \${NYLORUN_SANDBOXES_TOKEN:?run nylorun local sandbox enable}
375
384
  NYLORUN_SANDBOX_HARNESS_IMAGE: \${NYLORUN_RUNTIME_IMAGE:?run nylorun start} # pods copy the engine from it`;
376
385
  /** Pods join and connect to the Harness API at the Docker host's address. */
377
- const SANDBOXES_HARNESS_HOST = ",\${NYLORUN_SANDBOX_HOST_ADDRESS:?run nylorun sandbox enable}:\${NYLORUN_SANDBOX_HARNESS_PORT:?run nylorun sandbox enable}";
386
+ const SANDBOXES_HARNESS_HOST = ",\${NYLORUN_SANDBOX_HOST_ADDRESS:?run nylorun local sandbox enable}:\${NYLORUN_SANDBOX_HARNESS_PORT:?run nylorun local sandbox enable}";
378
387
  /** The Harness API, published for sandbox pods. */
379
388
  const SANDBOXES_RUNTIME_PORT = `
380
- - "\${NYLORUN_SANDBOX_BIND:?run nylorun sandbox enable}:\${NYLORUN_SANDBOX_HARNESS_PORT:?run nylorun sandbox enable}:4200" # Harness API, for sandbox pods`;
389
+ - "\${NYLORUN_SANDBOX_BIND:?run nylorun local sandbox enable}:\${NYLORUN_SANDBOX_HARNESS_PORT:?run nylorun local sandbox enable}:4200" # Harness API, for sandbox pods`;
381
390
  /** The gates accept the pods' Host; their calls carry run tokens. */
382
- const SANDBOXES_GATES_HOST = ",\${NYLORUN_SANDBOX_HOST_ADDRESS:?run nylorun sandbox enable}:\${NYLORUN_SANDBOX_GATES_PORT:?run nylorun sandbox enable}";
391
+ const SANDBOXES_GATES_HOST = ",\${NYLORUN_SANDBOX_HOST_ADDRESS:?run nylorun local sandbox enable}:\${NYLORUN_SANDBOX_GATES_PORT:?run nylorun local sandbox enable}";
383
392
  /** The gates and egress-gate, published for sandbox pods. */
384
393
  const SANDBOXES_GATES_PORT = ` ports:
385
- - "\${NYLORUN_SANDBOX_BIND:?run nylorun sandbox enable}:\${NYLORUN_SANDBOX_GATES_PORT:?run nylorun sandbox enable}:4100" # gates, for sandbox pods (run tokens)
386
- - "\${NYLORUN_SANDBOX_BIND:?run nylorun sandbox enable}:\${NYLORUN_SANDBOX_EGRESS_PORT:?run nylorun sandbox enable}:4200" # egress-gate, for sandbox pods (egress tokens)
394
+ - "\${NYLORUN_SANDBOX_BIND:?run nylorun local sandbox enable}:\${NYLORUN_SANDBOX_GATES_PORT:?run nylorun local sandbox enable}:4100" # gates, for sandbox pods (run tokens)
395
+ - "\${NYLORUN_SANDBOX_BIND:?run nylorun local sandbox enable}:\${NYLORUN_SANDBOX_EGRESS_PORT:?run nylorun local sandbox enable}:4200" # egress-gate, for sandbox pods (egress tokens)
387
396
  `;
388
397
  /** egress-gate (F7.2): pods' CONNECT proxy in the gateway. */
389
398
  const SANDBOXES_GATEWAY_ENV = `
@@ -396,19 +405,19 @@ const SANDBOXES_RUNTIME_MOUNT = `
396
405
  read_only: true
397
406
  tmpfs: { size: 4096, mode: 0755 } # empty, and listable by the runtime user`;
398
407
  /**
399
- * The sandboxes service. The pod-facing ports `nylorun sandbox enable` records
408
+ * The sandboxes service. The pod-facing ports `nylorun local sandbox enable` records
400
409
  * (NYLORUN_SANDBOX_*_PORT in .env), the Harness API's, the gates' and egress-gate's, are
401
410
  * published by the runtime and the gateway (above).
402
411
  */
403
412
  function sandboxesService(project) {
404
413
  return `
405
414
  sandboxes: # agent-sandbox driver; the only holder of the cluster credentials; not published
406
- image: \${NYLORUN_SANDBOXES_IMAGE:?run nylorun sandbox enable}
415
+ image: \${NYLORUN_SANDBOXES_IMAGE:?run nylorun local sandbox enable}
407
416
  container_name: ${project}-sandboxes
408
417
  labels: *tenant
409
418
  user: "\${NYLORUN_UID:?run nylorun start}:\${NYLORUN_GID:?run nylorun start}" # reads the 0600 token
410
419
  environment:
411
- NYLORUN_SANDBOXES_TOKEN: \${NYLORUN_SANDBOXES_TOKEN:?run nylorun sandbox enable}
420
+ NYLORUN_SANDBOXES_TOKEN: \${NYLORUN_SANDBOXES_TOKEN:?run nylorun local sandbox enable}
412
421
  NYLORUN_SANDBOXES_DIR: /run/nylorun/sandboxes
413
422
  NYLORUN_SANDBOXES_LISTEN: ":4300"
414
423
  extra_hosts:
@@ -55,11 +55,11 @@ export interface StackEnv {
55
55
  studioAnalyticsId: string;
56
56
  /** The Tenant's name, which the Runtime gives the Tenant it creates. */
57
57
  tenantName: string;
58
- /** Present once `nylorun sandbox enable` ran for the Tenant (F7.2). */
58
+ /** Present once `nylorun local sandbox enable` ran for the Tenant (F7.2). */
59
59
  sandboxes?: SandboxStackEnv;
60
60
  }
61
61
  /**
62
- * The sandboxes service's settings (`nylorun sandbox enable`): its bearer token (only the
62
+ * The sandboxes service's settings (`nylorun local sandbox enable`): its bearer token (only the
63
63
  * runtime presents it), its image, and the host ports pods reach (Harness API, gates,
64
64
  * egress) on the host address, published on the bind address. The ports persist.
65
65
  */
@@ -102,7 +102,7 @@ export function renderEnvFile(env) {
102
102
  line("studioFrameAncestors"),
103
103
  "",
104
104
  "# Studio's anonymous usage analytics; empty when off. Set on every start: turn",
105
- "# it off with nylorun telemetry disable or NYLORUN_TELEMETRY_DISABLED=1.",
105
+ "# it off with nylorun local telemetry disable or NYLORUN_TELEMETRY_DISABLED=1.",
106
106
  line("studioAnalyticsId"),
107
107
  "",
108
108
  "# The Tenant's name, which the Runtime gives the Tenant it creates on the first",
@@ -111,7 +111,7 @@ export function renderEnvFile(env) {
111
111
  "",
112
112
  ...(sandboxes
113
113
  ? [
114
- "# Sandboxes (nylorun sandbox enable): the runtime's token for the sandboxes",
114
+ "# Sandboxes (nylorun local sandbox enable): the runtime's token for the sandboxes",
115
115
  "# service, its image, and the host ports pods reach on the host address.",
116
116
  sandboxLine("token"),
117
117
  sandboxLine("image"),
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Images a local Tenant runs. The Runtime and Studio tags are pinned by this
3
3
  * CLI release; `NYLORUN_RUNTIME_IMAGE` and `NYLORUN_STUDIO_IMAGE` override them
4
- * (local builds, CI images). The sandboxes service (`nylorun sandbox enable`) is
4
+ * (local builds, CI images). The sandboxes service (`nylorun local sandbox enable`) is
5
5
  * versioned with the Runtime: `ghcr.io/nylorun/sandboxes:<runtime pin>`, or
6
6
  * `NYLORUN_SANDBOXES_IMAGE`. Postgres, Restate, s2-lite and RustFS are the
7
7
  * official images, pinned here; RustFS (the Object store, D35) by digest as well, so
@@ -2,7 +2,7 @@ import { pinnedVersion } from "./versions.js";
2
2
  /**
3
3
  * Images a local Tenant runs. The Runtime and Studio tags are pinned by this
4
4
  * CLI release; `NYLORUN_RUNTIME_IMAGE` and `NYLORUN_STUDIO_IMAGE` override them
5
- * (local builds, CI images). The sandboxes service (`nylorun sandbox enable`) is
5
+ * (local builds, CI images). The sandboxes service (`nylorun local sandbox enable`) is
6
6
  * versioned with the Runtime: `ghcr.io/nylorun/sandboxes:<runtime pin>`, or
7
7
  * `NYLORUN_SANDBOXES_IMAGE`. Postgres, Restate, s2-lite and RustFS are the
8
8
  * official images, pinned here; RustFS (the Object store, D35) by digest as well, so
@@ -5,17 +5,8 @@ export { checkDocker } from "./docker.js";
5
5
  export type { Check, DockerChecks } from "./docker.js";
6
6
  /** Real dependencies: docker on PATH, global fetch, the terminal. */
7
7
  export declare function defaultStackDeps(env: Readonly<Record<string, string | undefined>>): StackDeps;
8
- /** Entry for `nylorun start|stop|status|logs|reset|studio|ls|delete`. */
8
+ /** Entry for `nylorun start|stop|status|studio` and `nylorun local logs|reset|ls|delete`. */
9
9
  export declare function stackCommand(name: string, args: readonly string[], env: Readonly<Record<string, string | undefined>>): Promise<number>;
10
- /** Entry for `nylorun sandbox ls|rm`. */
11
- export declare function sandboxCommand(args: readonly string[], env: Readonly<Record<string, string | undefined>>): Promise<number>;
12
- export { sandboxUsage } from "./sandbox.js";
13
- /** Entry for `nylorun key put|list|rm`. */
14
- export declare function keyCommand(args: readonly string[], env: Readonly<Record<string, string | undefined>>): Promise<number>;
15
- export { keyUsage } from "./keys.js";
16
- /** Entry for `nylorun mcp inspect`. */
17
- export declare function mcpCommand(args: readonly string[], env: Readonly<Record<string, string | undefined>>): Promise<number>;
18
- export { mcpUsage } from "./mcp.js";
19
10
  /** `nylorun studio`, landing on `next` when given, else on the Tenant's page. */
20
11
  export declare function studioCommand(args: readonly string[], env: Readonly<Record<string, string | undefined>>, options?: {
21
12
  next?: string;
@@ -1,10 +1,8 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { createInterface } from "node:readline/promises";
3
+ import { text } from "node:stream/consumers";
3
4
  import { runStackCommand, runStudioCommand } from "./commands.js";
4
5
  import { spawnDocker } from "./docker.js";
5
- import { keyCommand as runKeyCommand } from "./keys.js";
6
- import { mcpCommand as runMcpCommand } from "./mcp.js";
7
- import { sandboxCommand as runSandboxCommand } from "./sandbox.js";
8
6
  import { pinnedVersion } from "./versions.js";
9
7
  import { loopbackPorts } from "./ports.js";
10
8
  export { ensureStack, isStackCommand, readStackStatus, stackUsage, studioLoginUrl, tenantStudioPath, STACK_SERVICES, } from "./commands.js";
@@ -52,6 +50,7 @@ export function defaultStackDeps(env) {
52
50
  }
53
51
  : {}),
54
52
  interactive,
53
+ stdin: { isTTY: Boolean(process.stdin.isTTY), text: () => text(process.stdin) },
55
54
  openBrowser: async (url) => {
56
55
  const command = browserCommand(env);
57
56
  return await new Promise((resolve) => {
@@ -77,25 +76,10 @@ export function defaultStackDeps(env) {
77
76
  },
78
77
  };
79
78
  }
80
- /** Entry for `nylorun start|stop|status|logs|reset|studio|ls|delete`. */
79
+ /** Entry for `nylorun start|stop|status|studio` and `nylorun local logs|reset|ls|delete`. */
81
80
  export async function stackCommand(name, args, env) {
82
81
  return await runStackCommand(name, args, defaultStackDeps(env));
83
82
  }
84
- /** Entry for `nylorun sandbox ls|rm`. */
85
- export async function sandboxCommand(args, env) {
86
- return await runSandboxCommand(defaultStackDeps(env), args);
87
- }
88
- export { sandboxUsage } from "./sandbox.js";
89
- /** Entry for `nylorun key put|list|rm`. */
90
- export async function keyCommand(args, env) {
91
- return await runKeyCommand(defaultStackDeps(env), args);
92
- }
93
- export { keyUsage } from "./keys.js";
94
- /** Entry for `nylorun mcp inspect`. */
95
- export async function mcpCommand(args, env) {
96
- return await runMcpCommand(defaultStackDeps(env), args);
97
- }
98
- export { mcpUsage } from "./mcp.js";
99
83
  /** `nylorun studio`, landing on `next` when given, else on the Tenant's page. */
100
84
  export async function studioCommand(args, env, options = {}) {
101
85
  return await runStudioCommand(args, defaultStackDeps(env), options);
@@ -1,4 +1,4 @@
1
1
  import { type StackDeps } from "./commands.js";
2
- export declare const keyUsage = " key put <id> [--management] [--tenant <name>]\n create the Tenant's application key <id> (--management: a management key), or\n rotate it (the old key stops working); prints the key once\n key list [--tenant <name>] [--json] the Tenant's keys: id, role, when issued (never the keys)\n key rm <id> [--tenant <name>] delete key <id>: it stops working at once";
3
- /** `nylorun key <put|list|rm> ...` */
2
+ export declare const keyUsage = " keys put <id> [--management] [--tenant <name>]\n create the Tenant's application key <id> (--management: a\n management key, on the Runtime's machine only), or rotate it\n (the old key stops working); prints the key once\n keys list [--tenant <name>] [--json]\n the Tenant's keys: id, role, when issued (never the keys)\n keys rm <id> [--tenant <name>] delete key <id>: it stops working at once";
3
+ /** `nylorun tenant keys <put|list|rm> ...` on a local Tenant. */
4
4
  export declare function keyCommand(deps: StackDeps, args: readonly string[]): Promise<number>;
@@ -1,20 +1,23 @@
1
1
  /**
2
- * `nylorun key put | list | rm`: the running local Tenant's keys, through `nylorun-operate` in its
3
- * runtime container. Revocable application keys by name, for app servers and other clients, and
2
+ * `nylorun tenant keys put | list | rm` on a local Tenant: its keys, through `nylorun-operate` in
3
+ * its runtime container (`client/tenant.ts` takes a remote installation's application keys
4
+ * through the Management API). Revocable application keys by name, for app servers and other clients, and
4
5
  * management keys (`--management`), which only this machine issues; none of these starts the
5
6
  * Tenant.
6
7
  */
7
8
  import { CliError } from "../errors.js";
8
9
  import { parseStackFlags, runningOperate } from "./commands.js";
9
10
  import { deleteOperatorKey, listOperatorKeys, putOperatorKey } from "./operator-keys.js";
10
- export const keyUsage = ` key put <id> [--management] [--tenant <name>]
11
- create the Tenant's application key <id> (--management: a management key), or
12
- rotate it (the old key stops working); prints the key once
13
- key list [--tenant <name>] [--json] the Tenant's keys: id, role, when issued (never the keys)
14
- key rm <id> [--tenant <name>] delete key <id>: it stops working at once`;
11
+ export const keyUsage = ` keys put <id> [--management] [--tenant <name>]
12
+ create the Tenant's application key <id> (--management: a
13
+ management key, on the Runtime's machine only), or rotate it
14
+ (the old key stops working); prints the key once
15
+ keys list [--tenant <name>] [--json]
16
+ the Tenant's keys: id, role, when issued (never the keys)
17
+ keys rm <id> [--tenant <name>] delete key <id>: it stops working at once`;
15
18
  const usageError = (message) => new CliError(message, 2);
16
19
  async function put(deps, args) {
17
- const usage = "nylorun key put <id> [--management] [--tenant <name>]";
20
+ const usage = "nylorun tenant keys put <id> [--management] [--tenant <name>]";
18
21
  const flags = parseStackFlags(args, { booleans: ["--management"], values: ["--tenant"] }, usage);
19
22
  if (flags.rest.length !== 1)
20
23
  throw usageError(`Usage: ${usage}`);
@@ -28,7 +31,7 @@ async function put(deps, args) {
28
31
  return 0;
29
32
  }
30
33
  async function list(deps, args) {
31
- const usage = "nylorun key list [--tenant <name>] [--json]";
34
+ const usage = "nylorun tenant keys list [--tenant <name>] [--json]";
32
35
  const flags = parseStackFlags(args, { booleans: ["--json"], values: ["--tenant"] }, usage);
33
36
  if (flags.rest.length)
34
37
  throw usageError(`Usage: ${usage}`);
@@ -47,7 +50,7 @@ async function list(deps, args) {
47
50
  return 0;
48
51
  }
49
52
  async function rm(deps, args) {
50
- const usage = "nylorun key rm <id> [--tenant <name>]";
53
+ const usage = "nylorun tenant keys rm <id> [--tenant <name>]";
51
54
  const flags = parseStackFlags(args, { values: ["--tenant"] }, usage);
52
55
  if (flags.rest.length !== 1)
53
56
  throw usageError(`Usage: ${usage}`);
@@ -61,7 +64,7 @@ async function rm(deps, args) {
61
64
  deps.out(`Deleted key ${id}: it no longer works.`);
62
65
  return 0;
63
66
  }
64
- /** `nylorun key <put|list|rm> ...` */
67
+ /** `nylorun tenant keys <put|list|rm> ...` on a local Tenant. */
65
68
  export async function keyCommand(deps, args) {
66
69
  const [sub, ...rest] = args;
67
70
  if (sub === "put")
@@ -10,7 +10,7 @@ export interface StackPaths {
10
10
  state: string;
11
11
  /**
12
12
  * cli-credentials.json: the operator key `cli` that `nylorun` commands use outside a linked
13
- * project (`nylorun sandbox`), mode 0600.
13
+ * project (`nylorun local sandbox`), mode 0600.
14
14
  */
15
15
  cliCredentials: string;
16
16
  /**
@@ -43,7 +43,7 @@ export declare function prepareStack(input: {
43
43
  /** The measurement id Studio reports page views to; absent when telemetry is off. */
44
44
  studioAnalyticsId?: string;
45
45
  /**
46
- * The sandboxes settings (`nylorun sandbox enable`); null removes them (`disable`).
46
+ * The sandboxes settings (`nylorun local sandbox enable`); null removes them (`disable`).
47
47
  * Undefined keeps the persisted ones while `sandboxes/cluster.json` exists.
48
48
  */
49
49
  sandboxes?: SandboxStackEnv | null;
@@ -25,7 +25,7 @@ export function tenantRoot(base, name) {
25
25
  export function assertTenantName(name, source) {
26
26
  // A Tenant id, such as a NYLORUN_TENANT kept from an older release.
27
27
  if (/^tn_/i.test(name))
28
- throw new CliError(`${source} must be a Tenant's name, not a Tenant id: ${name}. "nylorun ls" lists the Tenants on this machine.`, 2);
28
+ throw new CliError(`${source} must be a Tenant's name, not a Tenant id: ${name}. "nylorun local ls" lists the Tenants on this machine.`, 2);
29
29
  if (!TENANT_NAME.test(name))
30
30
  throw new CliError(`${source} must be lowercase letters, digits, "-" or "_", starting with a letter or digit: ${name}`, 2);
31
31
  return name;
@@ -1,7 +1,7 @@
1
- /** An image this CLI release pins in `cli/package.json` `nylorun` (D7). */
1
+ /** An image this CLI release pins in its `package.json` `nylorun` (D7). */
2
2
  export type PinnedImage = "runtime" | "studio";
3
3
  /**
4
- * The version this CLI release pins for an image, from `cli/package.json`
4
+ * The version this CLI release pins for an image, from its `package.json`
5
5
  * `nylorun.runtime` or `nylorun.studio`: the tag of `ghcr.io/nylorun/<name>`
6
6
  * a local Tenant runs unless `NYLORUN_RUNTIME_IMAGE` / `NYLORUN_STUDIO_IMAGE`
7
7
  * overrides it. The release tooling writes both pins.
@@ -3,11 +3,11 @@ import { dirname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { CliError } from "../errors.js";
5
5
  function cliPackageJsonPath() {
6
- // src/stack/*.ts and dist/stack/*.js both sit two levels below cli/.
6
+ // src/stack/*.ts and dist/stack/*.js both sit two levels below the package root.
7
7
  return join(dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json");
8
8
  }
9
9
  /**
10
- * The version this CLI release pins for an image, from `cli/package.json`
10
+ * The version this CLI release pins for an image, from its `package.json`
11
11
  * `nylorun.runtime` or `nylorun.studio`: the tag of `ghcr.io/nylorun/<name>`
12
12
  * a local Tenant runs unless `NYLORUN_RUNTIME_IMAGE` / `NYLORUN_STUDIO_IMAGE`
13
13
  * overrides it. The release tooling writes both pins.
@@ -22,7 +22,7 @@ export function pinnedVersion(name) {
22
22
  }
23
23
  const version = raw?.nylorun?.[name];
24
24
  if (typeof version !== "string" || version.trim() === "") {
25
- throw new CliError(`cli/package.json is missing "nylorun.${name}" (the pinned ${name} image version).`, 1);
25
+ throw new CliError(`nylorun's package.json is missing "nylorun.${name}" (the pinned ${name} image version).`, 1);
26
26
  }
27
27
  return version;
28
28
  }
@@ -8,7 +8,7 @@ export declare const STUDIO_ANALYTICS_ID = "G-K6RPDFH6Q6";
8
8
  /** `~/.nylorun/telemetry.json`: the developer's choice, for every Tenant on the machine. */
9
9
  export interface TelemetryFile {
10
10
  format: 1;
11
- /** False after `nylorun telemetry disable`; absent until a choice is made. */
11
+ /** False after `nylorun local telemetry disable`; absent until a choice is made. */
12
12
  enabled?: boolean;
13
13
  /** When `nylorun start` showed the notice (ISO time); it is shown once. */
14
14
  noticeShown?: string;
@@ -23,7 +23,7 @@ export declare function readTelemetry(nylorunRoot: string): Promise<TelemetryFil
23
23
  export declare function writeTelemetry(nylorunRoot: string, file: TelemetryFile): Promise<void>;
24
24
  /** On unless the environment or the developer's choice turns it off. */
25
25
  export declare function telemetryDecision(env: Readonly<Record<string, string | undefined>>, file: TelemetryFile): TelemetryDecision;
26
- /** `nylorun telemetry [status|enable|disable]`. */
26
+ /** `nylorun local telemetry [status|enable|disable]`. */
27
27
  export declare function telemetryCommand(args: readonly string[], context: {
28
28
  nylorunRoot: string;
29
29
  env: Readonly<Record<string, string | undefined>>;
package/dist/telemetry.js CHANGED
@@ -12,7 +12,7 @@ export const STUDIO_ANALYTICS_ID = "G-K6RPDFH6Q6";
12
12
  export const TELEMETRY_NOTICE = [
13
13
  "Studio collects anonymous usage data: which pages are opened, with every",
14
14
  "Tenant, agent and session id removed. Nothing you send to agents is collected.",
15
- 'Turn it off with "nylorun telemetry disable" or NYLORUN_TELEMETRY_DISABLED=1.',
15
+ 'Turn it off with "nylorun local telemetry disable" or NYLORUN_TELEMETRY_DISABLED=1.',
16
16
  "Learn more: https://github.com/nylorun/agents/blob/main/nylorun/README.md#telemetry",
17
17
  ].join("\n");
18
18
  export function telemetryPath(nylorunRoot) {
@@ -56,11 +56,11 @@ export function telemetryDecision(env, file) {
56
56
  if (flag(env.CI))
57
57
  return { enabled: false, reason: "CI is set" };
58
58
  if (file.enabled === false)
59
- return { enabled: false, reason: 'turned off with "nylorun telemetry disable"' };
59
+ return { enabled: false, reason: 'turned off with "nylorun local telemetry disable"' };
60
60
  return { enabled: true, reason: "on by default" };
61
61
  }
62
- const USAGE = "Usage: nylorun telemetry [status|enable|disable]";
63
- /** `nylorun telemetry [status|enable|disable]`. */
62
+ const USAGE = "Usage: nylorun local telemetry [status|enable|disable]";
63
+ /** `nylorun local telemetry [status|enable|disable]`. */
64
64
  export async function telemetryCommand(args, context) {
65
65
  const [action = "status", ...rest] = args;
66
66
  if (rest.length || !["status", "enable", "disable"].includes(action))