create-cogitator-app 0.3.1 → 0.4.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/dist/lib.js CHANGED
@@ -1,15 +1,18 @@
1
1
  // src/scaffold.ts
2
2
  import fs from "fs";
3
- import path from "path";
3
+ import path3 from "path";
4
4
  import * as p from "@clack/prompts";
5
5
 
6
6
  // src/utils/providers.ts
7
7
  var defaultModels = {
8
- ollama: "qwen3:8b",
8
+ ollama: "qwen3.5:9b",
9
9
  openai: "gpt-6.1-sol",
10
10
  anthropic: "claude-sonnet-5-5",
11
11
  google: "gemini-3.8-flash"
12
12
  };
13
+ function modelFor(options) {
14
+ return options.model?.trim() || defaultModels[options.provider];
15
+ }
13
16
  function providerEnvKey(provider) {
14
17
  const map = {
15
18
  ollama: "OLLAMA_BASE_URL",
@@ -19,65 +22,149 @@ function providerEnvKey(provider) {
19
22
  };
20
23
  return map[provider];
21
24
  }
22
- function providerConfig(provider) {
23
- switch (provider) {
24
- case "ollama":
25
- return [
26
- ` llm: {`,
27
- ` defaultProvider: 'ollama',`,
28
- ` providers: {`,
29
- ` ollama: { baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434' },`,
30
- ` },`,
31
- ` },`
32
- ].join("\n");
33
- case "openai":
34
- return [
35
- ` llm: {`,
36
- ` defaultProvider: 'openai',`,
37
- ` providers: {`,
38
- ` openai: { apiKey: process.env.OPENAI_API_KEY! },`,
39
- ` },`,
40
- ` },`
41
- ].join("\n");
42
- case "anthropic":
43
- return [
44
- ` llm: {`,
45
- ` defaultProvider: 'anthropic',`,
46
- ` providers: {`,
47
- ` anthropic: { apiKey: process.env.ANTHROPIC_API_KEY! },`,
48
- ` },`,
49
- ` },`
50
- ].join("\n");
51
- case "google":
52
- return [
53
- ` llm: {`,
54
- ` defaultProvider: 'google',`,
55
- ` providers: {`,
56
- ` google: { apiKey: process.env.GOOGLE_API_KEY! },`,
57
- ` },`,
58
- ` },`
59
- ].join("\n");
25
+ function apiKeyExpression(envKey, access) {
26
+ return access === "requireEnv" ? `requireEnv('${envKey}')` : `process.env.${envKey} ?? ''`;
27
+ }
28
+ function providerConfig(provider, access = "process-env") {
29
+ if (provider === "ollama") {
30
+ return [
31
+ ` llm: {`,
32
+ ` defaultProvider: 'ollama',`,
33
+ ` providers: {`,
34
+ ` ollama: { baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434' },`,
35
+ ` },`,
36
+ ` },`
37
+ ].join("\n");
60
38
  }
39
+ return [
40
+ ` llm: {`,
41
+ ` defaultProvider: '${provider}',`,
42
+ ` providers: {`,
43
+ ` ${provider}: { apiKey: ${apiKeyExpression(providerEnvKey(provider), access)} },`,
44
+ ` },`,
45
+ ` },`
46
+ ].join("\n");
61
47
  }
62
48
 
63
49
  // src/templates/versions.ts
50
+ import { existsSync, readFileSync } from "fs";
51
+ import path from "path";
52
+ import { fileURLToPath } from "url";
64
53
  var ZOD_VERSION = "^4.6.5";
54
+ var IOREDIS_VERSION = "^6.0.0";
55
+ var OWN_NAME = "create-cogitator-app";
56
+ var SCOPE = "@cogitator-ai/";
57
+ function isRecord(value) {
58
+ return typeof value === "object" && value !== null && !Array.isArray(value);
59
+ }
60
+ function readManifest(file) {
61
+ const parsed = JSON.parse(readFileSync(file, "utf-8"));
62
+ if (!isRecord(parsed)) throw new Error(`${file} is not a package manifest`);
63
+ return parsed;
64
+ }
65
+ function findOwnRoot(from) {
66
+ let dir = from;
67
+ while (true) {
68
+ const manifest = path.join(dir, "package.json");
69
+ if (existsSync(manifest) && readManifest(manifest).name === OWN_NAME) return dir;
70
+ const parent = path.dirname(dir);
71
+ if (parent === dir) throw new Error(`Cannot find the ${OWN_NAME} package.json above ${from}`);
72
+ dir = parent;
73
+ }
74
+ }
75
+ function linkedVersion(packageRoot, name) {
76
+ const manifest = path.join(packageRoot, "node_modules", name, "package.json");
77
+ if (!existsSync(manifest)) {
78
+ throw new Error(`Cannot resolve the version of ${name}: ${manifest} does not exist`);
79
+ }
80
+ const version = readManifest(manifest).version;
81
+ if (typeof version !== "string") throw new Error(`${manifest} has no version`);
82
+ return version;
83
+ }
84
+ function readCogitatorVersions(packageRoot) {
85
+ const devDependencies = readManifest(path.join(packageRoot, "package.json")).devDependencies;
86
+ const versions = {};
87
+ if (!isRecord(devDependencies)) return versions;
88
+ for (const [name, range] of Object.entries(devDependencies)) {
89
+ if (!name.startsWith(SCOPE) || typeof range !== "string") continue;
90
+ versions[name] = range.startsWith("workspace:") ? `^${linkedVersion(packageRoot, name)}` : range;
91
+ }
92
+ return versions;
93
+ }
94
+ var resolved;
95
+ function cogitatorVersion(name) {
96
+ resolved ??= readCogitatorVersions(findOwnRoot(path.dirname(fileURLToPath(import.meta.url))));
97
+ const range = resolved[name];
98
+ if (!range) {
99
+ throw new Error(`${name} is not a devDependency of ${OWN_NAME}, so its version is unknown`);
100
+ }
101
+ return range;
102
+ }
103
+
104
+ // src/templates/shared.ts
105
+ function tsString(value) {
106
+ const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace(/\p{Zl}/gu, "\\u2028").replace(/\p{Zp}/gu, "\\u2029");
107
+ return `'${escaped}'`;
108
+ }
109
+ function needsApiKey(provider) {
110
+ return provider !== "ollama";
111
+ }
112
+ function envHelperFiles(provider) {
113
+ if (!needsApiKey(provider)) return [];
114
+ const content = [
115
+ `export function requireEnv(name: string): string {`,
116
+ ` const value = process.env[name]`,
117
+ ` if (!value) {`,
118
+ ` throw new Error(\`\${name} is not set. Copy .env.example to .env and fill it in.\`)`,
119
+ ` }`,
120
+ ` return value`,
121
+ `}`,
122
+ ``
123
+ ].join("\n");
124
+ return [{ path: "src/env.ts", content }];
125
+ }
126
+ function envHelperImport(provider) {
127
+ return needsApiKey(provider) ? [`import { requireEnv } from './env.js'`] : [];
128
+ }
129
+ var RUN_MAIN = [
130
+ `main().catch((error: unknown) => {`,
131
+ ` console.error(error)`,
132
+ ` process.exitCode = 1`,
133
+ `})`,
134
+ ``
135
+ ];
136
+ function scriptTemplateScripts() {
137
+ return {
138
+ dev: "tsx watch --env-file-if-exists=.env src/index.ts",
139
+ start: "tsx --env-file-if-exists=.env src/index.ts",
140
+ build: "tsc",
141
+ typecheck: "tsc --noEmit"
142
+ };
143
+ }
144
+ function scriptTemplateDevDependencies() {
145
+ return {
146
+ typescript: "^5.8.0",
147
+ tsx: "^4.19.0",
148
+ "@types/node": "^22.0.0"
149
+ };
150
+ }
65
151
 
66
152
  // src/templates/basic.ts
67
153
  var basicTemplate = {
68
154
  files(options) {
69
- const model = defaultModels[options.provider];
155
+ const model = modelFor(options);
70
156
  const indexTs = [
71
157
  `import { Cogitator, Agent } from '@cogitator-ai/core'`,
72
158
  `import { searchTool, summarizeTool } from './tools.js'`,
159
+ ...envHelperImport(options.provider),
73
160
  ``,
74
161
  `const cogitator = new Cogitator({`,
75
- providerConfig(options.provider),
162
+ providerConfig(options.provider, "requireEnv"),
76
163
  `})`,
77
164
  ``,
78
165
  `const agent = new Agent({`,
79
- ` name: '${options.name}-agent',`,
80
- ` model: '${model}',`,
166
+ ` name: ${tsString(`${options.name}-agent`)},`,
167
+ ` model: ${tsString(model)},`,
81
168
  ` instructions: 'You are a helpful AI assistant. Use your tools to help the user.',`,
82
169
  ` tools: [searchTool, summarizeTool],`,
83
170
  ` temperature: 0.7,`,
@@ -91,8 +178,7 @@ var basicTemplate = {
91
178
  ` console.log(result.output)`,
92
179
  `}`,
93
180
  ``,
94
- `main().catch(console.error)`,
95
- ``
181
+ ...RUN_MAIN
96
182
  ].join("\n");
97
183
  const toolsTs = [
98
184
  `import { tool } from '@cogitator-ai/core'`,
@@ -125,42 +211,36 @@ var basicTemplate = {
125
211
  ].join("\n");
126
212
  return [
127
213
  { path: "src/index.ts", content: indexTs },
128
- { path: "src/tools.ts", content: toolsTs }
214
+ { path: "src/tools.ts", content: toolsTs },
215
+ ...envHelperFiles(options.provider)
129
216
  ];
130
217
  },
131
218
  dependencies() {
132
219
  return {
133
- "@cogitator-ai/core": "latest",
220
+ "@cogitator-ai/core": cogitatorVersion("@cogitator-ai/core"),
134
221
  zod: ZOD_VERSION
135
222
  };
136
223
  },
137
224
  devDependencies() {
138
- return {
139
- typescript: "^5.8.0",
140
- tsx: "^4.19.0",
141
- "@types/node": "^22.0.0"
142
- };
225
+ return scriptTemplateDevDependencies();
143
226
  },
144
227
  scripts() {
145
- return {
146
- dev: "tsx watch src/index.ts",
147
- start: "tsx src/index.ts",
148
- build: "tsc",
149
- typecheck: "tsc --noEmit"
150
- };
228
+ return scriptTemplateScripts();
151
229
  }
152
230
  };
153
231
 
154
232
  // src/templates/memory.ts
155
233
  var memoryTemplate = {
234
+ memoryAdapter: "redis",
156
235
  files(options) {
157
- const model = defaultModels[options.provider];
236
+ const model = modelFor(options);
158
237
  const indexTs = [
159
238
  `import { Cogitator, Agent } from '@cogitator-ai/core'`,
160
239
  `import { searchTool, noteTool } from './tools.js'`,
240
+ ...envHelperImport(options.provider),
161
241
  ``,
162
242
  `const cogitator = new Cogitator({`,
163
- providerConfig(options.provider),
243
+ providerConfig(options.provider, "requireEnv"),
164
244
  ` memory: {`,
165
245
  ` adapter: 'redis',`,
166
246
  ` redis: { url: process.env.REDIS_URL || 'redis://localhost:6379' },`,
@@ -168,8 +248,8 @@ var memoryTemplate = {
168
248
  `})`,
169
249
  ``,
170
250
  `const agent = new Agent({`,
171
- ` name: '${options.name}-agent',`,
172
- ` model: '${model}',`,
251
+ ` name: ${tsString(`${options.name}-agent`)},`,
252
+ ` model: ${tsString(model)},`,
173
253
  ` instructions: [`,
174
254
  ` 'You are an AI assistant with persistent memory.',`,
175
255
  ` 'You remember past conversations and user preferences.',`,
@@ -182,25 +262,26 @@ var memoryTemplate = {
182
262
  `async function main() {`,
183
263
  ` const threadId = 'demo-thread'`,
184
264
  ``,
185
- ` const result1 = await cogitator.run(agent, {`,
186
- ` input: 'My name is Alex and I prefer TypeScript.',`,
187
- ` threadId,`,
188
- ` useMemory: true,`,
189
- ` })`,
190
- ` console.log('Response 1:', result1.output)`,
191
- ``,
192
- ` const result2 = await cogitator.run(agent, {`,
193
- ` input: 'What is my name and preferred language?',`,
194
- ` threadId,`,
195
- ` useMemory: true,`,
196
- ` })`,
197
- ` console.log('Response 2:', result2.output)`,
198
- ``,
199
- ` await cogitator.close()`,
265
+ ` try {`,
266
+ ` const result1 = await cogitator.run(agent, {`,
267
+ ` input: 'My name is Alex and I prefer TypeScript.',`,
268
+ ` threadId,`,
269
+ ` useMemory: true,`,
270
+ ` })`,
271
+ ` console.log('Response 1:', result1.output)`,
272
+ ``,
273
+ ` const result2 = await cogitator.run(agent, {`,
274
+ ` input: 'What is my name and preferred language?',`,
275
+ ` threadId,`,
276
+ ` useMemory: true,`,
277
+ ` })`,
278
+ ` console.log('Response 2:', result2.output)`,
279
+ ` } finally {`,
280
+ ` await cogitator.close()`,
281
+ ` }`,
200
282
  `}`,
201
283
  ``,
202
- `main().catch(console.error)`,
203
- ``
284
+ ...RUN_MAIN
204
285
  ].join("\n");
205
286
  const toolsTs = [
206
287
  `import { tool } from '@cogitator-ai/core'`,
@@ -232,45 +313,38 @@ var memoryTemplate = {
232
313
  ].join("\n");
233
314
  return [
234
315
  { path: "src/index.ts", content: indexTs },
235
- { path: "src/tools.ts", content: toolsTs }
316
+ { path: "src/tools.ts", content: toolsTs },
317
+ ...envHelperFiles(options.provider)
236
318
  ];
237
319
  },
238
320
  dependencies() {
239
321
  return {
240
- "@cogitator-ai/core": "latest",
241
- "@cogitator-ai/memory": "latest",
242
- "@cogitator-ai/redis": "latest",
322
+ "@cogitator-ai/core": cogitatorVersion("@cogitator-ai/core"),
323
+ "@cogitator-ai/memory": cogitatorVersion("@cogitator-ai/memory"),
324
+ "@cogitator-ai/redis": cogitatorVersion("@cogitator-ai/redis"),
325
+ ioredis: IOREDIS_VERSION,
243
326
  zod: ZOD_VERSION
244
327
  };
245
328
  },
246
329
  devDependencies() {
247
- return {
248
- typescript: "^5.8.0",
249
- tsx: "^4.19.0",
250
- "@types/node": "^22.0.0"
251
- };
330
+ return scriptTemplateDevDependencies();
252
331
  },
253
332
  scripts() {
254
- return {
255
- dev: "tsx watch src/index.ts",
256
- start: "tsx src/index.ts",
257
- build: "tsc",
258
- typecheck: "tsc --noEmit"
259
- };
333
+ return scriptTemplateScripts();
260
334
  }
261
335
  };
262
336
 
263
337
  // src/templates/swarm.ts
264
338
  var swarmTemplate = {
265
339
  files(options) {
266
- const model = defaultModels[options.provider];
340
+ const model = modelFor(options);
267
341
  const researcherTs = [
268
342
  `import { Agent } from '@cogitator-ai/core'`,
269
343
  `import { searchTool } from '../tools.js'`,
270
344
  ``,
271
345
  `export const researcher = new Agent({`,
272
346
  ` name: 'researcher',`,
273
- ` model: '${model}',`,
347
+ ` model: ${tsString(model)},`,
274
348
  ` instructions: [`,
275
349
  ` 'You are a research specialist.',`,
276
350
  ` 'Use the search tool to find information.',`,
@@ -286,7 +360,7 @@ var swarmTemplate = {
286
360
  ``,
287
361
  `export const writer = new Agent({`,
288
362
  ` name: 'writer',`,
289
- ` model: '${model}',`,
363
+ ` model: ${tsString(model)},`,
290
364
  ` instructions: [`,
291
365
  ` 'You are a skilled content writer.',`,
292
366
  ` 'Take research findings and craft well-structured, engaging content.',`,
@@ -301,7 +375,7 @@ var swarmTemplate = {
301
375
  ``,
302
376
  `export const reviewer = new Agent({`,
303
377
  ` name: 'reviewer',`,
304
- ` model: '${model}',`,
378
+ ` model: ${tsString(model)},`,
305
379
  ` instructions: [`,
306
380
  ` 'You are a meticulous content reviewer.',`,
307
381
  ` 'Check for accuracy, grammar, and overall quality.',`,
@@ -333,13 +407,14 @@ var swarmTemplate = {
333
407
  `import { researcher } from './agents/researcher.js'`,
334
408
  `import { writer } from './agents/writer.js'`,
335
409
  `import { reviewer } from './agents/reviewer.js'`,
410
+ ...envHelperImport(options.provider),
336
411
  ``,
337
412
  `const cogitator = new Cogitator({`,
338
- providerConfig(options.provider),
413
+ providerConfig(options.provider, "requireEnv"),
339
414
  `})`,
340
415
  ``,
341
416
  `const team = new Swarm(cogitator, {`,
342
- ` name: '${options.name}-team',`,
417
+ ` name: ${tsString(`${options.name}-team`)},`,
343
418
  ` strategy: 'hierarchical',`,
344
419
  ` supervisor: reviewer,`,
345
420
  ` workers: [researcher, writer],`,
@@ -356,75 +431,68 @@ var swarmTemplate = {
356
431
  `async function main() {`,
357
432
  ` console.log('Starting swarm...')`,
358
433
  ``,
359
- ` const result = await team.run({`,
360
- ` input: 'Write a comprehensive article about AI agent orchestration.',`,
361
- ` })`,
362
- ``,
363
- ` console.log('Result:', result.output)`,
364
- ` console.log('Strategy:', team.strategyType)`,
434
+ ` try {`,
435
+ ` const result = await team.run({`,
436
+ ` input: 'Write a comprehensive article about AI agent orchestration.',`,
437
+ ` })`,
365
438
  ``,
366
- ` await team.close()`,
439
+ ` console.log('Result:', result.output)`,
440
+ ` console.log('Strategy:', team.strategyType)`,
441
+ ` } finally {`,
442
+ ` await team.close()`,
443
+ ` }`,
367
444
  `}`,
368
445
  ``,
369
- `main().catch(console.error)`,
370
- ``
446
+ ...RUN_MAIN
371
447
  ].join("\n");
372
448
  return [
373
449
  { path: "src/index.ts", content: indexTs },
374
450
  { path: "src/tools.ts", content: toolsTs },
375
451
  { path: "src/agents/researcher.ts", content: researcherTs },
376
452
  { path: "src/agents/writer.ts", content: writerTs },
377
- { path: "src/agents/reviewer.ts", content: reviewerTs }
453
+ { path: "src/agents/reviewer.ts", content: reviewerTs },
454
+ ...envHelperFiles(options.provider)
378
455
  ];
379
456
  },
380
457
  dependencies() {
381
458
  return {
382
- "@cogitator-ai/core": "latest",
383
- "@cogitator-ai/swarms": "latest",
459
+ "@cogitator-ai/core": cogitatorVersion("@cogitator-ai/core"),
460
+ "@cogitator-ai/swarms": cogitatorVersion("@cogitator-ai/swarms"),
384
461
  zod: ZOD_VERSION
385
462
  };
386
463
  },
387
464
  devDependencies() {
388
- return {
389
- typescript: "^5.8.0",
390
- tsx: "^4.19.0",
391
- "@types/node": "^22.0.0"
392
- };
465
+ return scriptTemplateDevDependencies();
393
466
  },
394
467
  scripts() {
395
- return {
396
- dev: "tsx watch src/index.ts",
397
- start: "tsx src/index.ts",
398
- build: "tsc",
399
- typecheck: "tsc --noEmit"
400
- };
468
+ return scriptTemplateScripts();
401
469
  }
402
470
  };
403
471
 
404
472
  // src/templates/workflow.ts
405
473
  var workflowTemplate = {
406
474
  files(options) {
407
- const model = defaultModels[options.provider];
475
+ const model = modelFor(options);
408
476
  const agentsTs = [
409
477
  `import { Agent } from '@cogitator-ai/core'`,
410
478
  ``,
411
479
  `export const analyzer = new Agent({`,
412
480
  ` name: 'analyzer',`,
413
- ` model: '${model}',`,
481
+ ` model: ${tsString(model)},`,
414
482
  ` instructions: 'You analyze input data and extract key information. Be thorough and structured.',`,
415
483
  ` temperature: 0.3,`,
416
484
  `})`,
417
485
  ``,
418
486
  `export const processor = new Agent({`,
419
487
  ` name: 'processor',`,
420
- ` model: '${model}',`,
488
+ ` model: ${tsString(model)},`,
421
489
  ` instructions: 'You process analyzed data and transform it into actionable insights.',`,
422
490
  ` temperature: 0.5,`,
423
491
  `})`,
424
492
  ``,
425
493
  `export const formatter = new Agent({`,
426
494
  ` name: 'formatter',`,
427
- ` model: '${model}',`,
495
+ ` model: ${tsString(model)},`,
428
496
  ` instructions: 'You format processed data into a clean, readable report.',`,
429
497
  ` temperature: 0.7,`,
430
498
  `})`,
@@ -439,9 +507,10 @@ var workflowTemplate = {
439
507
  ` functionNode,`,
440
508
  `} from '@cogitator-ai/workflows'`,
441
509
  `import { analyzer, processor, formatter } from './agents.js'`,
510
+ ...envHelperImport(options.provider),
442
511
  ``,
443
512
  `const cogitator = new Cogitator({`,
444
- providerConfig(options.provider),
513
+ providerConfig(options.provider, "requireEnv"),
445
514
  `})`,
446
515
  ``,
447
516
  `type WorkflowState = {`,
@@ -471,7 +540,7 @@ var workflowTemplate = {
471
540
  ` async (state) => state.report,`,
472
541
  `)`,
473
542
  ``,
474
- `const workflow = new WorkflowBuilder<WorkflowState>('${options.name}-workflow')`,
543
+ `const workflow = new WorkflowBuilder<WorkflowState>(${tsString(`${options.name}-workflow`)})`,
475
544
  ` .initialState({ input: '' })`,
476
545
  ` .entryPoint(analyzeNode.name)`,
477
546
  ` .addNode(analyzeNode.name, analyzeNode.fn)`,
@@ -489,48 +558,45 @@ var workflowTemplate = {
489
558
  ` input: 'Analyze the current state of AI agent frameworks and their adoption in enterprise.',`,
490
559
  ` })`,
491
560
  ``,
492
- ` console.log('Workflow completed!')`,
493
- ` console.log('Result:', JSON.stringify(result, null, 2))`,
561
+ ` if (result.error) throw result.error`,
562
+ ``,
563
+ ` console.log(\`Workflow completed in \${result.duration}ms\`)`,
564
+ ` console.log('Report:', result.state.report)`,
565
+ ` console.log('Nodes:', JSON.stringify(Object.fromEntries(result.nodeResults), null, 2))`,
494
566
  `}`,
495
567
  ``,
496
- `main().catch(console.error)`,
497
- ``
568
+ ...RUN_MAIN
498
569
  ].join("\n");
499
570
  return [
500
571
  { path: "src/index.ts", content: indexTs },
501
- { path: "src/agents.ts", content: agentsTs }
572
+ { path: "src/agents.ts", content: agentsTs },
573
+ ...envHelperFiles(options.provider)
502
574
  ];
503
575
  },
504
576
  dependencies() {
505
577
  return {
506
- "@cogitator-ai/core": "latest",
507
- "@cogitator-ai/workflows": "latest",
578
+ "@cogitator-ai/core": cogitatorVersion("@cogitator-ai/core"),
579
+ "@cogitator-ai/workflows": cogitatorVersion("@cogitator-ai/workflows"),
508
580
  zod: ZOD_VERSION
509
581
  };
510
582
  },
511
583
  devDependencies() {
512
- return {
513
- typescript: "^5.8.0",
514
- tsx: "^4.19.0",
515
- "@types/node": "^22.0.0"
516
- };
584
+ return scriptTemplateDevDependencies();
517
585
  },
518
586
  scripts() {
519
- return {
520
- dev: "tsx watch src/index.ts",
521
- start: "tsx src/index.ts",
522
- build: "tsc",
523
- typecheck: "tsc --noEmit"
524
- };
587
+ return scriptTemplateScripts();
525
588
  }
526
589
  };
527
590
 
528
591
  // src/templates/api-server.ts
529
592
  var BASE_PATH = "/api";
593
+ var PUBLIC_PATHS = ["/health", "/ready", "/docs", "/openapi.json"];
530
594
  var apiServerTemplate = {
595
+ memoryAdapter: "memory",
596
+ secrets: ["API_TOKEN"],
531
597
  healthPath: `${BASE_PATH}/health`,
532
598
  files(options) {
533
- const model = defaultModels[options.provider];
599
+ const model = modelFor(options);
534
600
  const agentsTs = [
535
601
  `import { Agent, tool } from '@cogitator-ai/core'`,
536
602
  `import { z } from 'zod'`,
@@ -548,7 +614,7 @@ var apiServerTemplate = {
548
614
  ``,
549
615
  `export const assistant = new Agent({`,
550
616
  ` name: 'assistant',`,
551
- ` model: '${model}',`,
617
+ ` model: ${tsString(model)},`,
552
618
  ` instructions: 'You are a helpful API assistant. Answer questions clearly and concisely.',`,
553
619
  ` tools: [searchTool],`,
554
620
  ` temperature: 0.7,`,
@@ -556,23 +622,45 @@ var apiServerTemplate = {
556
622
  ``,
557
623
  `export const coder = new Agent({`,
558
624
  ` name: 'coder',`,
559
- ` model: '${model}',`,
625
+ ` model: ${tsString(model)},`,
560
626
  ` instructions: 'You are an expert programmer. Write clean, well-structured code.',`,
561
627
  ` temperature: 0.3,`,
562
628
  `})`,
563
629
  ``
564
630
  ].join("\n");
565
631
  const indexTs = [
566
- `import express from 'express'`,
632
+ `import { timingSafeEqual } from 'node:crypto'`,
633
+ `import express, { type Request } from 'express'`,
567
634
  `import { Cogitator } from '@cogitator-ai/core'`,
568
635
  `import { CogitatorServer } from '@cogitator-ai/express'`,
569
636
  `import { assistant, coder } from './agents.js'`,
637
+ ...envHelperImport(options.provider),
638
+ ``,
639
+ `const production = process.env.NODE_ENV === 'production'`,
640
+ `const port = Number(process.env.PORT ?? 3000)`,
641
+ `const host = process.env.HOST ?? (production ? '0.0.0.0' : '127.0.0.1')`,
642
+ `const apiToken = process.env.API_TOKEN`,
643
+ `const corsOrigin = process.env.CORS_ORIGIN`,
644
+ `const publicPaths = new Set([${PUBLIC_PATHS.map(tsString).join(", ")}])`,
645
+ ``,
646
+ `function hasToken(header: string | undefined): boolean {`,
647
+ ` if (!apiToken || !header) return false`,
648
+ ` const expected = Buffer.from(\`Bearer \${apiToken}\`)`,
649
+ ` const given = Buffer.from(header)`,
650
+ ` return given.length === expected.length && timingSafeEqual(given, expected)`,
651
+ `}`,
652
+ ``,
653
+ `function authenticate(req: Request) {`,
654
+ ` if (!apiToken || publicPaths.has(req.path)) return undefined`,
655
+ ` if (!hasToken(req.get('authorization'))) throw new Error('Unauthorized')`,
656
+ ` return { userId: 'api-token' }`,
657
+ `}`,
570
658
  ``,
571
659
  `const app = express()`,
572
- `const port = process.env.PORT || 3000`,
573
660
  ``,
574
661
  `const cogitator = new Cogitator({`,
575
- providerConfig(options.provider),
662
+ providerConfig(options.provider, "requireEnv"),
663
+ ` memory: { adapter: 'memory' },`,
576
664
  `})`,
577
665
  ``,
578
666
  `const server = new CogitatorServer({`,
@@ -582,62 +670,62 @@ var apiServerTemplate = {
582
670
  ` config: {`,
583
671
  ` basePath: '${BASE_PATH}',`,
584
672
  ` enableSwagger: true,`,
585
- ` cors: { origin: '*' },`,
673
+ ` auth: authenticate,`,
674
+ ` ...(corsOrigin ? { cors: { origin: corsOrigin.split(',').map((o) => o.trim()) } } : {}),`,
586
675
  ` swagger: {`,
587
- ` title: '${options.name} API',`,
676
+ ` title: ${tsString(`${options.name} API`)},`,
588
677
  ` version: '1.0.0',`,
589
678
  ` },`,
590
679
  ` },`,
591
680
  `})`,
592
681
  ``,
593
682
  `async function main() {`,
683
+ ` if (production && !apiToken) {`,
684
+ ` throw new Error('Set API_TOKEN in production: it guards every route except health and docs')`,
685
+ ` }`,
686
+ ``,
594
687
  ` await server.init()`,
595
688
  ``,
596
- ` app.listen(port, () => {`,
597
- ` console.log(\`Server running at http://localhost:\${port}\`)`,
598
- ` console.log(\`Swagger docs at http://localhost:\${port}${BASE_PATH}/docs\`)`,
689
+ ` app.listen(port, host, () => {`,
690
+ ` console.log(\`Server running at http://\${host}:\${port}\`)`,
691
+ ` console.log(\`Swagger docs at http://\${host}:\${port}${BASE_PATH}/docs\`)`,
692
+ ` if (!apiToken) console.log('API_TOKEN is not set, so the API is open to anyone who can reach it')`,
599
693
  ` })`,
600
694
  `}`,
601
695
  ``,
602
- `main().catch(console.error)`,
603
- ``
696
+ ...RUN_MAIN
604
697
  ].join("\n");
605
698
  return [
606
699
  { path: "src/index.ts", content: indexTs },
607
- { path: "src/agents.ts", content: agentsTs }
700
+ { path: "src/agents.ts", content: agentsTs },
701
+ ...envHelperFiles(options.provider)
608
702
  ];
609
703
  },
610
704
  dependencies() {
611
705
  return {
612
- "@cogitator-ai/core": "latest",
613
- "@cogitator-ai/express": "latest",
706
+ "@cogitator-ai/core": cogitatorVersion("@cogitator-ai/core"),
707
+ "@cogitator-ai/express": cogitatorVersion("@cogitator-ai/express"),
614
708
  express: "^5.2.1",
615
709
  zod: ZOD_VERSION
616
710
  };
617
711
  },
618
712
  devDependencies() {
619
713
  return {
620
- typescript: "^5.8.0",
621
- tsx: "^4.19.0",
622
- "@types/node": "^22.0.0",
714
+ ...scriptTemplateDevDependencies(),
623
715
  "@types/express": "^5.0.6"
624
716
  };
625
717
  },
626
718
  scripts() {
627
- return {
628
- dev: "tsx watch src/index.ts",
629
- start: "tsx src/index.ts",
630
- build: "tsc",
631
- typecheck: "tsc --noEmit"
632
- };
719
+ return scriptTemplateScripts();
633
720
  }
634
721
  };
635
722
 
636
723
  // src/templates/nextjs.ts
637
724
  var nextjsTemplate = {
725
+ memoryAdapter: "memory",
638
726
  healthPath: "/api/health",
639
727
  files(options) {
640
- const model = defaultModels[options.provider];
728
+ const model = modelFor(options);
641
729
  const agentTs = [
642
730
  `import { Cogitator, Agent, tool } from '@cogitator-ai/core'`,
643
731
  `import { z } from 'zod'`,
@@ -655,11 +743,12 @@ var nextjsTemplate = {
655
743
  ``,
656
744
  `export const cogitator = new Cogitator({`,
657
745
  providerConfig(options.provider),
746
+ ` memory: { adapter: 'memory' },`,
658
747
  `})`,
659
748
  ``,
660
749
  `export const agent = new Agent({`,
661
- ` name: '${options.name}-chat',`,
662
- ` model: '${model}',`,
750
+ ` name: ${tsString(`${options.name}-chat`)},`,
751
+ ` model: ${tsString(model)},`,
663
752
  ` instructions: 'You are a helpful AI chat assistant. Be conversational and helpful.',`,
664
753
  ` tools: [searchTool],`,
665
754
  ` temperature: 0.7,`,
@@ -686,7 +775,7 @@ var nextjsTemplate = {
686
775
  `import { useState, useRef, useEffect } from 'react'`,
687
776
  ``,
688
777
  `export default function Home() {`,
689
- ` const { messages, input, setInput, send, isLoading } = useCogitatorChat({`,
778
+ ` const { messages, input, setInput, send, stop, isLoading, error } = useCogitatorChat({`,
690
779
  ` api: '/api/chat',`,
691
780
  ` })`,
692
781
  ` const messagesEndRef = useRef<HTMLDivElement>(null)`,
@@ -706,27 +795,29 @@ var nextjsTemplate = {
706
795
  ` <main className="flex min-h-screen flex-col items-center p-4 bg-gray-950 text-white">`,
707
796
  ` <div className="w-full max-w-2xl flex flex-col h-screen">`,
708
797
  ` <header className="py-6 text-center">`,
709
- ` <h1 className="text-2xl font-bold">${options.name}</h1>`,
798
+ ` <h1 className="text-2xl font-bold">{${tsString(options.name)}}</h1>`,
710
799
  ` <p className="text-gray-400 text-sm mt-1">Powered by Cogitator</p>`,
711
800
  ` </header>`,
712
801
  ``,
713
802
  ` <div className="flex-1 overflow-y-auto space-y-4 pb-4">`,
714
- ` {messages.map((msg, i) => (`,
715
- ` <div`,
716
- ` key={i}`,
717
- ` className={\`flex \${msg.role === 'user' ? 'justify-end' : 'justify-start'}\`}`,
718
- ` >`,
803
+ ` {messages`,
804
+ ` .filter((msg) => msg.content)`,
805
+ ` .map((msg) => (`,
719
806
  ` <div`,
720
- ` className={\`max-w-[80%] rounded-2xl px-4 py-2 \${`,
721
- ` msg.role === 'user'`,
722
- ` ? 'bg-blue-600 text-white'`,
723
- ` : 'bg-gray-800 text-gray-100'`,
724
- ` }\`}`,
807
+ ` key={msg.id}`,
808
+ ` className={\`flex \${msg.role === 'user' ? 'justify-end' : 'justify-start'}\`}`,
725
809
  ` >`,
726
- ` {msg.content}`,
810
+ ` <div`,
811
+ ` className={\`max-w-[80%] rounded-2xl px-4 py-2 \${`,
812
+ ` msg.role === 'user'`,
813
+ ` ? 'bg-blue-600 text-white'`,
814
+ ` : 'bg-gray-800 text-gray-100'`,
815
+ ` }\`}`,
816
+ ` >`,
817
+ ` {msg.content}`,
818
+ ` </div>`,
727
819
  ` </div>`,
728
- ` </div>`,
729
- ` ))}`,
820
+ ` ))}`,
730
821
  ` {isLoading && (`,
731
822
  ` <div className="flex justify-start">`,
732
823
  ` <div className="bg-gray-800 rounded-2xl px-4 py-2 text-gray-400">`,
@@ -734,6 +825,11 @@ var nextjsTemplate = {
734
825
  ` </div>`,
735
826
  ` </div>`,
736
827
  ` )}`,
828
+ ` {error && (`,
829
+ ` <div className="rounded-xl border border-red-800 bg-red-950 px-4 py-2 text-sm text-red-200">`,
830
+ ` {error.message}`,
831
+ ` </div>`,
832
+ ` )}`,
737
833
  ` <div ref={messagesEndRef} />`,
738
834
  ` </div>`,
739
835
  ``,
@@ -744,13 +840,23 @@ var nextjsTemplate = {
744
840
  ` placeholder="Type a message..."`,
745
841
  ` className="flex-1 bg-gray-800 border border-gray-700 rounded-xl px-4 py-3 text-white placeholder-gray-500 focus:outline-none focus:border-blue-500"`,
746
842
  ` />`,
747
- ` <button`,
748
- ` type="submit"`,
749
- ` disabled={isLoading || !input.trim()}`,
750
- ` className="bg-blue-600 hover:bg-blue-500 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded-xl px-6 py-3 font-medium transition-colors"`,
751
- ` >`,
752
- ` Send`,
753
- ` </button>`,
843
+ ` {isLoading ? (`,
844
+ ` <button`,
845
+ ` type="button"`,
846
+ ` onClick={stop}`,
847
+ ` className="bg-gray-700 hover:bg-gray-600 text-white rounded-xl px-6 py-3 font-medium transition-colors"`,
848
+ ` >`,
849
+ ` Stop`,
850
+ ` </button>`,
851
+ ` ) : (`,
852
+ ` <button`,
853
+ ` type="submit"`,
854
+ ` disabled={!input.trim()}`,
855
+ ` className="bg-blue-600 hover:bg-blue-500 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded-xl px-6 py-3 font-medium transition-colors"`,
856
+ ` >`,
857
+ ` Send`,
858
+ ` </button>`,
859
+ ` )}`,
754
860
  ` </form>`,
755
861
  ` </div>`,
756
862
  ` </main>`,
@@ -763,7 +869,7 @@ var nextjsTemplate = {
763
869
  `import './globals.css'`,
764
870
  ``,
765
871
  `export const metadata: Metadata = {`,
766
- ` title: '${options.name}',`,
872
+ ` title: ${tsString(options.name)},`,
767
873
  ` description: 'AI chat app powered by Cogitator',`,
768
874
  `}`,
769
875
  ``,
@@ -857,8 +963,8 @@ var nextjsTemplate = {
857
963
  },
858
964
  dependencies() {
859
965
  return {
860
- "@cogitator-ai/core": "latest",
861
- "@cogitator-ai/next": "latest",
966
+ "@cogitator-ai/core": cogitatorVersion("@cogitator-ai/core"),
967
+ "@cogitator-ai/next": cogitatorVersion("@cogitator-ai/next"),
862
968
  next: "^15.0.0",
863
969
  react: "^19.0.0",
864
970
  "react-dom": "^19.0.0",
@@ -881,7 +987,7 @@ var nextjsTemplate = {
881
987
  dev: "next dev",
882
988
  build: "next build",
883
989
  start: "next start",
884
- lint: "next lint"
990
+ typecheck: "tsc --noEmit"
885
991
  };
886
992
  }
887
993
  };
@@ -947,15 +1053,19 @@ function generateTsconfig() {
947
1053
  }
948
1054
 
949
1055
  // src/templates/base/gitignore.ts
950
- function generateGitignore() {
1056
+ var NEXT_OUTPUT = [".next/", "out/", "next-env.d.ts"];
1057
+ function generateGitignore(template) {
951
1058
  return {
952
1059
  path: ".gitignore",
953
1060
  content: [
954
1061
  "node_modules/",
955
1062
  "dist/",
1063
+ ...template === "nextjs" ? NEXT_OUTPUT : [],
956
1064
  ".env",
957
- ".env.local",
1065
+ ".env.*",
1066
+ "!.env.example",
958
1067
  "*.log",
1068
+ "*.tsbuildinfo",
959
1069
  ".DS_Store",
960
1070
  "coverage/",
961
1071
  ".turbo/",
@@ -973,16 +1083,16 @@ function generateEnvExample(provider, template) {
973
1083
  lines.push("# OLLAMA_BASE_URL=http://localhost:11434");
974
1084
  break;
975
1085
  case "openai":
976
- lines.push("# OpenAI");
977
- lines.push("OPENAI_API_KEY=sk-...");
1086
+ lines.push("# OpenAI, https://platform.openai.com/api-keys");
1087
+ lines.push("OPENAI_API_KEY=");
978
1088
  break;
979
1089
  case "anthropic":
980
- lines.push("# Anthropic");
981
- lines.push("ANTHROPIC_API_KEY=sk-ant-...");
1090
+ lines.push("# Anthropic, https://console.anthropic.com/settings/keys");
1091
+ lines.push("ANTHROPIC_API_KEY=");
982
1092
  break;
983
1093
  case "google":
984
- lines.push("# Google AI");
985
- lines.push("GOOGLE_API_KEY=...");
1094
+ lines.push("# Google AI, https://aistudio.google.com/apikey");
1095
+ lines.push("GOOGLE_API_KEY=");
986
1096
  break;
987
1097
  }
988
1098
  if (template === "memory") {
@@ -990,25 +1100,54 @@ function generateEnvExample(provider, template) {
990
1100
  lines.push("# Redis");
991
1101
  lines.push("REDIS_URL=redis://localhost:6379");
992
1102
  }
1103
+ if (template === "api-server") {
1104
+ lines.push("");
1105
+ lines.push("# Bearer token that every API route except health and docs requires.");
1106
+ lines.push("# Optional in development, where the server listens on 127.0.0.1 only,");
1107
+ lines.push("# required in production (NODE_ENV=production), where it listens on 0.0.0.0.");
1108
+ lines.push("API_TOKEN=");
1109
+ lines.push("");
1110
+ lines.push("# Comma-separated browser origins allowed to call the API, CORS is off without it");
1111
+ lines.push("# CORS_ORIGIN=http://localhost:5173");
1112
+ lines.push("");
1113
+ lines.push("# HOST=127.0.0.1");
1114
+ lines.push("# PORT=3000");
1115
+ }
993
1116
  lines.push("");
994
1117
  return { path: ".env.example", content: lines.join("\n") };
995
1118
  }
996
1119
 
997
1120
  // src/templates/base/docker-compose.ts
998
- function generateDockerCompose(provider) {
1121
+ function ollamaServices(model) {
1122
+ return [
1123
+ " ollama:",
1124
+ " image: ollama/ollama:latest",
1125
+ " ports:",
1126
+ ' - "11434:11434"',
1127
+ " volumes:",
1128
+ " - ollama_data:/root/.ollama",
1129
+ " healthcheck:",
1130
+ ' test: ["CMD", "ollama", "list"]',
1131
+ " interval: 5s",
1132
+ " timeout: 5s",
1133
+ " retries: 20",
1134
+ "",
1135
+ " ollama-pull:",
1136
+ " image: ollama/ollama:latest",
1137
+ " depends_on:",
1138
+ " ollama:",
1139
+ " condition: service_healthy",
1140
+ " environment:",
1141
+ " OLLAMA_HOST: http://ollama:11434",
1142
+ ` entrypoint: ["ollama", "pull", ${JSON.stringify(model)}]`,
1143
+ ' restart: "no"',
1144
+ ""
1145
+ ];
1146
+ }
1147
+ function generateDockerCompose(provider, model = defaultModels[provider]) {
999
1148
  const services = [];
1000
1149
  if (provider === "ollama") {
1001
- services.push(
1002
- ...[
1003
- " ollama:",
1004
- " image: ollama/ollama:latest",
1005
- " ports:",
1006
- ' - "11434:11434"',
1007
- " volumes:",
1008
- " - ollama_data:/root/.ollama",
1009
- ""
1010
- ]
1011
- );
1150
+ services.push(...ollamaServices(model));
1012
1151
  }
1013
1152
  services.push(
1014
1153
  ...[
@@ -1039,12 +1178,15 @@ function generateDockerCompose(provider) {
1039
1178
  }
1040
1179
 
1041
1180
  // src/templates/base/cogitator-yml.ts
1042
- function generateCogitatorYml(provider, template) {
1043
- const lines = [
1044
- "llm:",
1045
- ` defaultProvider: ${provider}`,
1046
- ` defaultModel: ${defaultModels[provider]}`
1047
- ];
1181
+ var HEADER = [
1182
+ "# Read by `cogitator run` and `cogitator deploy`: the provider and model, the",
1183
+ "# memory to provision and the deploy settings. The code in src/ configures",
1184
+ "# Cogitator itself, so change the provider, model or memory in both places.",
1185
+ ""
1186
+ ];
1187
+ function generateCogitatorYml(provider, template, model = defaultModels[provider]) {
1188
+ const generator = template ? getTemplate(template) : void 0;
1189
+ const lines = [...HEADER, "llm:", ` defaultProvider: ${provider}`, ` defaultModel: ${model}`];
1048
1190
  if (provider === "ollama") {
1049
1191
  lines.push(
1050
1192
  " providers:",
@@ -1052,7 +1194,7 @@ function generateCogitatorYml(provider, template) {
1052
1194
  " baseUrl: ${OLLAMA_BASE_URL:-http://localhost:11434}"
1053
1195
  );
1054
1196
  }
1055
- if (template === "memory") {
1197
+ if (generator?.memoryAdapter === "redis") {
1056
1198
  lines.push(
1057
1199
  "",
1058
1200
  "memory:",
@@ -1060,10 +1202,22 @@ function generateCogitatorYml(provider, template) {
1060
1202
  " redis:",
1061
1203
  " url: ${REDIS_URL:-redis://localhost:6379}"
1062
1204
  );
1205
+ } else if (generator?.memoryAdapter === "memory") {
1206
+ lines.push("", "memory:", " adapter: memory");
1207
+ }
1208
+ const deploy = [];
1209
+ if (generator?.healthPath) {
1210
+ deploy.push(" health:", ` path: ${generator.healthPath}`);
1211
+ }
1212
+ if (generator?.secrets?.length) {
1213
+ const secrets = [
1214
+ ...provider === "ollama" ? [] : [providerEnvKey(provider)],
1215
+ ...generator.secrets
1216
+ ];
1217
+ deploy.push(" secrets:", ...secrets.map((name) => ` - ${name}`));
1063
1218
  }
1064
- const healthPath = template ? getTemplate(template).healthPath : void 0;
1065
- if (healthPath) {
1066
- lines.push("", "deploy:", " health:", ` path: ${healthPath}`);
1219
+ if (deploy.length > 0) {
1220
+ lines.push("", "deploy:", ...deploy);
1067
1221
  }
1068
1222
  lines.push("");
1069
1223
  return { path: "cogitator.yml", content: lines.join("\n") };
@@ -1072,12 +1226,8 @@ function generateCogitatorYml(provider, template) {
1072
1226
  // src/utils/package-manager.ts
1073
1227
  import { execSync } from "child_process";
1074
1228
  function detectPackageManager() {
1075
- const ua = process.env.npm_config_user_agent;
1076
- if (ua) {
1077
- if (ua.startsWith("pnpm")) return "pnpm";
1078
- if (ua.startsWith("yarn")) return "yarn";
1079
- if (ua.startsWith("bun")) return "bun";
1080
- }
1229
+ const name = process.env.npm_config_user_agent?.split("/")[0];
1230
+ if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun") return name;
1081
1231
  return "pnpm";
1082
1232
  }
1083
1233
  function installDependencies(cwd, pm) {
@@ -1088,6 +1238,35 @@ function devCommand(pm) {
1088
1238
  return `${pm} dev`;
1089
1239
  }
1090
1240
 
1241
+ // src/next-steps.ts
1242
+ function nextSteps(options, state) {
1243
+ const steps = [];
1244
+ if (state.directory) {
1245
+ const dir = /^[\w./-]+$/.test(state.directory) ? state.directory : JSON.stringify(state.directory);
1246
+ steps.push({ command: `cd ${dir}` });
1247
+ }
1248
+ if (!state.installed) {
1249
+ steps.push({ command: `${options.packageManager} install` });
1250
+ }
1251
+ if (options.provider !== "ollama") {
1252
+ steps.push({
1253
+ command: "cp .env.example .env",
1254
+ note: `then set ${providerEnvKey(options.provider)} in .env`
1255
+ });
1256
+ }
1257
+ if (options.provider === "ollama" && !state.modelReady) {
1258
+ steps.push({
1259
+ command: `ollama pull ${modelFor(options)}`,
1260
+ note: options.docker ? "download the model the agents use, or let `docker compose up -d` pull it into the Ollama container" : "download the model the agents use"
1261
+ });
1262
+ }
1263
+ if (options.template === "memory" && options.docker) {
1264
+ steps.push({ command: "docker compose up -d redis", note: "the memory lives in Redis" });
1265
+ }
1266
+ steps.push({ command: devCommand(options.packageManager) });
1267
+ return steps;
1268
+ }
1269
+
1091
1270
  // src/utils/links.ts
1092
1271
  var DOCS_URL = "https://cogitator.app/docs";
1093
1272
  var REPO_URL = "https://github.com/cogitator-ai/Cogitator-AI";
@@ -1102,24 +1281,33 @@ var templateNames = {
1102
1281
  nextjs: "Next.js Chat App"
1103
1282
  };
1104
1283
  function generateReadme(options) {
1105
- const dev = devCommand(options.packageManager);
1106
1284
  const templateName = templateNames[options.template] || options.template;
1285
+ const steps = nextSteps(options, { installed: false, modelReady: false });
1107
1286
  const content = [
1108
1287
  `# ${options.name}`,
1109
1288
  "",
1110
- `> Created with [create-cogitator-app](${REPO_URL}) \u2014 ${templateName} template`,
1289
+ `> Created with [create-cogitator-app](${REPO_URL}) - ${templateName} template`,
1111
1290
  "",
1112
1291
  "## Getting Started",
1113
1292
  "",
1114
1293
  "```bash",
1115
- `# Install dependencies`,
1116
- `${options.packageManager} install`,
1117
- "",
1118
- `# Run the project`,
1119
- dev,
1294
+ ...steps.flatMap((step, i) => [
1295
+ ...i > 0 ? [""] : [],
1296
+ ...step.note ? [`# ${step.note}`] : [],
1297
+ step.command
1298
+ ]),
1120
1299
  "```",
1121
1300
  "",
1122
- ...options.docker ? ["## Docker Services", "", "```bash", "docker compose up -d", "```", ""] : [],
1301
+ ...options.docker ? [
1302
+ "## Docker Services",
1303
+ "",
1304
+ "```bash",
1305
+ "docker compose up -d",
1306
+ "```",
1307
+ "",
1308
+ options.provider === "ollama" ? "Starts Redis, Postgres and Ollama, and pulls the model into the Ollama container." : "Starts Redis and Postgres.",
1309
+ ""
1310
+ ] : [],
1123
1311
  "## Learn More",
1124
1312
  "",
1125
1313
  `- [Cogitator Documentation](${DOCS_URL})`,
@@ -1175,10 +1363,28 @@ function initGitRepo(cwd) {
1175
1363
  });
1176
1364
  }
1177
1365
 
1366
+ // src/utils/project-name.ts
1367
+ import path2 from "path";
1368
+ var PACKAGE_NAME = /^[a-z0-9][a-z0-9._-]*$/;
1369
+ var MAX_LENGTH = 214;
1370
+ function validateProjectName(name) {
1371
+ const trimmed = name.trim();
1372
+ if (!trimmed) return "Project name is required";
1373
+ if (trimmed.length > MAX_LENGTH) return `Use at most ${MAX_LENGTH} characters`;
1374
+ if (!PACKAGE_NAME.test(trimmed)) {
1375
+ return 'Use lowercase letters, digits, ".", "_" or "-", starting with a letter or digit';
1376
+ }
1377
+ return void 0;
1378
+ }
1379
+ function projectNameFromDirectory(dir, cwd = process.cwd()) {
1380
+ return path2.basename(path2.resolve(cwd, dir.trim()));
1381
+ }
1382
+
1178
1383
  // src/scaffold.ts
1384
+ var NODE_ENGINES = ">=22.12.0";
1179
1385
  function writeFile(basePath, file) {
1180
- const fullPath = path.join(basePath, file.path);
1181
- fs.mkdirSync(path.dirname(fullPath), { recursive: true });
1386
+ const fullPath = path3.join(basePath, file.path);
1387
+ fs.mkdirSync(path3.dirname(fullPath), { recursive: true });
1182
1388
  fs.writeFileSync(fullPath, file.content, "utf-8");
1183
1389
  }
1184
1390
  function buildPackageJson(options) {
@@ -1189,6 +1395,7 @@ function buildPackageJson(options) {
1189
1395
  version: "0.1.0",
1190
1396
  private: true,
1191
1397
  ...isNextjs ? {} : { type: "module" },
1398
+ engines: { node: NODE_ENGINES },
1192
1399
  scripts: template.scripts(),
1193
1400
  dependencies: template.dependencies(),
1194
1401
  devDependencies: template.devDependencies()
@@ -1200,9 +1407,9 @@ function collectFiles(options) {
1200
1407
  const files = [];
1201
1408
  files.push({ path: "package.json", content: buildPackageJson(options) });
1202
1409
  files.push(...template.files(options));
1203
- files.push(generateGitignore());
1410
+ files.push(generateGitignore(options.template));
1204
1411
  files.push(generateEnvExample(options.provider, options.template));
1205
- files.push(generateCogitatorYml(options.provider, options.template));
1412
+ files.push(generateCogitatorYml(options.provider, options.template, modelFor(options)));
1206
1413
  files.push(generateReadme(options));
1207
1414
  if (options.template !== "nextjs") {
1208
1415
  files.push(generateTsconfig());
@@ -1211,7 +1418,7 @@ function collectFiles(options) {
1211
1418
  files.push(generatePnpmWorkspace());
1212
1419
  }
1213
1420
  if (options.docker) {
1214
- files.push(generateDockerCompose(options.provider));
1421
+ files.push(generateDockerCompose(options.provider, modelFor(options)));
1215
1422
  }
1216
1423
  return files;
1217
1424
  }
@@ -1227,6 +1434,10 @@ function runStep(spinner2, messages, step) {
1227
1434
  }
1228
1435
  }
1229
1436
  async function scaffold(options) {
1437
+ const nameError = validateProjectName(options.name);
1438
+ if (nameError) {
1439
+ throw new Error(`Invalid project name "${options.name}": ${nameError}`);
1440
+ }
1230
1441
  if (fs.existsSync(options.path) && fs.readdirSync(options.path).length > 0) {
1231
1442
  throw new Error(`Directory "${options.path}" already exists and is not empty`);
1232
1443
  }
@@ -1261,7 +1472,46 @@ async function scaffold(options) {
1261
1472
 
1262
1473
  // src/prompts.ts
1263
1474
  import * as p2 from "@clack/prompts";
1264
- import path2 from "path";
1475
+ import path4 from "path";
1476
+
1477
+ // src/utils/ollama.ts
1478
+ var DEFAULT_OLLAMA_URL = "http://localhost:11434";
1479
+ function isRecord2(value) {
1480
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1481
+ }
1482
+ function trimTrailingSlashes(value) {
1483
+ let end = value.length;
1484
+ while (end > 0 && value[end - 1] === "/") end--;
1485
+ return value.slice(0, end);
1486
+ }
1487
+ function resolveOllamaUrl(env = process.env) {
1488
+ const raw = env.OLLAMA_BASE_URL?.trim() || env.OLLAMA_HOST?.trim();
1489
+ if (!raw) return DEFAULT_OLLAMA_URL;
1490
+ const withScheme = /^https?:\/\//i.test(raw) ? raw : `http://${raw}`;
1491
+ return trimTrailingSlashes(withScheme);
1492
+ }
1493
+ async function listOllamaModels(baseUrl, timeoutMs = 2e3) {
1494
+ try {
1495
+ const res = await fetch(`${baseUrl}/api/tags`, { signal: AbortSignal.timeout(timeoutMs) });
1496
+ if (!res.ok) return void 0;
1497
+ const data = await res.json();
1498
+ if (!isRecord2(data) || !Array.isArray(data.models)) return void 0;
1499
+ return data.models.flatMap(
1500
+ (model) => isRecord2(model) && typeof model.name === "string" ? [{ name: model.name, size: typeof model.size === "number" ? model.size : 0 }] : []
1501
+ );
1502
+ } catch {
1503
+ return void 0;
1504
+ }
1505
+ }
1506
+ function withTag(name) {
1507
+ return name.includes(":") ? name : `${name}:latest`;
1508
+ }
1509
+ function hasOllamaModel(installed, model) {
1510
+ const wanted = withTag(model);
1511
+ return installed.some((m) => withTag(m.name) === wanted);
1512
+ }
1513
+
1514
+ // src/prompts.ts
1265
1515
  var validTemplates = ["basic", "memory", "swarm", "workflow", "api-server", "nextjs"];
1266
1516
  var validProviders = ["ollama", "openai", "anthropic", "google"];
1267
1517
  var validPMs = ["pnpm", "npm", "yarn", "bun"];
@@ -1279,6 +1529,12 @@ function parseArgs(args) {
1279
1529
  const val = args[++i];
1280
1530
  if (validProviders.includes(val)) parsed.provider = val;
1281
1531
  }
1532
+ } else if (arg === "--model") {
1533
+ const val = args[i + 1];
1534
+ if (val !== void 0 && !val.startsWith("-")) {
1535
+ parsed.model = val;
1536
+ i++;
1537
+ }
1282
1538
  } else if (arg === "--pm") {
1283
1539
  if (i + 1 < args.length) {
1284
1540
  const val = args[++i];
@@ -1318,22 +1574,54 @@ function unlessCancelled(answer) {
1318
1574
  }
1319
1575
  return answer;
1320
1576
  }
1577
+ function validateDirectory(value) {
1578
+ if (!value) return void 0;
1579
+ if (!value.trim()) return "Project name is required";
1580
+ return validateProjectName(projectNameFromDirectory(value));
1581
+ }
1582
+ function formatSize(bytes) {
1583
+ return bytes > 0 ? `${(bytes / 1e9).toFixed(1)} GB` : void 0;
1584
+ }
1585
+ async function promptOllamaModel() {
1586
+ const installed = await listOllamaModels(resolveOllamaUrl());
1587
+ if (!installed) return void 0;
1588
+ const recommended = defaultModels.ollama;
1589
+ const others = installed.filter((model) => !hasOllamaModel([model], recommended));
1590
+ return unlessCancelled(
1591
+ await p2.select({
1592
+ message: "Which Ollama model?",
1593
+ options: [
1594
+ {
1595
+ value: recommended,
1596
+ label: recommended,
1597
+ hint: hasOllamaModel(installed, recommended) ? "recommended, installed" : "recommended, not installed yet"
1598
+ },
1599
+ ...others.map((model) => ({
1600
+ value: model.name,
1601
+ label: model.name,
1602
+ hint: formatSize(model.size)
1603
+ }))
1604
+ ],
1605
+ initialValue: recommended
1606
+ })
1607
+ );
1608
+ }
1321
1609
  async function collectOptions(args) {
1322
1610
  const interactive = !args.yes;
1323
- const rawName = args.name || (interactive ? unlessCancelled(
1611
+ const directory = (args.name || (interactive ? unlessCancelled(
1324
1612
  await p2.text({
1325
1613
  message: "Where should we create your project?",
1326
1614
  placeholder: `./${defaultAnswers.name}`,
1327
1615
  defaultValue: defaultAnswers.name,
1328
- validate: (value) => {
1329
- if (value && !value.trim()) return "Project name is required";
1330
- return void 0;
1331
- }
1616
+ validate: validateDirectory
1332
1617
  })
1333
- ) : defaultAnswers.name);
1334
- const name = rawName.trim();
1335
- const projectName = path2.basename(path2.normalize(name).replace(/^\.\//, "").replace(/^\.\\/, ""));
1336
- const projectPath = path2.resolve(process.cwd(), name);
1618
+ ) : defaultAnswers.name)).trim();
1619
+ const projectPath = path4.resolve(process.cwd(), directory);
1620
+ const projectName = projectNameFromDirectory(directory);
1621
+ const nameError = validateProjectName(projectName);
1622
+ if (nameError) {
1623
+ throw new Error(`Invalid project name "${projectName}": ${nameError}`);
1624
+ }
1337
1625
  const template = args.template ?? (interactive ? unlessCancelled(
1338
1626
  await p2.select({
1339
1627
  message: "Which template would you like?",
@@ -1348,7 +1636,7 @@ async function collectOptions(args) {
1348
1636
  {
1349
1637
  value: "ollama",
1350
1638
  label: "Ollama",
1351
- hint: `${defaultModels.ollama} \u2014 local, free, requires Ollama installed`
1639
+ hint: `${defaultModels.ollama}, local, free, requires Ollama installed`
1352
1640
  },
1353
1641
  { value: "openai", label: "OpenAI", hint: defaultModels.openai },
1354
1642
  { value: "anthropic", label: "Anthropic", hint: defaultModels.anthropic },
@@ -1357,6 +1645,7 @@ async function collectOptions(args) {
1357
1645
  initialValue: defaultAnswers.provider
1358
1646
  })
1359
1647
  ) : defaultAnswers.provider);
1648
+ const model = args.model ?? (interactive && provider === "ollama" ? await promptOllamaModel() : void 0);
1360
1649
  const packageManager = args.packageManager ?? (interactive ? unlessCancelled(
1361
1650
  await p2.select({
1362
1651
  message: "Package manager?",
@@ -1386,6 +1675,7 @@ async function collectOptions(args) {
1386
1675
  path: projectPath,
1387
1676
  template,
1388
1677
  provider,
1678
+ ...model !== void 0 && { model },
1389
1679
  packageManager,
1390
1680
  docker,
1391
1681
  git,
@@ -1402,6 +1692,7 @@ export {
1402
1692
  providerConfig,
1403
1693
  providerEnvKey,
1404
1694
  scaffold,
1405
- templateChoices
1695
+ templateChoices,
1696
+ validateProjectName
1406
1697
  };
1407
1698
  //# sourceMappingURL=lib.js.map