@m6d/cortex-cli 0.13.0 → 0.14.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 +86 -26
- package/package.json +1 -1
- package/src/cli.ts +2 -0
- package/src/commands/apply.ts +50 -33
- package/src/commands/management.ts +32 -21
- package/src/commands/new.ts +48 -25
- package/src/commands/setup.ts +610 -0
- package/src/contracts/manifest/index.ts +67 -26
- package/src/contracts/server/index.ts +108 -6
- package/src/contracts/wire/index.ts +7 -2
- package/src/management/client.ts +14 -0
- package/src/management/profiles.ts +6 -4
- package/src/scaffold/files.ts +183 -144
- package/src/scaffold/ports.ts +5 -6
- package/src/scaffold/templates/cspell.json.txt +41 -0
- package/src/scaffold/templates/dockerignore.txt +3 -4
- package/src/scaffold/templates/gitignore.txt +1 -2
- package/src/scaffold/templates/setup.ts.txt +18 -26
- package/src/ui/prompts.ts +23 -10
package/src/scaffold/files.ts
CHANGED
|
@@ -17,6 +17,9 @@ import dockerignoreText from "@/scaffold/templates/dockerignore.txt";
|
|
|
17
17
|
import eslintConfig from "@/scaffold/templates/eslint.config.mjs.txt";
|
|
18
18
|
import prettierConfig from "@/scaffold/templates/prettier.config.mjs.txt";
|
|
19
19
|
import fallowrc from "@/scaffold/templates/fallowrc.jsonc.txt";
|
|
20
|
+
// Project jargon is the project's to add. Only `mprocs`, which its own
|
|
21
|
+
// package.json names, is seeded: a stranger's vocabulary is noise in a repo.
|
|
22
|
+
import cspellJson from "@/scaffold/templates/cspell.json.txt";
|
|
20
23
|
import { applyPorts, type Ports } from "@/scaffold/ports";
|
|
21
24
|
|
|
22
25
|
/**
|
|
@@ -24,11 +27,13 @@ import { applyPorts, type Ports } from "@/scaffold/ports";
|
|
|
24
27
|
* `@m6d/cortex` range below are stamped by scripts/sync-package-versions.ts
|
|
25
28
|
* before the CLI publishes, so a scaffold pins what was current then.
|
|
26
29
|
*/
|
|
27
|
-
const CORTEX_VERSION = "0.
|
|
30
|
+
const CORTEX_VERSION = "0.14.1";
|
|
28
31
|
|
|
29
|
-
/**
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
+
/** This machine as Cortex's containers name it: the one private host the stack's egress policy admits. */
|
|
33
|
+
export const DOCKER_HOST = "host.docker.internal";
|
|
34
|
+
|
|
35
|
+
/** How Cortex's containers reach the backend on this machine, at the port drawn for it. */
|
|
36
|
+
const SERVER_URL = `http://${DOCKER_HOST}:{{port.server}}`;
|
|
32
37
|
|
|
33
38
|
/** The longest server name Cortex accepts. */
|
|
34
39
|
const SERVER_NAME_MAX = 100;
|
|
@@ -37,27 +42,76 @@ const SERVER_NAME_MAX = 100;
|
|
|
37
42
|
* The agent the manifest declares and the README sends you to chat with.
|
|
38
43
|
* Not `assistant`: Cortex holds that slug for its console's own assistant.
|
|
39
44
|
*/
|
|
40
|
-
const AGENT_SLUG = "helper";
|
|
45
|
+
export const AGENT_SLUG = "helper";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The embedding model the stack asks for, and the dimension the image's
|
|
49
|
+
* migrations give the vector columns. `cortex setup` probes a provider with
|
|
50
|
+
* the same pair before reusing it for embeddings.
|
|
51
|
+
*/
|
|
52
|
+
export const EMBEDDING = { model: "text-embedding-3-small", dimension: 768 } as const;
|
|
41
53
|
|
|
42
54
|
const DEV_DEPENDENCIES = {
|
|
43
|
-
// `cortex apply` and the management commands, at the release that wrote the project.
|
|
55
|
+
// `cortex apply`, `cortex setup` and the management commands, at the release that wrote the project.
|
|
44
56
|
"@m6d/cortex-cli": `~${pkg.version}`,
|
|
45
57
|
"@types/bun": "latest",
|
|
58
|
+
// The two runners of `bun run dev` and `bun run dev:agent`.
|
|
59
|
+
concurrently: "^10.0.6",
|
|
46
60
|
cspell: "^9.6.4",
|
|
47
61
|
eslint: "^9",
|
|
48
62
|
"eslint-config-prettier": "^10.1.8",
|
|
49
63
|
"eslint-plugin-prettier": "^5.5.5",
|
|
50
64
|
fallow: "^3.9.1",
|
|
65
|
+
mprocs: "^0.9.6",
|
|
51
66
|
prettier: "^3.6.2",
|
|
52
67
|
typescript: "^5",
|
|
53
68
|
"typescript-eslint": "^8.65.0",
|
|
54
69
|
};
|
|
55
70
|
|
|
56
|
-
|
|
71
|
+
/**
|
|
72
|
+
* `setup` puts `cortex setup` among the processes `bun run dev` starts; `env`
|
|
73
|
+
* holds values `cortex new` was given for `.env`, such as the model provider,
|
|
74
|
+
* which is then written beside `.env.example` with them filled in.
|
|
75
|
+
*/
|
|
76
|
+
type Input = {
|
|
77
|
+
name: string;
|
|
78
|
+
datasets: boolean;
|
|
79
|
+
setup: boolean;
|
|
80
|
+
ports: Ports;
|
|
81
|
+
env?: Record<string, string>;
|
|
82
|
+
};
|
|
57
83
|
|
|
58
84
|
/** The input, and the name the backend is registered under in Cortex. */
|
|
59
85
|
type Project = Input & { server: string };
|
|
60
86
|
|
|
87
|
+
/**
|
|
88
|
+
* `text` with each `KEY=` line named in `values` set to its value, and every
|
|
89
|
+
* other line left as written. `cortex new` fills `.env` with it, and
|
|
90
|
+
* `cortex setup` the `.env` it asks the model provider for.
|
|
91
|
+
*/
|
|
92
|
+
export function withEnvValues(text: string, values: Record<string, string>) {
|
|
93
|
+
return Object.entries(values).reduce(
|
|
94
|
+
(filled, [key, value]) =>
|
|
95
|
+
filled.replace(new RegExp(`^${key}=.*$`, "m"), () => `${key}=${value}`),
|
|
96
|
+
text,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** What `bun run dev` starts side by side, named as the runners label them. */
|
|
101
|
+
function devProcesses(setup: boolean) {
|
|
102
|
+
return {
|
|
103
|
+
cortex: "docker compose up",
|
|
104
|
+
server: "bun run dev:server",
|
|
105
|
+
...(setup ? { setup: "bun cortex setup --wait --yes" } : {}),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** One runner's command line: the process names, then each command quoted. */
|
|
110
|
+
function runAll(runner: "mprocs" | "concurrently", processes: Record<string, string>) {
|
|
111
|
+
const commands = Object.values(processes).map((command) => `"${command}"`);
|
|
112
|
+
return `${runner} --names ${Object.keys(processes).join(",")} ${commands.join(" ")}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
61
115
|
/** The name as the manifest `id` takes it: lowercase words and hyphens. */
|
|
62
116
|
export function manifestIdOf(name: string) {
|
|
63
117
|
return name
|
|
@@ -66,7 +120,9 @@ export function manifestIdOf(name: string) {
|
|
|
66
120
|
.replace(/^-+|-+$/g, "");
|
|
67
121
|
}
|
|
68
122
|
|
|
69
|
-
function packageJson({ name, datasets }: Project) {
|
|
123
|
+
function packageJson({ name, datasets, setup }: Project) {
|
|
124
|
+
const processes = devProcesses(setup);
|
|
125
|
+
|
|
70
126
|
return `${JSON.stringify(
|
|
71
127
|
{
|
|
72
128
|
name,
|
|
@@ -80,7 +136,11 @@ function packageJson({ name, datasets }: Project) {
|
|
|
80
136
|
// analyses — dead code, duplication, complexity — over everything.
|
|
81
137
|
audit: "fallow",
|
|
82
138
|
check: "tsc --noEmit",
|
|
83
|
-
|
|
139
|
+
// The same processes twice: a dashboard for people, and one
|
|
140
|
+
// labelled log for coding agents and CI.
|
|
141
|
+
dev: runAll("mprocs", processes),
|
|
142
|
+
"dev:agent": runAll("concurrently", processes),
|
|
143
|
+
"dev:server": "bun run --hot index.ts",
|
|
84
144
|
// A no-op where setup.ts is not copied in, as in an image build.
|
|
85
145
|
postinstall: "bun run --if-present setup.ts",
|
|
86
146
|
format: "prettier --write .",
|
|
@@ -90,7 +150,7 @@ function packageJson({ name, datasets }: Project) {
|
|
|
90
150
|
verify: "bun run format:check && bun run lint && bun run spellcheck && bun run check && bun run audit",
|
|
91
151
|
},
|
|
92
152
|
dependencies: {
|
|
93
|
-
"@m6d/cortex": "~0.
|
|
153
|
+
"@m6d/cortex": "~0.2.0",
|
|
94
154
|
// drizzle 1.0 is in beta; this is the build `@m6d/cortex/drizzle` is tested against.
|
|
95
155
|
...(datasets ? { "drizzle-orm": "1.0.0-beta.15-859cf75" } : {}),
|
|
96
156
|
hono: "^4.12.3",
|
|
@@ -118,16 +178,20 @@ ${datasets ? `import { datasets } from "./datasets";\n` : ""}import { server } f
|
|
|
118
178
|
const app = new Hono();
|
|
119
179
|
app.route("/cortex", server);
|
|
120
180
|
${datasets ? `app.route("/datasets", datasets);\n` : ""}
|
|
121
|
-
export default { port:
|
|
181
|
+
export default { port: {{port.server}}, fetch: app.fetch };
|
|
122
182
|
`;
|
|
123
183
|
}
|
|
124
184
|
|
|
125
|
-
function manifestTs({ name,
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
const serverUrl = process.env
|
|
129
|
-
if (!serverUrl)
|
|
130
|
-
|
|
185
|
+
function manifestTs({ name, datasets }: Project) {
|
|
186
|
+
const fromEnv = datasets
|
|
187
|
+
? `/** The server this backend is registered as, and where Cortex reaches it, from .env. */
|
|
188
|
+
const { CORTEX_SERVER_NAME: server, CORTEX_SERVER_URL: serverUrl } = process.env;
|
|
189
|
+
if (!server || !serverUrl) {
|
|
190
|
+
throw new Error("Apply from this project: its .env names the server.");
|
|
191
|
+
}`
|
|
192
|
+
: `/** The server this backend is registered as, from .env. */
|
|
193
|
+
const server = process.env["CORTEX_SERVER_NAME"];
|
|
194
|
+
if (!server) throw new Error("Apply from this project: its .env names the server.");`;
|
|
131
195
|
const datasetHosts = `
|
|
132
196
|
// The orders this backend serves (datasets.ts), for every agent on its server.
|
|
133
197
|
datasetHosts: [
|
|
@@ -137,7 +201,7 @@ if (!serverUrl) throw new Error("Set CORTEX_SERVER_URL in .env before applying t
|
|
|
137
201
|
credentials: [],
|
|
138
202
|
expose: ["orders"],
|
|
139
203
|
pinned: ["orders"],
|
|
140
|
-
audience: { server
|
|
204
|
+
audience: { server },
|
|
141
205
|
},
|
|
142
206
|
],`;
|
|
143
207
|
|
|
@@ -145,17 +209,19 @@ if (!serverUrl) throw new Error("Set CORTEX_SERVER_URL in .env before applying t
|
|
|
145
209
|
|
|
146
210
|
/*
|
|
147
211
|
* What this project declares in Cortex, which \`cortex apply\` makes Cortex
|
|
148
|
-
* match. The agent runs on
|
|
149
|
-
*
|
|
212
|
+
* match. The agent runs on this backend's server, whose variable its prompt
|
|
213
|
+
* reads and whose native tool it calls.
|
|
150
214
|
*/
|
|
151
|
-
|
|
215
|
+
|
|
216
|
+
${fromEnv}
|
|
217
|
+
|
|
152
218
|
export default defineManifest({
|
|
153
219
|
id: ${JSON.stringify(manifestIdOf(name))},
|
|
154
220
|
agents: [
|
|
155
221
|
{
|
|
156
222
|
slug: "${AGENT_SLUG}",
|
|
157
223
|
name: "Helper",
|
|
158
|
-
server
|
|
224
|
+
server,
|
|
159
225
|
prompt: "You are a helpful assistant. Address the user as {{userName}}.",
|
|
160
226
|
// No identity provider yet: every caller is one shared identity.
|
|
161
227
|
auth: { kind: "anonymous" },
|
|
@@ -165,35 +231,21 @@ export default defineManifest({
|
|
|
165
231
|
`;
|
|
166
232
|
}
|
|
167
233
|
|
|
168
|
-
/**
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
#
|
|
234
|
+
/** Every setting the project has, which setup.ts copies into .env, generating the secrets. */
|
|
235
|
+
function envExample({ server }: Project) {
|
|
236
|
+
return `# Every setting this project has. Bun loads .env from the directory you run
|
|
237
|
+
# in, into the backend and the \`cortex\` CLI; docker compose hands it to the
|
|
238
|
+
# Cortex containers and reads COMPOSE_PROJECT_NAME from it.
|
|
172
239
|
#
|
|
173
240
|
# This is the committed copy. \`bun install\` writes .env from it, generating
|
|
174
|
-
# the
|
|
241
|
+
# the secrets it leaves blank, and never changes a value .env already holds.
|
|
175
242
|
|
|
176
243
|
# The Compose project this checkout's Cortex stack runs as, drawn for each
|
|
177
244
|
# checkout, so a clone never shares another checkout's containers or volumes.
|
|
178
245
|
COMPOSE_PROJECT_NAME=
|
|
179
246
|
|
|
180
|
-
#
|
|
181
|
-
#
|
|
182
|
-
CORTEX_SERVER_KEY=
|
|
183
|
-
# Where Cortex reaches this backend from inside its containers.
|
|
184
|
-
CORTEX_SERVER_URL=${SERVER_URL}
|
|
185
|
-
`;
|
|
186
|
-
|
|
187
|
-
/** Cortex's settings, which setup.ts copies into cortex.env, generating the secrets. */
|
|
188
|
-
const CORTEX_ENV = `# Cortex's settings, which docker compose hands the Cortex containers in
|
|
189
|
-
# docker-compose.yml. The backend never loads cortex.env.
|
|
190
|
-
#
|
|
191
|
-
# This is the committed copy. \`bun install\` writes cortex.env from it,
|
|
192
|
-
# generating the secrets it leaves blank, and never changes a value
|
|
193
|
-
# cortex.env already holds.
|
|
194
|
-
|
|
195
|
-
# Cortex's first admin, created when the stack first starts, and
|
|
196
|
-
# the management key \`cortex login\` takes.
|
|
247
|
+
# Cortex's first admin, created when the stack first starts, and its
|
|
248
|
+
# management key.
|
|
197
249
|
DEFAULT_ADMIN_EMAIL=admin@example.com
|
|
198
250
|
DEFAULT_ADMIN_PASSWORD=
|
|
199
251
|
BOOTSTRAP_MANAGEMENT_KEY=
|
|
@@ -201,21 +253,47 @@ BOOTSTRAP_MANAGEMENT_KEY=
|
|
|
201
253
|
BETTER_AUTH_SECRET=
|
|
202
254
|
SECRETS_MASTER_KEY=
|
|
203
255
|
# Any OpenAI-compatible embeddings endpoint and model. Tool search and
|
|
204
|
-
# knowledge collections embed with it.
|
|
256
|
+
# knowledge collections embed with it, and stay off while the key is blank.
|
|
205
257
|
EMBEDDING_BASE_URL=https://api.openai.com/v1
|
|
206
|
-
EMBEDDING_API_KEY=
|
|
207
|
-
EMBEDDING_MODEL
|
|
258
|
+
EMBEDDING_API_KEY=
|
|
259
|
+
EMBEDDING_MODEL=${EMBEDDING.model}
|
|
260
|
+
# The private hosts Cortex's containers may call, and send a secret to over
|
|
261
|
+
# plain http, comma-separated: this machine, for the backend. A model provider
|
|
262
|
+
# on a private network goes here too. The policy admits a host by name,
|
|
263
|
+
# whatever the port: see README.md.
|
|
264
|
+
EGRESS_ALLOWED_HOSTS=${DOCKER_HOST}
|
|
265
|
+
|
|
266
|
+
# The Cortex \`cortex apply\` and \`cortex setup\` talk to: this checkout's,
|
|
267
|
+
# under its management key, so neither needs \`cortex login\`. \`-p <profile>\`
|
|
268
|
+
# still picks a saved one.
|
|
269
|
+
CORTEX_URL=http://localhost:{{port.cortex}}
|
|
270
|
+
CORTEX_KEY=\${BOOTSTRAP_MANAGEMENT_KEY}
|
|
271
|
+
|
|
272
|
+
# This backend as Cortex registers it: its name, where Cortex's containers
|
|
273
|
+
# reach it, and the key Cortex presents on every call to it.
|
|
274
|
+
CORTEX_SERVER_NAME=${server}
|
|
275
|
+
CORTEX_SERVER_URL=${SERVER_URL}
|
|
276
|
+
CORTEX_SERVER_KEY=
|
|
277
|
+
|
|
278
|
+
# The model provider \`cortex setup\` registers and the server's agents run on.
|
|
279
|
+
# While these are blank, setup registers the server and applies nothing.
|
|
280
|
+
MODEL_PROVIDER_NAME=
|
|
281
|
+
MODEL_PROVIDER_URL=
|
|
282
|
+
MODEL_PROVIDER_API_KEY=
|
|
283
|
+
MODEL_PROVIDER_MODEL=
|
|
208
284
|
`;
|
|
285
|
+
}
|
|
209
286
|
|
|
210
287
|
/** Cortex itself: its app, worker and engine, all on the one Cortex image, and their stores. */
|
|
211
288
|
function dockerCompose() {
|
|
212
289
|
return `# Cortex, for this project's agents to run on: the console at
|
|
213
290
|
# http://localhost:{{port.cortex}}, the engine widgets and trigger callers reach at
|
|
214
|
-
# http://localhost:{{port.engine}}, and the stores they keep state in. \`
|
|
215
|
-
#
|
|
291
|
+
# http://localhost:{{port.engine}}, and the stores they keep state in. \`bun run dev\`
|
|
292
|
+
# starts it; .env holds its secrets.
|
|
216
293
|
#
|
|
217
|
-
# The
|
|
218
|
-
# machine. Move one and the URLs that name it here and in README.md
|
|
294
|
+
# The host ports were picked at scaffold time from what was free on this
|
|
295
|
+
# machine. Move one and the URLs that name it here, in .env and in README.md
|
|
296
|
+
# follow.
|
|
219
297
|
# CORTEX_VERSION, set in the shell, runs another release of the image.
|
|
220
298
|
#
|
|
221
299
|
# The console's browser uploads (knowledge documents) go straight to
|
|
@@ -229,8 +307,12 @@ function dockerCompose() {
|
|
|
229
307
|
x-cortex: &cortex
|
|
230
308
|
image: syscodeia/cortex:\${CORTEX_VERSION:-${CORTEX_VERSION}}
|
|
231
309
|
working_dir: /repo/apps/cortex
|
|
232
|
-
|
|
310
|
+
# The whole .env, the backend's settings too. What \`environment\` sets
|
|
311
|
+
# wins over it, so nothing there can move these containers' ports or mode.
|
|
312
|
+
env_file: .env
|
|
233
313
|
environment: &cortex-environment
|
|
314
|
+
NODE_ENV: production
|
|
315
|
+
PORT: "3000"
|
|
234
316
|
DATABASE_URL: postgres://postgres:postgres@postgres:5432/cortex
|
|
235
317
|
REDIS_URL: redis://redis:6379
|
|
236
318
|
S3_ENDPOINT: http://minio:9000
|
|
@@ -240,15 +322,11 @@ x-cortex: &cortex
|
|
|
240
322
|
S3_BUCKET: cortex
|
|
241
323
|
BETTER_AUTH_ALLOWED_HOSTS: localhost:{{port.cortex}},127.0.0.1:{{port.cortex}}
|
|
242
324
|
# The dimension the image's migrations give the vector columns.
|
|
243
|
-
EMBEDDING_DIMENSION: "
|
|
325
|
+
EMBEDDING_DIMENSION: "${EMBEDDING.dimension}"
|
|
244
326
|
# The engine's public origin (browsers, trigger callers) and the
|
|
245
327
|
# address the app reaches it at inside the stack.
|
|
246
328
|
ENGINE_PUBLIC_URL: http://localhost:{{port.engine}}
|
|
247
329
|
ENGINE_INTERNAL_URL: http://engine:3001
|
|
248
|
-
# This backend is a private address seen from the containers, and the
|
|
249
|
-
# server key travels to it over plain http. The policy admits a host
|
|
250
|
-
# by name, whatever the port: see README.md.
|
|
251
|
-
EGRESS_ALLOWED_HOSTS: host.docker.internal
|
|
252
330
|
# Docker Desktop resolves host.docker.internal on its own; Linux needs this.
|
|
253
331
|
extra_hosts:
|
|
254
332
|
- host.docker.internal:host-gateway
|
|
@@ -353,6 +431,7 @@ services:
|
|
|
353
431
|
command: ["bun", "src/engine/server.ts"]
|
|
354
432
|
environment:
|
|
355
433
|
<<: *cortex-environment
|
|
434
|
+
PORT: "3001"
|
|
356
435
|
# Browser origins allowed to call the engine: the console's, for its playground and assistant.
|
|
357
436
|
ENGINE_CORS_ORIGINS: http://localhost:{{port.cortex}},http://127.0.0.1:{{port.cortex}}
|
|
358
437
|
ports:
|
|
@@ -369,57 +448,27 @@ volumes:
|
|
|
369
448
|
`;
|
|
370
449
|
}
|
|
371
450
|
|
|
372
|
-
function
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
"*.sqlite",
|
|
393
|
-
"scratch.*",
|
|
394
|
-
".env*",
|
|
395
|
-
"**/*.min.js",
|
|
396
|
-
"**/*.min.css",
|
|
397
|
-
"**/db/migrations/**",
|
|
398
|
-
"**/i18n/**",
|
|
399
|
-
"docs",
|
|
400
|
-
".claude",
|
|
401
|
-
".angular",
|
|
402
|
-
".vercel/**",
|
|
403
|
-
"vendor/**",
|
|
404
|
-
".gitignore",
|
|
405
|
-
],
|
|
406
|
-
dictionaries: [
|
|
407
|
-
"typescript",
|
|
408
|
-
"node",
|
|
409
|
-
"npm",
|
|
410
|
-
"html",
|
|
411
|
-
"css",
|
|
412
|
-
"bash",
|
|
413
|
-
"companies",
|
|
414
|
-
"softwareTerms",
|
|
415
|
-
],
|
|
416
|
-
},
|
|
417
|
-
null,
|
|
418
|
-
4,
|
|
419
|
-
)}\n`;
|
|
420
|
-
}
|
|
451
|
+
function readme({ name, server, datasets, setup }: Project) {
|
|
452
|
+
const gettingStarted = setup
|
|
453
|
+
? `That starts Cortex (\`docker compose up\`), this backend, and \`cortex setup\` in one dashboard.
|
|
454
|
+
Setup waits for Cortex, registers this backend as the server \`${server}\`, adds the model provider
|
|
455
|
+
in \`.env\`, and applies the manifest. Then it prints where to chat with \`${AGENT_SLUG}\`, and the first
|
|
456
|
+
time, how to sign in. It adds only what is missing, so every \`bun run dev\` runs it again.
|
|
457
|
+
|
|
458
|
+
While \`MODEL_PROVIDER_*\` in \`.env\` is blank, setup registers the server and applies nothing:
|
|
459
|
+
Cortex publishes no agent without a model. Run \`bun cortex setup\` in a terminal to be asked for
|
|
460
|
+
one.`
|
|
461
|
+
: `That starts Cortex (\`docker compose up\`) and this backend in one dashboard. Then register the
|
|
462
|
+
backend and apply the manifest, either way:
|
|
463
|
+
|
|
464
|
+
- \`bun cortex setup\` in a terminal asks for a model provider, registers the server
|
|
465
|
+
\`${server}\`, and applies the manifest.
|
|
466
|
+
- By hand: open the console at http://localhost:{{port.cortex}} and sign in with
|
|
467
|
+
\`DEFAULT_ADMIN_EMAIL\` and \`DEFAULT_ADMIN_PASSWORD\` from \`.env\`. Add a model provider on the
|
|
468
|
+
**Model providers** page, and store \`CORTEX_SERVER_KEY\` from \`.env\` on the **Secrets** page.
|
|
469
|
+
On the **Servers** page, add a server named \`${server}\` at \`${SERVER_URL}/cortex\` with that
|
|
470
|
+
secret as its key, and pick the model its agents run on. Then run \`bun cortex apply\`.`;
|
|
421
471
|
|
|
422
|
-
function readme({ name, server, datasets }: Project) {
|
|
423
472
|
return `# ${name}
|
|
424
473
|
|
|
425
474
|
A backend for Cortex agents, and Cortex itself to run them on.
|
|
@@ -433,48 +482,38 @@ A backend for Cortex agents, and Cortex itself to run them on.
|
|
|
433
482
|
- \`cortex.manifest.ts\` declares the \`${AGENT_SLUG}\` agent on this server${
|
|
434
483
|
datasets ? " and the dataset host" : ""
|
|
435
484
|
}. \`cortex apply\` makes Cortex match it.
|
|
436
|
-
- \`docker-compose.yml\` runs Cortex: its app, worker and engine, and the stores they use
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
485
|
+
- \`docker-compose.yml\` runs Cortex: its app, worker and engine, and the stores they use.
|
|
486
|
+
- \`.env\` holds every setting: Cortex's secrets, this backend's key, and the model provider.
|
|
487
|
+
\`setup.ts\` runs on every \`bun install\`: it writes \`.env\` from the committed \`.env.example\` and
|
|
488
|
+
generates each secret left blank, so a fresh clone gets its own secrets and its own Compose stack.
|
|
489
|
+
It never changes a value already set. \`.dockerignore\` keeps both files out of any image built
|
|
490
|
+
here.
|
|
442
491
|
|
|
443
492
|
## Getting started
|
|
444
493
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
\`\`\`sh
|
|
464
|
-
bun cortex login --url http://localhost:{{port.cortex}}
|
|
465
|
-
bun cortex apply
|
|
466
|
-
\`\`\`
|
|
467
|
-
|
|
468
|
-
Then chat with \`${AGENT_SLUG}\` on the console's **Playground** page. Edit \`cortex.manifest.ts\` and
|
|
469
|
-
apply again to change it; an apply changes only what the edit changed.
|
|
470
|
-
|
|
471
|
-
The backend listens on http://localhost:${SERVER_PORT}. Move it in \`index.ts\`, and
|
|
494
|
+
On a fresh clone, run \`bun install\` first (\`cortex new\` already did): it writes \`.env\`.
|
|
495
|
+
|
|
496
|
+
\`\`\`sh
|
|
497
|
+
bun run dev
|
|
498
|
+
\`\`\`
|
|
499
|
+
|
|
500
|
+
${gettingStarted}
|
|
501
|
+
|
|
502
|
+
Then chat with \`${AGENT_SLUG}\` on the console's **Playground** page. Edit \`cortex.manifest.ts\` and run
|
|
503
|
+
\`bun cortex apply\` to change it; an apply changes only what the edit changed. The CLI reaches this
|
|
504
|
+
checkout's Cortex through \`CORTEX_URL\` and \`CORTEX_KEY\` in \`.env\`, so it needs no login, and
|
|
505
|
+
\`-p <profile>\` picks a saved one instead.
|
|
506
|
+
|
|
507
|
+
\`bun run dev:agent\` runs the same processes without the dashboard, each log line labelled with
|
|
508
|
+
its process, for coding agents and CI.
|
|
509
|
+
|
|
510
|
+
The backend listens on http://localhost:{{port.server}}. Move it in \`index.ts\`, and
|
|
472
511
|
\`CORTEX_SERVER_URL\` and the server's URL in Cortex with it.
|
|
473
512
|
|
|
474
513
|
## Reaching this machine
|
|
475
514
|
|
|
476
515
|
Cortex's containers reach the backend at \`host.docker.internal\`, which \`EGRESS_ALLOWED_HOSTS\` in
|
|
477
|
-
\`
|
|
516
|
+
\`.env\` admits. The policy admits a host by name, not by port, so every HTTP tool, MCP
|
|
478
517
|
server and credential you define in Cortex can reach any port this machine listens
|
|
479
518
|
on, over plain http. Keep the stack on your own machine, and define only tools you trust.
|
|
480
519
|
|
|
@@ -501,14 +540,14 @@ export function scaffoldFiles(input: Input) {
|
|
|
501
540
|
"docker-compose.yml": dockerCompose(),
|
|
502
541
|
"package.json": packageJson(project),
|
|
503
542
|
"tsconfig.json": tsconfigJson,
|
|
504
|
-
".env.example":
|
|
505
|
-
"
|
|
543
|
+
".env.example": envExample(project),
|
|
544
|
+
...(project.env ? { ".env": withEnvValues(envExample(project), project.env) } : {}),
|
|
506
545
|
".gitignore": gitignoreText,
|
|
507
546
|
".dockerignore": dockerignoreText,
|
|
508
547
|
"README.md": readme(project),
|
|
509
548
|
"eslint.config.mjs": eslintConfig,
|
|
510
549
|
"prettier.config.mjs": prettierConfig,
|
|
511
|
-
"cspell.json":
|
|
550
|
+
"cspell.json": cspellJson,
|
|
512
551
|
".fallowrc.jsonc": fallowrc,
|
|
513
552
|
} satisfies Record<string, string>;
|
|
514
553
|
|
package/src/scaffold/ports.ts
CHANGED
|
@@ -4,10 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Only the app and the engine are published: the browser and this
|
|
6
6
|
* project's callers reach them, and nothing outside the stack reaches its
|
|
7
|
-
* databases.
|
|
8
|
-
* the same machine, so each project draws its
|
|
9
|
-
*
|
|
10
|
-
* to choose, and stays the documented default.
|
|
7
|
+
* databases. The backend listens on the third. Fixed ports would collide with
|
|
8
|
+
* the next project scaffolded on the same machine, so each project draws its
|
|
9
|
+
* own. Container ports stay canonical.
|
|
11
10
|
*
|
|
12
11
|
* Every generated file spells its ports `{{port.<name>}}` and gets them filled in
|
|
13
12
|
* by `applyPorts`. That is the point of the token: compose and the README
|
|
@@ -61,12 +60,12 @@ export function allocatePorts() {
|
|
|
61
60
|
return port;
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
return { cortex: next(), engine: next() };
|
|
63
|
+
return { cortex: next(), engine: next(), server: next() };
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
export type Ports = ReturnType<typeof allocatePorts>;
|
|
68
67
|
|
|
69
|
-
/** Fills in
|
|
68
|
+
/** Fills in every `{{port.<name>}}`. Applied to every file, once, at the end. */
|
|
70
69
|
export function applyPorts(contents: string, ports: Ports) {
|
|
71
70
|
return Object.entries(ports).reduce(
|
|
72
71
|
(text, [name, port]) => text.replaceAll(`{{port.${name}}}`, String(port)),
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.2",
|
|
3
|
+
"language": "en",
|
|
4
|
+
"words": ["mprocs"],
|
|
5
|
+
"ignorePaths": [
|
|
6
|
+
"node_modules/**",
|
|
7
|
+
"out/**",
|
|
8
|
+
"build/**",
|
|
9
|
+
"**/dist/**",
|
|
10
|
+
"coverage/**",
|
|
11
|
+
"*.tsbuildinfo",
|
|
12
|
+
"bun.lock",
|
|
13
|
+
"package-lock.json",
|
|
14
|
+
"pnpm-lock.yaml",
|
|
15
|
+
"yarn.lock",
|
|
16
|
+
"*.db",
|
|
17
|
+
"*.sqlite",
|
|
18
|
+
"scratch.*",
|
|
19
|
+
".env*",
|
|
20
|
+
"**/*.min.js",
|
|
21
|
+
"**/*.min.css",
|
|
22
|
+
"**/db/migrations/**",
|
|
23
|
+
"**/i18n/**",
|
|
24
|
+
"docs",
|
|
25
|
+
".claude",
|
|
26
|
+
".angular",
|
|
27
|
+
".vercel/**",
|
|
28
|
+
"vendor/**",
|
|
29
|
+
".gitignore"
|
|
30
|
+
],
|
|
31
|
+
"dictionaries": [
|
|
32
|
+
"typescript",
|
|
33
|
+
"node",
|
|
34
|
+
"npm",
|
|
35
|
+
"html",
|
|
36
|
+
"css",
|
|
37
|
+
"bash",
|
|
38
|
+
"companies",
|
|
39
|
+
"softwareTerms"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
#
|
|
1
|
+
# .env never goes into an image: it holds this checkout's secrets. With
|
|
2
|
+
# .env.example left out too, setup.ts writes none during the image's own
|
|
3
|
+
# `bun install`.
|
|
4
4
|
.env*
|
|
5
|
-
cortex.env*
|
|
6
5
|
node_modules/
|
|
7
6
|
.git/
|