@nylorun/cli 0.4.2-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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @nylorun/cli
2
2
 
3
+ ## 0.5.0-beta
4
+
5
+ ### Minor Changes
6
+
7
+ - db956bc: Studio creates Tenants. While the Host has none, Studio asks for a name and creates the first one. A Tenant with no agents shows **Connect your code**: its model provider, the `npx @nylorun/cli tenant use <id>` command, and `npm run dev`. It switches to the agent list when the first agent registers.
8
+
9
+ Every Tenant Studio creates registers the derived principal `project` (`PROJECT_PRINCIPAL_ID` in `@nylorun/admin`). `nylo tenant use` now falls back to that key, derived from the local admin key, so a Project links a Studio-created Tenant with no stored key. When it replaces a one-time application key, it keeps that key as `.nylorun/credentials.<tenantId>.json`, and `nylo tenant use <that id>` switches back. `nylorun up` again offers Studio for creating the first Tenant.
10
+
11
+ ### Patch Changes
12
+
13
+ - Pin admin to the tested release.
14
+ - Updated dependencies [db956bc]
15
+ - @nylorun/admin@0.5.0-beta
16
+
3
17
  ## 0.4.2-beta
4
18
 
5
19
  ### Patch Changes
package/README.md CHANGED
@@ -10,6 +10,7 @@ independent and never call each other. Depends on `@nylorun/agents` and
10
10
  ```sh
11
11
  npx nylorun up # the local stack (nylorun)
12
12
  npx @nylorun/cli tenant create # this project's Tenant, linked in .nylorun/
13
+ npx @nylorun/cli tenant use <id> # or: link a Tenant created in Studio
13
14
  ```
14
15
 
15
16
  ## Commands
@@ -47,6 +48,22 @@ Inside a Project (the nearest `.nylorun/` or `package.json`):
47
48
  Outside a Project it creates the Tenant and prints the three `NYLORUN_*`
48
49
  exports once; the Host keeps only a hash of the application key.
49
50
 
51
+ ## `nylo tenant use <name-or-id>`
52
+
53
+ Links the Project to another Tenant on the local Host. It uses the first key
54
+ that the Tenant accepts:
55
+
56
+ 1. The Project's `.nylorun/credentials.json`.
57
+ 2. A key it kept when the Project last left that Tenant
58
+ (`.nylorun/credentials.<tenantId>.json`).
59
+ 3. The key of the derived principal `project`, computed from this machine's
60
+ admin key. Every Tenant created in Studio registers that principal, so
61
+ Studio's **Connect your code** step needs only this command.
62
+
63
+ When it replaces an application key (shown only once, when the Tenant was
64
+ created), it keeps that key as `.nylorun/credentials.<tenantId>.json`, so
65
+ `nylo tenant use <that id>` switches back.
66
+
50
67
  ## Project link
51
68
 
52
69
  A **Project** stores only:
package/dist/cli.js CHANGED
@@ -12,7 +12,8 @@ const usage = `nylo <tenant|configure|env|doctor>
12
12
 
13
13
  Runtime client (the local stack's Runtime, or any Runtime by URL and key):
14
14
  tenant create [name] create a Tenant; in a Project, link it and seed it from .env
15
- tenant current|list [--json]|use <name-or-id>|status [--json]|reset|delete
15
+ tenant use <name-or-id> link this Project to a Tenant, e.g. one created in Studio
16
+ tenant current|list [--json]|status [--json]|reset|delete
16
17
  configure set the linked Tenant's model provider
17
18
  env print the linked Project's NYLORUN_* variables as exports
18
19
  doctor sandbox [--json] show which sandbox backend this Tenant's Host offers
@@ -9,10 +9,11 @@ export interface ProjectCredentials {
9
9
  /** Present only on format 0 files; ignored by version 1. */
10
10
  executors?: Record<string, string>;
11
11
  }
12
- export declare function readCredentials(projectRoot: string): Promise<ProjectCredentials | undefined>;
12
+ /** The Project's credentials, or with `tenantId` the key kept for that Tenant. */
13
+ export declare function readCredentials(projectRoot: string, tenantId?: string): Promise<ProjectCredentials | undefined>;
13
14
  export declare function writeCredentials(projectRoot: string, credentials: {
14
15
  applicationKey: string;
15
16
  principalId: string;
16
17
  format?: 0 | 1;
17
- }): Promise<void>;
18
- export declare function removeCredentials(projectRoot: string): Promise<void>;
18
+ }, tenantId?: string): Promise<void>;
19
+ export declare function removeCredentials(projectRoot: string, tenantId?: string): Promise<void>;
@@ -2,8 +2,9 @@ import { chmod, readFile, rename, rm, writeFile } from "node:fs/promises";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { CliError } from "../errors.js";
4
4
  import { credentialsPath, ensureProjectNylorunDir, } from "./link.js";
5
- export async function readCredentials(projectRoot) {
6
- const path = credentialsPath(projectRoot);
5
+ /** The Project's credentials, or with `tenantId` the key kept for that Tenant. */
6
+ export async function readCredentials(projectRoot, tenantId) {
7
+ const path = credentialsPath(projectRoot, tenantId);
7
8
  try {
8
9
  const value = JSON.parse(await readFile(path, "utf8"));
9
10
  if (typeof value?.applicationKey !== "string" ||
@@ -40,9 +41,9 @@ export async function readCredentials(projectRoot) {
40
41
  throw error;
41
42
  }
42
43
  }
43
- export async function writeCredentials(projectRoot, credentials) {
44
+ export async function writeCredentials(projectRoot, credentials, tenantId) {
44
45
  await ensureProjectNylorunDir(projectRoot);
45
- const path = credentialsPath(projectRoot);
46
+ const path = credentialsPath(projectRoot, tenantId);
46
47
  const temporary = `${path}.${randomUUID()}.tmp`;
47
48
  // Version 1 writes format 1 without executors (D§3.7).
48
49
  const body = `${JSON.stringify({
@@ -59,6 +60,6 @@ export async function writeCredentials(projectRoot, credentials) {
59
60
  await rm(temporary, { force: true });
60
61
  }
61
62
  }
62
- export async function removeCredentials(projectRoot) {
63
- await rm(credentialsPath(projectRoot), { force: true });
63
+ export async function removeCredentials(projectRoot, tenantId) {
64
+ await rm(credentialsPath(projectRoot, tenantId), { force: true });
64
65
  }
@@ -7,7 +7,11 @@ export interface ProjectLink {
7
7
  }
8
8
  export declare function nylorunDir(projectRoot: string): string;
9
9
  export declare function linkPath(projectRoot: string): string;
10
- export declare function credentialsPath(projectRoot: string): string;
10
+ /**
11
+ * `.nylorun/credentials.json`, or with `tenantId` the key `nylo tenant use`
12
+ * kept for a Tenant the Project was linked to before (`credentials.<id>.json`).
13
+ */
14
+ export declare function credentialsPath(projectRoot: string, tenantId?: string): string;
11
15
  /**
12
16
  * Create `.nylorun/` mode 0700 with a private `.gitignore` containing `*`.
13
17
  * The Project's own `.gitignore` is never edited.
@@ -9,8 +9,12 @@ export function nylorunDir(projectRoot) {
9
9
  export function linkPath(projectRoot) {
10
10
  return join(nylorunDir(projectRoot), "link.json");
11
11
  }
12
- export function credentialsPath(projectRoot) {
13
- return join(nylorunDir(projectRoot), "credentials.json");
12
+ /**
13
+ * `.nylorun/credentials.json`, or with `tenantId` the key `nylo tenant use`
14
+ * kept for a Tenant the Project was linked to before (`credentials.<id>.json`).
15
+ */
16
+ export function credentialsPath(projectRoot, tenantId) {
17
+ return join(nylorunDir(projectRoot), tenantId === undefined ? "credentials.json" : `credentials.${tenantId}.json`);
14
18
  }
15
19
  /**
16
20
  * Create `.nylorun/` mode 0700 with a private `.gitignore` containing `*`.
@@ -1,10 +1,10 @@
1
1
  import { createInterface } from "node:readline/promises";
2
2
  import { randomUUID } from "node:crypto";
3
- import { createAdmin } from "@nylorun/admin";
3
+ import { PROJECT_PRINCIPAL_ID, createAdmin, } from "@nylorun/admin";
4
4
  import { createClient, isTenantId } from "@nylorun/agents";
5
5
  import { CliError } from "../errors.js";
6
6
  import { resolveHome } from "../home.js";
7
- import { readCredentials, removeCredentials, } from "../project/credentials.js";
7
+ import { readCredentials, removeCredentials, writeCredentials, } from "../project/credentials.js";
8
8
  import { readLink, removeLink, writeLink, } from "../project/link.js";
9
9
  import { findProjectRoot, requireProjectRoot } from "../project/root.js";
10
10
  import { createProjectTenant, defaultTenantName, } from "../project/create-tenant.js";
@@ -144,20 +144,44 @@ export async function tenantCommand(args) {
144
144
  const tenants = await admin.listTenants();
145
145
  const selected = matchTenant(tenants, nameOrId);
146
146
  console.warn("Sharing a Tenant shares executor registrations across Projects that link to it.");
147
- const credentials = await readCredentials(root);
148
- if (!credentials) {
149
- throw new CliError("No Project credentials. Run nylo tenant create to create a Tenant first, or copy credentials for the target Tenant.", 1);
147
+ const authorizes = async (key) => {
148
+ try {
149
+ await createClient({ url: admin.url, key, tenant: selected.id }).transport.json("/v1/tenant", "GET");
150
+ return true;
151
+ }
152
+ catch {
153
+ return false;
154
+ }
155
+ };
156
+ const link = await readLink(root);
157
+ const current = await readCredentials(root);
158
+ const kept = await readCredentials(root, selected.id);
159
+ // The Project's own key, then one kept when it last left this Tenant, then
160
+ // the key derived from this machine's admin key (Tenants Studio creates).
161
+ let chosen;
162
+ for (const candidate of [current, kept])
163
+ if (!chosen && candidate && (await authorizes(candidate.applicationKey)))
164
+ chosen = candidate;
165
+ if (!chosen) {
166
+ const derived = admin.deriveTenantKey(selected.id, PROJECT_PRINCIPAL_ID);
167
+ if (await authorizes(derived))
168
+ chosen = { format: 1, applicationKey: derived, principalId: PROJECT_PRINCIPAL_ID };
150
169
  }
151
- const client = createClient({
152
- url: admin.url,
153
- key: credentials.applicationKey,
154
- tenant: selected.id,
155
- });
156
- try {
157
- await client.transport.json("/v1/tenant", "GET");
170
+ if (!chosen) {
171
+ throw new CliError(`No key of this Project authorizes Tenant ${selected.id}, and it has no derived "${PROJECT_PRINCIPAL_ID}" principal (Tenants created in Studio have one). Link a Tenant created in Studio, or create one with nylo tenant create.`, 1);
158
172
  }
159
- catch {
160
- throw new CliError(`Credentials do not authorize Tenant ${selected.id}. Create a new Tenant with nylo tenant create instead of reusing another Project's Tenant without its key.`, 1);
173
+ if (chosen !== current) {
174
+ // An application key is shown only once: keep the one being replaced.
175
+ if (current &&
176
+ link &&
177
+ link.tenantId !== selected.id &&
178
+ current.principalId !== PROJECT_PRINCIPAL_ID) {
179
+ await writeCredentials(root, current, link.tenantId);
180
+ console.warn(`Kept the key for Tenant ${link.tenantId} in .nylorun/credentials.${link.tenantId}.json; nylo tenant use ${link.tenantId} switches back.`);
181
+ }
182
+ await writeCredentials(root, chosen);
183
+ if (chosen === kept)
184
+ await removeCredentials(root, selected.id);
161
185
  }
162
186
  await writeLink(root, {
163
187
  format: 1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/cli",
3
- "version": "0.4.2-beta",
3
+ "version": "0.5.0-beta",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "Nylorun Runtime client: Tenants, Project links and model providers",
@@ -31,7 +31,7 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@nylorun/agents": "0.9.0-beta",
34
- "@nylorun/admin": "0.4.1-beta",
34
+ "@nylorun/admin": "0.5.0-beta",
35
35
  "@earendil-works/pi-ai": "0.87.0"
36
36
  },
37
37
  "devDependencies": {