@m6d/cortex-cli 0.21.0 → 0.23.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/package.json +1 -1
- package/src/commands/management.ts +8 -5
- package/src/commands/setup.ts +15 -8
- package/src/contracts/client-tools/index.ts +21 -0
- package/src/contracts/egress-policy/index.ts +1 -0
- package/src/contracts/egress-policy/url.ts +1 -1
- package/src/contracts/management/procedures.generated.ts +352 -184
- package/src/contracts/manifest/index.ts +186 -64
- package/src/contracts/runs/index.ts +187 -0
- package/src/contracts/schedule/index.ts +62 -0
- package/src/contracts/server/index.ts +101 -29
- package/src/contracts/triggers/index.ts +36 -93
- package/src/contracts/wire/index.ts +162 -55
- package/src/management/flags.ts +14 -4
- package/src/scaffold/files.ts +8 -3
- package/src/ui/prompts.ts +18 -24
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* alike.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import type { RunReason, RunStatus } from "../runs/index";
|
|
12
|
+
|
|
11
13
|
/** The UI locales every localized value is keyed by; the console's message catalogs follow it. */
|
|
12
14
|
export const LOCALES = ["en", "ar"] as const;
|
|
13
15
|
export type Locale = (typeof LOCALES)[number];
|
|
@@ -31,8 +33,8 @@ export function byLocale<T>(valueOf: (locale: Locale) => T) {
|
|
|
31
33
|
export const TOOL_SURFACES = ["inline", "modal"] as const;
|
|
32
34
|
export type ToolSurface = (typeof TOOL_SURFACES)[number];
|
|
33
35
|
|
|
34
|
-
/** `
|
|
35
|
-
export const MESSAGE_ROLES = ["system", "user", "assistant", "
|
|
36
|
+
/** `event` rows hold something that happened, a trigger's rendered instructions among them; the model sees them as `user` turns. */
|
|
37
|
+
export const MESSAGE_ROLES = ["system", "user", "assistant", "event"] as const;
|
|
36
38
|
export type MessageRole = (typeof MESSAGE_ROLES)[number];
|
|
37
39
|
|
|
38
40
|
/** The fenced blocks an agent may draw in its replies instead of text. */
|
|
@@ -46,6 +48,29 @@ export const DEFAULT_MEDIA_FORMS = [
|
|
|
46
48
|
"map",
|
|
47
49
|
] as const satisfies readonly MediaForm[];
|
|
48
50
|
|
|
51
|
+
/**
|
|
52
|
+
* What an agent does with a message someone sends while a run holds its
|
|
53
|
+
* thread: refuses it, queues it as the next turn, or steers the running turn
|
|
54
|
+
* with it before its next step (one that arrives too late is queued).
|
|
55
|
+
*/
|
|
56
|
+
export const WHILE_BUSY = ["refuse", "queue", "steer"] as const;
|
|
57
|
+
export type WhileBusy = (typeof WHILE_BUSY)[number];
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Where one message sent while a run holds its thread goes, named by its
|
|
61
|
+
* sender under `forwardedProps[MESSAGE_BUSY_KEY]`, or set on a waiting one
|
|
62
|
+
* by `PATCH /threads/:threadId/inputs/:inputId` (`{ busy }`): `steer` joins
|
|
63
|
+
* the running turn, `queue` waits for a turn of its own.
|
|
64
|
+
*/
|
|
65
|
+
export const MESSAGE_BUSY = ["queue", "steer"] as const;
|
|
66
|
+
export type MessageBusy = (typeof MESSAGE_BUSY)[number];
|
|
67
|
+
export const MESSAGE_BUSY_KEY = "busy";
|
|
68
|
+
|
|
69
|
+
/** What a message sent while a run holds the thread does: its sender's pick, never past an agent that refuses. */
|
|
70
|
+
export function busyFor(whileBusy: WhileBusy, picked: MessageBusy | undefined) {
|
|
71
|
+
return whileBusy === "refuse" ? whileBusy : (picked ?? whileBusy);
|
|
72
|
+
}
|
|
73
|
+
|
|
49
74
|
export const ATTACHMENT_MIME_TYPES = [
|
|
50
75
|
"image/png",
|
|
51
76
|
"image/jpeg",
|
|
@@ -164,6 +189,47 @@ export type ClientToolBinding = {
|
|
|
164
189
|
embedOrigin: string;
|
|
165
190
|
};
|
|
166
191
|
|
|
192
|
+
/**
|
|
193
|
+
* How the widget connects an end user's account for a `connectAccount`
|
|
194
|
+
* call, stamped on the call: where the engine starts a sign-in, at the URL
|
|
195
|
+
* browsers reach it, the agent it is for, and each server the call may
|
|
196
|
+
* name, by name: its display name, and the scope it still needs when the
|
|
197
|
+
* account is connected but lacks access.
|
|
198
|
+
*/
|
|
199
|
+
export type ConnectAccountBinding = {
|
|
200
|
+
startUrl: string;
|
|
201
|
+
agent: string;
|
|
202
|
+
servers: Record<string, { displayName: Partial<LocalizedText>; scope?: string }>;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Response of `GET /connections/:server?call=<toolCallId>`: where the
|
|
207
|
+
* caller's own account stands with the agent's MCP server of that name, for
|
|
208
|
+
* the card of that `connectAccount` call. `connected` with all a sign-in
|
|
209
|
+
* may ask for; `pending` while it is not connected, or lacks access a
|
|
210
|
+
* sign-in may still grant; `refused` once the card's own sign-in came back
|
|
211
|
+
* without it, or no sign-in may ask again.
|
|
212
|
+
*/
|
|
213
|
+
export type AccountConnection = {
|
|
214
|
+
status: "connected" | "pending" | "refused";
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* The form `POST /oauth/start` takes, posted into the window a sign-in runs
|
|
219
|
+
* in with its bearer in the body, so no link can start one: the console's
|
|
220
|
+
* operator token and the server's id for the shared connection.
|
|
221
|
+
*/
|
|
222
|
+
export type SharedSignInStart = { operatorToken: string; mcpServerId: string };
|
|
223
|
+
|
|
224
|
+
/** An end user's own start: their token, their agent, the server's name, and the `connectAccount` call it answers. */
|
|
225
|
+
export type EndUserSignInStart = {
|
|
226
|
+
token: string;
|
|
227
|
+
agent: string;
|
|
228
|
+
server: string;
|
|
229
|
+
threadId: string;
|
|
230
|
+
toolCallId: string;
|
|
231
|
+
};
|
|
232
|
+
|
|
167
233
|
/**
|
|
168
234
|
* What Cortex offers this agent's clients, fetched once at widget startup.
|
|
169
235
|
* The widget hides UI for anything absent; Cortex enforces each.
|
|
@@ -180,6 +246,8 @@ export type AgentCapabilities = {
|
|
|
180
246
|
messages: boolean;
|
|
181
247
|
/** True when the caller only reads, as an operator inspecting a user's thread does. */
|
|
182
248
|
readOnly: boolean;
|
|
249
|
+
/** What a message sent while a run holds the thread does; under `refuse`, nothing sends until the run ends. */
|
|
250
|
+
whileBusy: WhileBusy;
|
|
183
251
|
/** Prompt chips on the blank-chat landing per locale; clicking one sends its `prompt` as the first message. */
|
|
184
252
|
suggestions: Suggestions;
|
|
185
253
|
/** Headline of the blank-chat landing per locale; "" falls back to the widget's built-in copy. */
|
|
@@ -199,8 +267,8 @@ export type MessageMetadata = {
|
|
|
199
267
|
isAborted?: boolean;
|
|
200
268
|
tokenUsage?: TokenUsage;
|
|
201
269
|
attachments?: AttachmentSummary[];
|
|
202
|
-
/** Set on `
|
|
203
|
-
|
|
270
|
+
/** Set on `event` messages: what happened, and the input that brought it. */
|
|
271
|
+
event?: EventSummary;
|
|
204
272
|
};
|
|
205
273
|
|
|
206
274
|
/**
|
|
@@ -239,6 +307,22 @@ export const STATEMENT_TOOLS = ["executeCode", "queryDataset", "describeDataset"
|
|
|
239
307
|
*/
|
|
240
308
|
export const APPROVAL_TITLE_FIELD = "approvalTitle";
|
|
241
309
|
|
|
310
|
+
/**
|
|
311
|
+
* The chat's own tool by which an agent hands a task to a copy of itself.
|
|
312
|
+
* Its input's `brief` is the task, which the copy's card shows.
|
|
313
|
+
*/
|
|
314
|
+
export const DELEGATE_TASK_TOOL = "delegateTask";
|
|
315
|
+
|
|
316
|
+
/** The chat's tool by which an agent stops a copy it handed a task, by the subagent id its start answered. */
|
|
317
|
+
export const CANCEL_TASK_TOOL = "cancelTask";
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* The chat's tool by which an agent schedules a check-back: it comes back
|
|
321
|
+
* to the thread on its own at a time it picks, with a note to itself. Its
|
|
322
|
+
* output's `checkBackId` is the input the user may cancel.
|
|
323
|
+
*/
|
|
324
|
+
export const SCHEDULE_WAKE_TOOL = "scheduleWake";
|
|
325
|
+
|
|
242
326
|
/** A line the model wrote into a call input's `field`, trimmed; undefined while it streams in or is blank. */
|
|
243
327
|
export function modelLineOf(input: unknown, field: string) {
|
|
244
328
|
if (!input || typeof input !== "object") return undefined;
|
|
@@ -263,6 +347,25 @@ export type ToolCallMetadata = {
|
|
|
263
347
|
answeredBy?: "client";
|
|
264
348
|
/** Set beside `answeredBy` on a call of a client tool with an embedded page: the widget mounts the embed for it. */
|
|
265
349
|
embed?: ClientToolBinding;
|
|
350
|
+
/** Set beside `answeredBy` on a `connectAccount` call: the widget starts the sign-in with it. */
|
|
351
|
+
connect?: ConnectAccountBinding;
|
|
352
|
+
/**
|
|
353
|
+
* Set on a call a run paused on for the user's decision: the interrupt the run raised for
|
|
354
|
+
* it and the run it paused, so a client that reloads re-prompts the decision exactly as the
|
|
355
|
+
* live run asked it.
|
|
356
|
+
*/
|
|
357
|
+
paused?: { runId: string; interrupt: RunInterrupt };
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
/** An interrupt as a paused run raises it on its end, in AG-UI's shape. */
|
|
361
|
+
export type RunInterrupt = {
|
|
362
|
+
id: string;
|
|
363
|
+
reason: string;
|
|
364
|
+
message?: string;
|
|
365
|
+
toolCallId?: string;
|
|
366
|
+
responseSchema?: Record<string, unknown>;
|
|
367
|
+
expiresAt?: string;
|
|
368
|
+
metadata?: Record<string, unknown>;
|
|
266
369
|
};
|
|
267
370
|
|
|
268
371
|
/** The `metadata.cortex` a tool call carries, when Cortex stamped one. */
|
|
@@ -271,11 +374,16 @@ export function toolCallMetadataOf(part: { metadata?: unknown }) {
|
|
|
271
374
|
return metadata?.cortex ?? undefined;
|
|
272
375
|
}
|
|
273
376
|
|
|
274
|
-
|
|
377
|
+
/** An event that entered a conversation: a trigger fired, a schedule came due, a run finished. */
|
|
378
|
+
export type EventSummary = {
|
|
379
|
+
source: "trigger" | "schedule" | "run";
|
|
380
|
+
/** The trigger's name, or what the schedule or run was for. */
|
|
275
381
|
name: string;
|
|
276
|
-
|
|
277
|
-
|
|
382
|
+
inputId: string;
|
|
383
|
+
at: string;
|
|
278
384
|
payload: unknown;
|
|
385
|
+
/** What a trigger that leaves a note tells the thread, for its user to read. */
|
|
386
|
+
note?: string;
|
|
279
387
|
};
|
|
280
388
|
|
|
281
389
|
/**
|
|
@@ -289,19 +397,19 @@ export type TriggerFiringSummary = {
|
|
|
289
397
|
*/
|
|
290
398
|
export type CortexMessage<TPart = unknown> = {
|
|
291
399
|
id: string;
|
|
292
|
-
/** `
|
|
400
|
+
/** `event` rows carry what happened; the UIs render them as an event, not a bubble. */
|
|
293
401
|
role: MessageRole;
|
|
294
402
|
parts: TPart[];
|
|
295
403
|
metadata?: MessageMetadata;
|
|
296
404
|
};
|
|
297
405
|
|
|
298
406
|
/**
|
|
299
|
-
* A stored message as the SDK's chat client takes it. The SDK has no `
|
|
300
|
-
* role, so
|
|
301
|
-
* the envelope (`metadata.
|
|
407
|
+
* A stored message as the SDK's chat client takes it. The SDK has no `event`
|
|
408
|
+
* role, so an event message travels as a user turn; its true role stays on
|
|
409
|
+
* the envelope (`metadata.event`), which is what the UIs render the event from.
|
|
302
410
|
*/
|
|
303
411
|
export function toUiMessage<TPart>(message: CortexMessage<TPart>) {
|
|
304
|
-
return { ...message, role: message.role === "
|
|
412
|
+
return { ...message, role: message.role === "event" ? ("user" as const) : message.role };
|
|
305
413
|
}
|
|
306
414
|
|
|
307
415
|
/**
|
|
@@ -384,14 +492,20 @@ export type ThreadRunStartedEvent = {
|
|
|
384
492
|
|
|
385
493
|
/**
|
|
386
494
|
* Run `runId` ended, committed or not, and left the thread free, so it takes
|
|
387
|
-
* a new message now; whatever turn it committed is in the transcript.
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
*
|
|
495
|
+
* a new message now; whatever turn it committed is in the transcript.
|
|
496
|
+
* `status` and `reason` say how: `waiting` for a run parked on calls that
|
|
497
|
+
* wait. Sent only for a run announced as started, and never by one whose
|
|
498
|
+
* thread another run had already taken, so a finish naming a run other than
|
|
499
|
+
* the one a client saw start last is stale.
|
|
391
500
|
*/
|
|
392
501
|
export type ThreadRunFinishedEvent = {
|
|
393
502
|
type: "thread:run-finished";
|
|
394
|
-
payload: {
|
|
503
|
+
payload: {
|
|
504
|
+
thread: ThreadSummary;
|
|
505
|
+
runId: string;
|
|
506
|
+
status: RunStatus;
|
|
507
|
+
reason: RunReason | null;
|
|
508
|
+
};
|
|
395
509
|
};
|
|
396
510
|
|
|
397
511
|
export type ThreadMessagesUpdatedEvent = {
|
|
@@ -399,13 +513,26 @@ export type ThreadMessagesUpdatedEvent = {
|
|
|
399
513
|
payload: { thread: ThreadSummary };
|
|
400
514
|
};
|
|
401
515
|
|
|
516
|
+
/**
|
|
517
|
+
* The inputs waiting on the thread changed: one was offered, taken up as a
|
|
518
|
+
* turn of its own, or cancelled. `inputIds` are the ones still waiting, in
|
|
519
|
+
* the order dispatch takes them; `GET /threads/:threadId/inputs` reads
|
|
520
|
+
* them. A steer the running turn took in waits on until that turn stores
|
|
521
|
+
* it, which announces nothing: the run's stream carries it.
|
|
522
|
+
*/
|
|
523
|
+
export type ThreadInputsChangedEvent = {
|
|
524
|
+
type: "thread:inputs-changed";
|
|
525
|
+
payload: { threadId: string; inputIds: string[] };
|
|
526
|
+
};
|
|
527
|
+
|
|
402
528
|
export type WsEvent =
|
|
403
529
|
| ThreadCreatedEvent
|
|
404
530
|
| ThreadDeletedEvent
|
|
405
531
|
| ThreadTitleUpdatedEvent
|
|
406
532
|
| ThreadRunStartedEvent
|
|
407
533
|
| ThreadRunFinishedEvent
|
|
408
|
-
| ThreadMessagesUpdatedEvent
|
|
534
|
+
| ThreadMessagesUpdatedEvent
|
|
535
|
+
| ThreadInputsChangedEvent;
|
|
409
536
|
|
|
410
537
|
/** The header the widget sends the browser's IANA time zone in; the turn reasons in that clock. */
|
|
411
538
|
export const TIMEZONE_HEADER = "X-Cortex-Timezone";
|
|
@@ -430,53 +557,33 @@ export const OPERATOR_HEADER = "X-Cortex-Operator";
|
|
|
430
557
|
export const INSPECT_HEADER = "X-Cortex-Inspect";
|
|
431
558
|
|
|
432
559
|
/**
|
|
433
|
-
*
|
|
434
|
-
*
|
|
435
|
-
*
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Every reason a firing records beside its outcome: why it was dropped, why
|
|
442
|
-
* its payload was rejected (`invalid_payload`, `missing_thread_key`), that
|
|
443
|
-
* no turn ran for it (`no_turn`), or why a run with an output schema failed
|
|
444
|
-
* without an answer the schema accepted (`no_output`, `invalid_output`).
|
|
445
|
-
*/
|
|
446
|
-
export const TRIGGER_REASONS = [
|
|
447
|
-
...TRIGGER_DROP_REASONS,
|
|
448
|
-
"invalid_payload",
|
|
449
|
-
"missing_thread_key",
|
|
450
|
-
"no_turn",
|
|
451
|
-
"no_output",
|
|
452
|
-
"invalid_output",
|
|
453
|
-
] as const;
|
|
454
|
-
export type TriggerReason = (typeof TRIGGER_REASONS)[number];
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* Body of a `POST /chat` the thread did not take: 409 while a run holds the
|
|
458
|
-
* thread or a call on it waits for a decision, or when the answers it
|
|
459
|
-
* carried fit no open call, were already applied, or answer a call whose
|
|
460
|
-
* origin is out of reach (`no_turn`); 429 once the agent's request bucket is
|
|
461
|
-
* spent, with `Retry-After`.
|
|
560
|
+
* One input waiting on a thread, in the order dispatch takes them, in
|
|
561
|
+
* `GET /threads/:threadId/inputs`: the user message as it will be stored
|
|
562
|
+
* once a run takes it up, and how it waits: to steer the running turn, or
|
|
563
|
+
* for a turn of its own. `DELETE /threads/:threadId/inputs/:inputId`
|
|
564
|
+
* cancels it and `PATCH` (`{ busy }`) moves it to steer or queue, unless a
|
|
565
|
+
* run already took it in (404).
|
|
462
566
|
*/
|
|
463
|
-
export type
|
|
464
|
-
|
|
465
|
-
|
|
567
|
+
export type WaitingInput<TPart = unknown> = {
|
|
568
|
+
inputId: string;
|
|
569
|
+
delivery: "prompt" | "steer" | "followUp" | "answer" | "note";
|
|
570
|
+
message: CortexMessage<TPart>;
|
|
571
|
+
/** Whether it is text alone, which a running turn may take in as a steer; one with files waits for a turn of its own. */
|
|
572
|
+
textOnly: boolean;
|
|
466
573
|
};
|
|
467
574
|
|
|
468
575
|
/**
|
|
469
|
-
* The
|
|
470
|
-
* for approval, so the message goes back to the composer. Any other
|
|
471
|
-
* shows as a failed turn.
|
|
576
|
+
* The refusal a widget tells its user about by name: a call on the thread
|
|
577
|
+
* waits for approval, so the message goes back to the composer. Any other
|
|
578
|
+
* refusal shows as a failed turn.
|
|
472
579
|
*/
|
|
473
|
-
export type SendRefusal =
|
|
580
|
+
export type SendRefusal = "approval_pending";
|
|
474
581
|
|
|
475
582
|
/**
|
|
476
583
|
* What became of the client tool answers a `POST /chat` carried, by the
|
|
477
584
|
* status Cortex answered it with, apart from how the run it started goes.
|
|
478
585
|
* `persisted` (2xx): Cortex took them up, and sending them again changes
|
|
479
|
-
* nothing. `refused` (4xx,
|
|
586
|
+
* nothing. `refused` (4xx, an `InputReceipt` among them): it did not take
|
|
480
587
|
* them up as given, and the stored transcript says what stands. `lost`
|
|
481
588
|
* (5xx, or no answer at all): its word never came, and only the stored
|
|
482
589
|
* transcript can tell.
|
package/src/management/flags.ts
CHANGED
|
@@ -10,6 +10,8 @@ export type Flag = {
|
|
|
10
10
|
description: string;
|
|
11
11
|
/** A secret: never an argv flag (argv is world-readable), prompted masked or read from stdin. */
|
|
12
12
|
sensitive: boolean;
|
|
13
|
+
/** Takes `null`, which a blank prompted answer sends. */
|
|
14
|
+
nullable: boolean;
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
/** Follows a local `$ref` into `components/schemas`; anything else is returned as is. */
|
|
@@ -36,6 +38,12 @@ function isSensitive(property: JsonSchema, document: OpenApiDocument): boolean {
|
|
|
36
38
|
);
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
/** Whether `null` is allowed, as a type list or as zod's `anyOf` alternative for a nullable property. */
|
|
42
|
+
function isNullable(property: JsonSchema) {
|
|
43
|
+
if (Array.isArray(property.type)) return property.type.includes("null");
|
|
44
|
+
return (property.anyOf ?? []).some((alternative) => alternative.type === "null");
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
/** The flag kind a property's schema maps to; scalars and scalar arrays get typed flags, the rest is JSON. */
|
|
40
48
|
function kindOf(property: JsonSchema, document: OpenApiDocument) {
|
|
41
49
|
if (property.enum) return "enum" as const;
|
|
@@ -71,19 +79,21 @@ export function flagsFor(input: JsonSchema, document: OpenApiDocument) {
|
|
|
71
79
|
choices: property.enum?.map(String),
|
|
72
80
|
description: property.description ?? "",
|
|
73
81
|
sensitive: isSensitive(property, document),
|
|
82
|
+
nullable: isNullable(property),
|
|
74
83
|
} satisfies Flag;
|
|
75
84
|
});
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
/**
|
|
79
88
|
* A prompted answer, parsed and checked exactly as the flag's option would
|
|
80
|
-
* be; `undefined` when it does not fit, so the caller can ask again.
|
|
89
|
+
* be; `undefined` when it does not fit, so the caller can ask again. A blank
|
|
90
|
+
* answer is `null` for a nullable flag.
|
|
81
91
|
*/
|
|
82
92
|
export function parseAnswer(flag: Flag, answer: string) {
|
|
83
|
-
// A
|
|
84
|
-
|
|
93
|
+
// A secret's whitespace is part of it; any other answer of only spaces is blank.
|
|
94
|
+
if ((flag.sensitive ? answer : answer.trim()) === "") return flag.nullable ? null : undefined;
|
|
95
|
+
// A string is taken verbatim. Every other kind is lexical and trimmed before parsing.
|
|
85
96
|
const text = flag.kind === "string" ? answer : answer.trim();
|
|
86
|
-
if (text === "") return undefined;
|
|
87
97
|
return ANSWER_PARSERS[flag.kind](text, flag);
|
|
88
98
|
}
|
|
89
99
|
|
package/src/scaffold/files.ts
CHANGED
|
@@ -28,7 +28,7 @@ import { applyPorts, type Ports } from "@/scaffold/ports";
|
|
|
28
28
|
* `@m6d/cortex` range below are stamped by scripts/sync-package-versions.ts
|
|
29
29
|
* before the CLI publishes, so a scaffold pins what was current then.
|
|
30
30
|
*/
|
|
31
|
-
const CORTEX_VERSION = "0.
|
|
31
|
+
const CORTEX_VERSION = "0.22.0";
|
|
32
32
|
|
|
33
33
|
/** This machine as Cortex's containers name it: the one private host the stack's egress policy admits. */
|
|
34
34
|
export const DOCKER_HOST = "host.docker.internal";
|
|
@@ -147,7 +147,7 @@ function packageJson({ name, datasets, setup }: Project) {
|
|
|
147
147
|
verify: "bun run format:check && bun run lint && bun run spellcheck && bun run check && bun run audit",
|
|
148
148
|
},
|
|
149
149
|
dependencies: {
|
|
150
|
-
"@m6d/cortex": "~0.
|
|
150
|
+
"@m6d/cortex": "~0.10.0",
|
|
151
151
|
// drizzle 1.0 is in beta; this is the build `@m6d/cortex/drizzle` is tested against.
|
|
152
152
|
...(datasets ? { "drizzle-orm": "1.0.0-beta.15-859cf75" } : {}),
|
|
153
153
|
hono: "^4.12.3",
|
|
@@ -246,6 +246,9 @@ COMPOSE_PROJECT_NAME=
|
|
|
246
246
|
DEFAULT_ADMIN_EMAIL=admin@example.com
|
|
247
247
|
DEFAULT_ADMIN_PASSWORD=
|
|
248
248
|
BOOTSTRAP_MANAGEMENT_KEY=
|
|
249
|
+
# The Cortex license key you were given. Without one Cortex serves no console
|
|
250
|
+
# and runs no agent.
|
|
251
|
+
CORTEX_LICENSE_KEY=
|
|
249
252
|
# Session signing, the key the secrets store is encrypted with, and object
|
|
250
253
|
# storage's password (storage is published on this machine for uploads).
|
|
251
254
|
BETTER_AUTH_SECRET=
|
|
@@ -497,7 +500,9 @@ A backend for Cortex agents, and Cortex itself to run them on.
|
|
|
497
500
|
|
|
498
501
|
## Getting started
|
|
499
502
|
|
|
500
|
-
On a fresh clone, run \`bun install\` first (\`cortex new\` already did): it writes \`.env\`.
|
|
503
|
+
On a fresh clone, run \`bun install\` first (\`cortex new\` already did): it writes \`.env\`. Put
|
|
504
|
+
your Cortex license key in its \`CORTEX_LICENSE_KEY\`: without one Cortex serves no console and runs no
|
|
505
|
+
agent.
|
|
501
506
|
|
|
502
507
|
\`\`\`sh
|
|
503
508
|
bun run dev
|
package/src/ui/prompts.ts
CHANGED
|
@@ -84,40 +84,34 @@ export async function askToProceed(message: string) {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
*
|
|
88
|
-
* empty
|
|
87
|
+
* How an asked value is checked: re-asked while `check` complains about it,
|
|
88
|
+
* and while empty unless `blank` accepts an empty answer.
|
|
89
89
|
*/
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
)
|
|
90
|
+
type AskOptions = { check?: (value: string) => string | undefined; blank?: boolean };
|
|
91
|
+
|
|
92
|
+
function validator(name: string, { check = () => undefined, blank = false }: AskOptions) {
|
|
93
|
+
return function (value: string | undefined) {
|
|
94
|
+
if (value) return check(value);
|
|
95
|
+
return blank ? undefined : `${name} is required.`;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** A value a command was not given: asked by name and checked trimmed. */
|
|
100
|
+
export function askValue(name: string, { hint, ...options }: AskOptions & { hint?: string } = {}) {
|
|
101
|
+
const validate = validator(name, options);
|
|
95
102
|
return orExit(
|
|
96
103
|
text({
|
|
97
104
|
message: name,
|
|
98
105
|
placeholder: hint,
|
|
99
106
|
output,
|
|
100
|
-
validate:
|
|
101
|
-
const candidate = (value ?? "").trim();
|
|
102
|
-
return candidate.length === 0 ? `${name} is required.` : check(candidate);
|
|
103
|
-
},
|
|
107
|
+
validate: (value) => validate(value?.trim()),
|
|
104
108
|
}),
|
|
105
109
|
);
|
|
106
110
|
}
|
|
107
111
|
|
|
108
|
-
/** A credential: typed masked, never echoed, and
|
|
109
|
-
export function askSecret(
|
|
110
|
-
name:
|
|
111
|
-
check: (value: string) => string | undefined = () => undefined,
|
|
112
|
-
) {
|
|
113
|
-
return orExit(
|
|
114
|
-
password({
|
|
115
|
-
message: name,
|
|
116
|
-
output,
|
|
117
|
-
validate: (value) =>
|
|
118
|
-
(value ?? "").length === 0 ? `${name} is required.` : check(value ?? ""),
|
|
119
|
-
}),
|
|
120
|
-
);
|
|
112
|
+
/** A credential: typed masked, never echoed, and checked as typed. */
|
|
113
|
+
export function askSecret(name: string, options: AskOptions = {}) {
|
|
114
|
+
return orExit(password({ message: name, output, validate: validator(name, options) }));
|
|
121
115
|
}
|
|
122
116
|
|
|
123
117
|
type Step = { title: string; run: () => string | undefined | Promise<string | undefined> };
|