@cogitator-ai/cli 0.3.16 → 0.5.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.
Files changed (73) hide show
  1. package/README.md +160 -511
  2. package/dist/commands/assistant.d.ts +13 -0
  3. package/dist/commands/assistant.d.ts.map +1 -1
  4. package/dist/commands/assistant.js +64 -58
  5. package/dist/commands/assistant.js.map +1 -1
  6. package/dist/commands/build.d.ts +2 -0
  7. package/dist/commands/build.d.ts.map +1 -1
  8. package/dist/commands/build.js +115 -60
  9. package/dist/commands/build.js.map +1 -1
  10. package/dist/commands/daemon.d.ts.map +1 -1
  11. package/dist/commands/daemon.js +223 -262
  12. package/dist/commands/daemon.js.map +1 -1
  13. package/dist/commands/deploy.d.ts.map +1 -1
  14. package/dist/commands/deploy.js +80 -42
  15. package/dist/commands/deploy.js.map +1 -1
  16. package/dist/commands/init.d.ts +51 -0
  17. package/dist/commands/init.d.ts.map +1 -1
  18. package/dist/commands/init.js +349 -265
  19. package/dist/commands/init.js.map +1 -1
  20. package/dist/commands/logs.d.ts +1 -0
  21. package/dist/commands/logs.d.ts.map +1 -1
  22. package/dist/commands/logs.js +8 -4
  23. package/dist/commands/logs.js.map +1 -1
  24. package/dist/commands/models.d.ts +1 -1
  25. package/dist/commands/models.d.ts.map +1 -1
  26. package/dist/commands/models.js +83 -93
  27. package/dist/commands/models.js.map +1 -1
  28. package/dist/commands/run.d.ts +4 -1
  29. package/dist/commands/run.d.ts.map +1 -1
  30. package/dist/commands/run.js +156 -131
  31. package/dist/commands/run.js.map +1 -1
  32. package/dist/commands/skill.d.ts +17 -0
  33. package/dist/commands/skill.d.ts.map +1 -1
  34. package/dist/commands/skill.js +203 -81
  35. package/dist/commands/skill.js.map +1 -1
  36. package/dist/commands/status.d.ts.map +1 -1
  37. package/dist/commands/status.js +20 -24
  38. package/dist/commands/status.js.map +1 -1
  39. package/dist/commands/up.d.ts +4 -0
  40. package/dist/commands/up.d.ts.map +1 -1
  41. package/dist/commands/up.js +182 -130
  42. package/dist/commands/up.js.map +1 -1
  43. package/dist/commands/wizard.d.ts +6 -0
  44. package/dist/commands/wizard.d.ts.map +1 -1
  45. package/dist/commands/wizard.js +307 -271
  46. package/dist/commands/wizard.js.map +1 -1
  47. package/dist/index.js +8 -1
  48. package/dist/index.js.map +1 -1
  49. package/dist/utils/daemon.d.ts +37 -0
  50. package/dist/utils/daemon.d.ts.map +1 -0
  51. package/dist/utils/daemon.js +194 -0
  52. package/dist/utils/daemon.js.map +1 -0
  53. package/dist/utils/docker.d.ts +9 -0
  54. package/dist/utils/docker.d.ts.map +1 -1
  55. package/dist/utils/docker.js +67 -12
  56. package/dist/utils/docker.js.map +1 -1
  57. package/dist/utils/env.d.ts +7 -0
  58. package/dist/utils/env.d.ts.map +1 -0
  59. package/dist/utils/env.js +41 -0
  60. package/dist/utils/env.js.map +1 -0
  61. package/dist/utils/module-loader.d.ts +5 -0
  62. package/dist/utils/module-loader.d.ts.map +1 -0
  63. package/dist/utils/module-loader.js +52 -0
  64. package/dist/utils/module-loader.js.map +1 -0
  65. package/dist/utils/ollama.d.ts +20 -0
  66. package/dist/utils/ollama.d.ts.map +1 -0
  67. package/dist/utils/ollama.js +111 -0
  68. package/dist/utils/ollama.js.map +1 -0
  69. package/dist/utils/provider-models.d.ts +13 -0
  70. package/dist/utils/provider-models.d.ts.map +1 -0
  71. package/dist/utils/provider-models.js +81 -0
  72. package/dist/utils/provider-models.js.map +1 -0
  73. package/package.json +21 -19
@@ -1,296 +1,159 @@
1
1
  import { Command } from 'commander';
2
- import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
3
- import { join, resolve } from 'node:path';
4
- import { execSync } from 'node:child_process';
2
+ import { writeFileSync, mkdirSync, existsSync, readFileSync } from 'node:fs';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { execFileSync } from 'node:child_process';
5
6
  import * as p from '@clack/prompts';
6
7
  import chalk from 'chalk';
7
8
  import { printBanner } from '../utils/logger.js';
8
- const PROVIDER_MODELS = {
9
- anthropic: [
10
- { label: 'Claude Sonnet 4 (recommended)', value: 'anthropic/claude-sonnet-4-20250514' },
11
- { label: 'Claude Opus 4', value: 'anthropic/claude-opus-4-20250514' },
12
- { label: 'Claude Haiku 3.5', value: 'anthropic/claude-3-5-haiku-20241022' },
13
- ],
14
- openai: [
15
- { label: 'GPT-4o (recommended)', value: 'openai/gpt-4o' },
16
- { label: 'GPT-4o mini', value: 'openai/gpt-4o-mini' },
17
- { label: 'o3-mini', value: 'openai/o3-mini' },
18
- ],
19
- google: [
20
- { label: 'Gemini 2.5 Flash (recommended)', value: 'google/gemini-2.5-flash' },
21
- { label: 'Gemini 2.5 Pro', value: 'google/gemini-2.5-pro' },
22
- ],
23
- ollama: [
24
- { label: 'Llama 3.1 8B (recommended)', value: 'ollama/llama3.1:8b' },
25
- { label: 'Gemma 3 4B', value: 'ollama/gemma3:4b' },
26
- { label: 'Mistral 7B', value: 'ollama/mistral:7b' },
27
- ],
28
- };
29
- const API_KEY_NAMES = {
30
- anthropic: 'ANTHROPIC_API_KEY',
31
- openai: 'OPENAI_API_KEY',
32
- google: 'GOOGLE_API_KEY',
33
- };
34
- export const initCommand = new Command('init')
35
- .description('Create a new Cogitator AI assistant project')
36
- .argument('[name]', 'Project name')
37
- .option('--no-install', 'Skip dependency installation')
38
- .action(async (nameArg, options) => {
39
- printBanner();
40
- p.intro(chalk.bgCyan(chalk.black(' cogitator init ')));
41
- const answers = await collectAnswers(nameArg);
42
- if (!answers)
43
- return;
44
- const projectPath = resolve(process.cwd(), answers.projectName);
45
- if (existsSync(projectPath)) {
46
- p.cancel(`Directory "${answers.projectName}" already exists`);
47
- process.exit(1);
48
- }
49
- await p.tasks([
50
- {
51
- title: 'Creating project structure',
52
- task: () => {
53
- createProjectFiles(projectPath, answers);
54
- return 'Project files created';
55
- },
56
- },
57
- {
58
- title: 'Installing dependencies',
59
- enabled: options.install,
60
- task: () => {
61
- try {
62
- execSync('pnpm install', { cwd: projectPath, stdio: 'pipe' });
63
- return 'Dependencies installed';
64
- }
65
- catch {
66
- return 'Install failed — run pnpm install manually';
67
- }
68
- },
69
- },
70
- ]);
71
- const channelNames = answers.channels.join(', ') || 'webchat';
72
- p.note([`cd ${answers.projectName}`, !options.install ? 'pnpm install' : '', 'cogitator up']
73
- .filter(Boolean)
74
- .join('\n'), 'Next steps');
75
- p.outro(`${chalk.green('Your assistant is ready!')} Channels: ${chalk.cyan(channelNames)}`);
76
- });
77
- async function collectAnswers(nameArg) {
78
- const projectName = nameArg ??
79
- (await p.text({
80
- message: 'Project name',
81
- placeholder: 'my-assistant',
82
- validate: (v) => (!v.trim() ? 'Name is required' : undefined),
83
- }));
84
- if (p.isCancel(projectName)) {
85
- p.cancel('Setup cancelled');
86
- process.exit(0);
87
- }
88
- const provider = (await p.select({
89
- message: 'Which LLM provider?',
90
- options: [
91
- { value: 'anthropic', label: 'Anthropic (Claude)', hint: 'recommended' },
92
- { value: 'openai', label: 'OpenAI (GPT-4o)' },
93
- { value: 'google', label: 'Google (Gemini)' },
94
- { value: 'ollama', label: 'Ollama (local, free)', hint: 'no API key needed' },
95
- ],
96
- }));
97
- if (p.isCancel(provider)) {
98
- p.cancel('Setup cancelled');
99
- process.exit(0);
9
+ import { formatEnvLine } from '../utils/env.js';
10
+ import { DEFAULT_OLLAMA_URL, resolveOllamaUrl } from '../utils/ollama.js';
11
+ import { API_KEY_ENV, fetchOllamaModelOptions, fetchProviderModels, isSetupProvider, } from '../utils/provider-models.js';
12
+ /**
13
+ * pnpm 10.26+ skips dependency build scripts unless they are allowed, and pnpm 11 fails the
14
+ * install instead. These are the native/binary packages a Cogitator project pulls in.
15
+ */
16
+ export const PNPM_ALLOWED_BUILDS = ['better-sqlite3', 'esbuild', 'sharp'];
17
+ export const DEFAULT_POSTGRES_URL = 'postgresql://cogitator:cogitator@localhost:5432/cogitator';
18
+ const PROJECT_NAME = /^[a-z0-9][a-z0-9._-]*$/;
19
+ const COGITATOR_PACKAGES = ['@cogitator-ai/core', '@cogitator-ai/channels', '@cogitator-ai/memory'];
20
+ export function validateProjectName(name) {
21
+ const trimmed = name.trim();
22
+ if (!trimmed)
23
+ return 'Name is required';
24
+ if (trimmed.length > 214)
25
+ return 'Name must be 214 characters or fewer';
26
+ if (!PROJECT_NAME.test(trimmed)) {
27
+ return 'Use lowercase letters, digits, ".", "_" or "-" (must start with a letter or digit)';
100
28
  }
101
- let apiKey = '';
102
- if (provider !== 'ollama') {
103
- const keyResult = await p.password({
104
- message: `${API_KEY_NAMES[provider]}:`,
105
- validate: (v) => (!v.trim() ? 'API key is required' : undefined),
106
- });
107
- if (p.isCancel(keyResult)) {
108
- p.cancel('Setup cancelled');
109
- process.exit(0);
110
- }
111
- apiKey = keyResult;
112
- }
113
- const model = (await p.select({
114
- message: 'Default model',
115
- options: PROVIDER_MODELS[provider],
116
- }));
117
- if (p.isCancel(model)) {
118
- p.cancel('Setup cancelled');
119
- process.exit(0);
120
- }
121
- const channels = (await p.multiselect({
122
- message: 'Which channels to connect?',
123
- options: [
124
- { value: 'telegram', label: 'Telegram' },
125
- { value: 'discord', label: 'Discord' },
126
- { value: 'slack', label: 'Slack' },
127
- { value: 'webchat', label: 'WebChat (localhost)', hint: 'no setup needed' },
128
- ],
129
- required: false,
130
- }));
131
- if (p.isCancel(channels)) {
132
- p.cancel('Setup cancelled');
133
- process.exit(0);
134
- }
135
- let telegramToken;
136
- let discordToken;
137
- let slackToken;
138
- let slackSigningSecret;
139
- if (channels.includes('telegram')) {
140
- const token = await p.password({
141
- message: 'Telegram bot token (from @BotFather):',
142
- validate: (v) => (!v.trim() ? 'Token is required' : undefined),
143
- });
144
- if (p.isCancel(token)) {
145
- p.cancel('Setup cancelled');
146
- process.exit(0);
29
+ return undefined;
30
+ }
31
+ export function resolveDependencyVersions(cliPackageJson = join(dirname(fileURLToPath(import.meta.url)), '../../package.json')) {
32
+ const versions = {};
33
+ let deps = {};
34
+ try {
35
+ const parsed = JSON.parse(readFileSync(cliPackageJson, 'utf-8'));
36
+ if (typeof parsed === 'object' && parsed !== null && 'dependencies' in parsed) {
37
+ const value = parsed.dependencies;
38
+ if (typeof value === 'object' && value !== null)
39
+ deps = Object.fromEntries(Object.entries(value));
147
40
  }
148
- telegramToken = token;
149
41
  }
150
- if (channels.includes('discord')) {
151
- const token = await p.password({
152
- message: 'Discord bot token:',
153
- validate: (v) => (!v.trim() ? 'Token is required' : undefined),
154
- });
155
- if (p.isCancel(token)) {
156
- p.cancel('Setup cancelled');
157
- process.exit(0);
158
- }
159
- discordToken = token;
42
+ catch {
43
+ deps = {};
160
44
  }
161
- if (channels.includes('slack')) {
162
- const token = await p.password({
163
- message: 'Slack bot token (xoxb-...):',
164
- validate: (v) => (!v.trim() ? 'Token is required' : undefined),
165
- });
166
- if (p.isCancel(token)) {
167
- p.cancel('Setup cancelled');
168
- process.exit(0);
169
- }
170
- slackToken = token;
171
- const secret = await p.password({
172
- message: 'Slack signing secret:',
173
- validate: (v) => (!v.trim() ? 'Signing secret is required' : undefined),
174
- });
175
- if (p.isCancel(secret)) {
176
- p.cancel('Setup cancelled');
177
- process.exit(0);
178
- }
179
- slackSigningSecret = secret;
45
+ for (const name of COGITATOR_PACKAGES) {
46
+ const range = deps[name];
47
+ versions[name] =
48
+ typeof range === 'string' && /^\^?~?\d/.test(range)
49
+ ? `^${range.replace(/^[\^~]/, '')}`
50
+ : 'latest';
180
51
  }
181
- const memory = (await p.select({
182
- message: 'Memory adapter',
183
- options: [
184
- { value: 'memory', label: 'In-memory (no persistence)', hint: 'simple, for testing' },
185
- { value: 'sqlite', label: 'SQLite (recommended)', hint: 'zero config, file-based' },
186
- { value: 'postgres', label: 'PostgreSQL', hint: 'production-grade' },
187
- ],
188
- }));
189
- if (p.isCancel(memory)) {
190
- p.cancel('Setup cancelled');
191
- process.exit(0);
192
- }
193
- return {
194
- provider,
195
- apiKey,
196
- model,
197
- channels: channels.length > 0 ? channels : ['webchat'],
198
- telegramToken,
199
- discordToken,
200
- slackToken,
201
- slackSigningSecret,
202
- memory,
203
- projectName: projectName,
204
- };
52
+ return versions;
53
+ }
54
+ export function detectPackageManager(userAgent = process.env.npm_config_user_agent) {
55
+ const name = userAgent?.split('/')[0];
56
+ if (name === 'npm' || name === 'yarn' || name === 'bun' || name === 'pnpm')
57
+ return name;
58
+ return 'pnpm';
205
59
  }
206
- function createProjectFiles(projectPath, answers) {
207
- mkdirSync(join(projectPath, 'src'), { recursive: true });
60
+ function runScript(pm, script) {
61
+ return pm === 'npm' ? `npm run ${script}` : `${pm} ${script}`;
62
+ }
63
+ export function buildPackageJson(answers, options) {
208
64
  const deps = {
209
- '@cogitator-ai/core': '^0.1.0',
210
- '@cogitator-ai/channels': '^0.1.0',
211
- '@cogitator-ai/memory': '^0.1.0',
212
- zod: '^3.22.4',
65
+ ...options.dependencyVersions,
66
+ zod: '^4.0.0',
213
67
  };
214
68
  if (answers.channels.includes('telegram'))
215
69
  deps.grammy = '^1.20.0';
216
70
  if (answers.channels.includes('discord'))
217
71
  deps['discord.js'] = '^14.0.0';
218
72
  if (answers.channels.includes('slack'))
219
- deps['@slack/bolt'] = '^3.0.0';
73
+ deps['@slack/bolt'] = '^4.0.0';
220
74
  if (answers.channels.includes('webchat'))
221
- deps.ws = '^8.0.0';
222
- writeFileSync(join(projectPath, 'package.json'), JSON.stringify({
75
+ deps.ws = '^8.18.0';
76
+ if (answers.memory === 'sqlite')
77
+ deps['better-sqlite3'] = '^11.6.0';
78
+ if (answers.memory === 'postgres')
79
+ deps.pg = '^8.18.0';
80
+ const sortedDeps = Object.fromEntries(Object.entries(deps).sort(([a], [b]) => a.localeCompare(b)));
81
+ return (JSON.stringify({
223
82
  name: answers.projectName,
224
83
  version: '0.1.0',
84
+ private: true,
225
85
  type: 'module',
86
+ engines: { node: '>=22.12.0' },
226
87
  scripts: {
227
88
  dev: 'tsx watch src/agent.ts',
228
89
  start: 'tsx src/agent.ts',
229
90
  build: 'tsc',
230
91
  },
231
- dependencies: deps,
92
+ dependencies: sortedDeps,
232
93
  devDependencies: {
233
- '@types/node': '^20.10.0',
234
- tsx: '^4.7.0',
94
+ '@types/node': '^22.0.0',
95
+ tsx: '^4.21.0',
235
96
  typescript: '^5.3.0',
236
97
  },
237
- }, null, 2));
238
- writeFileSync(join(projectPath, 'tsconfig.json'), JSON.stringify({
239
- compilerOptions: {
240
- target: 'ES2022',
241
- module: 'NodeNext',
242
- moduleResolution: 'NodeNext',
243
- strict: true,
244
- esModuleInterop: true,
245
- skipLibCheck: true,
246
- outDir: './dist',
247
- rootDir: './src',
248
- },
249
- include: ['src/**/*'],
250
- }, null, 2));
251
- writeEnvFile(projectPath, answers);
252
- writeConfigFile(projectPath, answers);
253
- writeAgentFile(projectPath, answers);
254
- writeFileSync(join(projectPath, '.gitignore'), `node_modules/
255
- dist/
256
- .env
257
- *.log
258
- sessions.db
259
- `);
98
+ }, null, 2) + '\n');
260
99
  }
261
- function writeEnvFile(projectPath, answers) {
100
+ export function buildEnvFile(answers) {
262
101
  const lines = [];
263
- if (answers.apiKey && API_KEY_NAMES[answers.provider]) {
264
- lines.push(`${API_KEY_NAMES[answers.provider]}=${answers.apiKey}`);
102
+ if (answers.provider !== 'ollama' && answers.apiKey) {
103
+ lines.push(formatEnvLine(API_KEY_ENV[answers.provider], answers.apiKey));
265
104
  }
266
105
  if (answers.telegramToken)
267
- lines.push(`TELEGRAM_BOT_TOKEN=${answers.telegramToken}`);
106
+ lines.push(formatEnvLine('TELEGRAM_BOT_TOKEN', answers.telegramToken));
268
107
  if (answers.discordToken)
269
- lines.push(`DISCORD_BOT_TOKEN=${answers.discordToken}`);
108
+ lines.push(formatEnvLine('DISCORD_BOT_TOKEN', answers.discordToken));
270
109
  if (answers.slackToken)
271
- lines.push(`SLACK_BOT_TOKEN=${answers.slackToken}`);
272
- if (answers.slackSigningSecret)
273
- lines.push(`SLACK_SIGNING_SECRET=${answers.slackSigningSecret}`);
274
- if (lines.length > 0) {
275
- writeFileSync(join(projectPath, '.env'), lines.join('\n') + '\n');
110
+ lines.push(formatEnvLine('SLACK_BOT_TOKEN', answers.slackToken));
111
+ if (answers.slackSigningSecret) {
112
+ lines.push(formatEnvLine('SLACK_SIGNING_SECRET', answers.slackSigningSecret));
113
+ }
114
+ if (answers.memory === 'postgres') {
115
+ lines.push(formatEnvLine('DATABASE_URL', answers.databaseUrl ?? DEFAULT_POSTGRES_URL));
276
116
  }
117
+ return lines.length > 0 ? lines.join('\n') + '\n' : null;
277
118
  }
278
- function writeConfigFile(projectPath, answers) {
119
+ function memorySetup(memory) {
120
+ switch (memory) {
121
+ case 'sqlite':
122
+ return {
123
+ importName: 'SQLiteAdapter',
124
+ code: `const memory = new SQLiteAdapter({ provider: 'sqlite', path: './data/memory.db' });`,
125
+ };
126
+ case 'postgres':
127
+ return {
128
+ importName: 'PostgresAdapter',
129
+ code: `const memory = new PostgresAdapter({
130
+ provider: 'postgres',
131
+ connectionString: requireEnv('DATABASE_URL'),
132
+ });`,
133
+ };
134
+ case 'memory':
135
+ return {
136
+ importName: 'InMemoryAdapter',
137
+ code: `const memory = new InMemoryAdapter({ provider: 'memory' });`,
138
+ };
139
+ }
140
+ }
141
+ export function buildGatewayFile(answers) {
279
142
  const channelImports = [];
280
143
  const channelSetup = [];
281
144
  for (const ch of answers.channels) {
282
145
  switch (ch) {
283
146
  case 'telegram':
284
147
  channelImports.push('telegramChannel');
285
- channelSetup.push(` telegramChannel({ token: process.env.TELEGRAM_BOT_TOKEN! }),`);
148
+ channelSetup.push(` telegramChannel({ token: requireEnv('TELEGRAM_BOT_TOKEN') }),`);
286
149
  break;
287
150
  case 'discord':
288
151
  channelImports.push('discordChannel');
289
- channelSetup.push(` discordChannel({ token: process.env.DISCORD_BOT_TOKEN!, mentionOnly: true }),`);
152
+ channelSetup.push(` discordChannel({ token: requireEnv('DISCORD_BOT_TOKEN'), mentionOnly: true }),`);
290
153
  break;
291
154
  case 'slack':
292
155
  channelImports.push('slackChannel');
293
- channelSetup.push(` slackChannel({\n token: process.env.SLACK_BOT_TOKEN!,\n signingSecret: process.env.SLACK_SIGNING_SECRET!,\n }),`);
156
+ channelSetup.push(` slackChannel({\n token: requireEnv('SLACK_BOT_TOKEN'),\n signingSecret: requireEnv('SLACK_SIGNING_SECRET'),\n }),`);
294
157
  break;
295
158
  case 'webchat':
296
159
  channelImports.push('webchatChannel');
@@ -299,11 +162,23 @@ function writeConfigFile(projectPath, answers) {
299
162
  }
300
163
  }
301
164
  const providerConfig = answers.provider === 'ollama'
302
- ? ` ollama: { baseUrl: 'http://localhost:11434' },`
303
- : ` ${answers.provider}: { apiKey: process.env.${API_KEY_NAMES[answers.provider]} },`;
304
- const content = `import { Cogitator, Agent } from '@cogitator-ai/core';
165
+ ? ` ollama: { baseUrl: process.env.OLLAMA_URL ?? '${DEFAULT_OLLAMA_URL}' },`
166
+ : ` ${answers.provider}: { apiKey: requireEnv('${API_KEY_ENV[answers.provider]}') },`;
167
+ const memory = memorySetup(answers.memory);
168
+ return `import { existsSync } from 'node:fs';
169
+ import { fileURLToPath } from 'node:url';
170
+ import { Cogitator, Agent } from '@cogitator-ai/core';
305
171
  import { Gateway, ${channelImports.join(', ')} } from '@cogitator-ai/channels';
306
- import { InMemoryAdapter } from '@cogitator-ai/memory';
172
+ import { ${memory.importName} } from '@cogitator-ai/memory';
173
+
174
+ const envFile = fileURLToPath(new URL('../.env', import.meta.url));
175
+ if (existsSync(envFile)) process.loadEnvFile(envFile);
176
+
177
+ function requireEnv(name: string): string {
178
+ const value = process.env[name];
179
+ if (!value) throw new Error(\`Missing environment variable \${name} (see .env)\`);
180
+ return value;
181
+ }
307
182
 
308
183
  const agent = new Agent({
309
184
  name: 'assistant',
@@ -314,13 +189,19 @@ Be concise and friendly. Use tools when available.\`,
314
189
 
315
190
  const cogitator = new Cogitator({
316
191
  llm: {
192
+ defaultProvider: '${answers.provider}',
317
193
  providers: {
318
194
  ${providerConfig}
319
195
  },
320
196
  },
321
197
  });
322
198
 
323
- const memory = new InMemoryAdapter();
199
+ ${memory.code}
200
+
201
+ const connected = await memory.connect();
202
+ if (!connected.success) {
203
+ throw new Error(\`Failed to connect memory: \${connected.error}\`);
204
+ }
324
205
 
325
206
  export const gateway = new Gateway({
326
207
  agent,
@@ -338,26 +219,229 @@ ${channelSetup.join('\n')}
338
219
  },
339
220
  });
340
221
  `;
341
- writeFileSync(join(projectPath, 'src', 'gateway.ts'), content);
342
222
  }
343
- function writeAgentFile(projectPath, answers) {
344
- const content = `import { gateway } from './gateway.js';
223
+ export function buildAgentFile(answers) {
224
+ const webchatLine = answers.channels.includes('webchat')
225
+ ? ` console.log('WebChat: ws://localhost:18789/ws');\n`
226
+ : '';
227
+ return `import { gateway } from './gateway.js';
345
228
 
346
- async function main() {
347
- await gateway.start();
229
+ let stopping = false;
348
230
 
349
- console.log('Assistant is running!');
350
- console.log('Connected channels:', gateway.stats.connectedChannels.join(', '));
351
- ${answers.channels.includes('webchat') ? ` console.log('WebChat: ws://localhost:18789/ws');\n` : ''}
352
- process.on('SIGINT', async () => {
353
- console.log('\\nShutting down...');
354
- await gateway.stop();
355
- process.exit(0);
356
- });
231
+ async function shutdown(signal: string) {
232
+ if (stopping) return;
233
+ stopping = true;
234
+ console.log(\`\\nReceived \${signal}, shutting down...\`);
235
+ await gateway.stop();
236
+ process.exit(0);
357
237
  }
358
238
 
359
- main().catch(console.error);
360
- `;
361
- writeFileSync(join(projectPath, 'src', 'agent.ts'), content);
239
+ process.once('SIGINT', () => void shutdown('SIGINT'));
240
+ process.once('SIGTERM', () => void shutdown('SIGTERM'));
241
+
242
+ await gateway.start();
243
+
244
+ console.log('Assistant is running!');
245
+ console.log('Connected channels:', gateway.stats.connectedChannels.join(', '));
246
+ ${webchatLine}`;
247
+ }
248
+ export function buildGitignore(answers) {
249
+ const lines = ['node_modules/', 'dist/', '.env', '*.log', '.cogitator/'];
250
+ if (answers.memory === 'sqlite')
251
+ lines.push('data/');
252
+ return lines.join('\n') + '\n';
253
+ }
254
+ export const TSCONFIG = {
255
+ compilerOptions: {
256
+ target: 'ES2022',
257
+ module: 'NodeNext',
258
+ moduleResolution: 'NodeNext',
259
+ strict: true,
260
+ esModuleInterop: true,
261
+ skipLibCheck: true,
262
+ outDir: './dist',
263
+ rootDir: './src',
264
+ },
265
+ include: ['src/**/*'],
266
+ };
267
+ export function buildProjectFiles(answers, options) {
268
+ const files = {
269
+ 'package.json': buildPackageJson(answers, options),
270
+ 'tsconfig.json': JSON.stringify(TSCONFIG, null, 2) + '\n',
271
+ 'src/gateway.ts': buildGatewayFile(answers),
272
+ 'src/agent.ts': buildAgentFile(answers),
273
+ '.gitignore': buildGitignore(answers),
274
+ };
275
+ const env = buildEnvFile(answers);
276
+ if (env)
277
+ files['.env'] = env;
278
+ if (options.packageManager === 'pnpm') {
279
+ files['pnpm-workspace.yaml'] = [
280
+ 'allowBuilds:',
281
+ ...PNPM_ALLOWED_BUILDS.map((name) => ` ${name}: true`),
282
+ '',
283
+ ].join('\n');
284
+ }
285
+ return files;
286
+ }
287
+ export function writeProjectFiles(projectPath, files) {
288
+ for (const [relativePath, content] of Object.entries(files)) {
289
+ const fullPath = join(projectPath, relativePath);
290
+ mkdirSync(dirname(fullPath), { recursive: true });
291
+ writeFileSync(fullPath, content, relativePath === '.env' ? { mode: 0o600 } : undefined);
292
+ }
293
+ }
294
+ function cancelled() {
295
+ p.cancel('Setup cancelled');
296
+ process.exit(0);
297
+ }
298
+ function answer(result) {
299
+ if (p.isCancel(result))
300
+ cancelled();
301
+ return result;
302
+ }
303
+ async function askSecret(message, emptyMessage) {
304
+ return answer(await p.password({ message, validate: (v) => (!v?.trim() ? emptyMessage : undefined) })).trim();
305
+ }
306
+ async function collectAnswers(nameArg) {
307
+ const projectName = nameArg ??
308
+ answer(await p.text({
309
+ message: 'Project name',
310
+ placeholder: 'my-assistant',
311
+ validate: (v) => validateProjectName(v ?? ''),
312
+ })).trim();
313
+ const providerChoice = answer(await p.select({
314
+ message: 'Which LLM provider?',
315
+ options: [
316
+ { value: 'anthropic', label: 'Anthropic (Claude)', hint: 'recommended' },
317
+ { value: 'openai', label: 'OpenAI (GPT)' },
318
+ { value: 'google', label: 'Google (Gemini)' },
319
+ { value: 'ollama', label: 'Ollama (local, free)', hint: 'no API key needed' },
320
+ ],
321
+ }));
322
+ if (!isSetupProvider(providerChoice))
323
+ cancelled();
324
+ const provider = providerChoice;
325
+ const apiKey = provider === 'ollama'
326
+ ? ''
327
+ : await askSecret(`${API_KEY_ENV[provider]}:`, 'API key is required');
328
+ const spinner = p.spinner();
329
+ spinner.start('Fetching available models...');
330
+ const modelOptions = provider === 'ollama'
331
+ ? await fetchOllamaModelOptions(resolveOllamaUrl(), process.env.OLLAMA_API_KEY)
332
+ : await fetchProviderModels(provider);
333
+ spinner.stop(`Found ${modelOptions.length} models`);
334
+ const model = answer(await p.select({ message: 'Default model', options: modelOptions }));
335
+ const channelChoice = answer(await p.multiselect({
336
+ message: 'Which channels to connect?',
337
+ options: [
338
+ { value: 'telegram', label: 'Telegram' },
339
+ { value: 'discord', label: 'Discord' },
340
+ { value: 'slack', label: 'Slack' },
341
+ { value: 'webchat', label: 'WebChat (localhost)', hint: 'no setup needed' },
342
+ ],
343
+ required: false,
344
+ }));
345
+ const channels = channelChoice.filter((c) => c === 'telegram' || c === 'discord' || c === 'slack' || c === 'webchat');
346
+ const telegramToken = channels.includes('telegram')
347
+ ? await askSecret('Telegram bot token (from @BotFather):', 'Token is required')
348
+ : undefined;
349
+ const discordToken = channels.includes('discord')
350
+ ? await askSecret('Discord bot token:', 'Token is required')
351
+ : undefined;
352
+ const slackToken = channels.includes('slack')
353
+ ? await askSecret('Slack bot token (xoxb-...):', 'Token is required')
354
+ : undefined;
355
+ const slackSigningSecret = channels.includes('slack')
356
+ ? await askSecret('Slack signing secret:', 'Signing secret is required')
357
+ : undefined;
358
+ const memoryChoice = answer(await p.select({
359
+ message: 'Memory adapter',
360
+ options: [
361
+ { value: 'sqlite', label: 'SQLite (recommended)', hint: 'zero config, file-based' },
362
+ { value: 'memory', label: 'In-memory', hint: 'no persistence, for testing' },
363
+ { value: 'postgres', label: 'PostgreSQL', hint: 'production-grade' },
364
+ ],
365
+ }));
366
+ const memory = memoryChoice === 'memory' || memoryChoice === 'postgres' ? memoryChoice : 'sqlite';
367
+ const databaseUrl = memory === 'postgres'
368
+ ? answer(await p.text({
369
+ message: 'PostgreSQL connection string',
370
+ initialValue: DEFAULT_POSTGRES_URL,
371
+ validate: (v) => !/^postgres(ql)?:\/\//.test(v?.trim() ?? '') ? 'Expected postgres://...' : undefined,
372
+ })).trim()
373
+ : undefined;
374
+ return {
375
+ projectName,
376
+ provider,
377
+ apiKey,
378
+ model,
379
+ channels: channels.length > 0 ? channels : ['webchat'],
380
+ telegramToken,
381
+ discordToken,
382
+ slackToken,
383
+ slackSigningSecret,
384
+ memory,
385
+ databaseUrl,
386
+ };
362
387
  }
388
+ export const initCommand = new Command('init')
389
+ .description('Create a new Cogitator AI assistant project')
390
+ .argument('[name]', 'Project name')
391
+ .option('--no-install', 'Skip dependency installation')
392
+ .action(async (nameArg, options) => {
393
+ printBanner();
394
+ p.intro(chalk.bgCyan(chalk.black(' cogitator init ')));
395
+ if (nameArg !== undefined) {
396
+ const error = validateProjectName(nameArg);
397
+ if (error) {
398
+ p.cancel(`Invalid project name "${nameArg}": ${error}`);
399
+ process.exit(1);
400
+ }
401
+ }
402
+ const answers = await collectAnswers(nameArg?.trim());
403
+ const projectPath = resolve(process.cwd(), answers.projectName);
404
+ if (existsSync(projectPath)) {
405
+ p.cancel(`Directory "${answers.projectName}" already exists`);
406
+ process.exit(1);
407
+ }
408
+ const pm = detectPackageManager();
409
+ const files = buildProjectFiles(answers, {
410
+ dependencyVersions: resolveDependencyVersions(),
411
+ packageManager: pm,
412
+ });
413
+ let installed = false;
414
+ await p.tasks([
415
+ {
416
+ title: 'Creating project structure',
417
+ task: () => {
418
+ writeProjectFiles(projectPath, files);
419
+ return 'Project files created';
420
+ },
421
+ },
422
+ {
423
+ title: `Installing dependencies with ${pm}`,
424
+ enabled: options.install,
425
+ task: () => {
426
+ try {
427
+ execFileSync(pm, ['install'], { cwd: projectPath, stdio: 'pipe' });
428
+ installed = true;
429
+ return 'Dependencies installed';
430
+ }
431
+ catch {
432
+ return `Install failed — run "${pm} install" manually`;
433
+ }
434
+ },
435
+ },
436
+ ]);
437
+ p.note([
438
+ `cd ${answers.projectName}`,
439
+ installed ? '' : `${pm} install`,
440
+ `${runScript(pm, 'dev')} # start with hot reload`,
441
+ `cogitator assistant # or run with the live dashboard`,
442
+ ]
443
+ .filter(Boolean)
444
+ .join('\n'), 'Next steps', { format: (line) => chalk.dim(line) });
445
+ p.outro(`${chalk.green('Your assistant is ready!')} Channels: ${chalk.cyan(answers.channels.join(', '))}`);
446
+ });
363
447
  //# sourceMappingURL=init.js.map