@nylorun/cli 0.5.0-beta → 0.7.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,172 @@
1
1
  # @nylorun/cli
2
2
 
3
+ ## 0.7.0-beta
4
+
5
+ ### Major Changes
6
+
7
+ - 5ca1923: **Clients for one Tenant per installation: a stack per project, nothing selects a Tenant.** Upgrade these with the Runtime; they speak protocol 5. Existing stacks are left as they are: see MIGRATION.md.
8
+
9
+ - **Breaking (`nylorun`): one stack per project.** `nylorun start` in a project creates the project's stack (named after the project directory, or `--name`; Host root `~/.nylorun/stacks/<name>/`, Compose project `nylorun-<name>`, its own free ports and volumes), waits for its Runtime to create the stack's Tenant, writes the Project link (`.nylorun/link.json` format 2: `stack`, `hostUrl`, `hostId`, `tenantId`) and `.nylorun/credentials.json` (the key of the derived principal `project`, derived from the stack's admin key), and seeds the model provider from the project's `.env`. `nylorun ls` lists the machine's stacks and `nylorun delete <name>` removes one with its volumes and Host root. `nylorun status` shows the stack's Tenant; `nylorun studio` opens it. The old single stack under `~/.nylorun` is never touched: `start` notes it, and `nylorun legacy stop|delete` handles it. Every stack command takes `--name <stack>` (or `NYLORUN_STACK`, or the Project link's stack); `start --no-link` starts a stack without linking the directory; `nylorun reset` resets the selected stack only. The runtime container's healthcheck is now `/health`, so a Tenant that cannot open is reported by `start` from the Admin status at once instead of after a 300 s wait. `NYLORUN_HOME` still overrides the Host root. Stacks start and stop only when you say so.
10
+ - **Breaking (`@nylorun/cli`): no Tenant commands.** `nylo tenant create|use|list|current|delete` are removed; `nylo status`, `nylo reset` and `nylo endpoints` replace `nylo tenant status|reset|endpoints` on the linked installation. `nylo env` prints `NYLORUN_RUNTIME_URL` and `NYLORUN_SERVER_KEY` only. `nylo` no longer writes Project links.
11
+ - **Breaking (`@nylorun/agents`): no Tenant to name.** The `tenant` option (`createClient`, `Transport`, `resolveConnection`, `createActionHandler({ runtime })`, `JwksCache`) and `NYLORUN_TENANT` are gone, and no request sends `Nylorun-Tenant`; a connection is a URL and a key. A Project link of format 0 or 1 is refused with `connection_missing`, naming `npx nylorun start`. `verifyDeliveryToken`'s `tenantId` is optional: without it any Tenant issuer is accepted, since the installation's keys bind it. `TENANT_HEADER` is no longer re-exported.
12
+ - **Breaking (`@nylorun/admin`): status only.** `createTenant`, `listTenants`, `getTenant` and `deleteTenant` are removed; `status().tenant` names the Host's Tenant, and `deriveTenantKey` / `deriveStudioToken` derive its keys. Local Host resolution reads the stack's Host root (`stack` option, `NYLORUN_STACK`, or the Project link's `stack`); `NYLORUN_HOME` and `home` still override it. `stackHostRoot(name)` is exported.
13
+ - **Breaking (`@nylorun/studio`): Studio serves its installation's Tenant.** The Tenant picker, list and create are gone, with `/_studio/tenants`; `/` opens `/tenants/<id>`. The `/tenants/:tenant` routes and the login token's `tenant` claim stay for embedders and must name that Tenant. The proxy sends no `Nylorun-Tenant`.
14
+ - **`@nylorun/create-agent`:** the next steps are `npx nylorun start`, then `npm run dev`.
15
+ - `@nylorun/core`: `ProjectLinkFileSchema` accepts format 2 with `stack`, and `tenantId` is optional; `ERROR_CODES` loses `tenant_conflict` and `active_work`.
16
+
17
+ - 5ca1923: **One Tenant per installation: a database per Tenant, protocol 5.** A Runtime serves exactly one Tenant, the one its Postgres database holds. Two Tenants are two installations.
18
+
19
+ - **Breaking: fresh start.** The Tenant's state is in the fixed schema `nylorun` of its own database and its record in `nylorun_streams`, keyed by session (no `tenant_id`). A database written by an earlier Runtime (`tenant_<id>` schemas, or a record keyed by Tenant) is refused: the Host stays up but not ready, and `/v1/admin/status` names the cause `database-layout-old`. Point the Runtime at a new database (with the local stack, a new stack); the old one is never changed.
20
+ - **The Host creates its Tenant** on first start, in the migration transaction: `NYLORUN_TENANT_ID` (default a new id), `NYLORUN_TENANT_NAME` (default `default`), the Studio principal and the derived principals of `NYLORUN_DERIVED_PRINCIPALS` (comma-separated, default `project`), whose keys the admin key derives (`deriveTenantKey`). Later starts open the same Tenant and add derived principals configured since.
21
+ - **Breaking: no Tenant catalog.** `/v1/admin/tenants` and `/v1/admin/tenants/{tenantId}` are gone (404). `AdminStatus.tenants[]` is replaced by `AdminStatus.tenant` (`id`, `name`, `state: open | unavailable`, `envelope`, and `cause` when it could not be opened). `CreateTenantRequestSchema`, `AdminTenantSchema`, `AdminTenantStatusSchema`, `AdminTenantListSchema` and `QuarantineSchema` leave `@nylorun/core`; `HostTenantSchema` and `TenantCauseSchema` replace them. `@nylorun/admin`'s `listTenants`, `getTenant`, `deleteTenant` and `createTenant` are removed (see below).
22
+ - **Readiness instead of quarantine.** A Tenant that cannot be opened (`schema-too-new`, `kek-missing`, `migration-failed`, `envelope-invalid`, `database-layout-old`, …) fails `/ready` (check `tenant`, which replaces `discovery`) and is reported with its repair in `/v1/admin/status` and the log; every Tenant request gets the opaque 404. A failure outside it (Postgres unreachable) is retried.
23
+ - **Protocol 5.** `PROTOCOL_VERSION = 5`; the Host serves protocols 4 and 5 for one release. Clients no longer require `runtime-tenants`; the Host still advertises it. No request needs `Nylorun-Tenant`: a request without it reaches the Host's Tenant, and one naming another Tenant (or a malformed one), or a publishable key of another Tenant, gets the opaque 404. The OpenAPI documents drop the header parameter and the Admin Tenant routes.
24
+ - **Paths.** The Tenant directory is `<Host root>/tenant/` (was `tenants/<id>/`); `trash/` and the SQLite move to it are gone. The gates service serves its database's Tenant, and its `Nylorun-Tenant` header is optional (when sent, it must name that Tenant).
25
+ - **Breaking: `startEphemeralRuntime` needs a database; the in-memory Session Store is removed.** `StartEphemeralRuntimeOptions.database` is required: a Postgres URL, for which the Runtime opens a pool and ends it on `close()`, or a pool the caller ends. It creates its Tenant in that database through the same bootstrap as a Host, or serves the Tenant the database already holds; the data stays after `close()`, so give each test Tenant a database of its own. Durable Streams and scheduling stay in process. See `MIGRATION.md`.
26
+
27
+ ### Patch Changes
28
+
29
+ - Pin agents to the tested release.
30
+ - Pin admin to the tested release.
31
+ - Updated dependencies [5ca1923]
32
+ - Updated dependencies [5ca1923]
33
+ - Updated dependencies
34
+ - Updated dependencies
35
+ - @nylorun/agents@0.11.0-beta
36
+ - @nylorun/admin@0.7.0-beta
37
+
38
+ ## 0.6.0-beta
39
+
40
+ ### Minor Changes
41
+
42
+ - 4282d5f: **Breaking: executors are removed; protocol 3.** The Runtime delivers every Action (tool, hook, `fn`, `verify`) to the Action endpoint an agent registers. It no longer offers Actions for executors to claim. See `MIGRATION.md`, "Action endpoints replace executors".
43
+
44
+ - **Core.** `PROTOCOL_VERSION` is 3 and a Host serves only protocol 3. `action-endpoints` is a protocol feature instead of an optional Host feature. Removed:
45
+
46
+ - the executor, claim and Action-list schemas;
47
+ - `action_result` from `SessionCommandSchema`;
48
+ - the `claimed` Action status, `claimId` and `leaseExpiresAt`.
49
+
50
+ New: `ActionResultReceiptSchema`, the receipt of `POST /v1/actions/:id/result`, which is `AcceptedResponse` without `requestId`.
51
+
52
+ - **Runtime.** Removed:
53
+
54
+ - `PUT`/`GET /v1/executors`, `DELETE /v1/executors/:agentId`, `GET /v1/executors/connect`;
55
+ - `GET /v1/actions`, `POST /v1/actions/:id/claim`, and the executor form of `POST /v1/actions/:id/heartbeat`;
56
+ - the `tenant/work` stream;
57
+ - claim expiry;
58
+ - executor credentials.
59
+
60
+ `POST /v1/actions/:id/sandbox/:tool` takes the delivery token only. Tenant summaries report `inFlightDeliveries` instead of `connectedExecutors`. Tenant status lists each agent's endpoint and has an `endpoints` check instead of `executors`.
61
+
62
+ Postgres migration 6 drops the `executors` table. Actions an executor had claimed are handled like lost deliveries: a tool becomes `uncertain`, and a hook, `fn` or `verify` is delivered again.
63
+
64
+ - **Agents.** Removed: `connectAgents`, the `@nylorun/agents/executor` subpath, derived executor keys and `NYLORUN_EXECUTOR_KEY`. Serve agents with `createActionHandler` and call `register({ url })`.
65
+ - **CLI.** Executor keys are gone from Project credentials. `nylo tenant endpoints` shows each endpoint and its health.
66
+
67
+ - 82d95ef: **Action endpoints: background tools, CLI and Studio.**
68
+
69
+ - **Background tools (core, agents).** `tool({ …, background: true })` marks a tool that runs longer than an endpoint's timeout. The option is code-only and never serialized into the manifest. `createActionHandler` answers its delivery at once with `202`, runs the tool, heartbeats on the deadline the Runtime returns (each time with the newest delivery token), and posts the outcome. A heartbeat answered `409` (cancelled, lost or sent again) aborts the tool's `ctx.signal`, and nothing is posted. The new `waitUntil` option hands the background work to platforms that end a request's work with its response.
70
+ - **CLI.** `nylo tenant endpoints [--json]` lists each agent's Action endpoint and how it is doing, and `nylo tenant endpoints ping <agent>` pings one through the Runtime.
71
+ - **Studio.** Shows `action.delivered` ("Action delivered") and `action.delivery_failed` ("Delivery failed", with the endpoint's error and when it retries).
72
+
73
+ - 50d0fb5: **Browser access: web pages and apps call the Runtime with a publishable key.** A page ships a publishable key and gets subject tokens from its app server; the Runtime answers it directly, with CORS (optional Host feature `browser-access`).
74
+
75
+ - **Publishable keys.** `nr_pub_<tenantId>_…`, sent in `Nylorun-Key`, name the Tenant and one app, with an origin allowlist (exact origins, or `http://localhost:*` and `http://127.0.0.1:*` for development; none for native apps). `GET`/`POST /v1/access/publishable-keys`, `PUT`/`DELETE …/:id`. A key alone grants the policy's `anon` role, at most the public agent list, and reaches no session or vault. Postgres migration 4.
76
+ - **Host.** With browser access on, requests with an `Origin` reach Tenant routes; `/health`, `/ready` and admin routes still refuse them. Preflights for browser routes (agents, sessions, vaults, AG-UI, JWKS) are answered from the route alone and grant no credentials; the actual request must carry a publishable key whose allowlist names the origin, and only then do responses (JSON, errors, `401`, `429`, event streams) carry CORS headers. A disallowed origin or unknown key gets the opaque `404`. Tenant and executor keys sent with an `Origin` are refused before they are looked up. `Nylorun-Tenant` may be left out when `Nylorun-Key` names the Tenant; both must agree when both are sent. Browser access is on in the stack (`NYLORUN_BROWSER_ACCESS=off` turns it off) and off for a Host started from `host.json` unless `browserAccess` is true.
77
+ - **JWKS.** `GET /v1/access/jwks` is readable by any caller that reaches the Tenant.
78
+ - **Agents SDK.** `@nylorun/agents/browser`: `createBrowserClient({ url, publishableKey, token })` keeps subject tokens in memory, refreshes them a minute before expiry or after `401 token_expired`, one fetch at a time, and creates sessions and vaults owned by the token's subject; it loads no Node module. `createTokenEndpoint()` is the app server's token route. `client.access.publishableKeys` manages keys. The transport accepts a `token` source and a `publishableKey`, and event streams the Runtime ends at token expiry reconnect at once. The Tenant API client classes move to a module with no Node imports; `@nylorun/agents` and `/client` export the same names.
79
+ - **CLI.** `nylo access keys list|create|set-origins|revoke`.
80
+
81
+ - 6ab4c59: **Long sessions on any model: compaction.** A session whose history outgrows the model's context window keeps going, on a 16k local model as on a 1M hosted one.
82
+
83
+ - **Compaction.**
84
+ - **When.** Before each model call the engine estimates the prompt: the last reported usage, plus about four characters per token for what came after. If the estimate would not leave room for the reply, the engine first compacts: it asks the model to summarize the older history, then keeps about the newest 20,000 tokens (at most 30% of the window) verbatim.
85
+ - **What it keeps.** The cut never separates a tool call from its result. The current turn's request is always kept. A later compaction merges with the earlier summary.
86
+ - **Overflow.** If a provider still reports a context overflow, the engine compacts once and asks again.
87
+ - **Storage.** The summary is a `compaction` transcript entry that replaces the older entries in the session state; the event log keeps the full history. The summary call is a journaled model effect, so replays are deterministic.
88
+ - **New event.** `context.compacted` (`trigger`, `tokensBefore`, `tokensAfter`).
89
+ - **Custom endpoints.**
90
+ - **Settings.** Model Settings take `settings: { contextWindow, maxTokens, reasoning, compat }` for a custom OpenAI-compatible provider (vLLM, SGLang, llama.cpp, Ollama, LM Studio). `compat` is passed to pi-ai: `thinkingFormat`, `thinkingTokenBudgetField`, `chatTemplateKwargs` and the rest.
91
+ - **Defaults.** Without settings, a custom endpoint is assumed to have a 32k window and an 8k output limit. They used to be 128k and 16k.
92
+ - **Where to set them.** `nylo configure` asks for the window and output limit. Studio's Model Settings shows all four fields.
93
+ - **Storage.**
94
+ - Model effects no longer journal the model request next to the call, so each call's prompt is stored once.
95
+ - When a turn ends, its model effects are slimmed to their identity and status; the transcript holds the answers.
96
+ - Session storage now grows with the window, not with the square of the session's length.
97
+ - **Core.**
98
+ - `TranscriptEntry` adds `compaction` (`TranscriptCompactionEntry`).
99
+ - `ModelAdapterContext.compaction` marks a summary call.
100
+ - `CustomModelSettings` / `CustomModelSettingsSchema` describe the custom endpoint settings.
101
+
102
+ - 50d0fb5: **Subject tokens: a person's own credential for the Runtime.** An app server mints a short-lived token for one signed-in person, and their app calls the Runtime directly (optional Host feature `subject-tokens`). Requests with application keys and subject headers are unchanged.
103
+
104
+ - **Runtime.** `POST /v1/tokens` (application key only) mints an ES256 JWT for a subject and a role, valid 60–900 seconds. The Tenant API accepts it as a bearer and resolves its scopes and agents from the role on every request. Forged, foreign or malformed tokens get the opaque `404`; an expired token, a revoked subject, a revoked key or a removed role gets `401 token_expired` with `WWW-Authenticate`. Tokens carry only `agents:read`, `sessions:own` and `vaults:own`; they may not set session `info`, send `message.manifest` or store OAuth refresh credentials, and `GET /v1/agents` shows them `{ agentId, name, description }` of their role's agents only.
105
+ - **Access policy.** `GET`/`PUT /v1/access/policy`: roles with token scopes, an agent allowlist and limits (`turnsPerHour`, `concurrentTurns`, answered with `429 limit_exceeded` and `Retry-After`). Without roles nothing is minted.
106
+ - **Signing keys.** Per Tenant, the private key sealed with the vault KEK: `GET /v1/access/signing-keys`, `POST …/rotate` (refused while the previous key may still verify live tokens; `force` for incidents), `POST …/:kid/revoke`, `GET /v1/access/jwks`. A Tenant with signing keys and no KEK is quarantined `kek-missing`.
107
+ - **Revocation.** `POST /v1/access/revocations` ends a subject's tokens; their open event streams end with `event: nylorun.closed` on every process. A stream opened with a token also ends when the token expires.
108
+ - Postgres migration 3 adds `signing_keys`, `subject_epochs`, `subject_usage` and an index on the session owner and status. New error codes `token_expired` and `limit_exceeded`.
109
+ - **Agents SDK.** `client.tokens.create()`, `client.access.getPolicy()`/`putPolicy()`/`revokeSubject()`/`jwks()` and `client.access.signingKeys.list()`/`rotate()`/`revoke()`, each checking the Host feature first.
110
+ - **CLI.** `nylo access policy get|set|init`, `nylo access signing-keys list|rotate|revoke`, `nylo access revoke <subject>` and `nylo access token` for trying the API.
111
+
112
+ ### Patch Changes
113
+
114
+ - f39b79f: **`nylo doctor sandbox` shows the Tenant's default sandbox**: `none`, `virtual`, or a Tenant sandbox that sessions naming no sandbox get.
115
+ - 6ab4c59: **Model calls don't strand sessions.** A model provider failure is now a known outcome, not a lost call. The Runtime retries what can be retried, and otherwise fails the turn with `model.<code>`, so the session accepts the next message instead of sitting `uncertain` until it is cancelled.
116
+
117
+ - **Runtime.**
118
+
119
+ - **Upgrade.** The model adapter moves to pi-ai 0.99.1 and always streams.
120
+ - **Per-call settings.**
121
+ - Every call carries the session id, so OpenAI and other providers reuse the prompt cache and route to the same backend.
122
+ - Provider auth no longer reads the process environment.
123
+ - Rate limits, overloads, timeouts and transient errors are retried: 3 attempts with backoff, honouring `Retry-After`.
124
+ - A stream that produces nothing for 300 s is aborted and retried. `TenantConfig.modelCall` sets attempts, backoff, the idle timeout and the request timeout.
125
+ - **Failures.** Anything else fails the turn with one of these codes:
126
+
127
+ - `model.context_overflow`, `model.rate_limited`, `model.overloaded`, `model.timeout`, `model.transient`
128
+ - `model.content_policy`, `model.auth` (whose message says where to fix the credential)
129
+ - `model.invalid_request`, `model.invalid_output`
130
+
131
+ Only a call whose outcome was lost, such as a Worker dying mid-call, is still `uncertain`.
132
+
133
+ - **Structured output.** A structured final answer is repaired (control characters, a Markdown code fence) before it is parsed.
134
+ - **Model history.** Replayed history keeps the model that produced each message, so a model switch no longer sends one model's signatures or tool-call ids to another. Reasoning from OpenAI-compatible servers (`reasoning_content`, `reasoning`) is sent back only within the turn that produced it.
135
+
136
+ - **Events.**
137
+ - `message.assistant` adds `model` (`provider`, `model`), `finishReason` and `usage`.
138
+ - A new `model.failed` transcript event (`code`, `message`, `retryable`) is written instead of `message.assistant` when a call fails.
139
+ - **Core and harness.**
140
+ - New types and helpers: `ModelFailureOutcome`, `ModelFailureCode`, `MODEL_FAILURE_CODES`, `isModelFailureOutcome`, `ModelProducer`.
141
+ - `PromptItem` assistant messages may carry `producer`.
142
+ - `ModelUsage` adds `cacheWriteTokens`.
143
+ - A model adapter may return a failure outcome.
144
+ - The durable engine version is `hosted-3`: a turn that is running when the Runtime is upgraded fails once with `execution.incompatible`, and the next message works.
145
+ - **CLI.**
146
+ - `nylo configure` passes a stable installation id to OAuth logins that need one (OpenAI "Sign in with ChatGPT"), stored as `cli-installation-id` in the Host root.
147
+ - A custom OpenAI-compatible provider now prompts for its API key instead of failing.
148
+
149
+ - Pin agents to the tested release.
150
+ - Pin admin to the tested release.
151
+ - Updated dependencies [c7614a4]
152
+ - Updated dependencies [5f23047]
153
+ - Updated dependencies [4282d5f]
154
+ - Updated dependencies [82d95ef]
155
+ - Updated dependencies [e28b8a9]
156
+ - Updated dependencies [50d0fb5]
157
+ - Updated dependencies [50d0fb5]
158
+ - Updated dependencies [c121144]
159
+ - Updated dependencies [50d0fb5]
160
+ - Updated dependencies [c121144]
161
+ - Updated dependencies [9546ac7]
162
+ - Updated dependencies [9d52189]
163
+ - Updated dependencies [9d52189]
164
+ - Updated dependencies [50d0fb5]
165
+ - Updated dependencies
166
+ - Updated dependencies
167
+ - @nylorun/agents@0.10.0-beta
168
+ - @nylorun/admin@0.6.0-beta
169
+
3
170
  ## 0.5.0-beta
4
171
 
5
172
  ### Minor Changes
package/README.md CHANGED
@@ -1,84 +1,75 @@
1
1
  # @nylorun/cli
2
2
 
3
- The Runtime client, command `nylo`: it acts on a Runtime's Tenants, the local
4
- stack's or any Runtime reachable by URL and key. Setting up and running the
5
- local stack is [`nylorun`](../nylorun/README.md)'s job; the two packages are
6
- independent and never call each other. Depends on `@nylorun/agents` and
7
- `@nylorun/admin` only among Nylorun packages. Vocabulary:
3
+ The Runtime client, command `nylo`: it acts on the one Tenant of an
4
+ installation, a local stack or any Runtime reachable by URL and key. Setting up
5
+ and running the local stack is [`nylorun`](../nylorun/README.md)'s job; the two
6
+ packages are independent and never call each other. Depends on
7
+ `@nylorun/agents` and `@nylorun/admin` only among Nylorun packages. Vocabulary:
8
8
  [runtime/src/CONTEXT.md](../runtime/src/CONTEXT.md).
9
9
 
10
10
  ```sh
11
- npx nylorun up # the local stack (nylorun)
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
11
+ npx nylorun start # in the project: its stack, its Tenant and the Project link
12
+ npx @nylorun/cli status # the linked Tenant
14
13
  ```
15
14
 
16
15
  ## Commands
17
16
 
18
17
  ```sh
19
- nylo tenant create [name] # create a Tenant; in a Project, link it and seed it from .env
20
- nylo tenant current|list [--json]|use <name-or-id>|status [--json]|reset|delete
21
- nylo configure # set or replace the model provider on the linked Tenant
18
+ nylo status [--json] # the Tenant, its checks and counts, and the link's stack
19
+ nylo reset [--sessions|--sandboxes|--all] [--yes]
20
+ nylo endpoints [--json]|ping <agent> # the registered Action endpoints and their health
21
+ nylo access … # access policy, publishable and signing keys, subject tokens
22
+ nylo configure # set or replace the Tenant's model provider
22
23
  nylo env # export lines for the linked Project
23
24
  nylo doctor sandbox [--json] # sandbox backend via the Tenant API
24
25
  ```
25
26
 
26
- Local stack commands (`up`, `down`, `start`, `stop`, `status`, `logs`,
27
- `studio`, `reset`) exit 2 naming `npx nylorun <command>`. `nylorun dev` was
28
- removed: link once with `nylo tenant create`, then run the project's own
29
- `npm run dev` (`tsx watch src/main.ts`).
27
+ Every command acts on the linked installation: the Project link and
28
+ credentials (below), or `NYLORUN_RUNTIME_URL` and `NYLORUN_SERVER_KEY` when the
29
+ Project has no link. An installation serves one Tenant, so nothing selects it.
30
30
 
31
- ## `nylo tenant create`
31
+ Local stack commands (`up`, `down`, `start`, `stop`, `logs`, `studio`) exit 2
32
+ naming `npx nylorun <command>`. `nylo tenant …` exits 2: `npx nylorun start`
33
+ creates the stack, its Tenant and the link, and `nylo status|reset|endpoints`
34
+ replace `nylo tenant status|reset|endpoints`. `nylorun dev` was removed: run
35
+ `npx nylorun start` once, then the project's own `npm run dev`
36
+ (`tsx watch src/main.ts`).
32
37
 
33
- The Admin API connection resolves from `NYLORUN_ADMIN_URL` and
34
- `NYLORUN_ADMIN_KEY`, else from the local Host root (`NYLORUN_HOME` or
35
- `~/.nylorun`: `host.json` and the admin key that `nylorun up` wrote). With no
36
- Runtime answering, it exits 6 and names `npx nylorun up`.
38
+ ## `nylo status`
37
39
 
38
- Inside a Project (the nearest `.nylorun/` or `package.json`):
40
+ Reads the Tenant API status (`GET /v1/tenant`) and prints the Tenant, the
41
+ stack the link names, the Runtime URL, the checks, the counts and the sandbox
42
+ backend. When the Tenant is not open it does not answer; `nylo status` then
43
+ asks the Admin API (`/v1/admin/status`, through the local stack's Host root or
44
+ `NYLORUN_ADMIN_URL` and `NYLORUN_ADMIN_KEY`) and prints why, with the repair.
39
45
 
40
- 1. Creates the Tenant, named after `package.json` unless a name is given.
41
- 2. Writes the format-1 link and credentials (see below). A Project that is
42
- already linked is refused: use `nylo tenant use` or `nylo tenant delete`.
43
- 3. Seeds the Tenant from `.env`: `NYLORUN_SANDBOX`, and the model provider from
44
- `MODEL_PROVIDER`, `MODEL`, `MODEL_PROVIDER_API_KEY` (or a credential in
45
- `.nylorun/auth.json`) into the Tenant vault. Without them it says to set the
46
- provider in Studio or with `nylo configure`.
46
+ ## `nylo reset`
47
47
 
48
- Outside a Project it creates the Tenant and prints the three `NYLORUN_*`
49
- exports once; the Host keeps only a hash of the application key.
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.
48
+ Clears the Tenant's sessions (the default), its sandboxes, or all its data
49
+ (`--all`), after draining work in flight. `--all` asks first; pass `--yes` when
50
+ not in a terminal. The Project link and credentials are kept.
66
51
 
67
52
  ## Project link
68
53
 
69
- A **Project** stores only:
54
+ `npx nylorun start` writes it; `nylo` only reads it. A **Project** stores only:
70
55
 
71
- - `.nylorun/link.json`: `{ format, hostUrl, hostId, tenantId }`
72
- - `.nylorun/credentials.json`: application key and principal id (0600), no
73
- executor tokens
56
+ - `.nylorun/link.json`: `{ format: 2, stack, hostUrl, hostId, tenantId }`
57
+ (`tenantId` is information)
58
+ - `.nylorun/credentials.json`: the key of the derived principal `project` and
59
+ its id (0600)
74
60
  - `.nylorun/.gitignore` containing `*`
75
61
 
76
- `connectAgents` in `@nylorun/agents` reads the link (or the three variables),
77
- so the project's `npm run dev` and `npm start` need no Nylorun tool.
62
+ A format 0 or 1 link named a Tenant on an older multi-Tenant Host: `nylo`
63
+ refuses it and says to run `npx nylorun start`, which starts the project's own
64
+ stack and links it again.
65
+
66
+ `createActionHandler` and `createClient` in `@nylorun/agents` read the link (or
67
+ the two variables), so the project's `npm run dev` and `npm start` need no
68
+ Nylorun tool.
78
69
 
79
70
  ```sh
80
71
  eval "$(npx @nylorun/cli env)"
81
- # → NYLORUN_RUNTIME_URL, NYLORUN_SERVER_KEY, NYLORUN_TENANT
72
+ # → NYLORUN_RUNTIME_URL, NYLORUN_SERVER_KEY
82
73
  ```
83
74
 
84
75
  ## Exit codes
@@ -95,8 +86,9 @@ eval "$(npx @nylorun/cli env)"
95
86
 
96
87
  | Symptom | What to do |
97
88
  | --- | --- |
98
- | No Runtime answers | `npx nylorun up`, then `npx nylorun status` |
99
- | Quarantined Tenant | `nylo tenant status` shows the reason and `repair` |
89
+ | No Runtime answers | `npx nylorun start`, then `npx nylorun status` |
90
+ | Tenant not open | `nylo status` shows the cause and its `repair` |
91
+ | Old Project link refused | `npx nylorun start` in the project |
100
92
  | `426` from the Runtime | Upgrade the CLI, or pin a matching older set |
101
93
 
102
94
  See [MIGRATION.md](../MIGRATION.md).
@@ -0,0 +1,24 @@
1
+ import { type AgentsClient } from "@nylorun/agents";
2
+ export declare const accessUsage = "nylo access <policy|keys|signing-keys|revoke|token>\n\n policy get print the access policy\n policy set <file> replace it with a JSON file\n policy init write a starter \"user\" role (sessions:own agents:read, every agent,\n 60 turns per hour, 2 at once)\n signing-keys list list the signing keys\n signing-keys rotate [--force] rotate; --force signs outstanding tokens out\n signing-keys revoke <kid> revoke a previous or standby key\n keys list list the publishable keys\n keys create --name <n> [--origin <o>]\u2026\n a publishable key for a web page or an app\n (origins: https://app.example.com or http://localhost:*)\n keys set-origins <id> [<o>\u2026] replace a key's origins ([] for native apps only)\n keys revoke <id> revoke a publishable key\n revoke <subject> end a subject's tokens and open streams\n token --subject <s> --role <r> [--ttl <seconds>]\n mint a subject token (for trying the API with curl)";
3
+ /** The starter policy `policy init` writes. */
4
+ export declare const STARTER_POLICY: {
5
+ version: 1;
6
+ roles: {
7
+ user: {
8
+ scopes: ("agents:read" | "sessions:own")[];
9
+ agents: "*";
10
+ limits: {
11
+ turnsPerHour: number;
12
+ concurrentTurns: number;
13
+ };
14
+ };
15
+ };
16
+ anon: {
17
+ scopes: never[];
18
+ agents: never[];
19
+ };
20
+ tokens: {
21
+ maxTtlSeconds: number;
22
+ };
23
+ };
24
+ export declare function accessCommand(input: readonly string[], client?: () => Promise<AgentsClient>): Promise<void>;
@@ -0,0 +1,144 @@
1
+ /**
2
+ * `nylo access`: the linked Tenant's access policy, signing keys and revocations, and a
3
+ * development command that mints a subject token (Host feature `subject-tokens`). Uses the
4
+ * Project's application key, like every other Tenant command.
5
+ */
6
+ import { readFile } from "node:fs/promises";
7
+ import { createClient, resolveConnection, } from "@nylorun/agents";
8
+ import { CliError } from "../errors.js";
9
+ export const accessUsage = `nylo access <policy|keys|signing-keys|revoke|token>
10
+
11
+ policy get print the access policy
12
+ policy set <file> replace it with a JSON file
13
+ policy init write a starter "user" role (sessions:own agents:read, every agent,
14
+ 60 turns per hour, 2 at once)
15
+ signing-keys list list the signing keys
16
+ signing-keys rotate [--force] rotate; --force signs outstanding tokens out
17
+ signing-keys revoke <kid> revoke a previous or standby key
18
+ keys list list the publishable keys
19
+ keys create --name <n> [--origin <o>]…
20
+ a publishable key for a web page or an app
21
+ (origins: https://app.example.com or http://localhost:*)
22
+ keys set-origins <id> [<o>…] replace a key's origins ([] for native apps only)
23
+ keys revoke <id> revoke a publishable key
24
+ revoke <subject> end a subject's tokens and open streams
25
+ token --subject <s> --role <r> [--ttl <seconds>]
26
+ mint a subject token (for trying the API with curl)`;
27
+ /** The starter policy `policy init` writes. */
28
+ export const STARTER_POLICY = {
29
+ version: 1,
30
+ roles: {
31
+ user: {
32
+ scopes: ["sessions:own", "agents:read"],
33
+ agents: "*",
34
+ limits: { turnsPerHour: 60, concurrentTurns: 2 },
35
+ },
36
+ },
37
+ anon: { scopes: [], agents: [] },
38
+ tokens: { maxTtlSeconds: 600 },
39
+ };
40
+ const usageError = (message = accessUsage) => new CliError(message, 2);
41
+ async function linkedClient() {
42
+ let connection;
43
+ try {
44
+ connection = await resolveConnection();
45
+ }
46
+ catch (error) {
47
+ throw new CliError(error instanceof Error ? error.message : String(error), 1);
48
+ }
49
+ return createClient({ url: connection.url, key: connection.key });
50
+ }
51
+ const print = (value) => console.log(JSON.stringify(value, null, 2));
52
+ function option(args, name) {
53
+ const index = args.indexOf(name);
54
+ if (index === -1)
55
+ return undefined;
56
+ const value = args[index + 1];
57
+ if (!value || value.startsWith("--"))
58
+ throw usageError(`${name} requires a value.`);
59
+ args.splice(index, 2);
60
+ return value;
61
+ }
62
+ export async function accessCommand(input, client = linkedClient) {
63
+ const [topic, action, ...rest] = input;
64
+ const args = [...rest];
65
+ if (topic === "policy") {
66
+ if (action === "get" && args.length === 0)
67
+ return print(await (await client()).access.getPolicy());
68
+ if (action === "init" && args.length === 0)
69
+ return print(await (await client()).access.putPolicy(STARTER_POLICY));
70
+ if (action === "set" && args.length === 1) {
71
+ let parsed;
72
+ try {
73
+ parsed = JSON.parse(await readFile(args[0], "utf8"));
74
+ }
75
+ catch (error) {
76
+ throw new CliError(`Could not read ${args[0]}: ${error instanceof Error ? error.message : String(error)}`, 1);
77
+ }
78
+ // Accept the file as the policy itself or as `{ policy }`, as `policy get` prints it.
79
+ const candidate = parsed && typeof parsed === "object" && "policy" in parsed
80
+ ? parsed.policy
81
+ : parsed;
82
+ // The Runtime validates it and names what is wrong.
83
+ return print(await (await client()).access.putPolicy(candidate));
84
+ }
85
+ throw usageError();
86
+ }
87
+ if (topic === "signing-keys") {
88
+ const keys = async () => (await client()).access.signingKeys;
89
+ if (action === "list" && args.length === 0)
90
+ return print(await (await keys()).list());
91
+ if (action === "rotate") {
92
+ const force = args[0] === "--force";
93
+ if (args.length > (force ? 1 : 0))
94
+ throw usageError();
95
+ return print(await (await keys()).rotate({ force }));
96
+ }
97
+ if (action === "revoke" && args.length === 1)
98
+ return print(await (await keys()).revoke(args[0]));
99
+ throw usageError();
100
+ }
101
+ if (topic === "keys") {
102
+ const keys = async () => (await client()).access.publishableKeys;
103
+ if (action === "list" && args.length === 0)
104
+ return print(await (await keys()).list());
105
+ if (action === "create") {
106
+ const name = option(args, "--name");
107
+ const origins = [];
108
+ for (let origin = option(args, "--origin"); origin; origin = option(args, "--origin"))
109
+ origins.push(origin);
110
+ if (!name || args.length > 0)
111
+ throw usageError();
112
+ return print(await (await keys()).create({ name, origins }));
113
+ }
114
+ if (action === "set-origins" && args.length >= 1) {
115
+ const [keyId, ...origins] = args;
116
+ return print(await (await keys()).update(keyId, { origins }));
117
+ }
118
+ if (action === "revoke" && args.length === 1)
119
+ return print(await (await keys()).revoke(args[0]));
120
+ throw usageError();
121
+ }
122
+ if (topic === "revoke") {
123
+ if (!action || args.length > 0)
124
+ throw usageError();
125
+ return print(await (await client()).access.revokeSubject(action));
126
+ }
127
+ if (topic === "token") {
128
+ const all = [action, ...args].filter((arg) => arg !== undefined);
129
+ const subject = option(all, "--subject");
130
+ const role = option(all, "--role");
131
+ const ttl = option(all, "--ttl");
132
+ if (!subject || !role || all.length > 0)
133
+ throw usageError();
134
+ const ttlSeconds = ttl === undefined ? undefined : Number(ttl);
135
+ if (ttlSeconds !== undefined && !Number.isInteger(ttlSeconds))
136
+ throw usageError("--ttl must be a whole number of seconds.");
137
+ return print(await (await client()).tokens.create({
138
+ subject,
139
+ role,
140
+ ...(ttlSeconds === undefined ? {} : { ttlSeconds }),
141
+ }));
142
+ }
143
+ throw usageError();
144
+ }
package/dist/cli.js CHANGED
@@ -5,24 +5,35 @@ import { putHostModel } from "./model/host-model.js";
5
5
  import { CliError } from "./errors.js";
6
6
  import { findProjectRoot } from "./project/root.js";
7
7
  import { printLinkedEnvExports } from "./project/env.js";
8
- import { readLink as readProjectLink } from "./project/link.js";
9
- import { readCredentials as readProjectCredentials } from "./project/credentials.js";
10
- import { tenantCommand } from "./tenant/commands.js";
11
- const usage = `nylo <tenant|configure|env|doctor>
8
+ import { linkedConnection } from "./project/connection.js";
9
+ import { accessCommand } from "./access/commands.js";
10
+ import { endpointsCommand, resetCommand, statusCommand, } from "./installation/commands.js";
11
+ const usage = `nylo <status|reset|endpoints|access|configure|env|doctor>
12
12
 
13
- Runtime client (the local stack's Runtime, or any Runtime by URL and key):
14
- tenant create [name] create a Tenant; in a Project, link it and seed it from .env
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
13
+ Runtime client for the linked installation and its one Tenant (the Project link that
14
+ npx nylorun start writes, or NYLORUN_RUNTIME_URL and NYLORUN_SERVER_KEY):
15
+ status [--json] the Tenant, its checks and counts, and the stack
16
+ reset [--sessions|--sandboxes|--all] [--yes]
17
+ clear the Tenant's sessions, sandboxes or all its data
18
+ endpoints [--json]|ping <agent> the registered Action endpoints and their health
19
+ access policy get|set <file>|init the access policy for subject tokens (nylo access --help)
20
+ access keys list|create|set-origins|revoke publishable keys for web pages and apps
21
+ access signing-keys list|rotate|revoke the Tenant's token signing keys
22
+ access revoke <subject> end a subject's tokens and open streams
23
+ access token --subject <s> --role <r> mint a subject token for trying the API
17
24
  configure set the linked Tenant's model provider
18
25
  env print the linked Project's NYLORUN_* variables as exports
19
26
  doctor sandbox [--json] show which sandbox backend this Tenant's Host offers
20
27
 
21
- The local stack is managed by the nylorun package: npx nylorun up|down|status|logs|studio`;
28
+ The local stack is managed by the nylorun package: npx nylorun start in a project creates
29
+ its stack, its Tenant and the Project link.`;
22
30
  /** Local stack commands, which moved to the nylorun package. */
23
31
  const STACK_COMMANDS = new Set([
24
- "up", "down", "start", "stop", "status", "logs", "studio", "reset", "stack", "runtime", "restart", "run",
32
+ "up", "down", "start", "stop", "logs", "studio", "stack", "runtime", "restart", "run",
25
33
  ]);
34
+ const TENANT_REMOVED = `nylo tenant was removed: an installation serves one Tenant.
35
+ Run "npx nylorun start" in your project to create its stack, its Tenant and the Project link.
36
+ Then use nylo status, nylo reset and nylo endpoints on the linked installation.`;
26
37
  function parseFlags(args, allowed = {}) {
27
38
  const booleans = new Set();
28
39
  const values = new Map();
@@ -58,25 +69,6 @@ function parseFlags(args, allowed = {}) {
58
69
  return { rest, booleans, values };
59
70
  }
60
71
  const usageError = (message) => new CliError(message, 2);
61
- async function resolveLinkedAuth(projectRoot) {
62
- const link = await readProjectLink(projectRoot);
63
- const credentials = await readProjectCredentials(projectRoot);
64
- if (link && credentials) {
65
- return {
66
- url: link.hostUrl,
67
- key: credentials.applicationKey,
68
- tenantId: link.tenantId,
69
- tenantName: link.tenantId,
70
- };
71
- }
72
- const url = process.env.NYLORUN_RUNTIME_URL?.trim();
73
- const key = process.env.NYLORUN_SERVER_KEY?.trim();
74
- const tenantId = process.env.NYLORUN_TENANT?.trim();
75
- if (url && key && tenantId) {
76
- return { url, key, tenantId, tenantName: tenantId };
77
- }
78
- throw new CliError(`No Project link in ${projectRoot}. Run nylo tenant create, or set NYLORUN_RUNTIME_URL, NYLORUN_SERVER_KEY and NYLORUN_TENANT.`, 1);
79
- }
80
72
  async function main() {
81
73
  const [command, ...args] = process.argv.slice(2);
82
74
  if (!command || command === "--help" || command === "-h")
@@ -84,7 +76,20 @@ async function main() {
84
76
  if (process.platform === "win32")
85
77
  throw new CliError("Nylorun does not run on native Windows. Use WSL2: install Node 24 and Docker (Docker Desktop's WSL integration) inside your WSL distribution and run nylorun there (https://learn.microsoft.com/windows/wsl/install).", 1);
86
78
  if (command === "tenant")
87
- return await tenantCommand(args);
79
+ throw usageError(TENANT_REMOVED);
80
+ if (command === "status")
81
+ return await statusCommand(args);
82
+ if (command === "reset")
83
+ return await resetCommand(args);
84
+ if (command === "endpoints")
85
+ return await endpointsCommand(args);
86
+ if (command === "access") {
87
+ if (args[0] === "--help" || args[0] === "-h" || args.length === 0) {
88
+ const { accessUsage } = await import("./access/commands.js");
89
+ return void console.log(accessUsage);
90
+ }
91
+ return await accessCommand(args);
92
+ }
88
93
  if (command === "env") {
89
94
  if (args.length)
90
95
  throw usageError("Usage: nylo env");
@@ -100,7 +105,7 @@ async function main() {
100
105
  return;
101
106
  }
102
107
  if (command === "dev")
103
- throw usageError("nylorun dev was removed: run nylo tenant create once in your project, then your project's npm run dev.");
108
+ throw usageError('nylorun dev was removed: run "npx nylorun start" once in your project, then your project\'s npm run dev.');
104
109
  if (STACK_COMMANDS.has(command))
105
110
  throw usageError(`The local stack moved to the nylorun package: npx nylorun ${command}`);
106
111
  if (command === "configure") {
@@ -108,24 +113,20 @@ async function main() {
108
113
  if (flags.rest.length)
109
114
  throw usageError(usage);
110
115
  const projectRoot = findProjectRoot() ?? process.cwd();
111
- const auth = await resolveLinkedAuth(projectRoot);
116
+ const auth = await linkedConnection(projectRoot);
112
117
  const controller = new AbortController();
113
118
  const cancel = (signal) => controller.abort(new ConfigurationCancelled(signal));
114
119
  process.once("SIGINT", () => cancel("SIGINT"));
115
120
  process.once("SIGTERM", () => cancel("SIGTERM"));
116
121
  const health = await fetch(`${auth.url}/health`).catch(() => undefined);
117
122
  if (!health?.ok)
118
- throw new CliError(`No Runtime is listening at ${auth.url}. Start the local stack with "npx nylorun up".`, 6);
119
- const catalog = await fetchModelCatalog({
120
- url: auth.url,
121
- key: auth.key,
122
- tenantId: auth.tenantId,
123
- });
123
+ throw new CliError(`No Runtime is listening at ${auth.url}. Start the local stack with "npx nylorun start".`, 6);
124
+ const catalog = await fetchModelCatalog({ url: auth.url, key: auth.key });
124
125
  const prompted = await configureProvider({
125
126
  signal: controller.signal,
126
127
  catalog,
127
128
  });
128
- await putHostModel(auth.url, auth.key, prompted, auth.tenantId);
129
+ await putHostModel(auth.url, auth.key, prompted);
129
130
  return;
130
131
  }
131
132
  throw usageError(usage);