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