@m6d/cortex-cli 0.20.0 → 0.21.0
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/README.md +3 -3
- package/package.json +1 -1
- package/src/commands/new.ts +4 -4
- package/src/commands/setup.ts +19 -10
- package/src/contracts/management/procedures.generated.ts +345 -162
- package/src/contracts/manifest/index.ts +62 -91
- package/src/contracts/server/index.ts +33 -9
- package/src/contracts/triggers/index.ts +9 -9
- package/src/contracts/wire/index.ts +11 -0
- package/src/scaffold/files.ts +11 -13
package/README.md
CHANGED
|
@@ -62,8 +62,8 @@ as `--yes`, so a missing name is a hard error rather than a hang.
|
|
|
62
62
|
|
|
63
63
|
The image runs at the Cortex release that was current when this CLI was published
|
|
64
64
|
(`CORTEX_VERSION` in the shell picks another); the backend depends on the `@m6d/cortex` release of
|
|
65
|
-
that time.
|
|
66
|
-
|
|
65
|
+
that time. The server is registered under the project name as the manifest `id` takes it:
|
|
66
|
+
lowercase words joined by hyphens, cut to Cortex's 64 characters.
|
|
67
67
|
|
|
68
68
|
## `cortex setup`
|
|
69
69
|
|
|
@@ -107,7 +107,7 @@ at and a new procedure needs no CLI release.
|
|
|
107
107
|
cortex login # asks for the URL and a management key
|
|
108
108
|
cortex status
|
|
109
109
|
cortex agents list
|
|
110
|
-
cortex agents create --
|
|
110
|
+
cortex agents create --name support --displayName '{"en":"Support"}' --serverId <uuid>
|
|
111
111
|
cortex tools create --json tool.json --publish
|
|
112
112
|
cortex tools list -o json | jq '.items[].name'
|
|
113
113
|
cortex logout
|
package/package.json
CHANGED
package/src/commands/new.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { CORTEX_MANIFEST_ID } from "@cortex/contracts/manifest";
|
|
|
7
7
|
|
|
8
8
|
import pkg from "../../package.json";
|
|
9
9
|
import { askProvider } from "@/commands/setup";
|
|
10
|
-
import {
|
|
10
|
+
import { AGENT_NAME, manifestIdOf, scaffoldFiles } from "@/scaffold/files";
|
|
11
11
|
import { allocatePorts, type Ports } from "@/scaffold/ports";
|
|
12
12
|
import { askConfirm, askName, canAsk, intro, log, outro, runSteps } from "@/ui/prompts";
|
|
13
13
|
import * as report from "@/ui/report";
|
|
@@ -30,7 +30,7 @@ export function registerNew(program: Command<[], { verbose?: true }>) {
|
|
|
30
30
|
// way and `--yes` is not the only way to say "yes". Declaring the
|
|
31
31
|
// positive first leaves the value `undefined` when neither is passed,
|
|
32
32
|
// which is what marks the question as still unanswered.
|
|
33
|
-
.option("--setup", `Set up the ${
|
|
33
|
+
.option("--setup", `Set up the ${AGENT_NAME} agent on the first bun run dev`)
|
|
34
34
|
.option("--no-setup", "Leave Cortex empty; you register the backend yourself")
|
|
35
35
|
.option("--yes", "Accept the defaults instead of asking");
|
|
36
36
|
|
|
@@ -151,7 +151,7 @@ async function askSetup(flag: boolean | undefined, interactive: boolean) {
|
|
|
151
151
|
flag ??
|
|
152
152
|
(!interactive ||
|
|
153
153
|
(await askConfirm(
|
|
154
|
-
`Set up the ${
|
|
154
|
+
`Set up the ${AGENT_NAME} agent when you first run bun run dev?`,
|
|
155
155
|
true,
|
|
156
156
|
)));
|
|
157
157
|
const provider = setup && interactive ? await askProvider() : undefined;
|
|
@@ -178,7 +178,7 @@ function warnInstall(project: string, output: string, verbose: boolean) {
|
|
|
178
178
|
/** What the first `bun run dev` sets up: the agent, the server alone (no model yet), or nothing. */
|
|
179
179
|
function nextSteps(project: string, ports: Ports, setup: boolean, provider: boolean) {
|
|
180
180
|
const runs = {
|
|
181
|
-
agent: step("bun run dev", `→ sets up ${
|
|
181
|
+
agent: step("bun run dev", `→ sets up ${AGENT_NAME}, then prints where to chat`),
|
|
182
182
|
server:
|
|
183
183
|
step("bun run dev", "→ registers the server") +
|
|
184
184
|
step("bun cortex setup", "adds a model provider, then applies"),
|
package/src/commands/setup.ts
CHANGED
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
scrubCredentialStrings,
|
|
14
14
|
} from "@cortex/contracts/egress-policy";
|
|
15
15
|
import { ManagementClientError, managementPath } from "@cortex/contracts/management";
|
|
16
|
-
import { MODEL_PROVIDER_LIMITS,
|
|
16
|
+
import { MODEL_PROVIDER_LIMITS, SETTING_BOUNDS } from "@cortex/contracts/manifest";
|
|
17
|
+
import { displayNameSchema, NAME_MAX_LENGTH, nameSchema } from "@cortex/contracts/server";
|
|
17
18
|
import { z } from "zod";
|
|
18
19
|
|
|
19
20
|
import {
|
|
@@ -43,7 +44,7 @@ const POLL_MS = 2_000;
|
|
|
43
44
|
|
|
44
45
|
/** The backend as `cortex new` writes it into .env, which Bun loads from the directory it runs in. */
|
|
45
46
|
const projectSchema = z.object({
|
|
46
|
-
CORTEX_SERVER_NAME:
|
|
47
|
+
CORTEX_SERVER_NAME: nameSchema,
|
|
47
48
|
CORTEX_SERVER_URL: z.url(),
|
|
48
49
|
CORTEX_SERVER_KEY: z.string().min(1),
|
|
49
50
|
});
|
|
@@ -57,7 +58,7 @@ const ENV_SAFE = /^[^\s$#'"`\\]+$/;
|
|
|
57
58
|
|
|
58
59
|
/** The model provider in .env: all four values, or none. */
|
|
59
60
|
const providerFields = z.object({
|
|
60
|
-
MODEL_PROVIDER_NAME:
|
|
61
|
+
MODEL_PROVIDER_NAME: nameSchema,
|
|
61
62
|
MODEL_PROVIDER_URL: z
|
|
62
63
|
.string()
|
|
63
64
|
.max(MODEL_PROVIDER_LIMITS.urlLength)
|
|
@@ -80,12 +81,14 @@ type Profile = ReturnType<typeof requireProfile>;
|
|
|
80
81
|
|
|
81
82
|
const namedSchema = z.object({ id: z.string(), name: z.string() });
|
|
82
83
|
const providerRowSchema = namedSchema.extend({
|
|
84
|
+
displayName: displayNameSchema,
|
|
83
85
|
baseUrl: z.string(),
|
|
84
86
|
secretName: z.string(),
|
|
85
87
|
models: z.array(z.string()),
|
|
86
88
|
efforts: z.array(z.string()),
|
|
87
89
|
});
|
|
88
90
|
const serverSchema = namedSchema.extend({
|
|
91
|
+
displayName: displayNameSchema,
|
|
89
92
|
description: z.string().nullable(),
|
|
90
93
|
url: z.string().nullable(),
|
|
91
94
|
keySecretName: z.string().nullable(),
|
|
@@ -270,20 +273,23 @@ async function findByName<TItem extends typeof namedSchema>(
|
|
|
270
273
|
|
|
271
274
|
/**
|
|
272
275
|
* A secret's name for one value: its owner's name, what it is to the owner,
|
|
273
|
-
* and a short digest of the value, the owner's name cut
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
276
|
+
* and a short digest of the value, the owner's name cut, and stripped of the
|
|
277
|
+
* hyphens the cut leaves it ending in, so the whole is a name Cortex holds.
|
|
278
|
+
* A name then holds one value only, so a secret that exists is the value
|
|
279
|
+
* .env holds, and a changed key gets a secret of its own while every row
|
|
280
|
+
* presenting the old one keeps it.
|
|
277
281
|
*/
|
|
278
282
|
function secretFor(owner: string, role: "api-key" | "server-key", value: string) {
|
|
279
283
|
const suffix = `-${role}-${createHash("sha256").update(value).digest("hex").slice(0, 8)}`;
|
|
280
|
-
return `${owner.slice(0, NAME_MAX_LENGTH - suffix.length)}${suffix}`;
|
|
284
|
+
return `${owner.slice(0, NAME_MAX_LENGTH - suffix.length).replace(/-+$/, "")}${suffix}`;
|
|
281
285
|
}
|
|
282
286
|
|
|
283
287
|
async function ensureSecret(profile: Profile, name: string, value: string) {
|
|
284
288
|
const found = await findByName(profile, "secrets", name, namedSchema);
|
|
285
289
|
if (!found) {
|
|
286
|
-
await callOrFail(profile, () =>
|
|
290
|
+
await callOrFail(profile, () =>
|
|
291
|
+
managementClient(profile).secrets.set({ name, displayName: {}, value }),
|
|
292
|
+
);
|
|
287
293
|
}
|
|
288
294
|
}
|
|
289
295
|
|
|
@@ -333,6 +339,7 @@ async function ensureProvider(profile: Profile, provider: Provider) {
|
|
|
333
339
|
await callOrFail(profile, () =>
|
|
334
340
|
managementClient(profile).modelProviders.create({
|
|
335
341
|
name: provider.name,
|
|
342
|
+
displayName: {},
|
|
336
343
|
baseUrl: provider.baseUrl,
|
|
337
344
|
secretName,
|
|
338
345
|
models,
|
|
@@ -349,6 +356,7 @@ async function ensureProvider(profile: Profile, provider: Provider) {
|
|
|
349
356
|
await callOrFail(profile, () =>
|
|
350
357
|
managementClient(profile).modelProviders.update({
|
|
351
358
|
id: found.id,
|
|
359
|
+
displayName: found.displayName,
|
|
352
360
|
baseUrl: found.baseUrl,
|
|
353
361
|
secretName,
|
|
354
362
|
models,
|
|
@@ -395,6 +403,7 @@ async function ensureServer(
|
|
|
395
403
|
await callOrFail(profile, () =>
|
|
396
404
|
managementClient(profile).servers.create({
|
|
397
405
|
name,
|
|
406
|
+
displayName: {},
|
|
398
407
|
description: "",
|
|
399
408
|
url,
|
|
400
409
|
keySecretName,
|
|
@@ -417,7 +426,7 @@ async function ensureServer(
|
|
|
417
426
|
await callOrFail(profile, () =>
|
|
418
427
|
managementClient(profile).servers.update({
|
|
419
428
|
id: server.id,
|
|
420
|
-
|
|
429
|
+
displayName: server.displayName,
|
|
421
430
|
description: server.description ?? "",
|
|
422
431
|
url,
|
|
423
432
|
keySecretName,
|