@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.
- package/README.md +160 -511
- 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 +115 -60
- 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 +51 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +349 -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 +156 -131
- 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 +307 -271
- 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 +81 -0
- package/dist/utils/provider-models.js.map +1 -0
- package/package.json +21 -19
package/dist/commands/init.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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;
|
|
42
|
+
catch {
|
|
43
|
+
deps = {};
|
|
160
44
|
}
|
|
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;
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
207
|
-
|
|
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
|
-
|
|
210
|
-
|
|
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'] = '^
|
|
73
|
+
deps['@slack/bolt'] = '^4.0.0';
|
|
220
74
|
if (answers.channels.includes('webchat'))
|
|
221
|
-
deps.ws = '^8.
|
|
222
|
-
|
|
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:
|
|
92
|
+
dependencies: sortedDeps,
|
|
232
93
|
devDependencies: {
|
|
233
|
-
'@types/node': '^
|
|
234
|
-
tsx: '^4.
|
|
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
|
|
100
|
+
export function buildEnvFile(answers) {
|
|
262
101
|
const lines = [];
|
|
263
|
-
if (answers.
|
|
264
|
-
lines.push(
|
|
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(
|
|
106
|
+
lines.push(formatEnvLine('TELEGRAM_BOT_TOKEN', answers.telegramToken));
|
|
268
107
|
if (answers.discordToken)
|
|
269
|
-
lines.push(
|
|
108
|
+
lines.push(formatEnvLine('DISCORD_BOT_TOKEN', answers.discordToken));
|
|
270
109
|
if (answers.slackToken)
|
|
271
|
-
lines.push(
|
|
272
|
-
if (answers.slackSigningSecret)
|
|
273
|
-
lines.push(
|
|
274
|
-
|
|
275
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
? `
|
|
303
|
-
: `
|
|
304
|
-
const
|
|
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 {
|
|
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
|
-
|
|
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
|
|
344
|
-
const
|
|
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
|
-
|
|
347
|
-
await gateway.start();
|
|
229
|
+
let stopping = false;
|
|
348
230
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|