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