@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/wizard.js
CHANGED
|
@@ -4,96 +4,84 @@ import { resolve } from 'node:path';
|
|
|
4
4
|
import * as p from '@clack/prompts';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { stringify, parse as parseYaml } from 'yaml';
|
|
7
|
-
import {
|
|
7
|
+
import { AssistantConfigSchema } from '@cogitator-ai/channels';
|
|
8
8
|
import { printBanner } from '../utils/logger.js';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
{ label: 'GPT-4o', value: 'openai/gpt-4o' },
|
|
17
|
-
{ label: 'GPT-4o mini', value: 'openai/gpt-4o-mini' },
|
|
18
|
-
{ label: 'o3-mini', value: 'openai/o3-mini' },
|
|
19
|
-
],
|
|
20
|
-
google: [
|
|
21
|
-
{ label: 'Gemini 2.5 Flash', value: 'google/gemini-2.5-flash' },
|
|
22
|
-
{ label: 'Gemini 2.5 Pro', value: 'google/gemini-2.5-pro' },
|
|
23
|
-
],
|
|
24
|
-
ollama: [
|
|
25
|
-
{ label: 'Llama 3.1 8B', value: 'ollama/llama3.1:8b' },
|
|
26
|
-
{ label: 'Gemma 3 4B', value: 'ollama/gemma3:4b' },
|
|
27
|
-
{ label: 'Mistral 7B', value: 'ollama/mistral:7b' },
|
|
28
|
-
],
|
|
9
|
+
import { mergeEnvContent, parseDotenv } from '../utils/env.js';
|
|
10
|
+
import { DEFAULT_OLLAMA_URL, resolveOllamaUrl } from '../utils/ollama.js';
|
|
11
|
+
import { API_KEY_ENV, fetchOllamaModelOptions, fetchProviderModels, isSetupProvider, withProviderPrefix, } from '../utils/provider-models.js';
|
|
12
|
+
export const CHANNEL_TOKEN_ENV = {
|
|
13
|
+
telegram: 'TG_TOKEN',
|
|
14
|
+
discord: 'DISCORD_TOKEN',
|
|
15
|
+
slack: 'SLACK_BOT_TOKEN',
|
|
29
16
|
};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
17
|
+
const CHANNELS = ['telegram', 'discord', 'slack'];
|
|
18
|
+
const DEFAULT_MEMORY_PATH = '~/.cogitator/memory.db';
|
|
19
|
+
const NOTE_STYLE = { format: (line) => chalk.dim(line) };
|
|
20
|
+
export function splitCommandLine(command) {
|
|
21
|
+
const parts = [];
|
|
22
|
+
let current = '';
|
|
23
|
+
let quote = null;
|
|
24
|
+
let hasToken = false;
|
|
25
|
+
for (let i = 0; i < command.length; i++) {
|
|
26
|
+
const ch = command[i];
|
|
27
|
+
if (quote) {
|
|
28
|
+
if (ch === quote) {
|
|
29
|
+
quote = null;
|
|
30
|
+
}
|
|
31
|
+
else if (ch === '\\' && quote === '"' && i + 1 < command.length) {
|
|
32
|
+
current += command[++i];
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
current += ch;
|
|
36
|
+
}
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (ch === '"' || ch === "'") {
|
|
40
|
+
quote = ch;
|
|
41
|
+
hasToken = true;
|
|
42
|
+
}
|
|
43
|
+
else if (ch === '\\' && i + 1 < command.length) {
|
|
44
|
+
current += command[++i];
|
|
45
|
+
hasToken = true;
|
|
46
|
+
}
|
|
47
|
+
else if (/\s/.test(ch)) {
|
|
48
|
+
if (hasToken)
|
|
49
|
+
parts.push(current);
|
|
50
|
+
current = '';
|
|
51
|
+
hasToken = false;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
current += ch;
|
|
55
|
+
hasToken = true;
|
|
41
56
|
}
|
|
42
|
-
if (models.length === 0)
|
|
43
|
-
return FALLBACK_MODELS[provider] ?? [];
|
|
44
|
-
return models.map((m) => ({
|
|
45
|
-
label: m.displayName,
|
|
46
|
-
value: m.id,
|
|
47
|
-
}));
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return FALLBACK_MODELS[provider] ?? [];
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
async function fetchOllamaModels(baseUrl) {
|
|
54
|
-
try {
|
|
55
|
-
const res = await fetch(`${baseUrl}/api/tags`);
|
|
56
|
-
if (!res.ok)
|
|
57
|
-
return FALLBACK_MODELS.ollama;
|
|
58
|
-
const data = (await res.json());
|
|
59
|
-
if (!data.models?.length)
|
|
60
|
-
return FALLBACK_MODELS.ollama;
|
|
61
|
-
return data.models.map((m) => ({
|
|
62
|
-
label: m.name,
|
|
63
|
-
value: `ollama/${m.name}`,
|
|
64
|
-
hint: formatBytes(m.size),
|
|
65
|
-
}));
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
return FALLBACK_MODELS.ollama;
|
|
69
57
|
}
|
|
58
|
+
if (quote)
|
|
59
|
+
throw new Error('Unterminated quote in command');
|
|
60
|
+
if (hasToken)
|
|
61
|
+
parts.push(current);
|
|
62
|
+
return parts;
|
|
70
63
|
}
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
function extractMcpName(args) {
|
|
64
|
+
export function extractMcpName(args) {
|
|
65
|
+
const strip = (name) => name
|
|
66
|
+
.replace(/@[^@/]*$/, '')
|
|
67
|
+
.replace(/^(?:mcp-|server-)+/, '')
|
|
68
|
+
.replace(/(?:-mcp|-server)+$/, '');
|
|
77
69
|
const pkg = args.find((a) => a.startsWith('@') || (!a.startsWith('-') && a.includes('/')));
|
|
78
70
|
if (pkg) {
|
|
79
|
-
const name = pkg.split('/').pop() ?? pkg;
|
|
80
|
-
return name
|
|
71
|
+
const name = pkg.startsWith('@') ? (pkg.split('/')[1] ?? pkg) : (pkg.split('/').pop() ?? pkg);
|
|
72
|
+
return strip(name) || 'mcp-server';
|
|
81
73
|
}
|
|
82
74
|
const nonFlag = args.find((a) => !a.startsWith('-'));
|
|
83
75
|
if (nonFlag)
|
|
84
|
-
return nonFlag
|
|
76
|
+
return strip(nonFlag) || 'mcp-server';
|
|
85
77
|
return 'mcp-server';
|
|
86
78
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
telegram: 'TG_TOKEN',
|
|
94
|
-
discord: 'DISCORD_TOKEN',
|
|
95
|
-
slack: 'SLACK_BOT_TOKEN',
|
|
96
|
-
};
|
|
79
|
+
export function parsePathList(raw) {
|
|
80
|
+
return raw
|
|
81
|
+
.split(',')
|
|
82
|
+
.map((s) => s.trim())
|
|
83
|
+
.filter((s) => s.length > 0);
|
|
84
|
+
}
|
|
97
85
|
function cancel() {
|
|
98
86
|
p.cancel('Setup cancelled');
|
|
99
87
|
process.exit(0);
|
|
@@ -103,6 +91,28 @@ function prompt(result) {
|
|
|
103
91
|
cancel();
|
|
104
92
|
return result;
|
|
105
93
|
}
|
|
94
|
+
function loadExistingConfig(configPath) {
|
|
95
|
+
try {
|
|
96
|
+
const raw = parseYaml(readFileSync(configPath, 'utf-8'));
|
|
97
|
+
const parsed = AssistantConfigSchema.safeParse(raw);
|
|
98
|
+
if (parsed.success)
|
|
99
|
+
return parsed.data;
|
|
100
|
+
p.log.warn('Existing config is not valid; known values will be pre-filled where possible');
|
|
101
|
+
const partial = AssistantConfigSchema.partial().safeParse(raw);
|
|
102
|
+
return partial.success ? partial.data : {};
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
p.log.warn('Failed to parse existing config, starting fresh');
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function askSecret(opts) {
|
|
110
|
+
const value = prompt(await p.password({
|
|
111
|
+
message: opts.current ? `${opts.message} (leave blank to keep current)` : opts.message,
|
|
112
|
+
validate: (v) => (!opts.current && !v?.trim() ? opts.required : undefined),
|
|
113
|
+
})).trim();
|
|
114
|
+
return value || undefined;
|
|
115
|
+
}
|
|
106
116
|
export const wizardCommand = new Command('wizard')
|
|
107
117
|
.description('Interactive assistant setup — generates cogitator.yml + .env')
|
|
108
118
|
.option('--edit', 'Edit existing cogitator.yml')
|
|
@@ -110,88 +120,125 @@ export const wizardCommand = new Command('wizard')
|
|
|
110
120
|
printBanner();
|
|
111
121
|
const configPath = resolve(process.cwd(), 'cogitator.yml');
|
|
112
122
|
const envPath = resolve(process.cwd(), '.env');
|
|
123
|
+
const existingEnvContent = existsSync(envPath) ? readFileSync(envPath, 'utf-8') : '';
|
|
124
|
+
const existingEnv = parseDotenv(existingEnvContent);
|
|
125
|
+
const editing = options.edit === true && existsSync(configPath);
|
|
126
|
+
if (!options.edit && existsSync(configPath)) {
|
|
127
|
+
const overwrite = prompt(await p.confirm({
|
|
128
|
+
message: 'cogitator.yml already exists. Overwrite it? (use --edit to modify instead)',
|
|
129
|
+
initialValue: false,
|
|
130
|
+
}));
|
|
131
|
+
if (!overwrite)
|
|
132
|
+
cancel();
|
|
133
|
+
}
|
|
113
134
|
let existing = {};
|
|
114
|
-
if (
|
|
135
|
+
if (editing) {
|
|
115
136
|
p.intro(chalk.bgCyan(chalk.black(' Cogitator Assistant Setup (editing existing config) ')));
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
p.log.warn('Failed to parse existing config, starting fresh');
|
|
121
|
-
}
|
|
122
|
-
p.note(`Loaded ${chalk.dim(configPath)}`, 'Existing config found');
|
|
137
|
+
existing = loadExistingConfig(configPath);
|
|
138
|
+
p.note(`Loaded ${chalk.dim(configPath)}`, 'Existing config found', NOTE_STYLE);
|
|
123
139
|
}
|
|
124
140
|
else {
|
|
125
141
|
p.intro(chalk.bgCyan(chalk.black(' Cogitator Personal Assistant Setup ')));
|
|
126
142
|
}
|
|
127
|
-
const
|
|
143
|
+
const envUpdates = new Map();
|
|
144
|
+
const existingUserName = existing.personality?.match(/assistant for ([^.\n]+)\./)?.[1];
|
|
128
145
|
const userName = prompt(await p.text({
|
|
129
146
|
message: "What's your name?",
|
|
130
147
|
placeholder: 'Alex',
|
|
131
148
|
...(existingUserName ? { initialValue: existingUserName } : {}),
|
|
132
|
-
validate: (v) => (!v
|
|
133
|
-
}));
|
|
149
|
+
validate: (v) => (!v?.trim() ? 'Name is required' : undefined),
|
|
150
|
+
})).trim();
|
|
134
151
|
const assistantName = prompt(await p.text({
|
|
135
152
|
message: 'Assistant name',
|
|
136
153
|
placeholder: 'jarvis',
|
|
137
154
|
initialValue: existing.name ?? 'jarvis',
|
|
138
|
-
validate: (v) => (!v
|
|
139
|
-
}));
|
|
140
|
-
const
|
|
155
|
+
validate: (v) => (!v?.trim() ? 'Name is required' : undefined),
|
|
156
|
+
})).trim();
|
|
157
|
+
const providerChoice = prompt(await p.select({
|
|
141
158
|
message: 'LLM Provider',
|
|
142
159
|
initialValue: existing.llm?.provider ?? 'google',
|
|
143
160
|
options: [
|
|
144
161
|
{ value: 'google', label: 'Google (Gemini)', hint: 'recommended' },
|
|
145
162
|
{ value: 'anthropic', label: 'Anthropic (Claude)' },
|
|
146
|
-
{ value: 'openai', label: 'OpenAI (GPT
|
|
163
|
+
{ value: 'openai', label: 'OpenAI (GPT)' },
|
|
147
164
|
{ value: 'ollama', label: 'Ollama', hint: 'local or cloud' },
|
|
148
165
|
],
|
|
149
166
|
}));
|
|
150
|
-
|
|
151
|
-
|
|
167
|
+
if (!isSetupProvider(providerChoice))
|
|
168
|
+
cancel();
|
|
169
|
+
const provider = providerChoice;
|
|
170
|
+
let ollamaUrl = DEFAULT_OLLAMA_URL;
|
|
171
|
+
let ollamaApiKey;
|
|
152
172
|
if (provider === 'ollama') {
|
|
173
|
+
const currentUrl = resolveOllamaUrl(existingEnv);
|
|
153
174
|
const ollamaMode = prompt(await p.select({
|
|
154
175
|
message: 'Ollama deployment',
|
|
155
|
-
initialValue: 'local',
|
|
176
|
+
initialValue: currentUrl === DEFAULT_OLLAMA_URL ? 'local' : 'cloud',
|
|
156
177
|
options: [
|
|
157
178
|
{ value: 'local', label: 'Local', hint: 'running on this machine' },
|
|
158
179
|
{ value: 'cloud', label: 'Cloud / Remote', hint: 'custom URL + optional API key' },
|
|
159
180
|
],
|
|
160
181
|
}));
|
|
161
182
|
if (ollamaMode === 'cloud') {
|
|
162
|
-
|
|
183
|
+
const rawUrl = prompt(await p.text({
|
|
163
184
|
message: 'Ollama URL',
|
|
164
|
-
placeholder: 'https://ollama.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
185
|
+
placeholder: 'https://ollama.com',
|
|
186
|
+
...(currentUrl !== DEFAULT_OLLAMA_URL ? { initialValue: currentUrl } : {}),
|
|
187
|
+
validate: (v) => {
|
|
188
|
+
const url = v?.trim();
|
|
189
|
+
if (!url)
|
|
190
|
+
return 'URL is required';
|
|
191
|
+
try {
|
|
192
|
+
new URL(url);
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return 'Enter a valid URL, e.g. https://ollama.com';
|
|
197
|
+
}
|
|
198
|
+
},
|
|
170
199
|
}));
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
200
|
+
ollamaUrl = resolveOllamaUrl({}, rawUrl);
|
|
201
|
+
envUpdates.set('OLLAMA_URL', ollamaUrl);
|
|
202
|
+
const key = prompt(await p.password({
|
|
203
|
+
message: existingEnv.OLLAMA_API_KEY
|
|
204
|
+
? 'API key (leave blank to keep current)'
|
|
205
|
+
: 'API key (leave blank if not needed)',
|
|
206
|
+
})).trim();
|
|
207
|
+
if (key)
|
|
208
|
+
envUpdates.set('OLLAMA_API_KEY', key);
|
|
209
|
+
ollamaApiKey = key || existingEnv.OLLAMA_API_KEY;
|
|
210
|
+
}
|
|
211
|
+
else if (existingEnv.OLLAMA_URL) {
|
|
212
|
+
envUpdates.set('OLLAMA_URL', DEFAULT_OLLAMA_URL);
|
|
174
213
|
}
|
|
175
214
|
}
|
|
176
215
|
else {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
216
|
+
const keyName = API_KEY_ENV[provider];
|
|
217
|
+
const key = await askSecret({
|
|
218
|
+
message: `${keyName}:`,
|
|
219
|
+
current: existingEnv[keyName] ?? process.env[keyName],
|
|
220
|
+
required: 'API key is required',
|
|
221
|
+
});
|
|
222
|
+
if (key)
|
|
223
|
+
envUpdates.set(keyName, key);
|
|
181
224
|
}
|
|
182
225
|
const modelSpinner = p.spinner();
|
|
183
226
|
modelSpinner.start('Fetching available models...');
|
|
184
|
-
const modelOptions = provider === 'ollama'
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const
|
|
227
|
+
const modelOptions = provider === 'ollama'
|
|
228
|
+
? await fetchOllamaModelOptions(ollamaUrl, ollamaApiKey)
|
|
229
|
+
: await fetchProviderModels(provider);
|
|
230
|
+
modelSpinner.stop(`Found ${chalk.cyan(modelOptions.length)} models`);
|
|
231
|
+
const existingModel = existing.llm?.model && existing.llm.provider === provider
|
|
232
|
+
? withProviderPrefix(provider, existing.llm.model)
|
|
233
|
+
: undefined;
|
|
189
234
|
const model = prompt(await p.select({
|
|
190
235
|
message: 'Model',
|
|
191
|
-
...(
|
|
236
|
+
...(existingModel && modelOptions.some((m) => m.value === existingModel)
|
|
237
|
+
? { initialValue: existingModel }
|
|
238
|
+
: {}),
|
|
192
239
|
options: modelOptions,
|
|
193
240
|
}));
|
|
194
|
-
const existingChannels =
|
|
241
|
+
const existingChannels = CHANNELS.filter((ch) => existing.channels?.[ch] !== undefined);
|
|
195
242
|
const selectedChannels = prompt(await p.multiselect({
|
|
196
243
|
message: 'Channels (terminal is always available)',
|
|
197
244
|
options: [
|
|
@@ -201,44 +248,38 @@ export const wizardCommand = new Command('wizard')
|
|
|
201
248
|
],
|
|
202
249
|
...(existingChannels.length > 0 ? { initialValues: existingChannels } : {}),
|
|
203
250
|
required: false,
|
|
204
|
-
}));
|
|
205
|
-
const envLines = [];
|
|
206
|
-
if (provider === 'ollama') {
|
|
207
|
-
if (ollamaUrl !== 'http://localhost:11434') {
|
|
208
|
-
envLines.push(`OLLAMA_URL=${ollamaUrl}`);
|
|
209
|
-
}
|
|
210
|
-
if (apiKey) {
|
|
211
|
-
envLines.push(`OLLAMA_API_KEY=${apiKey}`);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
else if (apiKey && API_KEY_NAMES[provider]) {
|
|
215
|
-
envLines.push(`${API_KEY_NAMES[provider]}=${apiKey}`);
|
|
216
|
-
}
|
|
251
|
+
})).filter((c) => c === 'telegram' || c === 'discord' || c === 'slack');
|
|
217
252
|
const channelsConfig = {};
|
|
218
253
|
for (const ch of selectedChannels) {
|
|
219
|
-
const
|
|
254
|
+
const tokenEnv = CHANNEL_TOKEN_ENV[ch];
|
|
255
|
+
const token = await askSecret({
|
|
220
256
|
message: `${ch} bot token:`,
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
257
|
+
current: existingEnv[tokenEnv],
|
|
258
|
+
required: 'Token is required',
|
|
259
|
+
});
|
|
260
|
+
if (token)
|
|
261
|
+
envUpdates.set(tokenEnv, token);
|
|
262
|
+
const existingOwners = existing.channels?.[ch]?.ownerIds?.join(', ');
|
|
263
|
+
const ownerIdsRaw = prompt(await p.text({
|
|
264
|
+
message: `${ch} owner user ID(s), comma-separated (for admin commands)`,
|
|
225
265
|
placeholder: 'your user ID',
|
|
226
|
-
|
|
266
|
+
...(existingOwners ? { initialValue: existingOwners } : {}),
|
|
267
|
+
validate: (v) => parsePathList(v ?? '').length === 0 ? 'Owner ID is required' : undefined,
|
|
227
268
|
}));
|
|
228
|
-
envLines.push(`${CHANNEL_TOKEN_ENV[ch]}=${token}`);
|
|
229
269
|
if (ch === 'slack') {
|
|
230
|
-
const signingSecret =
|
|
270
|
+
const signingSecret = await askSecret({
|
|
231
271
|
message: 'Slack signing secret:',
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
272
|
+
current: existingEnv.SLACK_SIGNING_SECRET,
|
|
273
|
+
required: 'Required',
|
|
274
|
+
});
|
|
275
|
+
if (signingSecret)
|
|
276
|
+
envUpdates.set('SLACK_SIGNING_SECRET', signingSecret);
|
|
235
277
|
}
|
|
236
|
-
channelsConfig[ch] = { ownerIds:
|
|
278
|
+
channelsConfig[ch] = { ownerIds: parsePathList(ownerIdsRaw) };
|
|
237
279
|
}
|
|
238
|
-
const existingCaps = Object.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
});
|
|
280
|
+
const existingCaps = Object.entries(existing.capabilities ?? {})
|
|
281
|
+
.filter(([, value]) => value === true || (typeof value === 'object' && value !== null))
|
|
282
|
+
.map(([key]) => key);
|
|
242
283
|
const selectedCapabilities = prompt(await p.multiselect({
|
|
243
284
|
message: 'Capabilities',
|
|
244
285
|
options: [
|
|
@@ -261,107 +302,118 @@ export const wizardCommand = new Command('wizard')
|
|
|
261
302
|
}));
|
|
262
303
|
const capabilities = {};
|
|
263
304
|
for (const cap of selectedCapabilities) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
paths: pathsRaw
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
else if (cap === 'browser') {
|
|
277
|
-
const browserOpts = prompt(await p.multiselect({
|
|
278
|
-
message: 'Browser options',
|
|
279
|
-
options: [
|
|
280
|
-
{ value: 'visible', label: 'Visible window', hint: 'see what the agent does' },
|
|
281
|
-
{
|
|
282
|
-
value: 'stealth',
|
|
283
|
-
label: 'Stealth mode',
|
|
284
|
-
hint: 'anti-detection, human-like behavior',
|
|
285
|
-
},
|
|
286
|
-
],
|
|
287
|
-
required: false,
|
|
288
|
-
}));
|
|
289
|
-
if (browserOpts.includes('visible') || browserOpts.includes('stealth')) {
|
|
290
|
-
capabilities.browser = {
|
|
291
|
-
headless: !browserOpts.includes('visible'),
|
|
292
|
-
stealth: browserOpts.includes('stealth'),
|
|
293
|
-
};
|
|
305
|
+
switch (cap) {
|
|
306
|
+
case 'fileSystem': {
|
|
307
|
+
const existingFsPaths = existing.capabilities?.fileSystem?.paths?.join(', ');
|
|
308
|
+
const pathsRaw = prompt(await p.text({
|
|
309
|
+
message: 'Paths to allow for file system access',
|
|
310
|
+
placeholder: '~/Documents, ~/Projects',
|
|
311
|
+
...(existingFsPaths ? { initialValue: existingFsPaths } : {}),
|
|
312
|
+
validate: (v) => parsePathList(v ?? '').length === 0 ? 'At least one path is required' : undefined,
|
|
313
|
+
}));
|
|
314
|
+
capabilities.fileSystem = { paths: parsePathList(pathsRaw) };
|
|
315
|
+
break;
|
|
294
316
|
}
|
|
295
|
-
|
|
296
|
-
|
|
317
|
+
case 'browser': {
|
|
318
|
+
const current = existing.capabilities?.browser;
|
|
319
|
+
const currentOpts = typeof current === 'object'
|
|
320
|
+
? [...(current.headless ? [] : ['visible']), ...(current.stealth ? ['stealth'] : [])]
|
|
321
|
+
: [];
|
|
322
|
+
const browserOpts = prompt(await p.multiselect({
|
|
323
|
+
message: 'Browser options',
|
|
324
|
+
options: [
|
|
325
|
+
{ value: 'visible', label: 'Visible window', hint: 'see what the agent does' },
|
|
326
|
+
{
|
|
327
|
+
value: 'stealth',
|
|
328
|
+
label: 'Stealth mode',
|
|
329
|
+
hint: 'anti-detection, human-like behavior',
|
|
330
|
+
},
|
|
331
|
+
],
|
|
332
|
+
...(currentOpts.length > 0 ? { initialValues: currentOpts } : {}),
|
|
333
|
+
required: false,
|
|
334
|
+
}));
|
|
335
|
+
capabilities.browser =
|
|
336
|
+
browserOpts.length > 0
|
|
337
|
+
? {
|
|
338
|
+
headless: !browserOpts.includes('visible'),
|
|
339
|
+
stealth: browserOpts.includes('stealth'),
|
|
340
|
+
}
|
|
341
|
+
: true;
|
|
342
|
+
break;
|
|
297
343
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
344
|
+
case 'rag': {
|
|
345
|
+
const existingRagPaths = existing.capabilities?.rag?.paths?.join(', ');
|
|
346
|
+
const pathsRaw = prompt(await p.text({
|
|
347
|
+
message: 'Paths to index for RAG',
|
|
348
|
+
placeholder: '~/Documents/notes, ~/wiki',
|
|
349
|
+
...(existingRagPaths ? { initialValue: existingRagPaths } : {}),
|
|
350
|
+
validate: (v) => parsePathList(v ?? '').length === 0 ? 'At least one path is required' : undefined,
|
|
351
|
+
}));
|
|
352
|
+
capabilities.rag = { paths: parsePathList(pathsRaw) };
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
case 'github': {
|
|
356
|
+
const ghToken = await askSecret({
|
|
357
|
+
message: 'GitHub Personal Access Token (for GitHub API):',
|
|
358
|
+
current: existingEnv.GITHUB_TOKEN,
|
|
359
|
+
required: 'Token is required for GitHub capability',
|
|
360
|
+
});
|
|
361
|
+
if (ghToken)
|
|
362
|
+
envUpdates.set('GITHUB_TOKEN', ghToken);
|
|
363
|
+
capabilities.github = true;
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
case 'selfTools':
|
|
367
|
+
capabilities.selfTools = existing.capabilities?.selfTools ?? true;
|
|
368
|
+
break;
|
|
369
|
+
case 'webSearch':
|
|
370
|
+
case 'deviceTools':
|
|
371
|
+
case 'scheduler':
|
|
372
|
+
case 'selfConfig':
|
|
373
|
+
capabilities[cap] = true;
|
|
374
|
+
break;
|
|
321
375
|
}
|
|
322
376
|
}
|
|
323
|
-
const mcpServers = {};
|
|
324
|
-
const existingMcpNames = Object.keys(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
initialValue: existingMcpNames.length > 0,
|
|
328
|
-
}));
|
|
329
|
-
if (addMcp) {
|
|
330
|
-
let addMore = true;
|
|
331
|
-
while (addMore) {
|
|
332
|
-
const fullCommand = prompt(await p.text({
|
|
333
|
-
message: 'Full command to start MCP server',
|
|
334
|
-
placeholder: 'npx -y @modelcontextprotocol/server-filesystem /home',
|
|
335
|
-
validate: (v) => (!v.trim() ? 'Command is required' : undefined),
|
|
336
|
-
}));
|
|
337
|
-
const parts = fullCommand
|
|
338
|
-
.trim()
|
|
339
|
-
.split(/\s+/)
|
|
340
|
-
.filter((s) => s.length > 0);
|
|
341
|
-
const command = parts[0];
|
|
342
|
-
const args = parts.slice(1);
|
|
343
|
-
const detectedName = extractMcpName(args);
|
|
344
|
-
const name = prompt(await p.text({
|
|
345
|
-
message: 'Server name',
|
|
346
|
-
initialValue: detectedName,
|
|
347
|
-
validate: (v) => (!v.trim() ? 'Name is required' : undefined),
|
|
348
|
-
}));
|
|
349
|
-
mcpServers[name] = { command, args };
|
|
350
|
-
p.log.success(`Added MCP server: ${chalk.cyan(name)}`);
|
|
351
|
-
addMore = prompt(await p.confirm({
|
|
352
|
-
message: 'Add another MCP server?',
|
|
353
|
-
initialValue: false,
|
|
354
|
-
}));
|
|
355
|
-
}
|
|
377
|
+
const mcpServers = { ...(existing.mcpServers ?? {}) };
|
|
378
|
+
const existingMcpNames = Object.keys(mcpServers);
|
|
379
|
+
if (existingMcpNames.length > 0) {
|
|
380
|
+
p.log.info(`Keeping MCP servers: ${existingMcpNames.join(', ')}`);
|
|
356
381
|
}
|
|
357
|
-
|
|
358
|
-
message: '
|
|
359
|
-
initialValue:
|
|
360
|
-
options: [
|
|
361
|
-
{ value: 'sqlite', label: 'SQLite (recommended)', hint: 'zero config, file-based' },
|
|
362
|
-
{ value: 'postgres', label: 'PostgreSQL', hint: 'production-grade' },
|
|
363
|
-
],
|
|
382
|
+
let addMore = prompt(await p.confirm({
|
|
383
|
+
message: existingMcpNames.length > 0 ? 'Add more MCP servers?' : 'Add MCP servers?',
|
|
384
|
+
initialValue: false,
|
|
364
385
|
}));
|
|
386
|
+
while (addMore) {
|
|
387
|
+
const fullCommand = prompt(await p.text({
|
|
388
|
+
message: 'Full command to start MCP server',
|
|
389
|
+
placeholder: 'npx -y @modelcontextprotocol/server-filesystem /home',
|
|
390
|
+
validate: (v) => {
|
|
391
|
+
if (!v?.trim())
|
|
392
|
+
return 'Command is required';
|
|
393
|
+
try {
|
|
394
|
+
splitCommandLine(v);
|
|
395
|
+
return undefined;
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
return error instanceof Error ? error.message : 'Invalid command';
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
}));
|
|
402
|
+
const [command, ...args] = splitCommandLine(fullCommand);
|
|
403
|
+
const name = prompt(await p.text({
|
|
404
|
+
message: 'Server name',
|
|
405
|
+
initialValue: extractMcpName(args),
|
|
406
|
+
validate: (v) => (!v?.trim() ? 'Name is required' : undefined),
|
|
407
|
+
})).trim();
|
|
408
|
+
mcpServers[name] = { command, args };
|
|
409
|
+
p.log.success(`Added MCP server: ${chalk.cyan(name)}`);
|
|
410
|
+
addMore = prompt(await p.confirm({ message: 'Add another MCP server?', initialValue: false }));
|
|
411
|
+
}
|
|
412
|
+
const memoryPath = prompt(await p.text({
|
|
413
|
+
message: 'SQLite memory database path',
|
|
414
|
+
initialValue: existing.memory?.path ?? DEFAULT_MEMORY_PATH,
|
|
415
|
+
validate: (v) => (!v?.trim() ? 'Path is required' : undefined),
|
|
416
|
+
})).trim();
|
|
365
417
|
const defaultPersonality = [
|
|
366
418
|
`You are ${assistantName}, a personal AI assistant for ${userName}.`,
|
|
367
419
|
`Be concise, friendly, and proactive. Use tools when they can help.`,
|
|
@@ -371,55 +423,39 @@ export const wizardCommand = new Command('wizard')
|
|
|
371
423
|
message: 'Assistant personality',
|
|
372
424
|
placeholder: 'Describe how the assistant should behave...',
|
|
373
425
|
initialValue: existing.personality ?? defaultPersonality,
|
|
426
|
+
validate: (v) => (!v?.trim() ? 'Personality is required' : undefined),
|
|
374
427
|
}));
|
|
375
428
|
const config = {
|
|
429
|
+
...existing,
|
|
376
430
|
name: assistantName,
|
|
377
431
|
personality,
|
|
378
|
-
llm: {
|
|
379
|
-
provider: provider,
|
|
380
|
-
model,
|
|
381
|
-
},
|
|
432
|
+
llm: { provider, model },
|
|
382
433
|
channels: channelsConfig,
|
|
383
434
|
capabilities,
|
|
384
|
-
...(Object.keys(mcpServers).length > 0 ? { mcpServers } : {}),
|
|
435
|
+
...(Object.keys(mcpServers).length > 0 ? { mcpServers } : { mcpServers: undefined }),
|
|
385
436
|
memory: {
|
|
386
|
-
adapter: memoryAdapter,
|
|
387
|
-
path: memoryAdapter === 'sqlite' ? '~/.cogitator/memory.db' : undefined,
|
|
388
437
|
autoExtract: true,
|
|
389
438
|
knowledgeGraph: true,
|
|
390
439
|
compaction: { threshold: 50 },
|
|
440
|
+
...existing.memory,
|
|
441
|
+
adapter: 'sqlite',
|
|
442
|
+
path: memoryPath,
|
|
391
443
|
},
|
|
392
|
-
stream: { flushInterval: 600, minChunkSize: 30 },
|
|
444
|
+
stream: existing.stream ?? { flushInterval: 600, minChunkSize: 30 },
|
|
393
445
|
};
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
writeFileSync(envPath, merged);
|
|
446
|
+
const validated = AssistantConfigSchema.parse(config);
|
|
447
|
+
writeFileSync(configPath, stringify(validated, { lineWidth: 120 }));
|
|
448
|
+
if (envUpdates.size > 0) {
|
|
449
|
+
writeFileSync(envPath, mergeEnvContent(existingEnvContent, envUpdates), { mode: 0o600 });
|
|
399
450
|
}
|
|
400
451
|
p.note([
|
|
401
452
|
`${chalk.dim('Config:')} cogitator.yml`,
|
|
402
|
-
|
|
453
|
+
envUpdates.size > 0 ? `${chalk.dim('Env:')} .env` : '',
|
|
403
454
|
'',
|
|
404
455
|
`${chalk.dim('Next:')} cogitator up`,
|
|
405
456
|
]
|
|
406
457
|
.filter(Boolean)
|
|
407
|
-
.join('\n'), 'Files written');
|
|
458
|
+
.join('\n'), 'Files written', NOTE_STYLE);
|
|
408
459
|
p.outro(chalk.green('Setup complete! Run: cogitator up'));
|
|
409
460
|
});
|
|
410
|
-
function mergeEnvLines(existing, newLines) {
|
|
411
|
-
const lines = existing.split('\n').filter(Boolean);
|
|
412
|
-
const map = new Map();
|
|
413
|
-
for (const line of lines) {
|
|
414
|
-
const eq = line.indexOf('=');
|
|
415
|
-
if (eq > 0)
|
|
416
|
-
map.set(line.slice(0, eq), line);
|
|
417
|
-
}
|
|
418
|
-
for (const line of newLines) {
|
|
419
|
-
const eq = line.indexOf('=');
|
|
420
|
-
if (eq > 0)
|
|
421
|
-
map.set(line.slice(0, eq), line);
|
|
422
|
-
}
|
|
423
|
-
return [...map.values()].join('\n') + '\n';
|
|
424
|
-
}
|
|
425
461
|
//# sourceMappingURL=wizard.js.map
|