@nylorun/admin 0.5.0-beta → 0.6.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0-beta
4
+
5
+ ### Minor Changes
6
+
7
+ - 50d0fb5: **The Admin API on its own listener.** A Runtime can serve the Admin API on an operator listener, so the port that faces browsers and reverse proxies serves the Tenant API alone. The stack does this by default.
8
+
9
+ - **Runtime.** With an operator listener (`adminPort` in `host.json`, or `NYLORUN_ADMIN_LISTEN_PORT`, `NYLORUN_ADMIN_LISTEN_HOST` and `NYLORUN_ADMIN_ALLOWED_HOSTS` in a container), the public listener answers `/v1/admin/**` with the opaque `404` and the operator listener serves the Admin API, Host shutdown and the Tenant API, never to browsers. Each checks `Host` against its own port. `/ready` needs both listening; a taken port on either exits with code 98. Without one, a single listener serves everything as before.
10
+ - **Stack.** `nylorun start` publishes the operator port on loopback (`NYLORUN_ADMIN_PORT`, default 8788), writes it to `host.json` as `adminPort`, and points Studio at `runtime:4001`. `nylorun status` prints it.
11
+ - **Admin client.** Reads `adminPort` from `host.json` and sends Admin API requests there (`admin.adminUrl`); `admin.url` stays the Tenant API URL. A `host.json` without `adminPort` keeps working.
12
+
13
+ - 9d52189: **Embedding Studio in a desktop app.** Studio can be shown inside a desktop app such as Babai Desktop, in an iframe loaded from its URL and signed in by `postMessage` with a token limited to one Tenant.
14
+
15
+ - **`nylorun`.** The local stack lets Babai's origins frame Studio: `NYLORUN_STUDIO_FRAME_ANCESTORS` in `stack/.env` defaults to `nylorun://localhost http://nylorun.localhost` and is passed to the Studio container. `nylorun start --studio-embed-origin <origin>` (repeatable) adds an exact origin, such as a desktop app's dev server, and keeps it across starts until `--studio-embed-origin-reset`. Wildcards are refused. `nylorun status` lists the origins under `Embeds`, and `status --json` as `studio.embedOrigins`.
16
+ - **`@nylorun/admin`.** `mintStudioLoginToken({ studioUrl, adminKey, tenant?, subject? })` mints a single-use Studio login token from an app's backend. With `tenant`, the session it leads to reaches only that Tenant.
17
+ - **Studio.** `POST /_studio/sessions` exchanges such a token for a one-hour bearer session kept in the frame's memory; dashboard pages send `frame-ancestors` from the allowlist instead of `X-Frame-Options: DENY`; `?embed=1` hides Studio's branding, follows the app's theme and routes, and reports its own; `/tenants/:tenant/sessions/:session` opens a session by id. The cookie login of `nylorun studio` is unchanged.
18
+
19
+ The dashboard routes `/tenants/:tenant`, `/tenants/:tenant/agents/:agent`, `/tenants/:tenant/agents/:agent/sessions/:session`, `/tenants/:tenant/sessions/:session`, `/tenants/:tenant/vault` and `/tenants/:tenant/settings` are now a public contract for embedders: removing or changing one is a breaking change.
20
+
21
+ ### Patch Changes
22
+
23
+ - Pin core to the tested release.
24
+ - Updated dependencies [c7614a4]
25
+ - Updated dependencies [679c488]
26
+ - Updated dependencies [c85cd9e]
27
+ - Updated dependencies [4282d5f]
28
+ - Updated dependencies [82d95ef]
29
+ - Updated dependencies [f48f12f]
30
+ - Updated dependencies [50d0fb5]
31
+ - Updated dependencies [50d0fb5]
32
+ - Updated dependencies [c121144]
33
+ - Updated dependencies [6ab4c59]
34
+ - Updated dependencies [6ab4c59]
35
+ - Updated dependencies [2ab8ed1]
36
+ - Updated dependencies [c121144]
37
+ - Updated dependencies [9546ac7]
38
+ - Updated dependencies [9546ac7]
39
+ - Updated dependencies [9d52189]
40
+ - Updated dependencies [50d0fb5]
41
+ - Updated dependencies [18468d9]
42
+ - @nylorun/core@0.9.0-beta
43
+
3
44
  ## 0.5.0-beta
4
45
 
5
46
  ### Minor Changes
package/dist/client.d.ts CHANGED
@@ -1,7 +1,13 @@
1
1
  import { type AdminStatus, type AdminTenant, type AdminTenantStatus, type TenantEnvelope } from "@nylorun/core/contracts";
2
2
  export type AdminSource = "options" | "environment" | "local-host";
3
3
  export interface ResolvedAdmin {
4
+ /** The Host's Tenant API URL (what a Project link names). */
4
5
  url: string;
6
+ /**
7
+ * Where the Admin API answers when the Host serves it on its own operator listener
8
+ * (`adminPort` in host.json). Defaults to `url`.
9
+ */
10
+ adminUrl?: string;
5
11
  key: string;
6
12
  source: AdminSource;
7
13
  home: string;
@@ -15,7 +21,10 @@ export declare function resolveAdminConnection(options?: {
15
21
  home?: string;
16
22
  }): ResolvedAdmin;
17
23
  export declare class AdminClient {
24
+ /** The Host's Tenant API URL. */
18
25
  readonly url: string;
26
+ /** Where Admin API requests go: the operator listener, or `url` on a single-port Host. */
27
+ readonly adminUrl: string;
19
28
  readonly source: AdminSource;
20
29
  private readonly key;
21
30
  private compatible;
package/dist/client.js CHANGED
@@ -73,13 +73,19 @@ function readLocalHost(home) {
73
73
  }
74
74
  const host = config.host;
75
75
  const port = config.port;
76
+ const adminPort = config.adminPort;
76
77
  const adminKey = credentials.adminKey;
77
78
  if (typeof host !== "string" || typeof port !== "number")
78
79
  return undefined;
79
80
  if (typeof adminKey !== "string" || !/^[0-9a-f]{64}$/.test(adminKey)) {
80
81
  return undefined;
81
82
  }
82
- return { url: `http://${host}:${port}`, key: adminKey };
83
+ return {
84
+ url: `http://${host}:${port}`,
85
+ // An older Host serves the Admin API on its only port.
86
+ adminUrl: `http://${host}:${typeof adminPort === "number" ? adminPort : port}`,
87
+ key: adminKey,
88
+ };
83
89
  }
84
90
  /**
85
91
  * Resolve Admin API connection once: options → environment → local Host.
@@ -116,6 +122,7 @@ export function resolveAdminConnection(options) {
116
122
  if (local) {
117
123
  return {
118
124
  url: local.url.replace(/\/$/, ""),
125
+ adminUrl: local.adminUrl.replace(/\/$/, ""),
119
126
  key: local.key,
120
127
  source: "local-host",
121
128
  home,
@@ -180,7 +187,10 @@ async function readBody(response) {
180
187
  }
181
188
  }
182
189
  export class AdminClient {
190
+ /** The Host's Tenant API URL. */
183
191
  url;
192
+ /** Where Admin API requests go: the operator listener, or `url` on a single-port Host. */
193
+ adminUrl;
184
194
  source;
185
195
  key;
186
196
  compatible = false;
@@ -188,6 +198,7 @@ export class AdminClient {
188
198
  features = [];
189
199
  constructor(resolved) {
190
200
  this.url = resolved.url;
201
+ this.adminUrl = resolved.adminUrl ?? resolved.url;
191
202
  this.source = resolved.source;
192
203
  this.key = resolved.key;
193
204
  }
@@ -206,7 +217,7 @@ export class AdminClient {
206
217
  async ensureCompatible(signal) {
207
218
  if (this.compatible)
208
219
  return;
209
- const response = await fetch(`${this.url}/health`, {
220
+ const response = await fetch(`${this.adminUrl}/health`, {
210
221
  method: "GET",
211
222
  redirect: "error",
212
223
  signal,
@@ -233,7 +244,7 @@ export class AdminClient {
233
244
  }
234
245
  async request(path, init = {}, options = {}) {
235
246
  await this.ensureCompatible(init.signal === null ? undefined : init.signal);
236
- const response = await fetch(this.url + path, {
247
+ const response = await fetch(this.adminUrl + path, {
237
248
  ...init,
238
249
  headers: this.adminHeaders(init),
239
250
  redirect: "error",
package/dist/index.d.ts CHANGED
@@ -6,8 +6,12 @@ export { ERROR_CODES, PROTOCOL_FEATURES, compareVersions };
6
6
  export type { ErrorCode };
7
7
  export { AdminError };
8
8
  export { PROJECT_PRINCIPAL_ID, deriveStudioToken, deriveTenantKey };
9
+ export { mintStudioLoginToken } from "./studio-login.js";
9
10
  export interface Admin {
11
+ /** The Host's Tenant API URL. */
10
12
  readonly url: string;
13
+ /** Where Admin API requests go: the operator listener, or `url` on a single-port Host. */
14
+ readonly adminUrl: string;
11
15
  readonly source: "options" | "environment" | "local-host";
12
16
  status(): Promise<AdminStatus>;
13
17
  listTenants(): Promise<AdminTenant[]>;
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { AdminError } from "./errors.js";
5
5
  export { ERROR_CODES, PROTOCOL_FEATURES, compareVersions };
6
6
  export { AdminError };
7
7
  export { PROJECT_PRINCIPAL_ID, deriveStudioToken, deriveTenantKey };
8
+ export { mintStudioLoginToken } from "./studio-login.js";
8
9
  export function createAdmin(options) {
9
10
  return new AdminClient(resolveAdminConnection(options));
10
11
  }
@@ -0,0 +1,20 @@
1
+ import { type StudioLoginTokenResponse } from "@nylorun/core/contracts";
2
+ /**
3
+ * Mints a single-use Studio login token with the admin key (`POST
4
+ * /_studio/login-tokens`, Studio §8.4). An app that embeds Studio, such as
5
+ * Babai, calls this from its backend and hands only the token to its page,
6
+ * which passes it to the framed Studio in `init`.
7
+ *
8
+ * - `tenant` limits the session the token leads to to one Tenant; omit it for
9
+ * a Host-wide token (what `nylorun studio` uses).
10
+ * - `subject` names the person, for Studio's logs.
11
+ */
12
+ export declare function mintStudioLoginToken(options: {
13
+ /** Studio's URL, e.g. `studio.url` from `nylorun status --json`. */
14
+ studioUrl: string;
15
+ adminKey: string;
16
+ tenant?: string;
17
+ subject?: string;
18
+ fetch?: typeof fetch;
19
+ signal?: AbortSignal;
20
+ }): Promise<StudioLoginTokenResponse>;
@@ -0,0 +1,56 @@
1
+ import { StudioLoginTokenRequestSchema, StudioLoginTokenResponseSchema, } from "@nylorun/core/contracts";
2
+ import { AdminError } from "./errors.js";
3
+ /**
4
+ * Mints a single-use Studio login token with the admin key (`POST
5
+ * /_studio/login-tokens`, Studio §8.4). An app that embeds Studio, such as
6
+ * Babai, calls this from its backend and hands only the token to its page,
7
+ * which passes it to the framed Studio in `init`.
8
+ *
9
+ * - `tenant` limits the session the token leads to to one Tenant; omit it for
10
+ * a Host-wide token (what `nylorun studio` uses).
11
+ * - `subject` names the person, for Studio's logs.
12
+ */
13
+ export async function mintStudioLoginToken(options) {
14
+ const body = StudioLoginTokenRequestSchema.safeParse({
15
+ ...(options.tenant !== undefined ? { tenant: options.tenant } : {}),
16
+ ...(options.subject !== undefined ? { subject: options.subject } : {}),
17
+ });
18
+ if (!body.success)
19
+ throw new AdminError("invalid_request", `Invalid Studio login token request: ${body.error.issues[0]?.message ?? "invalid"}`);
20
+ const fetcher = options.fetch ?? fetch;
21
+ const url = new URL("/_studio/login-tokens", options.studioUrl);
22
+ let response;
23
+ try {
24
+ response = await fetcher(url, {
25
+ method: "POST",
26
+ headers: {
27
+ authorization: `Bearer ${options.adminKey}`,
28
+ "content-type": "application/json",
29
+ accept: "application/json",
30
+ },
31
+ body: JSON.stringify(body.data),
32
+ redirect: "error",
33
+ signal: options.signal ?? AbortSignal.timeout(5000),
34
+ });
35
+ }
36
+ catch (error) {
37
+ throw new AdminError("connection_missing", `Studio is unavailable at ${url.origin}: ${error instanceof Error ? error.message : String(error)}`);
38
+ }
39
+ const text = await response.text();
40
+ if (!response.ok) {
41
+ let message = `Studio refused a login token (HTTP ${response.status}).`;
42
+ try {
43
+ const parsed = JSON.parse(text);
44
+ if (typeof parsed.message === "string")
45
+ message = parsed.message;
46
+ }
47
+ catch {
48
+ /* keep the status message */
49
+ }
50
+ throw new AdminError(response.status === 401 ? "host_rejected" : "request_rejected", message, { status: response.status });
51
+ }
52
+ const parsed = StudioLoginTokenResponseSchema.safeParse(JSON.parse(text));
53
+ if (!parsed.success)
54
+ throw new AdminError("incompatible_host", "This Studio does not support login tokens limited to a Tenant; update the stack.");
55
+ return parsed.data;
56
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/admin",
3
- "version": "0.5.0-beta",
3
+ "version": "0.6.0-beta",
4
4
  "description": "Nylorun Admin API client for Tenants and Host status",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -24,7 +24,7 @@
24
24
  "clean": "node -e \"import('node:fs').then(({rmSync})=>rmSync('dist',{recursive:true,force:true}))\""
25
25
  },
26
26
  "dependencies": {
27
- "@nylorun/core": "0.8.0-beta"
27
+ "@nylorun/core": "0.9.0-beta"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "zod": "^4.6.5"