@nylorun/cli 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 +132 -0
- package/README.md +2 -3
- package/dist/access/commands.d.ts +24 -0
- package/dist/access/commands.js +148 -0
- package/dist/cli.js +14 -1
- package/dist/doctor.d.ts +7 -0
- package/dist/doctor.js +9 -0
- package/dist/model/configure.d.ts +12 -0
- package/dist/model/configure.js +54 -5
- package/dist/model/host-model.d.ts +4 -0
- package/dist/model/host-model.js +1 -0
- package/dist/project/credentials.d.ts +2 -4
- package/dist/project/credentials.js +1 -8
- package/dist/tenant/commands.d.ts +14 -0
- package/dist/tenant/commands.js +51 -2
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,137 @@
|
|
|
1
1
|
# @nylorun/cli
|
|
2
2
|
|
|
3
|
+
## 0.6.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 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".
|
|
8
|
+
|
|
9
|
+
- **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:
|
|
10
|
+
|
|
11
|
+
- the executor, claim and Action-list schemas;
|
|
12
|
+
- `action_result` from `SessionCommandSchema`;
|
|
13
|
+
- the `claimed` Action status, `claimId` and `leaseExpiresAt`.
|
|
14
|
+
|
|
15
|
+
New: `ActionResultReceiptSchema`, the receipt of `POST /v1/actions/:id/result`, which is `AcceptedResponse` without `requestId`.
|
|
16
|
+
|
|
17
|
+
- **Runtime.** Removed:
|
|
18
|
+
|
|
19
|
+
- `PUT`/`GET /v1/executors`, `DELETE /v1/executors/:agentId`, `GET /v1/executors/connect`;
|
|
20
|
+
- `GET /v1/actions`, `POST /v1/actions/:id/claim`, and the executor form of `POST /v1/actions/:id/heartbeat`;
|
|
21
|
+
- the `tenant/work` stream;
|
|
22
|
+
- claim expiry;
|
|
23
|
+
- executor credentials.
|
|
24
|
+
|
|
25
|
+
`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`.
|
|
26
|
+
|
|
27
|
+
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.
|
|
28
|
+
|
|
29
|
+
- **Agents.** Removed: `connectAgents`, the `@nylorun/agents/executor` subpath, derived executor keys and `NYLORUN_EXECUTOR_KEY`. Serve agents with `createActionHandler` and call `register({ url })`.
|
|
30
|
+
- **CLI.** Executor keys are gone from Project credentials. `nylo tenant endpoints` shows each endpoint and its health.
|
|
31
|
+
|
|
32
|
+
- 82d95ef: **Action endpoints: background tools, CLI and Studio.**
|
|
33
|
+
|
|
34
|
+
- **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.
|
|
35
|
+
- **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.
|
|
36
|
+
- **Studio.** Shows `action.delivered` ("Action delivered") and `action.delivery_failed` ("Delivery failed", with the endpoint's error and when it retries).
|
|
37
|
+
|
|
38
|
+
- 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`).
|
|
39
|
+
|
|
40
|
+
- **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.
|
|
41
|
+
- **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.
|
|
42
|
+
- **JWKS.** `GET /v1/access/jwks` is readable by any caller that reaches the Tenant.
|
|
43
|
+
- **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.
|
|
44
|
+
- **CLI.** `nylo access keys list|create|set-origins|revoke`.
|
|
45
|
+
|
|
46
|
+
- 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.
|
|
47
|
+
|
|
48
|
+
- **Compaction.**
|
|
49
|
+
- **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.
|
|
50
|
+
- **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.
|
|
51
|
+
- **Overflow.** If a provider still reports a context overflow, the engine compacts once and asks again.
|
|
52
|
+
- **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.
|
|
53
|
+
- **New event.** `context.compacted` (`trigger`, `tokensBefore`, `tokensAfter`).
|
|
54
|
+
- **Custom endpoints.**
|
|
55
|
+
- **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.
|
|
56
|
+
- **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.
|
|
57
|
+
- **Where to set them.** `nylo configure` asks for the window and output limit. Studio's Model Settings shows all four fields.
|
|
58
|
+
- **Storage.**
|
|
59
|
+
- Model effects no longer journal the model request next to the call, so each call's prompt is stored once.
|
|
60
|
+
- When a turn ends, its model effects are slimmed to their identity and status; the transcript holds the answers.
|
|
61
|
+
- Session storage now grows with the window, not with the square of the session's length.
|
|
62
|
+
- **Core.**
|
|
63
|
+
- `TranscriptEntry` adds `compaction` (`TranscriptCompactionEntry`).
|
|
64
|
+
- `ModelAdapterContext.compaction` marks a summary call.
|
|
65
|
+
- `CustomModelSettings` / `CustomModelSettingsSchema` describe the custom endpoint settings.
|
|
66
|
+
|
|
67
|
+
- 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.
|
|
68
|
+
|
|
69
|
+
- **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.
|
|
70
|
+
- **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.
|
|
71
|
+
- **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`.
|
|
72
|
+
- **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.
|
|
73
|
+
- 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`.
|
|
74
|
+
- **Agents SDK.** `client.tokens.create()`, `client.access.getPolicy()`/`putPolicy()`/`revokeSubject()`/`jwks()` and `client.access.signingKeys.list()`/`rotate()`/`revoke()`, each checking the Host feature first.
|
|
75
|
+
- **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.
|
|
76
|
+
|
|
77
|
+
### Patch Changes
|
|
78
|
+
|
|
79
|
+
- f39b79f: **`nylo doctor sandbox` shows the Tenant's default sandbox**: `none`, `virtual`, or a Tenant sandbox that sessions naming no sandbox get.
|
|
80
|
+
- 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.
|
|
81
|
+
|
|
82
|
+
- **Runtime.**
|
|
83
|
+
|
|
84
|
+
- **Upgrade.** The model adapter moves to pi-ai 0.99.1 and always streams.
|
|
85
|
+
- **Per-call settings.**
|
|
86
|
+
- Every call carries the session id, so OpenAI and other providers reuse the prompt cache and route to the same backend.
|
|
87
|
+
- Provider auth no longer reads the process environment.
|
|
88
|
+
- Rate limits, overloads, timeouts and transient errors are retried: 3 attempts with backoff, honouring `Retry-After`.
|
|
89
|
+
- A stream that produces nothing for 300 s is aborted and retried. `TenantConfig.modelCall` sets attempts, backoff, the idle timeout and the request timeout.
|
|
90
|
+
- **Failures.** Anything else fails the turn with one of these codes:
|
|
91
|
+
|
|
92
|
+
- `model.context_overflow`, `model.rate_limited`, `model.overloaded`, `model.timeout`, `model.transient`
|
|
93
|
+
- `model.content_policy`, `model.auth` (whose message says where to fix the credential)
|
|
94
|
+
- `model.invalid_request`, `model.invalid_output`
|
|
95
|
+
|
|
96
|
+
Only a call whose outcome was lost, such as a Worker dying mid-call, is still `uncertain`.
|
|
97
|
+
|
|
98
|
+
- **Structured output.** A structured final answer is repaired (control characters, a Markdown code fence) before it is parsed.
|
|
99
|
+
- **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.
|
|
100
|
+
|
|
101
|
+
- **Events.**
|
|
102
|
+
- `message.assistant` adds `model` (`provider`, `model`), `finishReason` and `usage`.
|
|
103
|
+
- A new `model.failed` transcript event (`code`, `message`, `retryable`) is written instead of `message.assistant` when a call fails.
|
|
104
|
+
- **Core and harness.**
|
|
105
|
+
- New types and helpers: `ModelFailureOutcome`, `ModelFailureCode`, `MODEL_FAILURE_CODES`, `isModelFailureOutcome`, `ModelProducer`.
|
|
106
|
+
- `PromptItem` assistant messages may carry `producer`.
|
|
107
|
+
- `ModelUsage` adds `cacheWriteTokens`.
|
|
108
|
+
- A model adapter may return a failure outcome.
|
|
109
|
+
- 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.
|
|
110
|
+
- **CLI.**
|
|
111
|
+
- `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.
|
|
112
|
+
- A custom OpenAI-compatible provider now prompts for its API key instead of failing.
|
|
113
|
+
|
|
114
|
+
- Pin agents to the tested release.
|
|
115
|
+
- Pin admin to the tested release.
|
|
116
|
+
- Updated dependencies [c7614a4]
|
|
117
|
+
- Updated dependencies [5f23047]
|
|
118
|
+
- Updated dependencies [4282d5f]
|
|
119
|
+
- Updated dependencies [82d95ef]
|
|
120
|
+
- Updated dependencies [e28b8a9]
|
|
121
|
+
- Updated dependencies [50d0fb5]
|
|
122
|
+
- Updated dependencies [50d0fb5]
|
|
123
|
+
- Updated dependencies [c121144]
|
|
124
|
+
- Updated dependencies [50d0fb5]
|
|
125
|
+
- Updated dependencies [c121144]
|
|
126
|
+
- Updated dependencies [9546ac7]
|
|
127
|
+
- Updated dependencies [9d52189]
|
|
128
|
+
- Updated dependencies [9d52189]
|
|
129
|
+
- Updated dependencies [50d0fb5]
|
|
130
|
+
- Updated dependencies
|
|
131
|
+
- Updated dependencies
|
|
132
|
+
- @nylorun/agents@0.10.0-beta
|
|
133
|
+
- @nylorun/admin@0.6.0-beta
|
|
134
|
+
|
|
3
135
|
## 0.5.0-beta
|
|
4
136
|
|
|
5
137
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -69,11 +69,10 @@ created), it keeps that key as `.nylorun/credentials.<tenantId>.json`, so
|
|
|
69
69
|
A **Project** stores only:
|
|
70
70
|
|
|
71
71
|
- `.nylorun/link.json`: `{ format, hostUrl, hostId, tenantId }`
|
|
72
|
-
- `.nylorun/credentials.json`: application key and principal id (0600)
|
|
73
|
-
executor tokens
|
|
72
|
+
- `.nylorun/credentials.json`: application key and principal id (0600)
|
|
74
73
|
- `.nylorun/.gitignore` containing `*`
|
|
75
74
|
|
|
76
|
-
`
|
|
75
|
+
`createActionHandler` and `createClient` in `@nylorun/agents` read the link (or the three variables),
|
|
77
76
|
so the project's `npm run dev` and `npm start` need no Nylorun tool.
|
|
78
77
|
|
|
79
78
|
```sh
|
|
@@ -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,148 @@
|
|
|
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({
|
|
50
|
+
url: connection.url,
|
|
51
|
+
key: connection.key,
|
|
52
|
+
tenant: connection.tenant,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const print = (value) => console.log(JSON.stringify(value, null, 2));
|
|
56
|
+
function option(args, name) {
|
|
57
|
+
const index = args.indexOf(name);
|
|
58
|
+
if (index === -1)
|
|
59
|
+
return undefined;
|
|
60
|
+
const value = args[index + 1];
|
|
61
|
+
if (!value || value.startsWith("--"))
|
|
62
|
+
throw usageError(`${name} requires a value.`);
|
|
63
|
+
args.splice(index, 2);
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
export async function accessCommand(input, client = linkedClient) {
|
|
67
|
+
const [topic, action, ...rest] = input;
|
|
68
|
+
const args = [...rest];
|
|
69
|
+
if (topic === "policy") {
|
|
70
|
+
if (action === "get" && args.length === 0)
|
|
71
|
+
return print(await (await client()).access.getPolicy());
|
|
72
|
+
if (action === "init" && args.length === 0)
|
|
73
|
+
return print(await (await client()).access.putPolicy(STARTER_POLICY));
|
|
74
|
+
if (action === "set" && args.length === 1) {
|
|
75
|
+
let parsed;
|
|
76
|
+
try {
|
|
77
|
+
parsed = JSON.parse(await readFile(args[0], "utf8"));
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
throw new CliError(`Could not read ${args[0]}: ${error instanceof Error ? error.message : String(error)}`, 1);
|
|
81
|
+
}
|
|
82
|
+
// Accept the file as the policy itself or as `{ policy }`, as `policy get` prints it.
|
|
83
|
+
const candidate = parsed && typeof parsed === "object" && "policy" in parsed
|
|
84
|
+
? parsed.policy
|
|
85
|
+
: parsed;
|
|
86
|
+
// The Runtime validates it and names what is wrong.
|
|
87
|
+
return print(await (await client()).access.putPolicy(candidate));
|
|
88
|
+
}
|
|
89
|
+
throw usageError();
|
|
90
|
+
}
|
|
91
|
+
if (topic === "signing-keys") {
|
|
92
|
+
const keys = async () => (await client()).access.signingKeys;
|
|
93
|
+
if (action === "list" && args.length === 0)
|
|
94
|
+
return print(await (await keys()).list());
|
|
95
|
+
if (action === "rotate") {
|
|
96
|
+
const force = args[0] === "--force";
|
|
97
|
+
if (args.length > (force ? 1 : 0))
|
|
98
|
+
throw usageError();
|
|
99
|
+
return print(await (await keys()).rotate({ force }));
|
|
100
|
+
}
|
|
101
|
+
if (action === "revoke" && args.length === 1)
|
|
102
|
+
return print(await (await keys()).revoke(args[0]));
|
|
103
|
+
throw usageError();
|
|
104
|
+
}
|
|
105
|
+
if (topic === "keys") {
|
|
106
|
+
const keys = async () => (await client()).access.publishableKeys;
|
|
107
|
+
if (action === "list" && args.length === 0)
|
|
108
|
+
return print(await (await keys()).list());
|
|
109
|
+
if (action === "create") {
|
|
110
|
+
const name = option(args, "--name");
|
|
111
|
+
const origins = [];
|
|
112
|
+
for (let origin = option(args, "--origin"); origin; origin = option(args, "--origin"))
|
|
113
|
+
origins.push(origin);
|
|
114
|
+
if (!name || args.length > 0)
|
|
115
|
+
throw usageError();
|
|
116
|
+
return print(await (await keys()).create({ name, origins }));
|
|
117
|
+
}
|
|
118
|
+
if (action === "set-origins" && args.length >= 1) {
|
|
119
|
+
const [keyId, ...origins] = args;
|
|
120
|
+
return print(await (await keys()).update(keyId, { origins }));
|
|
121
|
+
}
|
|
122
|
+
if (action === "revoke" && args.length === 1)
|
|
123
|
+
return print(await (await keys()).revoke(args[0]));
|
|
124
|
+
throw usageError();
|
|
125
|
+
}
|
|
126
|
+
if (topic === "revoke") {
|
|
127
|
+
if (!action || args.length > 0)
|
|
128
|
+
throw usageError();
|
|
129
|
+
return print(await (await client()).access.revokeSubject(action));
|
|
130
|
+
}
|
|
131
|
+
if (topic === "token") {
|
|
132
|
+
const all = [action, ...args].filter((arg) => arg !== undefined);
|
|
133
|
+
const subject = option(all, "--subject");
|
|
134
|
+
const role = option(all, "--role");
|
|
135
|
+
const ttl = option(all, "--ttl");
|
|
136
|
+
if (!subject || !role || all.length > 0)
|
|
137
|
+
throw usageError();
|
|
138
|
+
const ttlSeconds = ttl === undefined ? undefined : Number(ttl);
|
|
139
|
+
if (ttlSeconds !== undefined && !Number.isInteger(ttlSeconds))
|
|
140
|
+
throw usageError("--ttl must be a whole number of seconds.");
|
|
141
|
+
return print(await (await client()).tokens.create({
|
|
142
|
+
subject,
|
|
143
|
+
role,
|
|
144
|
+
...(ttlSeconds === undefined ? {} : { ttlSeconds }),
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
throw usageError();
|
|
148
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -8,12 +8,18 @@ import { printLinkedEnvExports } from "./project/env.js";
|
|
|
8
8
|
import { readLink as readProjectLink } from "./project/link.js";
|
|
9
9
|
import { readCredentials as readProjectCredentials } from "./project/credentials.js";
|
|
10
10
|
import { tenantCommand } from "./tenant/commands.js";
|
|
11
|
-
|
|
11
|
+
import { accessCommand } from "./access/commands.js";
|
|
12
|
+
const usage = `nylo <tenant|access|configure|env|doctor>
|
|
12
13
|
|
|
13
14
|
Runtime client (the local stack's Runtime, or any Runtime by URL and key):
|
|
14
15
|
tenant create [name] create a Tenant; in a Project, link it and seed it from .env
|
|
15
16
|
tenant use <name-or-id> link this Project to a Tenant, e.g. one created in Studio
|
|
16
17
|
tenant current|list [--json]|status [--json]|reset|delete
|
|
18
|
+
access policy get|set <file>|init the access policy for subject tokens (nylo access --help)
|
|
19
|
+
access keys list|create|set-origins|revoke publishable keys for web pages and apps
|
|
20
|
+
access signing-keys list|rotate|revoke the Tenant's token signing keys
|
|
21
|
+
access revoke <subject> end a subject's tokens and open streams
|
|
22
|
+
access token --subject <s> --role <r> mint a subject token for trying the API
|
|
17
23
|
configure set the linked Tenant's model provider
|
|
18
24
|
env print the linked Project's NYLORUN_* variables as exports
|
|
19
25
|
doctor sandbox [--json] show which sandbox backend this Tenant's Host offers
|
|
@@ -85,6 +91,13 @@ async function main() {
|
|
|
85
91
|
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
92
|
if (command === "tenant")
|
|
87
93
|
return await tenantCommand(args);
|
|
94
|
+
if (command === "access") {
|
|
95
|
+
if (args[0] === "--help" || args[0] === "-h" || args.length === 0) {
|
|
96
|
+
const { accessUsage } = await import("./access/commands.js");
|
|
97
|
+
return void console.log(accessUsage);
|
|
98
|
+
}
|
|
99
|
+
return await accessCommand(args);
|
|
100
|
+
}
|
|
88
101
|
if (command === "env") {
|
|
89
102
|
if (args.length)
|
|
90
103
|
throw usageError("Usage: nylo env");
|
package/dist/doctor.d.ts
CHANGED
|
@@ -11,6 +11,13 @@ export type SandboxReport = {
|
|
|
11
11
|
version?: string;
|
|
12
12
|
}[];
|
|
13
13
|
defaultImage?: string;
|
|
14
|
+
/** The Tenant's sandbox configuration (Runtimes with Sandboxes v3). */
|
|
15
|
+
config?: {
|
|
16
|
+
default: "none" | "virtual" | Record<string, unknown>;
|
|
17
|
+
limits?: {
|
|
18
|
+
network?: readonly string[];
|
|
19
|
+
};
|
|
20
|
+
};
|
|
14
21
|
};
|
|
15
22
|
/** `nylo doctor sandbox`: Tenant sandbox report via the Tenant API (F2-4). */
|
|
16
23
|
export declare function doctorSandbox(options: {
|
package/dist/doctor.js
CHANGED
|
@@ -45,6 +45,15 @@ export async function doctorSandbox(options) {
|
|
|
45
45
|
? "auto (seed Tenant sandbox.backend via nylo tenant create / .env NYLORUN_SANDBOX)"
|
|
46
46
|
: report.preference,
|
|
47
47
|
]);
|
|
48
|
+
if (report.config)
|
|
49
|
+
rows.push([
|
|
50
|
+
"default",
|
|
51
|
+
report.config.default === "none"
|
|
52
|
+
? "none: sessions get a sandbox only when opened with one (set a default with PUT /v1/tenant/sandbox)"
|
|
53
|
+
: report.config.default === "virtual"
|
|
54
|
+
? "virtual: sessions that name no sandbox get one"
|
|
55
|
+
: "a Tenant sandbox: sessions that name no sandbox get it",
|
|
56
|
+
]);
|
|
48
57
|
rows.push([
|
|
49
58
|
"selected",
|
|
50
59
|
report.backend
|
|
@@ -9,6 +9,11 @@ export type PromptedModel = {
|
|
|
9
9
|
provider: string;
|
|
10
10
|
model: string;
|
|
11
11
|
baseUrl?: string;
|
|
12
|
+
/** A custom endpoint's context window and output limit, when given. */
|
|
13
|
+
settings?: {
|
|
14
|
+
contextWindow?: number;
|
|
15
|
+
maxTokens?: number;
|
|
16
|
+
};
|
|
12
17
|
auth: Credential;
|
|
13
18
|
};
|
|
14
19
|
export type ModelCatalog = {
|
|
@@ -46,4 +51,11 @@ export declare function configureProvider(options?: {
|
|
|
46
51
|
output?: Writable;
|
|
47
52
|
/** Tenant API catalog from GET /v1/tenant/models. */
|
|
48
53
|
catalog?: ModelCatalog;
|
|
54
|
+
/** This installation's stable id, for OAuth flows that need one (OpenAI). */
|
|
55
|
+
deviceId?: () => string;
|
|
49
56
|
}): Promise<PromptedModel>;
|
|
57
|
+
/**
|
|
58
|
+
* A stable id for this CLI installation, created on first use under the Host root.
|
|
59
|
+
* OpenAI's "Sign in with ChatGPT" sends it as the agent host id.
|
|
60
|
+
*/
|
|
61
|
+
export declare function installationId(home?: string): string;
|
package/dist/model/configure.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
2
5
|
import { builtinModels } from "@earendil-works/pi-ai/providers/all";
|
|
3
6
|
import { createProvider, } from "@earendil-works/pi-ai";
|
|
4
7
|
import { stream, streamSimple, } from "@earendil-works/pi-ai/api/openai-completions";
|
|
8
|
+
import { resolveHome } from "../home.js";
|
|
5
9
|
export class ConfigurationCancelled extends Error {
|
|
6
10
|
signal;
|
|
7
11
|
exitCode;
|
|
@@ -36,8 +40,14 @@ function modelsFor(selection, credentials, catalog) {
|
|
|
36
40
|
baseUrl: selection.custom.baseUrl,
|
|
37
41
|
auth: {
|
|
38
42
|
apiKey: {
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
name: "Custom API key",
|
|
44
|
+
async login(interaction) {
|
|
45
|
+
const key = await interaction.prompt({
|
|
46
|
+
type: "secret",
|
|
47
|
+
message: "API key",
|
|
48
|
+
});
|
|
49
|
+
return { type: "api_key", key: key.trim() };
|
|
50
|
+
},
|
|
41
51
|
async resolve() {
|
|
42
52
|
const credential = await credentials.read("custom");
|
|
43
53
|
if (!credential || credential.type !== "api_key")
|
|
@@ -119,6 +129,7 @@ export async function fetchModelCatalog(options) {
|
|
|
119
129
|
}
|
|
120
130
|
// Internal options also allow isolated prompt tests without changing process globals.
|
|
121
131
|
export async function configureProvider(options = {}) {
|
|
132
|
+
const login = { getDeviceId: options.deviceId ?? installationId };
|
|
122
133
|
const output = options.output ?? process.stdout;
|
|
123
134
|
const controller = new AbortController();
|
|
124
135
|
const signal = controller.signal;
|
|
@@ -161,6 +172,16 @@ export async function configureProvider(options = {}) {
|
|
|
161
172
|
signal.throwIfAborted();
|
|
162
173
|
return prompt.question(message, { signal });
|
|
163
174
|
}
|
|
175
|
+
/** A positive whole number of tokens, or undefined for the default. */
|
|
176
|
+
async function tokens(message) {
|
|
177
|
+
const answer = (await question(message)).trim();
|
|
178
|
+
if (!answer)
|
|
179
|
+
return undefined;
|
|
180
|
+
const value = Number(answer);
|
|
181
|
+
if (!Number.isInteger(value) || value < 1)
|
|
182
|
+
throw new Error(`${message.split(" (")[0]} must be a whole number of tokens.`);
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
164
185
|
try {
|
|
165
186
|
signal.throwIfAborted();
|
|
166
187
|
output.write("0. Custom OpenAI-compatible provider\n");
|
|
@@ -173,6 +194,12 @@ export async function configureProvider(options = {}) {
|
|
|
173
194
|
const model = (await question("Model id: ")).trim();
|
|
174
195
|
if (!baseUrl || !model)
|
|
175
196
|
throw new Error("A base URL and model id are required.");
|
|
197
|
+
const contextWindow = await tokens("Context window in tokens (Enter for 32768): ");
|
|
198
|
+
const maxTokens = await tokens("Max output tokens (Enter for 8192): ");
|
|
199
|
+
const settings = {
|
|
200
|
+
...(contextWindow ? { contextWindow } : {}),
|
|
201
|
+
...(maxTokens ? { maxTokens } : {}),
|
|
202
|
+
};
|
|
176
203
|
const selection = {
|
|
177
204
|
provider: "custom",
|
|
178
205
|
model,
|
|
@@ -180,8 +207,11 @@ export async function configureProvider(options = {}) {
|
|
|
180
207
|
};
|
|
181
208
|
const custom = modelsFor(selection, store, options.catalog);
|
|
182
209
|
if (!(await custom.models.checkAuth("custom", { signal })))
|
|
183
|
-
await custom.models.login("custom", "api_key", interaction());
|
|
184
|
-
return
|
|
210
|
+
await custom.models.login("custom", "api_key", interaction(), login);
|
|
211
|
+
return {
|
|
212
|
+
...prompted(selection),
|
|
213
|
+
...(Object.keys(settings).length ? { settings } : {}),
|
|
214
|
+
};
|
|
185
215
|
}
|
|
186
216
|
else {
|
|
187
217
|
const chosen = providers[choice - 1];
|
|
@@ -203,7 +233,7 @@ export async function configureProvider(options = {}) {
|
|
|
203
233
|
if (answer === "2")
|
|
204
234
|
method = "oauth";
|
|
205
235
|
}
|
|
206
|
-
await registry.models.login(chosen.id, method, interaction());
|
|
236
|
+
await registry.models.login(chosen.id, method, interaction(), login);
|
|
207
237
|
}
|
|
208
238
|
return prompted({ provider: chosen.id, model: model.id });
|
|
209
239
|
}
|
|
@@ -256,3 +286,22 @@ export async function configureProvider(options = {}) {
|
|
|
256
286
|
};
|
|
257
287
|
}
|
|
258
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* A stable id for this CLI installation, created on first use under the Host root.
|
|
291
|
+
* OpenAI's "Sign in with ChatGPT" sends it as the agent host id.
|
|
292
|
+
*/
|
|
293
|
+
export function installationId(home = resolveHome()) {
|
|
294
|
+
const file = join(home, "cli-installation-id");
|
|
295
|
+
try {
|
|
296
|
+
const existing = readFileSync(file, "utf8").trim();
|
|
297
|
+
if (existing)
|
|
298
|
+
return existing;
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
/* Created below on first use. */
|
|
302
|
+
}
|
|
303
|
+
const id = randomUUID();
|
|
304
|
+
mkdirSync(home, { recursive: true });
|
|
305
|
+
writeFileSync(file, `${id}\n`, { mode: 0o600 });
|
|
306
|
+
return id;
|
|
307
|
+
}
|
|
@@ -8,6 +8,10 @@ type HostModelView = {
|
|
|
8
8
|
readonly model: string;
|
|
9
9
|
readonly authType: "api_key" | "oauth";
|
|
10
10
|
readonly baseUrl?: string;
|
|
11
|
+
readonly settings?: {
|
|
12
|
+
contextWindow?: number;
|
|
13
|
+
maxTokens?: number;
|
|
14
|
+
};
|
|
11
15
|
};
|
|
12
16
|
export declare function getHostModel(runtimeUrl: string, serverKey: string, tenantId?: string): Promise<HostModelView>;
|
|
13
17
|
export declare function putHostModel(runtimeUrl: string, serverKey: string, model: PromptedModel, tenantId?: string): Promise<HostModelView>;
|
package/dist/model/host-model.js
CHANGED
|
@@ -41,6 +41,7 @@ export async function putHostModel(runtimeUrl, serverKey, model, tenantId) {
|
|
|
41
41
|
provider: model.provider,
|
|
42
42
|
model: model.model,
|
|
43
43
|
...(model.baseUrl ? { baseUrl: model.baseUrl } : {}),
|
|
44
|
+
...(model.settings ? { settings: model.settings } : {}),
|
|
44
45
|
auth: model.auth,
|
|
45
46
|
}),
|
|
46
47
|
});
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Project-local credentials (mode 0600).
|
|
3
|
-
*
|
|
2
|
+
* Project-local credentials (mode 0600): the application key and its principal. A format 0
|
|
3
|
+
* file may still carry a legacy `executors` map from before protocol 3; it is ignored.
|
|
4
4
|
*/
|
|
5
5
|
export interface ProjectCredentials {
|
|
6
6
|
format: 0 | 1;
|
|
7
7
|
applicationKey: string;
|
|
8
8
|
principalId: string;
|
|
9
|
-
/** Present only on format 0 files; ignored by version 1. */
|
|
10
|
-
executors?: Record<string, string>;
|
|
11
9
|
}
|
|
12
10
|
/** The Project's credentials, or with `tenantId` the key kept for that Tenant. */
|
|
13
11
|
export declare function readCredentials(projectRoot: string, tenantId?: string): Promise<ProjectCredentials | undefined>;
|
|
@@ -22,17 +22,10 @@ export async function readCredentials(projectRoot, tenantId) {
|
|
|
22
22
|
throw new CliError(`Project credentials at ${path} have an unsupported format. Upgrade the CLI.`, 1);
|
|
23
23
|
}
|
|
24
24
|
await chmod(path, 0o600);
|
|
25
|
-
const executors = format === 0 &&
|
|
26
|
-
value.executors &&
|
|
27
|
-
typeof value.executors === "object" &&
|
|
28
|
-
!Array.isArray(value.executors)
|
|
29
|
-
? value.executors
|
|
30
|
-
: undefined;
|
|
31
25
|
return {
|
|
32
26
|
format,
|
|
33
27
|
applicationKey: value.applicationKey,
|
|
34
28
|
principalId: value.principalId,
|
|
35
|
-
...(executors ? { executors } : {}),
|
|
36
29
|
};
|
|
37
30
|
}
|
|
38
31
|
catch (error) {
|
|
@@ -45,7 +38,7 @@ export async function writeCredentials(projectRoot, credentials, tenantId) {
|
|
|
45
38
|
await ensureProjectNylorunDir(projectRoot);
|
|
46
39
|
const path = credentialsPath(projectRoot, tenantId);
|
|
47
40
|
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
48
|
-
// Version 1 writes format 1
|
|
41
|
+
// Version 1 writes format 1 with only the application key and principal (D§3.7).
|
|
49
42
|
const body = `${JSON.stringify({
|
|
50
43
|
format: credentials.format ?? 1,
|
|
51
44
|
applicationKey: credentials.applicationKey,
|
|
@@ -2,3 +2,17 @@ import { type AdminTenant } from "@nylorun/admin";
|
|
|
2
2
|
/** Exact-and-unique name or id match. */
|
|
3
3
|
export declare function matchTenant(tenants: readonly AdminTenant[], nameOrId: string): AdminTenant;
|
|
4
4
|
export declare function tenantCommand(args: readonly string[]): Promise<void>;
|
|
5
|
+
/** One line of `nylo tenant endpoints`: the agent, its URL and version, and how it is doing. */
|
|
6
|
+
export declare function endpointLine(endpoint: {
|
|
7
|
+
agentId: string;
|
|
8
|
+
url: string;
|
|
9
|
+
implementationVersion: string;
|
|
10
|
+
health: {
|
|
11
|
+
consecutiveFailures: number;
|
|
12
|
+
lastSuccessAt?: string;
|
|
13
|
+
lastError?: {
|
|
14
|
+
code: string;
|
|
15
|
+
message: string;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
}): string;
|
package/dist/tenant/commands.js
CHANGED
|
@@ -83,7 +83,7 @@ async function createTenant(options) {
|
|
|
83
83
|
export async function tenantCommand(args) {
|
|
84
84
|
const [verb, ...rest] = args;
|
|
85
85
|
if (!verb || verb === "--help" || verb === "-h") {
|
|
86
|
-
console.log(`nylo tenant create [name]|current|list [--json]|use <name-or-id>|status [--json]|reset [--sessions|--sandboxes|--all] [--yes]|delete <name-or-id> [--yes]`);
|
|
86
|
+
console.log(`nylo tenant create [name]|current|list [--json]|use <name-or-id>|status [--json]|endpoints [--json]|endpoints ping <agent>|reset [--sessions|--sandboxes|--all] [--yes]|delete <name-or-id> [--yes]`);
|
|
87
87
|
return;
|
|
88
88
|
}
|
|
89
89
|
const home = resolveHome();
|
|
@@ -143,7 +143,7 @@ export async function tenantCommand(args) {
|
|
|
143
143
|
}
|
|
144
144
|
const tenants = await admin.listTenants();
|
|
145
145
|
const selected = matchTenant(tenants, nameOrId);
|
|
146
|
-
console.warn("Sharing a Tenant shares
|
|
146
|
+
console.warn("Sharing a Tenant shares Action endpoint registrations across Projects that link to it.");
|
|
147
147
|
const authorizes = async (key) => {
|
|
148
148
|
try {
|
|
149
149
|
await createClient({ url: admin.url, key, tenant: selected.id }).transport.json("/v1/tenant", "GET");
|
|
@@ -192,6 +192,10 @@ export async function tenantCommand(args) {
|
|
|
192
192
|
console.log(`Linked to ${selected.name ?? selected.id} ${selected.id}`);
|
|
193
193
|
return;
|
|
194
194
|
}
|
|
195
|
+
if (verb === "endpoints") {
|
|
196
|
+
await endpointsCommand(rest);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
195
199
|
if (verb === "status") {
|
|
196
200
|
const json = rest.includes("--json");
|
|
197
201
|
if (rest.some((a) => a !== "--json")) {
|
|
@@ -343,3 +347,48 @@ async function confirmOrThrow(prompt) {
|
|
|
343
347
|
rl.close();
|
|
344
348
|
}
|
|
345
349
|
}
|
|
350
|
+
/** The Project's application client, from its link and credentials. */
|
|
351
|
+
async function projectClient() {
|
|
352
|
+
const root = requireProjectRoot();
|
|
353
|
+
const link = await readLink(root);
|
|
354
|
+
const credentials = await readCredentials(root);
|
|
355
|
+
if (!link || !credentials)
|
|
356
|
+
throw new CliError("No Project link. Run nylo tenant create to create one.", 1);
|
|
357
|
+
return createClient({ url: link.hostUrl, key: credentials.applicationKey, tenant: link.tenantId });
|
|
358
|
+
}
|
|
359
|
+
/** `nylo tenant endpoints [--json]` and `nylo tenant endpoints ping <agent>`. */
|
|
360
|
+
async function endpointsCommand(args) {
|
|
361
|
+
const client = await projectClient();
|
|
362
|
+
if (args[0] === "ping") {
|
|
363
|
+
const agentId = args[1];
|
|
364
|
+
if (!agentId || args.length > 2)
|
|
365
|
+
throw new CliError("Usage: nylo tenant endpoints ping <agent>", 2);
|
|
366
|
+
const answer = await client.transport.json(`/v1/endpoints/${encodeURIComponent(agentId)}/ping`, "POST");
|
|
367
|
+
console.log(`${answer.agentId} serves ${answer.implementationVersion}${answer.manifestHash ? ` ${answer.manifestHash}` : ""}`);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const json = args.includes("--json");
|
|
371
|
+
if (args.some((a) => a !== "--json"))
|
|
372
|
+
throw new CliError("Usage: nylo tenant endpoints [--json] | nylo tenant endpoints ping <agent>", 2);
|
|
373
|
+
const body = await client.transport.json("/v1/endpoints", "GET");
|
|
374
|
+
if (json) {
|
|
375
|
+
console.log(JSON.stringify(body, null, 2));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (body.endpoints.length === 0) {
|
|
379
|
+
console.log("No Action endpoints. Register one with createActionHandler(...).register({ url }).");
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
for (const endpoint of body.endpoints)
|
|
383
|
+
console.log(endpointLine(endpoint));
|
|
384
|
+
}
|
|
385
|
+
/** One line of `nylo tenant endpoints`: the agent, its URL and version, and how it is doing. */
|
|
386
|
+
export function endpointLine(endpoint) {
|
|
387
|
+
const { health } = endpoint;
|
|
388
|
+
const state = health.consecutiveFailures > 0
|
|
389
|
+
? `failing (${health.consecutiveFailures}): ${health.lastError?.message || health.lastError?.code || "unknown"}`
|
|
390
|
+
: health.lastSuccessAt
|
|
391
|
+
? `ok (last ${health.lastSuccessAt})`
|
|
392
|
+
: "no deliveries yet";
|
|
393
|
+
return `${endpoint.agentId} ${endpoint.url} ${endpoint.implementationVersion} ${state}`;
|
|
394
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nylorun/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0-beta",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"description": "Nylorun Runtime client: Tenants, Project links and model providers",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"prepack": "npm run build"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@nylorun/agents": "0.
|
|
34
|
-
"@nylorun/admin": "0.
|
|
35
|
-
"@earendil-works/pi-ai": "0.
|
|
33
|
+
"@nylorun/agents": "0.10.0-beta",
|
|
34
|
+
"@nylorun/admin": "0.6.0-beta",
|
|
35
|
+
"@earendil-works/pi-ai": "0.99.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|