@m6d/cortex-cli 0.22.0 → 0.24.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.
@@ -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 string is taken verbatim: a secret's whitespace is part of it. Every
84
- // other kind is lexical and trimmed before parsing.
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
 
@@ -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.21.0";
31
+ const CORTEX_VERSION = "0.23.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.9.0",
150
+ "@m6d/cortex": "~0.11.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
- * A required value a command was not given: asked by name, re-asked while
88
- * empty or while `check` returns a complaint about the trimmed value.
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
- export function askValue(
91
- name: string,
92
- hint?: string,
93
- check: (value: string) => string | undefined = () => undefined,
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: function (value) {
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 re-asked while `check` complains. */
109
- export function askSecret(
110
- name: string,
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> };