@m6d/cortex-cli 0.18.0 → 0.20.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 +1 -1
- package/package.json +3 -3
- package/src/commands/apply.ts +12 -7
- package/src/commands/management.ts +17 -15
- package/src/commands/setup.ts +72 -60
- package/src/contracts/datasets/dataset-sql.ts +1 -1
- package/src/contracts/management/index.ts +181 -0
- package/src/contracts/management/procedures.generated.ts +5893 -0
- package/src/contracts/manifest/index.ts +71 -40
- package/src/management/client.ts +19 -77
- package/src/scaffold/files.ts +32 -26
- package/src/scaffold/ports.ts +5 -4
- package/src/scaffold/templates/cspell.json.txt +1 -1
- package/src/scaffold/templates/oxlintrc.json.txt +67 -0
- package/src/scaffold/templates/setup.ts.txt +1 -0
- package/src/scaffold/templates/eslint.config.mjs.txt +0 -20
|
@@ -1476,22 +1476,6 @@ export const agentSettingsSchema = z.strictObject({
|
|
|
1476
1476
|
/** Off, end users cannot message the agent: only its triggers reach it. */
|
|
1477
1477
|
messageTrigger: z.boolean().default(true),
|
|
1478
1478
|
defaultLocale: localeSchema.default(BASE_LOCALE),
|
|
1479
|
-
rateLimits: z
|
|
1480
|
-
.strictObject({
|
|
1481
|
-
allUsers: z
|
|
1482
|
-
.strictObject({
|
|
1483
|
-
messagesPerMinute: rateLimit(agentRateLimits.messagesPerMinute, "allUsers"),
|
|
1484
|
-
tokensPerDay: rateLimit(agentRateLimits.tokensPerDay, "allUsers"),
|
|
1485
|
-
})
|
|
1486
|
-
.prefault({}),
|
|
1487
|
-
perUser: z
|
|
1488
|
-
.strictObject({
|
|
1489
|
-
messagesPerMinute: rateLimit(agentRateLimits.messagesPerMinute, "perUser"),
|
|
1490
|
-
tokensPerDay: rateLimit(agentRateLimits.tokensPerDay, "perUser"),
|
|
1491
|
-
})
|
|
1492
|
-
.prefault({}),
|
|
1493
|
-
})
|
|
1494
|
-
.prefault({}),
|
|
1495
1479
|
context: z
|
|
1496
1480
|
.strictObject({
|
|
1497
1481
|
maxContextTokens: setting(agentContext.maxContextTokens),
|
|
@@ -1516,30 +1500,77 @@ export const agentSettingsSchema = z.strictObject({
|
|
|
1516
1500
|
/** An agent's name in characters. The console caps its editor at the same. */
|
|
1517
1501
|
export const AGENT_LIMITS = { nameLength: 120 } as const;
|
|
1518
1502
|
|
|
1519
|
-
const manifestAgentSchema = z
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
})
|
|
1503
|
+
const manifestAgentSchema = z
|
|
1504
|
+
.strictObject({
|
|
1505
|
+
slug: agentSlugSchema,
|
|
1506
|
+
name: z.string().trim().min(1).max(AGENT_LIMITS.nameLength),
|
|
1507
|
+
/**
|
|
1508
|
+
* The server it belongs to, by name; a manifest never creates one. Left
|
|
1509
|
+
* out, the agent has no server: no native tools, no server variables,
|
|
1510
|
+
* no `server` auth, and no model to inherit, so it names its own `chat`.
|
|
1511
|
+
*/
|
|
1512
|
+
server: nameSchema.optional(),
|
|
1513
|
+
/** Rich text: it may mention tools and skills, and read `{{variables}}`. */
|
|
1514
|
+
prompt: z.string().trim().min(1),
|
|
1515
|
+
auth: agentAuthSchema,
|
|
1516
|
+
/**
|
|
1517
|
+
* `chat` left out runs on the server's pick; `vision` and
|
|
1518
|
+
* `summarization` left out use none of their own.
|
|
1519
|
+
*/
|
|
1520
|
+
models: z
|
|
1521
|
+
.strictObject({
|
|
1522
|
+
chat: modelSchema.optional(),
|
|
1523
|
+
vision: modelSchema.omit({ effort: true }).optional(),
|
|
1524
|
+
summarization: modelSchema.omit({ effort: true }).optional(),
|
|
1525
|
+
})
|
|
1526
|
+
.default({}),
|
|
1527
|
+
settings: agentSettingsSchema.prefault({}),
|
|
1528
|
+
/** Messages a minute and model tokens a UTC day, for all users together and for each; null, no limit. */
|
|
1529
|
+
rateLimits: z
|
|
1530
|
+
.strictObject({
|
|
1531
|
+
allUsers: z
|
|
1532
|
+
.strictObject({
|
|
1533
|
+
messagesPerMinute: rateLimit(agentRateLimits.messagesPerMinute, "allUsers"),
|
|
1534
|
+
tokensPerDay: rateLimit(agentRateLimits.tokensPerDay, "allUsers"),
|
|
1535
|
+
})
|
|
1536
|
+
.prefault({}),
|
|
1537
|
+
perUser: z
|
|
1538
|
+
.strictObject({
|
|
1539
|
+
messagesPerMinute: rateLimit(agentRateLimits.messagesPerMinute, "perUser"),
|
|
1540
|
+
tokensPerDay: rateLimit(agentRateLimits.tokensPerDay, "perUser"),
|
|
1541
|
+
})
|
|
1542
|
+
.prefault({}),
|
|
1543
|
+
})
|
|
1544
|
+
.prefault({}),
|
|
1545
|
+
suggestions: suggestionsSchema.default(() => byLocale(() => [])),
|
|
1546
|
+
greeting: greetingSchema.default(() => byLocale(() => "")),
|
|
1547
|
+
overridable: overridable([
|
|
1548
|
+
"name",
|
|
1549
|
+
"prompt",
|
|
1550
|
+
"models",
|
|
1551
|
+
"settings",
|
|
1552
|
+
"rateLimits",
|
|
1553
|
+
"suggestions",
|
|
1554
|
+
"greeting",
|
|
1555
|
+
]),
|
|
1556
|
+
})
|
|
1557
|
+
.superRefine((agent, context) => {
|
|
1558
|
+
if (agent.server !== undefined) return;
|
|
1559
|
+
if (agent.auth.kind === "server") {
|
|
1560
|
+
context.addIssue({
|
|
1561
|
+
code: "custom",
|
|
1562
|
+
path: ["auth"],
|
|
1563
|
+
message: "server auth needs a server",
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1566
|
+
if (agent.models.chat === undefined) {
|
|
1567
|
+
context.addIssue({
|
|
1568
|
+
code: "custom",
|
|
1569
|
+
path: ["models", "chat"],
|
|
1570
|
+
message: "an agent with no server names its own chat model",
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1573
|
+
});
|
|
1543
1574
|
export type ManifestAgent = z.input<typeof manifestAgentSchema>;
|
|
1544
1575
|
|
|
1545
1576
|
/**
|
package/src/management/client.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { createManagementClient, MANAGEMENT_BASE_PATH } from "@cortex/contracts/management";
|
|
6
|
+
import type { ManagementTarget } from "@cortex/contracts/management";
|
|
6
7
|
import { z } from "zod";
|
|
7
8
|
|
|
8
9
|
import { profilesPath } from "./profiles";
|
|
@@ -109,7 +110,7 @@ export async function loadOpenApi(url: string) {
|
|
|
109
110
|
const path = cachePath(url);
|
|
110
111
|
const cached = readCache(path);
|
|
111
112
|
try {
|
|
112
|
-
const response = await fetch(`${url}/
|
|
113
|
+
const response = await fetch(`${url}${MANAGEMENT_BASE_PATH}/openapi.json`, {
|
|
113
114
|
headers: cached ? { "If-None-Match": cached.etag } : {},
|
|
114
115
|
});
|
|
115
116
|
if (response.status === 304 && cached)
|
|
@@ -159,71 +160,30 @@ export function inputSchemaOf(operation: OpenApiOperation) {
|
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
/**
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
* anything else JSON-encoded, which is how the document declares it. A
|
|
165
|
-
* mutation posts its input as the JSON body. A refusal throws
|
|
166
|
-
* `ManagementError` with the error body's code, message, detail and sentence.
|
|
163
|
+
* Where `profile`'s management calls go; a Cortex that does not answer at
|
|
164
|
+
* all rejects as `UnreachableError`, apart from one that refused.
|
|
167
165
|
*/
|
|
168
|
-
export
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
headers: {
|
|
179
|
-
Authorization: `Bearer ${profile.key}`,
|
|
180
|
-
...(method === "post" ? { "Content-Type": "application/json" } : {}),
|
|
181
|
-
},
|
|
182
|
-
body: method === "post" && input !== undefined ? JSON.stringify(input) : undefined,
|
|
183
|
-
}).catch((error: unknown) => {
|
|
184
|
-
throw new UnreachableError(error);
|
|
185
|
-
});
|
|
186
|
-
const body: unknown = await response.json().catch(() => null);
|
|
187
|
-
if (!response.ok) {
|
|
188
|
-
const { code, message, refusal, cause } = (body ?? {}) as {
|
|
189
|
-
code?: string;
|
|
190
|
-
message?: string;
|
|
191
|
-
refusal?: string;
|
|
192
|
-
cause?: unknown;
|
|
193
|
-
};
|
|
194
|
-
throw new ManagementError(
|
|
195
|
-
response.status,
|
|
196
|
-
code ?? "error",
|
|
197
|
-
message ?? response.statusText,
|
|
198
|
-
isRecord(cause) ? cause : undefined,
|
|
199
|
-
refusal,
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
return body;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function isScalar(value: unknown): value is string | number | boolean | null {
|
|
206
|
-
return value === null || ["string", "number", "boolean"].includes(typeof value);
|
|
166
|
+
export function managementTarget(profile: { url: string; key: string }) {
|
|
167
|
+
return {
|
|
168
|
+
...profile,
|
|
169
|
+
// Every day range a command takes is this machine's days.
|
|
170
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
171
|
+
fetch: (url: string, init: RequestInit) =>
|
|
172
|
+
fetch(url, init).catch((error: unknown) => {
|
|
173
|
+
throw new UnreachableError(error);
|
|
174
|
+
}),
|
|
175
|
+
} satisfies ManagementTarget;
|
|
207
176
|
}
|
|
208
177
|
|
|
209
|
-
/**
|
|
210
|
-
function
|
|
211
|
-
|
|
212
|
-
for (const [name, value] of Object.entries(input)) {
|
|
213
|
-
if (value === undefined) continue;
|
|
214
|
-
if (isScalar(value)) params.set(name, String(value));
|
|
215
|
-
else if (Array.isArray(value) && value.length > 0 && value.every(isScalar)) {
|
|
216
|
-
for (const item of value) params.append(name, String(item));
|
|
217
|
-
} else params.set(name, JSON.stringify(value));
|
|
218
|
-
}
|
|
219
|
-
const encoded = params.toString();
|
|
220
|
-
return encoded === "" ? "" : `?${encoded}`;
|
|
178
|
+
/** The typed client for the calls the CLI makes by name, over `managementTarget`. */
|
|
179
|
+
export function managementClient(profile: { url: string; key: string }) {
|
|
180
|
+
return createManagementClient(managementTarget(profile));
|
|
221
181
|
}
|
|
222
182
|
|
|
223
183
|
/**
|
|
224
184
|
* Cortex did not answer at all: refused, reset, unresolvable. `fetch` rejects
|
|
225
185
|
* only for these, with whatever its runtime throws (Bun's is a plain `Error`
|
|
226
|
-
* with a `code`), so `
|
|
186
|
+
* with a `code`), so `managementTarget` names them once here.
|
|
227
187
|
*/
|
|
228
188
|
export class UnreachableError extends Error {
|
|
229
189
|
public constructor(cause: unknown) {
|
|
@@ -231,21 +191,3 @@ export class UnreachableError extends Error {
|
|
|
231
191
|
this.name = "UnreachableError";
|
|
232
192
|
}
|
|
233
193
|
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* A refusal; `detail` is what the refusal names, when it names something (an
|
|
237
|
-
* entry, the row holding a name), and `refusal` the sentence Cortex words it
|
|
238
|
-
* in, when it has one.
|
|
239
|
-
*/
|
|
240
|
-
export class ManagementError extends Error {
|
|
241
|
-
public constructor(
|
|
242
|
-
public readonly status: number,
|
|
243
|
-
public readonly code: string,
|
|
244
|
-
message: string,
|
|
245
|
-
public readonly detail?: Record<string, unknown>,
|
|
246
|
-
public readonly refusal?: string,
|
|
247
|
-
) {
|
|
248
|
-
super(message);
|
|
249
|
-
this.name = "ManagementError";
|
|
250
|
-
}
|
|
251
|
-
}
|
package/src/scaffold/files.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Every file `cortex new` writes, as one pure function. Files with nothing to
|
|
3
3
|
* fill in are verbatim text under templates/; the ones that vary with the
|
|
4
4
|
* project are assembled here. The tooling configs are inlined rather than
|
|
5
|
-
* pulled from `@m6d/
|
|
6
|
-
* those are `private: true` at `0.0.0`
|
|
7
|
-
*
|
|
5
|
+
* pulled from `@m6d/prettier-config`, `@m6d/tsconfig` and the root
|
|
6
|
+
* `.oxlintrc.json`: those are `private: true` at `0.0.0` (the lint config
|
|
7
|
+
* loads `@m6d/oxlint-plugin`) and resolvable only through this workspace, and
|
|
8
|
+
* a standalone scaffold has no workspace.
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
import { NAME_MAX_LENGTH } from "@cortex/contracts/manifest";
|
|
@@ -16,11 +17,11 @@ import datasetsTs from "@/scaffold/templates/datasets.ts.txt";
|
|
|
16
17
|
import tsconfigJson from "@/scaffold/templates/tsconfig.json.txt";
|
|
17
18
|
import gitignoreText from "@/scaffold/templates/gitignore.txt";
|
|
18
19
|
import dockerignoreText from "@/scaffold/templates/dockerignore.txt";
|
|
19
|
-
import
|
|
20
|
+
import oxlintrc from "@/scaffold/templates/oxlintrc.json.txt";
|
|
20
21
|
import prettierConfig from "@/scaffold/templates/prettier.config.mjs.txt";
|
|
21
22
|
import fallowrc from "@/scaffold/templates/fallowrc.jsonc.txt";
|
|
22
|
-
// Project jargon is the project's to add. Only
|
|
23
|
-
// package.json
|
|
23
|
+
// Project jargon is the project's to add. Only the tool names its own
|
|
24
|
+
// package.json carries are seeded: a stranger's vocabulary is noise in a repo.
|
|
24
25
|
import cspellJson from "@/scaffold/templates/cspell.json.txt";
|
|
25
26
|
import { applyPorts, type Ports } from "@/scaffold/ports";
|
|
26
27
|
|
|
@@ -29,7 +30,7 @@ import { applyPorts, type Ports } from "@/scaffold/ports";
|
|
|
29
30
|
* `@m6d/cortex` range below are stamped by scripts/sync-package-versions.ts
|
|
30
31
|
* before the CLI publishes, so a scaffold pins what was current then.
|
|
31
32
|
*/
|
|
32
|
-
const CORTEX_VERSION = "0.
|
|
33
|
+
const CORTEX_VERSION = "0.19.0";
|
|
33
34
|
|
|
34
35
|
/** This machine as Cortex's containers name it: the one private host the stack's egress policy admits. */
|
|
35
36
|
export const DOCKER_HOST = "host.docker.internal";
|
|
@@ -54,18 +55,16 @@ export const EMBEDDING = { model: "text-embedding-3-small", dimension: 768 } as
|
|
|
54
55
|
const DEV_DEPENDENCIES = {
|
|
55
56
|
// `cortex apply`, `cortex setup` and the management commands, at the release that wrote the project.
|
|
56
57
|
"@m6d/cortex-cli": `~${pkg.version}`,
|
|
57
|
-
"@types/bun": "1.
|
|
58
|
+
"@types/bun": "1.4.4",
|
|
58
59
|
// The two runners of `bun run dev` and `bun run dev:agent`.
|
|
59
60
|
concurrently: "^10.0.6",
|
|
60
61
|
cspell: "^9.6.4",
|
|
61
|
-
eslint: "^9",
|
|
62
|
-
"eslint-config-prettier": "^10.1.8",
|
|
63
|
-
"eslint-plugin-prettier": "^5.5.5",
|
|
64
62
|
fallow: "^3.9.1",
|
|
65
63
|
mprocs: "^0.9.6",
|
|
64
|
+
oxlint: "^1.90.0",
|
|
65
|
+
"oxlint-tsgolint": "^7.0.2005",
|
|
66
66
|
prettier: "^3.8.1",
|
|
67
|
-
typescript: "
|
|
68
|
-
"typescript-eslint": "^8.65.0",
|
|
67
|
+
typescript: "^7.0.2",
|
|
69
68
|
};
|
|
70
69
|
|
|
71
70
|
/**
|
|
@@ -145,12 +144,12 @@ function packageJson({ name, datasets, setup }: Project) {
|
|
|
145
144
|
postinstall: "bun run --if-present setup.ts",
|
|
146
145
|
format: "prettier --write .",
|
|
147
146
|
"format:check": "prettier --check .",
|
|
148
|
-
lint: "
|
|
147
|
+
lint: "oxlint",
|
|
149
148
|
spellcheck: 'cspell "**/*.{ts,mjs,json,md}"',
|
|
150
149
|
verify: "bun run format:check && bun run lint && bun run spellcheck && bun run check && bun run audit",
|
|
151
150
|
},
|
|
152
151
|
dependencies: {
|
|
153
|
-
"@m6d/cortex": "~0.
|
|
152
|
+
"@m6d/cortex": "~0.7.0",
|
|
154
153
|
// drizzle 1.0 is in beta; this is the build `@m6d/cortex/drizzle` is tested against.
|
|
155
154
|
...(datasets ? { "drizzle-orm": "1.0.0-beta.15-859cf75" } : {}),
|
|
156
155
|
hono: "^4.12.3",
|
|
@@ -249,9 +248,11 @@ COMPOSE_PROJECT_NAME=
|
|
|
249
248
|
DEFAULT_ADMIN_EMAIL=admin@example.com
|
|
250
249
|
DEFAULT_ADMIN_PASSWORD=
|
|
251
250
|
BOOTSTRAP_MANAGEMENT_KEY=
|
|
252
|
-
# Session signing,
|
|
251
|
+
# Session signing, the key the secrets store is encrypted with, and object
|
|
252
|
+
# storage's password (storage is published on this machine for uploads).
|
|
253
253
|
BETTER_AUTH_SECRET=
|
|
254
254
|
SECRETS_MASTER_KEY=
|
|
255
|
+
S3_SECRET_ACCESS_KEY=
|
|
255
256
|
# Any OpenAI-compatible embeddings endpoint and model. Tool search and
|
|
256
257
|
# knowledge collections embed with it, and stay off while the key is blank.
|
|
257
258
|
EMBEDDING_BASE_URL=https://api.openai.com/v1
|
|
@@ -288,17 +289,14 @@ MODEL_PROVIDER_MODEL=
|
|
|
288
289
|
function dockerCompose() {
|
|
289
290
|
return `# Cortex, for this project's agents to run on: the console at
|
|
290
291
|
# http://localhost:{{port.cortex}}, the engine widgets and trigger callers reach at
|
|
291
|
-
# http://localhost:{{port.engine}},
|
|
292
|
-
#
|
|
292
|
+
# http://localhost:{{port.engine}}, storage the console uploads to at
|
|
293
|
+
# http://localhost:{{port.storage}}, and the stores they keep state in.
|
|
294
|
+
# \`bun run dev\` starts it; .env holds its secrets.
|
|
293
295
|
#
|
|
294
296
|
# The host ports were picked at scaffold time from what was free on this
|
|
295
297
|
# machine. Move one and the URLs that name it here, in .env and in README.md
|
|
296
298
|
# follow.
|
|
297
299
|
# CORTEX_VERSION, set in the shell, runs another release of the image.
|
|
298
|
-
#
|
|
299
|
-
# The console's browser uploads (knowledge documents) go straight to
|
|
300
|
-
# S3_ENDPOINT, which only this stack resolves. Point it at storage the
|
|
301
|
-
# browser reaches too before relying on them.
|
|
302
300
|
|
|
303
301
|
# The stack runs as .env's COMPOSE_PROJECT_NAME, drawn for each checkout by
|
|
304
302
|
# setup.ts, so no two checkouts share containers or volumes, whatever their
|
|
@@ -316,9 +314,13 @@ x-cortex: &cortex
|
|
|
316
314
|
DATABASE_URL: postgres://postgres:postgres@postgres:5432/cortex
|
|
317
315
|
REDIS_URL: redis://redis:6379
|
|
318
316
|
S3_ENDPOINT: http://minio:9000
|
|
317
|
+
# The console's uploads go straight to storage, from the browser.
|
|
318
|
+
S3_PUBLIC_ENDPOINT: http://localhost:{{port.storage}}
|
|
319
319
|
S3_REGION: us-east-1
|
|
320
320
|
S3_ACCESS_KEY_ID: minio
|
|
321
|
-
|
|
321
|
+
# Interpolated like storage's own, so both read the one value even when the
|
|
322
|
+
# shell exports another.
|
|
323
|
+
S3_SECRET_ACCESS_KEY: \${S3_SECRET_ACCESS_KEY:?run bun install to generate .env}
|
|
322
324
|
S3_BUCKET: cortex
|
|
323
325
|
BETTER_AUTH_ALLOWED_HOSTS: localhost:{{port.cortex}},127.0.0.1:{{port.cortex}}
|
|
324
326
|
# The dimension the image's migrations give the vector columns.
|
|
@@ -366,9 +368,13 @@ services:
|
|
|
366
368
|
command: server /data
|
|
367
369
|
environment:
|
|
368
370
|
MINIO_ROOT_USER: minio
|
|
369
|
-
MINIO_ROOT_PASSWORD:
|
|
371
|
+
MINIO_ROOT_PASSWORD: \${S3_SECRET_ACCESS_KEY:?run bun install to generate .env}
|
|
372
|
+
# The console's uploads come from its origin alone.
|
|
373
|
+
MINIO_API_CORS_ALLOW_ORIGIN: http://localhost:{{port.cortex}},http://127.0.0.1:{{port.cortex}}
|
|
370
374
|
volumes:
|
|
371
375
|
- minio-data:/data
|
|
376
|
+
ports:
|
|
377
|
+
- "127.0.0.1:{{port.storage}}:9000"
|
|
372
378
|
healthcheck:
|
|
373
379
|
test: ["CMD", "bash", "-c", "</dev/tcp/127.0.0.1/9000"]
|
|
374
380
|
interval: 5s
|
|
@@ -381,7 +387,7 @@ services:
|
|
|
381
387
|
minio:
|
|
382
388
|
condition: service_healthy
|
|
383
389
|
environment:
|
|
384
|
-
MC_HOST_local: http://minio
|
|
390
|
+
MC_HOST_local: http://minio:\${S3_SECRET_ACCESS_KEY:?run bun install to generate .env}@minio:9000
|
|
385
391
|
command: ["mb", "--ignore-existing", "local/cortex"]
|
|
386
392
|
|
|
387
393
|
# One-shot: migrate the database, then create the first admin and its key.
|
|
@@ -547,7 +553,7 @@ export function scaffoldFiles(input: Input) {
|
|
|
547
553
|
".gitignore": gitignoreText,
|
|
548
554
|
".dockerignore": dockerignoreText,
|
|
549
555
|
"README.md": readme(project),
|
|
550
|
-
"
|
|
556
|
+
".oxlintrc.json": oxlintrc,
|
|
551
557
|
"prettier.config.mjs": prettierConfig,
|
|
552
558
|
"cspell.json": cspellJson,
|
|
553
559
|
".fallowrc.jsonc": fallowrc,
|
package/src/scaffold/ports.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Host ports for the Cortex stack a generated project runs, chosen once at
|
|
3
3
|
* scaffold time.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* project's callers reach them
|
|
7
|
-
*
|
|
5
|
+
* The app, the engine and storage are published: the browser and this
|
|
6
|
+
* project's callers reach them (storage through the upload links Cortex
|
|
7
|
+
* signs), and nothing outside the stack reaches its databases. The backend
|
|
8
|
+
* listens on the fourth. Fixed ports would collide with
|
|
8
9
|
* the next project scaffolded on the same machine, so each project draws its
|
|
9
10
|
* own. Container ports stay canonical.
|
|
10
11
|
*
|
|
@@ -60,7 +61,7 @@ export function allocatePorts() {
|
|
|
60
61
|
return port;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
return { cortex: next(), engine: next(), server: next() };
|
|
64
|
+
return { cortex: next(), engine: next(), storage: next(), server: next() };
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
export type Ports = ReturnType<typeof allocatePorts>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
3
|
+
// typescript-eslint's recommended type-checked set; nothing else runs.
|
|
4
|
+
"categories": {
|
|
5
|
+
"correctness": "off"
|
|
6
|
+
},
|
|
7
|
+
"options": {
|
|
8
|
+
"typeAware": true,
|
|
9
|
+
"reportUnusedDisableDirectives": "warn"
|
|
10
|
+
},
|
|
11
|
+
"ignorePatterns": ["**/node_modules/**", "**/dist/**", "**/.fallow/**"],
|
|
12
|
+
"overrides": [
|
|
13
|
+
{
|
|
14
|
+
"files": ["**/*.ts"],
|
|
15
|
+
"plugins": ["typescript"],
|
|
16
|
+
"rules": {
|
|
17
|
+
"no-var": "error",
|
|
18
|
+
"prefer-const": "error",
|
|
19
|
+
"prefer-rest-params": "error",
|
|
20
|
+
"prefer-spread": "error",
|
|
21
|
+
"no-array-constructor": "error",
|
|
22
|
+
"no-unused-expressions": "error",
|
|
23
|
+
"no-unused-vars": "error",
|
|
24
|
+
"typescript/await-thenable": "error",
|
|
25
|
+
"typescript/ban-ts-comment": "error",
|
|
26
|
+
"typescript/no-array-delete": "error",
|
|
27
|
+
"typescript/no-base-to-string": "error",
|
|
28
|
+
"typescript/no-duplicate-enum-values": "error",
|
|
29
|
+
"typescript/no-duplicate-type-constituents": "error",
|
|
30
|
+
"typescript/no-empty-object-type": "error",
|
|
31
|
+
"typescript/no-explicit-any": "error",
|
|
32
|
+
"typescript/no-extra-non-null-assertion": "error",
|
|
33
|
+
"typescript/no-floating-promises": "error",
|
|
34
|
+
"typescript/no-for-in-array": "error",
|
|
35
|
+
"typescript/no-implied-eval": "error",
|
|
36
|
+
"typescript/no-misused-new": "error",
|
|
37
|
+
"typescript/no-misused-promises": "error",
|
|
38
|
+
"typescript/no-namespace": "error",
|
|
39
|
+
"typescript/no-non-null-asserted-optional-chain": "error",
|
|
40
|
+
"typescript/no-redundant-type-constituents": "error",
|
|
41
|
+
"typescript/no-require-imports": "error",
|
|
42
|
+
"typescript/no-this-alias": "error",
|
|
43
|
+
"typescript/no-unnecessary-type-assertion": "error",
|
|
44
|
+
"typescript/no-unnecessary-type-constraint": "error",
|
|
45
|
+
"typescript/no-unsafe-argument": "error",
|
|
46
|
+
"typescript/no-unsafe-assignment": "error",
|
|
47
|
+
"typescript/no-unsafe-call": "error",
|
|
48
|
+
"typescript/no-unsafe-declaration-merging": "error",
|
|
49
|
+
"typescript/no-unsafe-enum-comparison": "error",
|
|
50
|
+
"typescript/no-unsafe-function-type": "error",
|
|
51
|
+
"typescript/no-unsafe-member-access": "error",
|
|
52
|
+
"typescript/no-unsafe-return": "error",
|
|
53
|
+
"typescript/no-unsafe-unary-minus": "error",
|
|
54
|
+
"typescript/no-wrapper-object-types": "error",
|
|
55
|
+
"typescript/only-throw-error": "error",
|
|
56
|
+
"typescript/prefer-as-const": "error",
|
|
57
|
+
"typescript/prefer-namespace-keyword": "error",
|
|
58
|
+
"typescript/prefer-promise-reject-errors": "error",
|
|
59
|
+
"typescript/require-await": "error",
|
|
60
|
+
"typescript/restrict-plus-operands": "error",
|
|
61
|
+
"typescript/restrict-template-expressions": "error",
|
|
62
|
+
"typescript/triple-slash-reference": "error",
|
|
63
|
+
"typescript/unbound-method": "error"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
@@ -30,6 +30,7 @@ const GENERATED = [
|
|
|
30
30
|
["BOOTSTRAP_MANAGEMENT_KEY", () => `cortex_mgmt_${token()}`],
|
|
31
31
|
["BETTER_AUTH_SECRET", key],
|
|
32
32
|
["SECRETS_MASTER_KEY", key],
|
|
33
|
+
["S3_SECRET_ACCESS_KEY", token],
|
|
33
34
|
] as const;
|
|
34
35
|
|
|
35
36
|
const source = existsSync(".env") ? ".env" : ".env.example";
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
-
import prettierConfig from "eslint-config-prettier";
|
|
3
|
-
import prettierPlugin from "eslint-plugin-prettier";
|
|
4
|
-
import tseslint from "typescript-eslint";
|
|
5
|
-
|
|
6
|
-
export default defineConfig([
|
|
7
|
-
globalIgnores(["**/node_modules/**", "**/dist/**", "**/.fallow/**"]),
|
|
8
|
-
prettierConfig,
|
|
9
|
-
{
|
|
10
|
-
plugins: { prettier: prettierPlugin },
|
|
11
|
-
rules: { "prettier/prettier": "error" },
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
files: ["**/*.ts"],
|
|
15
|
-
extends: [tseslint.configs.recommendedTypeChecked],
|
|
16
|
-
languageOptions: {
|
|
17
|
-
parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname },
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
]);
|