zilmate 1.7.6 → 1.7.8
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/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +9 -7
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/doctor.d.ts +8 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +14 -1
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/menu.d.ts.map +1 -1
- package/dist/cli/menu.js +28 -5
- package/dist/cli/menu.js.map +1 -1
- package/dist/cli/setup.d.ts +7 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +257 -410
- package/dist/cli/setup.js.map +1 -1
- package/dist/config/env.d.ts +7 -0
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +9 -0
- package/dist/config/env.js.map +1 -1
- package/dist/index.js +46 -3
- package/dist/index.js.map +1 -1
- package/dist/runtime/chat-bridge.d.ts +1 -1
- package/dist/runtime/chat-bridge.d.ts.map +1 -1
- package/dist/runtime/chat-bridge.js +1 -4
- package/dist/runtime/chat-bridge.js.map +1 -1
- package/package.json +7 -1
- package/scripts/release-github.mjs +9 -12
package/dist/cli/setup.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { randomBytes, randomUUID } from 'node:crypto';
|
|
4
|
-
import { execFile
|
|
4
|
+
import { execFile } from 'node:child_process';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import readline from 'node:readline/promises';
|
|
7
7
|
import { stdin as input, stdout as output } from 'node:process';
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import { printPanel, printZilMateBanner } from './format.js';
|
|
10
10
|
import { runCameraDoctor } from '../tools/desktop.tool.js';
|
|
11
|
-
import { initWorkspace } from '../workspace/init.js';
|
|
12
11
|
import { workspaceLayout } from '../workspace/paths.js';
|
|
13
|
-
import { startCloudflareQuickTunnel } from './tunnel.js';
|
|
14
|
-
import { startJobWebhookServer } from '../jobs/webhook-server.js';
|
|
15
12
|
const execFileAsync = promisify(execFile);
|
|
16
13
|
const defaults = {
|
|
17
14
|
ZILO_MANAGER_MODEL: 'minimax/minimax-m3',
|
|
@@ -47,7 +44,7 @@ function parseEnvFile(content) {
|
|
|
47
44
|
const [, key, rawValue] = match;
|
|
48
45
|
if (!key || rawValue === undefined)
|
|
49
46
|
continue;
|
|
50
|
-
values.set(key, rawValue.replace(/^"(.*)"$/, '
|
|
47
|
+
values.set(key, rawValue.replace(/^"(.*)"$/, ''));
|
|
51
48
|
}
|
|
52
49
|
return values;
|
|
53
50
|
}
|
|
@@ -70,487 +67,280 @@ async function askOptionalSecret(rl, prompt) {
|
|
|
70
67
|
const value = await rl.question(prompt);
|
|
71
68
|
return value.trim();
|
|
72
69
|
}
|
|
73
|
-
async function askYesNo(rl,
|
|
74
|
-
const
|
|
75
|
-
const answer = (await rl.question(`${prompt} (${suffix}) `)).trim().toLowerCase();
|
|
70
|
+
async function askYesNo(rl, question, fallback) {
|
|
71
|
+
const answer = (await rl.question(`${question} (${fallback ? 'Y/n' : 'y/N'}): `)).trim().toLowerCase();
|
|
76
72
|
if (!answer)
|
|
77
|
-
return
|
|
73
|
+
return fallback;
|
|
78
74
|
return answer === 'y' || answer === 'yes';
|
|
79
75
|
}
|
|
80
|
-
async function askSection(rl, title, description,
|
|
76
|
+
async function askSection(rl, title, description, question, fallback) {
|
|
81
77
|
console.log(chalk.cyan(`\n${title}`));
|
|
82
78
|
console.log(chalk.gray(description));
|
|
83
|
-
return askYesNo(rl,
|
|
79
|
+
return askYesNo(rl, question, fallback);
|
|
84
80
|
}
|
|
85
|
-
async function
|
|
86
|
-
|
|
81
|
+
async function readEnvValues(path) {
|
|
82
|
+
if (!existsSync(path))
|
|
83
|
+
return new Map();
|
|
87
84
|
try {
|
|
88
|
-
|
|
89
|
-
return
|
|
85
|
+
const content = await readFile(path, 'utf8');
|
|
86
|
+
return parseEnvFile(content);
|
|
90
87
|
}
|
|
91
88
|
catch {
|
|
92
|
-
return
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
function cameraInstallCommand() {
|
|
96
|
-
if (process.platform === 'win32') {
|
|
97
|
-
return {
|
|
98
|
-
command: 'winget',
|
|
99
|
-
args: ['install', '--id', 'Gyan.FFmpeg', '-e', '--accept-package-agreements', '--accept-source-agreements'],
|
|
100
|
-
label: 'winget install --id Gyan.FFmpeg -e',
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
if (process.platform === 'darwin')
|
|
104
|
-
return { command: 'brew', args: ['install', 'ffmpeg'], label: 'brew install ffmpeg' };
|
|
105
|
-
if (process.platform === 'linux')
|
|
106
|
-
return { command: 'sudo', args: ['apt-get', 'install', '-y', 'ffmpeg'], label: 'sudo apt-get install -y ffmpeg' };
|
|
107
|
-
return undefined;
|
|
108
|
-
}
|
|
109
|
-
async function installCameraDependency() {
|
|
110
|
-
const install = cameraInstallCommand();
|
|
111
|
-
if (!install) {
|
|
112
|
-
console.log(chalk.yellow('No automatic ffmpeg installer is known for this OS. Install ffmpeg manually, then run `zilmate camera doctor`.'));
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
if (process.platform === 'win32' && !(await commandExists('winget'))) {
|
|
116
|
-
console.log(chalk.yellow('winget is not available. Install ffmpeg manually or with another package manager, then run `zilmate camera doctor`.'));
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
if (process.platform === 'darwin' && !(await commandExists('brew'))) {
|
|
120
|
-
console.log(chalk.yellow('Homebrew is not available. Install Homebrew or install ffmpeg manually, then run `zilmate camera doctor`.'));
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
if (process.platform === 'linux' && !(await commandExists('sudo'))) {
|
|
124
|
-
console.log(chalk.yellow(`Automatic install needs sudo. Run manually: ${install.label}`));
|
|
125
|
-
return false;
|
|
89
|
+
return new Map();
|
|
126
90
|
}
|
|
127
|
-
console.log(chalk.gray(`Running: ${install.label}`));
|
|
128
|
-
await new Promise((resolve, reject) => {
|
|
129
|
-
const child = spawn(install.command, install.args, { stdio: 'inherit', windowsHide: false });
|
|
130
|
-
child.on('error', reject);
|
|
131
|
-
child.on('close', (code) => {
|
|
132
|
-
if (code === 0)
|
|
133
|
-
resolve();
|
|
134
|
-
else
|
|
135
|
-
reject(new Error(`${install.label} exited with code ${code}`));
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
return commandExists('ffmpeg');
|
|
139
|
-
}
|
|
140
|
-
function cloudflareInstallCommand() {
|
|
141
|
-
if (process.platform === 'win32') {
|
|
142
|
-
return {
|
|
143
|
-
command: 'winget',
|
|
144
|
-
args: ['install', '--id', 'Cloudflare.cloudflared', '-e', '--accept-package-agreements', '--accept-source-agreements'],
|
|
145
|
-
label: 'winget install --id Cloudflare.cloudflared -e',
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
if (process.platform === 'darwin')
|
|
149
|
-
return { command: 'brew', args: ['install', 'cloudflared'], label: 'brew install cloudflared' };
|
|
150
|
-
if (process.platform === 'linux')
|
|
151
|
-
return { command: 'sudo', args: ['apt-get', 'install', '-y', 'cloudflared'], label: 'sudo apt-get install -y cloudflared' };
|
|
152
|
-
return undefined;
|
|
153
|
-
}
|
|
154
|
-
async function installCloudflareDependency() {
|
|
155
|
-
const install = cloudflareInstallCommand();
|
|
156
|
-
if (!install) {
|
|
157
|
-
console.log(chalk.yellow('No automatic cloudflared installer is known for this OS. Install cloudflared manually, then run `zilmate jobs listen --tunnel`.'));
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
if (process.platform === 'win32' && !(await commandExists('winget'))) {
|
|
161
|
-
console.log(chalk.yellow('winget is not available. Install cloudflared manually or with another package manager.'));
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
if (process.platform === 'darwin' && !(await commandExists('brew'))) {
|
|
165
|
-
console.log(chalk.yellow('Homebrew is not available. Install Homebrew or install cloudflared manually.'));
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
if (process.platform === 'linux' && !(await commandExists('sudo'))) {
|
|
169
|
-
console.log(chalk.yellow(`Automatic install needs sudo. Run manually: ${install.label}`));
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
console.log(chalk.gray(`Running: ${install.label}`));
|
|
173
|
-
await new Promise((resolve, reject) => {
|
|
174
|
-
const child = spawn(install.command, install.args, { stdio: 'inherit', windowsHide: false });
|
|
175
|
-
child.on('error', reject);
|
|
176
|
-
child.on('close', (code) => {
|
|
177
|
-
if (code === 0)
|
|
178
|
-
resolve();
|
|
179
|
-
else
|
|
180
|
-
reject(new Error(`${install.label} exited with code ${code}`));
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
return commandExists('cloudflared');
|
|
184
|
-
}
|
|
185
|
-
function buildEnv(values) {
|
|
186
|
-
const lines = [
|
|
187
|
-
['AI_GATEWAY_API_KEY', values.get('AI_GATEWAY_API_KEY') || ''],
|
|
188
|
-
['COMPOSIO_API_KEY', values.get('COMPOSIO_API_KEY') || ''],
|
|
189
|
-
['ZILMATE_USER_ID', values.get('ZILMATE_USER_ID') || ''],
|
|
190
|
-
['TAVILY_API_KEY', values.get('TAVILY_API_KEY') || ''],
|
|
191
|
-
['UPSTASH_REDIS_REST_URL', values.get('UPSTASH_REDIS_REST_URL') || ''],
|
|
192
|
-
['UPSTASH_REDIS_REST_TOKEN', values.get('UPSTASH_REDIS_REST_TOKEN') || ''],
|
|
193
|
-
['ZILMATE_JOBS_ENABLED', values.get('ZILMATE_JOBS_ENABLED') || 'false'],
|
|
194
|
-
['UPSTASH_QSTASH_TOKEN', values.get('UPSTASH_QSTASH_TOKEN') || ''],
|
|
195
|
-
['ZILMATE_PUBLIC_JOB_WEBHOOK_URL', values.get('ZILMATE_PUBLIC_JOB_WEBHOOK_URL') || ''],
|
|
196
|
-
['ZILMATE_JOB_WEBHOOK_SECRET', values.get('ZILMATE_JOB_WEBHOOK_SECRET') || ''],
|
|
197
|
-
['ZILMATE_TRIGGER_WORKFLOWS_ENABLED', values.get('ZILMATE_TRIGGER_WORKFLOWS_ENABLED') || 'false'],
|
|
198
|
-
['DEEPGRAM_API_KEY', values.get('DEEPGRAM_API_KEY') || ''],
|
|
199
|
-
['ZILMATE_VOICE_ENABLED', values.get('ZILMATE_VOICE_ENABLED') || 'false'],
|
|
200
|
-
['ZILMATE_VOICE_MODE', values.get('ZILMATE_VOICE_MODE') || 'agent'],
|
|
201
|
-
['ZILMATE_VOICE_LISTEN_MODEL', values.get('ZILMATE_VOICE_LISTEN_MODEL') || 'flux-general-en'],
|
|
202
|
-
['ZILMATE_VOICE_LISTEN_VERSION', values.get('ZILMATE_VOICE_LISTEN_VERSION') || 'v2'],
|
|
203
|
-
['ZILMATE_VOICE_TTS_MODEL', values.get('ZILMATE_VOICE_TTS_MODEL') || 'aura-2-thalia-en'],
|
|
204
|
-
['ZILMATE_VOICE_LANGUAGE', values.get('ZILMATE_VOICE_LANGUAGE') || 'en'],
|
|
205
|
-
['ZILMATE_VOICE_LANGUAGE_HINTS', values.get('ZILMATE_VOICE_LANGUAGE_HINTS') || ''],
|
|
206
|
-
['ZILMATE_VOICE_BARGE_IN', values.get('ZILMATE_VOICE_BARGE_IN') || 'true'],
|
|
207
|
-
['ZILMATE_VOICE_INPUT_DEVICE', values.get('ZILMATE_VOICE_INPUT_DEVICE') || defaults.ZILMATE_VOICE_INPUT_DEVICE],
|
|
208
|
-
['ZILMATE_SCREENSHOT_MODEL', values.get('ZILMATE_SCREENSHOT_MODEL') || defaults.ZILMATE_SCREENSHOT_MODEL],
|
|
209
|
-
['ZILMATE_CAMERA_DEVICE', values.get('ZILMATE_CAMERA_DEVICE') || defaults.ZILMATE_CAMERA_DEVICE],
|
|
210
|
-
['ZILMATE_FILE_ROOTS', values.get('ZILMATE_FILE_ROOTS') || defaults.ZILMATE_FILE_ROOTS],
|
|
211
|
-
['ZILMATE_WORKSPACE', values.get('ZILMATE_WORKSPACE') || ''],
|
|
212
|
-
['ZILMATE_WEBHOOK_PORT', values.get('ZILMATE_WEBHOOK_PORT') || '8787'],
|
|
213
|
-
['ZILO_MANAGER_MODEL', values.get('ZILO_MANAGER_MODEL') || defaults.ZILO_MANAGER_MODEL],
|
|
214
|
-
['ZILO_HELP_MODEL', values.get('ZILO_HELP_MODEL') || defaults.ZILO_HELP_MODEL],
|
|
215
|
-
['ZILO_POST_MODEL', values.get('ZILO_POST_MODEL') || defaults.ZILO_POST_MODEL],
|
|
216
|
-
['ZILO_IMAGE_DEFAULT_PROVIDER', values.get('ZILO_IMAGE_DEFAULT_PROVIDER') || defaults.ZILO_IMAGE_DEFAULT_PROVIDER],
|
|
217
|
-
['ZILO_IMAGE_OPENAI_MODEL', values.get('ZILO_IMAGE_OPENAI_MODEL') || defaults.ZILO_IMAGE_OPENAI_MODEL],
|
|
218
|
-
['ZILO_IMAGE_GEMINI_MODEL', values.get('ZILO_IMAGE_GEMINI_MODEL') || defaults.ZILO_IMAGE_GEMINI_MODEL],
|
|
219
|
-
['ZILO_IMAGE_MODEL', values.get('ZILO_IMAGE_MODEL') || defaults.ZILO_IMAGE_MODEL],
|
|
220
|
-
];
|
|
221
|
-
return `${lines.map(([key, value]) => `${key}=${formatEnvValue(value)}`).join('\n')}\n`;
|
|
222
|
-
}
|
|
223
|
-
function printSetupPrep() {
|
|
224
|
-
printPanel('Before setup starts', [
|
|
225
|
-
['Required', 'AI Gateway key'],
|
|
226
|
-
['Apps/tools', 'Composio key if you want Gmail, Slack, GitHub, Notion, etc.'],
|
|
227
|
-
['Web research', 'Tavily key if you want live web research'],
|
|
228
|
-
['Memory/jobs', 'Upstash Redis keys if you want cloud-backed storage'],
|
|
229
|
-
['Hosted schedules', 'QStash token; optional Cloudflare tunnel for public webhook'],
|
|
230
|
-
['Voice', 'Deepgram key if you want realtime voice'],
|
|
231
|
-
['Camera', 'ffmpeg if you want laptop camera capture'],
|
|
232
|
-
]);
|
|
233
|
-
console.log(chalk.gray('You can skip any optional section and run setup again later.'));
|
|
234
91
|
}
|
|
235
|
-
async function
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
merged.set(key, value);
|
|
92
|
+
async function writeEnvValues(path, values, options = {}) {
|
|
93
|
+
let content = '';
|
|
94
|
+
if (options.merge && existsSync(path)) {
|
|
95
|
+
const existing = await readFile(path, 'utf8');
|
|
96
|
+
const existingLines = existing.split(/\r?\n/);
|
|
97
|
+
const usedKeys = new Set();
|
|
98
|
+
for (const line of existingLines) {
|
|
99
|
+
const trimmed = line.trim();
|
|
100
|
+
const match = /^([A-Z0-9_]+)=/.exec(trimmed);
|
|
101
|
+
if (match && values.has(match[1]) && (!options.touchedKeys || options.touchedKeys.has(match[1]))) {
|
|
102
|
+
content += `${match[1]}=${formatEnvValue(values.get(match[1]))}\n`;
|
|
103
|
+
usedKeys.add(match[1]);
|
|
248
104
|
}
|
|
249
|
-
else
|
|
250
|
-
|
|
105
|
+
else {
|
|
106
|
+
content += `${line}\n`;
|
|
251
107
|
}
|
|
252
108
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
merged.set(key, value);
|
|
257
|
-
}
|
|
258
|
-
for (const [key, value] of Object.entries(defaults)) {
|
|
259
|
-
if (!merged.has(key) || merged.get(key)?.trim() === '')
|
|
260
|
-
merged.set(key, value);
|
|
261
|
-
}
|
|
262
|
-
await writeFile(envPath, buildEnv(merged), 'utf8');
|
|
263
|
-
}
|
|
264
|
-
function envHasValue(values, key) {
|
|
265
|
-
const value = values.get(key);
|
|
266
|
-
return Boolean(value && value.trim() !== '');
|
|
267
|
-
}
|
|
268
|
-
async function configureCloudflareTunnelSection(rl, values) {
|
|
269
|
-
console.log(chalk.cyan('\nCloudflare quick tunnel'));
|
|
270
|
-
console.log(chalk.gray('QStash hosted schedules need a public HTTPS webhook. cloudflared creates a free quick tunnel to your laptop.'));
|
|
271
|
-
console.log(chalk.gray('Install: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/'));
|
|
272
|
-
console.log(chalk.gray('While your laptop is on, keep the tunnel alive with: zilmate jobs listen --tunnel'));
|
|
273
|
-
let hasCloudflared = await commandExists('cloudflared');
|
|
274
|
-
if (!hasCloudflared) {
|
|
275
|
-
if (await askYesNo(rl, 'cloudflared is missing. Install Cloudflare tunnel dependency now?', false)) {
|
|
276
|
-
try {
|
|
277
|
-
hasCloudflared = await installCloudflareDependency();
|
|
278
|
-
console.log(hasCloudflared ? chalk.green('cloudflared is ready.') : chalk.yellow('cloudflared was not detected after install.'));
|
|
279
|
-
}
|
|
280
|
-
catch (error) {
|
|
281
|
-
console.log(chalk.yellow(error instanceof Error ? error.message : String(error)));
|
|
109
|
+
for (const [key, value] of values) {
|
|
110
|
+
if (!usedKeys.has(key)) {
|
|
111
|
+
content += `${key}=${formatEnvValue(value)}\n`;
|
|
282
112
|
}
|
|
283
113
|
}
|
|
284
114
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (envHasValue(values, 'ZILMATE_PUBLIC_JOB_WEBHOOK_URL')) {
|
|
290
|
-
console.log(chalk.green(`Existing webhook: ${values.get('ZILMATE_PUBLIC_JOB_WEBHOOK_URL')}`));
|
|
291
|
-
if (!await askYesNo(rl, 'Create a new Cloudflare quick tunnel URL?', false))
|
|
292
|
-
return;
|
|
115
|
+
else {
|
|
116
|
+
for (const [key, value] of values) {
|
|
117
|
+
content += `${key}=${formatEnvValue(value)}\n`;
|
|
118
|
+
}
|
|
293
119
|
}
|
|
294
|
-
await
|
|
120
|
+
await writeFile(path, content.trim() + '\n', 'utf8');
|
|
295
121
|
}
|
|
296
|
-
async function
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
try {
|
|
301
|
-
hasCloudflared = await installCloudflareDependency();
|
|
302
|
-
console.log(hasCloudflared ? chalk.green('cloudflared is ready.') : chalk.yellow('cloudflared was not detected after install.'));
|
|
303
|
-
}
|
|
304
|
-
catch (error) {
|
|
305
|
-
console.log(chalk.yellow(error instanceof Error ? error.message : String(error)));
|
|
306
|
-
}
|
|
307
|
-
}
|
|
122
|
+
async function commandExists(command) {
|
|
123
|
+
try {
|
|
124
|
+
await execFileAsync(command, ['--version']);
|
|
125
|
+
return true;
|
|
308
126
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
return;
|
|
127
|
+
catch {
|
|
128
|
+
return false;
|
|
312
129
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const server = await startJobWebhookServer(port);
|
|
319
|
-
try {
|
|
320
|
-
const tunnel = await startCloudflareQuickTunnel(server.url);
|
|
321
|
-
const webhookUrl = `${tunnel.url.replace(/\/$/, '')}/jobs/webhook`;
|
|
322
|
-
values.set('ZILMATE_PUBLIC_JOB_WEBHOOK_URL', webhookUrl);
|
|
323
|
-
values.set('ZILMATE_WEBHOOK_PORT', String(port));
|
|
324
|
-
console.log(chalk.green(`Public webhook URL: ${webhookUrl}`));
|
|
325
|
-
console.log(chalk.yellow('Quick tunnels change when restarted. Run `zilmate jobs listen --tunnel` while your laptop is on to keep QStash reachable.'));
|
|
130
|
+
}
|
|
131
|
+
async function installCameraDependency() {
|
|
132
|
+
if (process.platform === 'darwin') {
|
|
133
|
+
await execFileAsync('brew', ['install', 'ffmpeg']);
|
|
134
|
+
return true;
|
|
326
135
|
}
|
|
327
|
-
|
|
328
|
-
await
|
|
136
|
+
if (process.platform === 'linux') {
|
|
137
|
+
await execFileAsync('sudo', ['apt-get', 'update']);
|
|
138
|
+
await execFileAsync('sudo', ['apt-get', 'install', '-y', 'ffmpeg']);
|
|
139
|
+
return true;
|
|
329
140
|
}
|
|
141
|
+
return false;
|
|
330
142
|
}
|
|
331
143
|
export async function runSetup(options = {}) {
|
|
332
144
|
const envPath = options.path || '.env';
|
|
333
145
|
const existing = await readEnvValues(envPath);
|
|
334
146
|
const rl = readline.createInterface({ input, output });
|
|
147
|
+
const mergeMode = options.force ? false : existing.size > 0;
|
|
335
148
|
const touchedKeys = new Set();
|
|
336
|
-
const mergeMode = existing.size > 0 && !options.force;
|
|
337
149
|
try {
|
|
338
|
-
printZilMateBanner('Setup');
|
|
339
|
-
console.log(chalk.gray(`Config file: ${envPath}`));
|
|
150
|
+
printZilMateBanner('ZilMate Setup');
|
|
340
151
|
if (mergeMode) {
|
|
341
|
-
console.log(chalk.
|
|
342
|
-
console.log(chalk.gray('Use --force to reconfigure all sections, or answer yes when prompted to replace a specific key.'));
|
|
152
|
+
console.log(chalk.gray(`Merging settings into existing ${envPath}.`));
|
|
343
153
|
}
|
|
344
154
|
else {
|
|
345
|
-
console.log(chalk.gray(
|
|
155
|
+
console.log(chalk.gray(`Creating new ${envPath}.`));
|
|
346
156
|
}
|
|
347
|
-
if (!options.yes)
|
|
348
|
-
printSetupPrep();
|
|
349
157
|
const values = new Map(existing);
|
|
350
|
-
const layout = await initWorkspace();
|
|
351
|
-
if (!values.get('ZILMATE_WORKSPACE')) {
|
|
352
|
-
values.set('ZILMATE_WORKSPACE', layout.root);
|
|
353
|
-
touchedKeys.add('ZILMATE_WORKSPACE');
|
|
354
|
-
}
|
|
355
|
-
console.log(chalk.gray(`ZilMate workspace: ${layout.root}`));
|
|
356
|
-
if (options.aiGatewayKey)
|
|
357
|
-
values.set('AI_GATEWAY_API_KEY', options.aiGatewayKey);
|
|
358
|
-
if (options.composioKey !== undefined)
|
|
359
|
-
values.set('COMPOSIO_API_KEY', options.composioKey);
|
|
360
|
-
if (options.zilmateUserId !== undefined)
|
|
361
|
-
values.set('ZILMATE_USER_ID', options.zilmateUserId);
|
|
362
|
-
if (options.tavilyKey !== undefined)
|
|
363
|
-
values.set('TAVILY_API_KEY', options.tavilyKey);
|
|
364
|
-
if (options.redisUrl !== undefined)
|
|
365
|
-
values.set('UPSTASH_REDIS_REST_URL', options.redisUrl);
|
|
366
|
-
if (options.redisToken !== undefined)
|
|
367
|
-
values.set('UPSTASH_REDIS_REST_TOKEN', options.redisToken);
|
|
368
|
-
if (options.jobsEnabled !== undefined)
|
|
369
|
-
values.set('ZILMATE_JOBS_ENABLED', normalizeBooleanOption(options.jobsEnabled) ? 'true' : 'false');
|
|
370
|
-
if (options.qstashToken !== undefined)
|
|
371
|
-
values.set('UPSTASH_QSTASH_TOKEN', options.qstashToken);
|
|
372
|
-
if (options.publicJobWebhookUrl !== undefined)
|
|
373
|
-
values.set('ZILMATE_PUBLIC_JOB_WEBHOOK_URL', options.publicJobWebhookUrl);
|
|
374
|
-
if (options.jobWebhookSecret !== undefined)
|
|
375
|
-
values.set('ZILMATE_JOB_WEBHOOK_SECRET', options.jobWebhookSecret);
|
|
376
|
-
if (options.triggerWorkflowsEnabled !== undefined)
|
|
377
|
-
values.set('ZILMATE_TRIGGER_WORKFLOWS_ENABLED', normalizeBooleanOption(options.triggerWorkflowsEnabled) ? 'true' : 'false');
|
|
378
|
-
if (options.deepgramApiKey !== undefined)
|
|
379
|
-
values.set('DEEPGRAM_API_KEY', options.deepgramApiKey);
|
|
380
|
-
if (options.voiceEnabled !== undefined)
|
|
381
|
-
values.set('ZILMATE_VOICE_ENABLED', normalizeBooleanOption(options.voiceEnabled) ? 'true' : 'false');
|
|
382
|
-
if (options.voiceListenModel !== undefined) {
|
|
383
|
-
values.set('ZILMATE_VOICE_LISTEN_MODEL', options.voiceListenModel);
|
|
384
|
-
values.set('ZILMATE_VOICE_LISTEN_VERSION', options.voiceListenModel.startsWith('flux-') ? 'v2' : 'v1');
|
|
385
|
-
}
|
|
386
|
-
if (options.voiceTtsModel !== undefined)
|
|
387
|
-
values.set('ZILMATE_VOICE_TTS_MODEL', options.voiceTtsModel);
|
|
388
|
-
if (options.voiceLanguage !== undefined)
|
|
389
|
-
values.set('ZILMATE_VOICE_LANGUAGE', options.voiceLanguage);
|
|
390
|
-
if (options.voiceInputDevice !== undefined)
|
|
391
|
-
values.set('ZILMATE_VOICE_INPUT_DEVICE', options.voiceInputDevice);
|
|
392
|
-
if (options.cameraDevice !== undefined)
|
|
393
|
-
values.set('ZILMATE_CAMERA_DEVICE', options.cameraDevice);
|
|
394
|
-
if (options.screenshotModel !== undefined)
|
|
395
|
-
values.set('ZILMATE_SCREENSHOT_MODEL', options.screenshotModel);
|
|
396
|
-
if (options.fileRoots !== undefined)
|
|
397
|
-
values.set('ZILMATE_FILE_ROOTS', options.fileRoots);
|
|
398
|
-
const installCloudflareDeps = options.installCloudflareDeps === undefined ? undefined : normalizeBooleanOption(options.installCloudflareDeps);
|
|
399
|
-
if (installCloudflareDeps && !(await commandExists('cloudflared'))) {
|
|
400
|
-
await installCloudflareDependency();
|
|
401
|
-
}
|
|
402
|
-
const currentGatewayKey = values.get('AI_GATEWAY_API_KEY');
|
|
403
158
|
if (options.aiGatewayKey) {
|
|
404
159
|
values.set('AI_GATEWAY_API_KEY', options.aiGatewayKey);
|
|
160
|
+
touchedKeys.add('AI_GATEWAY_API_KEY');
|
|
405
161
|
}
|
|
406
|
-
else if (!
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
else if (currentGatewayKey && !options.yes) {
|
|
410
|
-
if (!mergeMode || await askYesNo(rl, 'AI_GATEWAY_API_KEY already exists. Replace it?', false)) {
|
|
411
|
-
values.set('AI_GATEWAY_API_KEY', await askRequiredSecret(rl, 'AI_GATEWAY_API_KEY: '));
|
|
412
|
-
touchedKeys.add('AI_GATEWAY_API_KEY');
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
else if (!currentGatewayKey) {
|
|
162
|
+
else if (!values.has('AI_GATEWAY_API_KEY') || options.force) {
|
|
163
|
+
console.log(chalk.cyan('\nAI Gateway'));
|
|
164
|
+
console.log(chalk.gray('ZilMate uses the Vercel AI Gateway for LLM access. You need an API key from the gateway.'));
|
|
416
165
|
values.set('AI_GATEWAY_API_KEY', await askRequiredSecret(rl, 'AI_GATEWAY_API_KEY: '));
|
|
417
166
|
touchedKeys.add('AI_GATEWAY_API_KEY');
|
|
418
167
|
}
|
|
419
|
-
if (
|
|
420
|
-
values.set('
|
|
421
|
-
|
|
422
|
-
if (!options.yes && options.composioKey === undefined && await askSection(rl, 'Composio app tools', 'Connectors let ZilMate use apps like Gmail, Slack, GitHub, Notion, Supabase, Stripe, Linear, Discord, and more through Composio.', 'Enable Composio app tools?', Boolean(values.get('COMPOSIO_API_KEY')))) {
|
|
423
|
-
const composioKey = await askOptionalSecret(rl, 'COMPOSIO_API_KEY (blank to skip): ');
|
|
424
|
-
values.set('COMPOSIO_API_KEY', composioKey);
|
|
425
|
-
}
|
|
426
|
-
else if (!options.yes && options.composioKey === undefined) {
|
|
427
|
-
values.set('COMPOSIO_API_KEY', '');
|
|
428
|
-
}
|
|
429
|
-
if (!options.yes && options.tavilyKey === undefined && await askSection(rl, 'Web research', 'Tavily enables live web search, extraction, crawling, mapping, and deeper research when local knowledge is not enough.', 'Enable Tavily web research?', Boolean(values.get('TAVILY_API_KEY')))) {
|
|
430
|
-
const tavilyKey = await askOptionalSecret(rl, 'TAVILY_API_KEY (blank to skip): ');
|
|
431
|
-
values.set('TAVILY_API_KEY', tavilyKey);
|
|
168
|
+
if (options.composioKey) {
|
|
169
|
+
values.set('COMPOSIO_API_KEY', options.composioKey);
|
|
170
|
+
touchedKeys.add('COMPOSIO_API_KEY');
|
|
432
171
|
}
|
|
433
|
-
else if (!options.yes &&
|
|
434
|
-
|
|
172
|
+
else if (!options.yes && (!values.has('COMPOSIO_API_KEY') || options.force)) {
|
|
173
|
+
if (await askSection(rl, 'External App Intelligence (Composio)', 'Enable ZilMate to use Stripe, HubSpot, GitHub, Gmail, and 100+ other apps via Composio.', 'Configure Composio now?', true)) {
|
|
174
|
+
values.set('COMPOSIO_API_KEY', await askRequiredSecret(rl, 'COMPOSIO_API_KEY: '));
|
|
175
|
+
touchedKeys.add('COMPOSIO_API_KEY');
|
|
176
|
+
}
|
|
435
177
|
}
|
|
436
|
-
if (
|
|
437
|
-
values.set('
|
|
438
|
-
|
|
178
|
+
if (options.zilmateUserId) {
|
|
179
|
+
values.set('ZILMATE_USER_ID', options.zilmateUserId);
|
|
180
|
+
touchedKeys.add('ZILMATE_USER_ID');
|
|
439
181
|
}
|
|
440
|
-
else if (
|
|
441
|
-
|
|
442
|
-
|
|
182
|
+
else if (values.get('COMPOSIO_API_KEY') && (!values.get('ZILMATE_USER_ID') || options.force)) {
|
|
183
|
+
const suggestedId = `zilmate-${randomUUID().slice(0, 8)}`;
|
|
184
|
+
const userId = (await rl.question(`ZilMate User ID (default: ${suggestedId}): `)).trim();
|
|
185
|
+
values.set('ZILMATE_USER_ID', userId || suggestedId);
|
|
186
|
+
touchedKeys.add('ZILMATE_USER_ID');
|
|
443
187
|
}
|
|
444
|
-
if (
|
|
445
|
-
|
|
446
|
-
|
|
188
|
+
if (options.tavilyKey) {
|
|
189
|
+
values.set('TAVILY_API_KEY', options.tavilyKey);
|
|
190
|
+
touchedKeys.add('TAVILY_API_KEY');
|
|
447
191
|
}
|
|
448
|
-
else if (!values.has('
|
|
449
|
-
|
|
192
|
+
else if (!options.yes && (!values.has('TAVILY_API_KEY') || options.force)) {
|
|
193
|
+
if (await askSection(rl, 'Web Research (Tavily)', 'Tavily allows ZilMate to perform real-time web searches for accurate up-to-date information.', 'Configure Tavily now?', true)) {
|
|
194
|
+
values.set('TAVILY_API_KEY', await askRequiredSecret(rl, 'TAVILY_API_KEY: '));
|
|
195
|
+
touchedKeys.add('TAVILY_API_KEY');
|
|
196
|
+
}
|
|
450
197
|
}
|
|
451
|
-
if (
|
|
452
|
-
|
|
453
|
-
|
|
198
|
+
if (options.redisUrl && options.redisToken) {
|
|
199
|
+
values.set('UPSTASH_REDIS_REST_URL', options.redisUrl);
|
|
200
|
+
values.set('UPSTASH_REDIS_REST_TOKEN', options.redisToken);
|
|
201
|
+
touchedKeys.add('UPSTASH_REDIS_REST_URL');
|
|
202
|
+
touchedKeys.add('UPSTASH_REDIS_REST_TOKEN');
|
|
203
|
+
}
|
|
204
|
+
else if (!options.yes && (!values.has('UPSTASH_REDIS_REST_URL') || options.force)) {
|
|
205
|
+
if (await askSection(rl, 'Persistent Multi-Instance Memory (Redis)', 'By default, ZilMate uses local files for memory. Use Upstash Redis for distributed memory and reliable background jobs.', 'Configure Upstash Redis now?', false)) {
|
|
206
|
+
values.set('UPSTASH_REDIS_REST_URL', await askRequiredSecret(rl, 'UPSTASH_REDIS_REST_URL: '));
|
|
207
|
+
values.set('UPSTASH_REDIS_REST_TOKEN', await askRequiredSecret(rl, 'UPSTASH_REDIS_REST_TOKEN: '));
|
|
208
|
+
touchedKeys.add('UPSTASH_REDIS_REST_URL');
|
|
209
|
+
touchedKeys.add('UPSTASH_REDIS_REST_TOKEN');
|
|
454
210
|
}
|
|
455
211
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
const existingSecret = values.get('ZILMATE_JOB_WEBHOOK_SECRET');
|
|
467
|
-
const secret = await askOptionalSecret(rl, `ZILMATE_JOB_WEBHOOK_SECRET (blank to ${existingSecret ? 'keep existing' : 'auto-generate'}): `);
|
|
468
|
-
values.set('ZILMATE_JOB_WEBHOOK_SECRET', secret || existingSecret || newWebhookSecret());
|
|
212
|
+
const jobsEnabled = options.jobsEnabled !== undefined ? normalizeBooleanOption(options.jobsEnabled) : undefined;
|
|
213
|
+
if (jobsEnabled !== undefined) {
|
|
214
|
+
values.set('ZILMATE_JOBS_ENABLED', jobsEnabled ? 'true' : 'false');
|
|
215
|
+
touchedKeys.add('ZILMATE_JOBS_ENABLED');
|
|
216
|
+
}
|
|
217
|
+
else if (!options.yes && (!values.has('ZILMATE_JOBS_ENABLED') || options.force)) {
|
|
218
|
+
if (await askSection(rl, 'Background Jobs', 'ZilMate can run background tasks, schedules, and trigger-based workflows.', 'Enable background jobs?', true)) {
|
|
219
|
+
values.set('ZILMATE_JOBS_ENABLED', 'true');
|
|
220
|
+
touchedKeys.add('ZILMATE_JOBS_ENABLED');
|
|
469
221
|
}
|
|
470
222
|
else {
|
|
471
|
-
values.set('
|
|
472
|
-
|
|
223
|
+
values.set('ZILMATE_JOBS_ENABLED', 'false');
|
|
224
|
+
touchedKeys.add('ZILMATE_JOBS_ENABLED');
|
|
473
225
|
}
|
|
474
226
|
}
|
|
475
|
-
if (
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
values.set('ZILMATE_TRIGGER_WORKFLOWS_ENABLED', 'false');
|
|
227
|
+
if (values.get('ZILMATE_JOBS_ENABLED') === 'true') {
|
|
228
|
+
if (options.qstashToken && options.publicJobWebhookUrl) {
|
|
229
|
+
values.set('UPSTASH_QSTASH_TOKEN', options.qstashToken);
|
|
230
|
+
values.set('ZILMATE_PUBLIC_JOB_WEBHOOK_URL', options.publicJobWebhookUrl);
|
|
231
|
+
touchedKeys.add('UPSTASH_QSTASH_TOKEN');
|
|
232
|
+
touchedKeys.add('ZILMATE_PUBLIC_JOB_WEBHOOK_URL');
|
|
482
233
|
}
|
|
483
|
-
else if (
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
234
|
+
else if (!options.yes && (!values.has('UPSTASH_QSTASH_TOKEN') || options.force)) {
|
|
235
|
+
if (await askSection(rl, 'Hosted Job Scheduling (QStash)', 'Local schedules stop when the laptop closes. Use Upstash QStash for durable, hosted scheduling and retries.', 'Configure Upstash QStash now?', false)) {
|
|
236
|
+
values.set('UPSTASH_QSTASH_TOKEN', await askRequiredSecret(rl, 'UPSTASH_QSTASH_TOKEN: '));
|
|
237
|
+
values.set('ZILMATE_PUBLIC_JOB_WEBHOOK_URL', await askRequiredSecret(rl, 'ZILMATE_PUBLIC_JOB_WEBHOOK_URL: '));
|
|
238
|
+
touchedKeys.add('UPSTASH_QSTASH_TOKEN');
|
|
239
|
+
touchedKeys.add('ZILMATE_PUBLIC_JOB_WEBHOOK_URL');
|
|
488
240
|
}
|
|
489
241
|
}
|
|
490
|
-
|
|
491
|
-
|
|
242
|
+
if (values.get('UPSTASH_QSTASH_TOKEN') && (!values.get('ZILMATE_JOB_WEBHOOK_SECRET') || options.force)) {
|
|
243
|
+
const secret = options.jobWebhookSecret || newWebhookSecret();
|
|
244
|
+
values.set('ZILMATE_JOB_WEBHOOK_SECRET', secret);
|
|
245
|
+
touchedKeys.add('ZILMATE_JOB_WEBHOOK_SECRET');
|
|
492
246
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
if (deepgramKey)
|
|
503
|
-
values.set('DEEPGRAM_API_KEY', deepgramKey);
|
|
504
|
-
const listenModel = (await rl.question(`Listen model (${values.get('ZILMATE_VOICE_LISTEN_MODEL') || 'flux-general-en'}): `)).trim();
|
|
505
|
-
if (listenModel) {
|
|
506
|
-
values.set('ZILMATE_VOICE_LISTEN_MODEL', listenModel);
|
|
507
|
-
values.set('ZILMATE_VOICE_LISTEN_VERSION', listenModel.startsWith('flux-') ? 'v2' : 'v1');
|
|
508
|
-
}
|
|
509
|
-
else if (!values.get('ZILMATE_VOICE_LISTEN_MODEL')) {
|
|
510
|
-
values.set('ZILMATE_VOICE_LISTEN_MODEL', 'flux-general-en');
|
|
511
|
-
values.set('ZILMATE_VOICE_LISTEN_VERSION', 'v2');
|
|
247
|
+
const triggersEnabled = options.triggerWorkflowsEnabled !== undefined ? normalizeBooleanOption(options.triggerWorkflowsEnabled) : undefined;
|
|
248
|
+
if (triggersEnabled !== undefined) {
|
|
249
|
+
values.set('ZILMATE_TRIGGER_WORKFLOWS_ENABLED', triggersEnabled ? 'true' : 'false');
|
|
250
|
+
touchedKeys.add('ZILMATE_TRIGGER_WORKFLOWS_ENABLED');
|
|
251
|
+
}
|
|
252
|
+
else if (!options.yes && (!values.has('ZILMATE_TRIGGER_WORKFLOWS_ENABLED') || options.force)) {
|
|
253
|
+
if (await askSection(rl, 'Composio Triggers', 'Automatically start ZilMate jobs when events occur in Slack, Stripe, Gmail, etc.', 'Enable trigger workflows?', true)) {
|
|
254
|
+
values.set('ZILMATE_TRIGGER_WORKFLOWS_ENABLED', 'true');
|
|
255
|
+
touchedKeys.add('ZILMATE_TRIGGER_WORKFLOWS_ENABLED');
|
|
512
256
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
const language = (await rl.question(`Language (${values.get('ZILMATE_VOICE_LANGUAGE') || 'en'}): `)).trim();
|
|
517
|
-
if (language)
|
|
518
|
-
values.set('ZILMATE_VOICE_LANGUAGE', language);
|
|
519
|
-
values.set('ZILMATE_VOICE_MODE', 'agent');
|
|
520
|
-
values.set('ZILMATE_VOICE_BARGE_IN', 'true');
|
|
521
|
-
const currentInputDevice = values.get('ZILMATE_VOICE_INPUT_DEVICE') || '';
|
|
522
|
-
if (await askYesNo(rl, 'Set a terminal microphone input device?', Boolean(currentInputDevice))) {
|
|
523
|
-
const voiceInputDevice = (await rl.question(`ZILMATE_VOICE_INPUT_DEVICE${currentInputDevice ? ` (${currentInputDevice})` : ' (blank for default mic)'}: `)).trim();
|
|
524
|
-
values.set('ZILMATE_VOICE_INPUT_DEVICE', voiceInputDevice || currentInputDevice);
|
|
257
|
+
else {
|
|
258
|
+
values.set('ZILMATE_TRIGGER_WORKFLOWS_ENABLED', 'false');
|
|
259
|
+
touchedKeys.add('ZILMATE_TRIGGER_WORKFLOWS_ENABLED');
|
|
525
260
|
}
|
|
526
261
|
}
|
|
527
|
-
|
|
528
|
-
|
|
262
|
+
}
|
|
263
|
+
if (options.deepgramApiKey) {
|
|
264
|
+
values.set('DEEPGRAM_API_KEY', options.deepgramApiKey);
|
|
265
|
+
touchedKeys.add('DEEPGRAM_API_KEY');
|
|
266
|
+
}
|
|
267
|
+
else if (!options.yes && (!values.has('DEEPGRAM_API_KEY') || options.force)) {
|
|
268
|
+
if (await askSection(rl, 'Realtime Voice (Deepgram)', 'Enable ZilMate to listen and speak in realtime with ultra-low latency.', 'Configure Deepgram voice now?', false)) {
|
|
269
|
+
values.set('DEEPGRAM_API_KEY', await askRequiredSecret(rl, 'DEEPGRAM_API_KEY: '));
|
|
270
|
+
touchedKeys.add('DEEPGRAM_API_KEY');
|
|
529
271
|
}
|
|
530
272
|
}
|
|
531
|
-
|
|
532
|
-
|
|
273
|
+
if (values.get('DEEPGRAM_API_KEY')) {
|
|
274
|
+
const voiceEnabled = options.voiceEnabled !== undefined ? normalizeBooleanOption(options.voiceEnabled) : undefined;
|
|
275
|
+
if (voiceEnabled !== undefined) {
|
|
276
|
+
values.set('ZILMATE_VOICE_ENABLED', voiceEnabled ? 'true' : 'false');
|
|
277
|
+
touchedKeys.add('ZILMATE_VOICE_ENABLED');
|
|
278
|
+
}
|
|
279
|
+
else if (!options.yes && (!values.has('ZILMATE_VOICE_ENABLED') || options.force)) {
|
|
280
|
+
values.set('ZILMATE_VOICE_ENABLED', (await askYesNo(rl, 'Enable realtime voice by default?', true)) ? 'true' : 'false');
|
|
281
|
+
touchedKeys.add('ZILMATE_VOICE_ENABLED');
|
|
282
|
+
}
|
|
533
283
|
}
|
|
534
|
-
if (!values.get('ZILMATE_SCREENSHOT_MODEL'))
|
|
535
|
-
values.set('ZILMATE_SCREENSHOT_MODEL', defaults.ZILMATE_SCREENSHOT_MODEL);
|
|
536
|
-
if (!values.has('ZILMATE_CAMERA_DEVICE'))
|
|
537
|
-
values.set('ZILMATE_CAMERA_DEVICE', '');
|
|
538
|
-
if (!values.has('ZILMATE_FILE_ROOTS'))
|
|
539
|
-
values.set('ZILMATE_FILE_ROOTS', '');
|
|
540
284
|
if (!options.yes && options.fileRoots === undefined) {
|
|
541
|
-
if (await askSection(rl, 'Local
|
|
285
|
+
if (await askSection(rl, 'Local File Access', 'Let ZilMate search, read, summarize, create, move, copy, and rename files inside approved roots. Writes still ask for approval.', 'Configure extra file access roots?', Boolean(values.get('ZILMATE_FILE_ROOTS')))) {
|
|
542
286
|
const currentRoots = values.get('ZILMATE_FILE_ROOTS') || '';
|
|
543
287
|
const roots = (await rl.question(`ZILMATE_FILE_ROOTS comma-separated${currentRoots ? ` (${currentRoots})` : ' (blank for current folder only)'}: `)).trim();
|
|
544
288
|
values.set('ZILMATE_FILE_ROOTS', roots || currentRoots);
|
|
289
|
+
touchedKeys.add('ZILMATE_FILE_ROOTS');
|
|
545
290
|
}
|
|
546
291
|
}
|
|
547
292
|
if (!options.yes && options.screenshotModel === undefined) {
|
|
548
293
|
if (await askSection(rl, 'Screen and photo understanding', 'ZilMate uses a vision model to describe screenshots and camera photos. The default is Gemini 3.1 Flash Lite through the AI Gateway.', 'Use the default screenshot/photo vision model?', true)) {
|
|
549
294
|
values.set('ZILMATE_SCREENSHOT_MODEL', values.get('ZILMATE_SCREENSHOT_MODEL') || defaults.ZILMATE_SCREENSHOT_MODEL);
|
|
295
|
+
touchedKeys.add('ZILMATE_SCREENSHOT_MODEL');
|
|
550
296
|
}
|
|
551
297
|
else {
|
|
552
298
|
const model = (await rl.question(`ZILMATE_SCREENSHOT_MODEL (${values.get('ZILMATE_SCREENSHOT_MODEL') || defaults.ZILMATE_SCREENSHOT_MODEL}): `)).trim();
|
|
553
299
|
values.set('ZILMATE_SCREENSHOT_MODEL', model || values.get('ZILMATE_SCREENSHOT_MODEL') || defaults.ZILMATE_SCREENSHOT_MODEL);
|
|
300
|
+
touchedKeys.add('ZILMATE_SCREENSHOT_MODEL');
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// Chat Integration Section
|
|
304
|
+
if (!options.yes && (!values.has('CHAT_INTEGRATION_ENABLED') || options.force)) {
|
|
305
|
+
if (await askSection(rl, 'Chat Channels (Slack, Telegram, iMessage)', 'Enable ZilMate to respond to you on Slack, Telegram, or iMessage, and proactively report business events.', 'Configure Chat Channels now?', false)) {
|
|
306
|
+
values.set('CHAT_INTEGRATION_ENABLED', 'true');
|
|
307
|
+
touchedKeys.add('CHAT_INTEGRATION_ENABLED');
|
|
308
|
+
if (await askYesNo(rl, 'Configure Slack?', Boolean(values.get('SLACK_BOT_TOKEN')))) {
|
|
309
|
+
const slackToken = options.slackBotToken || await askRequiredSecret(rl, 'SLACK_BOT_TOKEN: ');
|
|
310
|
+
values.set('SLACK_BOT_TOKEN', slackToken);
|
|
311
|
+
touchedKeys.add('SLACK_BOT_TOKEN');
|
|
312
|
+
const slackSecret = options.slackSigningSecret || await askOptionalSecret(rl, 'SLACK_SIGNING_SECRET (optional): ');
|
|
313
|
+
if (slackSecret) {
|
|
314
|
+
values.set('SLACK_SIGNING_SECRET', slackSecret);
|
|
315
|
+
touchedKeys.add('SLACK_SIGNING_SECRET');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (await askYesNo(rl, 'Configure Telegram?', Boolean(values.get('TELEGRAM_BOT_TOKEN')))) {
|
|
319
|
+
const tgToken = options.telegramBotToken || await askRequiredSecret(rl, 'TELEGRAM_BOT_TOKEN: ');
|
|
320
|
+
values.set('TELEGRAM_BOT_TOKEN', tgToken);
|
|
321
|
+
touchedKeys.add('TELEGRAM_BOT_TOKEN');
|
|
322
|
+
}
|
|
323
|
+
if (await askYesNo(rl, 'Configure iMessage?', values.get('CHAT_IMESSAGE_ENABLED') === 'true')) {
|
|
324
|
+
values.set('CHAT_IMESSAGE_ENABLED', 'true');
|
|
325
|
+
touchedKeys.add('CHAT_IMESSAGE_ENABLED');
|
|
326
|
+
if (process.platform === 'darwin') {
|
|
327
|
+
values.set('IMESSAGE_LOCAL', (await askYesNo(rl, 'Use local iMessage database (macOS only)?', true)) ? 'true' : 'false');
|
|
328
|
+
touchedKeys.add('IMESSAGE_LOCAL');
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
values.set('IMESSAGE_LOCAL', 'false');
|
|
332
|
+
touchedKeys.add('IMESSAGE_LOCAL');
|
|
333
|
+
console.log(chalk.yellow('Non-macOS platform detected. iMessage will run in Remote Mode via Photon bridge.'));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
values.set('CHAT_IMESSAGE_ENABLED', 'false');
|
|
338
|
+
touchedKeys.add('CHAT_IMESSAGE_ENABLED');
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
values.set('CHAT_INTEGRATION_ENABLED', 'false');
|
|
343
|
+
touchedKeys.add('CHAT_INTEGRATION_ENABLED');
|
|
554
344
|
}
|
|
555
345
|
}
|
|
556
346
|
const installCameraDeps = options.installCameraDeps === undefined ? undefined : normalizeBooleanOption(options.installCameraDeps);
|
|
@@ -575,6 +365,7 @@ export async function runSetup(options = {}) {
|
|
|
575
365
|
if (await askYesNo(rl, 'Set a specific camera device now?', Boolean(currentDevice))) {
|
|
576
366
|
const cameraDevice = (await rl.question(`Camera device${currentDevice ? ` (${currentDevice})` : ' (blank for auto-detect)'}: `)).trim();
|
|
577
367
|
values.set('ZILMATE_CAMERA_DEVICE', cameraDevice || currentDevice);
|
|
368
|
+
touchedKeys.add('ZILMATE_CAMERA_DEVICE');
|
|
578
369
|
}
|
|
579
370
|
const checks = await runCameraDoctor();
|
|
580
371
|
console.log(chalk.gray(`Camera check: ${checks.map((check) => `${check.name} ${check.status}`).join(', ')}`));
|
|
@@ -595,6 +386,7 @@ export async function runSetup(options = {}) {
|
|
|
595
386
|
['Workspace', values.get('ZILMATE_WORKSPACE') || workspaceLayout().root],
|
|
596
387
|
['Trigger workflows', values.get('ZILMATE_TRIGGER_WORKFLOWS_ENABLED') === 'true' ? 'enabled' : 'disabled'],
|
|
597
388
|
['Voice', values.get('ZILMATE_VOICE_ENABLED') === 'true' ? values.get('DEEPGRAM_API_KEY') ? 'enabled' : 'enabled, missing Deepgram key' : 'disabled'],
|
|
389
|
+
['Chat', values.get('CHAT_INTEGRATION_ENABLED') === 'true' ? 'enabled' : 'disabled'],
|
|
598
390
|
['Camera', await commandExists('ffmpeg') ? 'ready' : 'needs ffmpeg'],
|
|
599
391
|
['Tunnel', await commandExists('cloudflared') ? 'ready' : 'needs cloudflared'],
|
|
600
392
|
]);
|
|
@@ -697,4 +489,59 @@ export async function setVoiceEnabled(enabled, options = {}) {
|
|
|
697
489
|
['Next', enabled ? values.get('DEEPGRAM_API_KEY') ? 'zilmate voice doctor' : 'zilmate voice setup' : 'zilmate voice enable'],
|
|
698
490
|
]);
|
|
699
491
|
}
|
|
492
|
+
export async function runChatSetup(options = {}) {
|
|
493
|
+
const envPath = options.path || '.env';
|
|
494
|
+
const existing = await readEnvValues(envPath);
|
|
495
|
+
const rl = readline.createInterface({ input, output });
|
|
496
|
+
try {
|
|
497
|
+
printZilMateBanner('Chat setup');
|
|
498
|
+
console.log(chalk.gray('Configure Slack, Telegram, and iMessage channels for ZilMate.'));
|
|
499
|
+
const values = new Map(existing);
|
|
500
|
+
values.set('CHAT_INTEGRATION_ENABLED', 'true');
|
|
501
|
+
if (options.slackBotToken) {
|
|
502
|
+
values.set('SLACK_BOT_TOKEN', options.slackBotToken);
|
|
503
|
+
if (options.slackSigningSecret)
|
|
504
|
+
values.set('SLACK_SIGNING_SECRET', options.slackSigningSecret);
|
|
505
|
+
}
|
|
506
|
+
else if (await askYesNo(rl, 'Configure Slack?', Boolean(values.get('SLACK_BOT_TOKEN')))) {
|
|
507
|
+
values.set('SLACK_BOT_TOKEN', await askRequiredSecret(rl, 'SLACK_BOT_TOKEN: '));
|
|
508
|
+
values.set('SLACK_SIGNING_SECRET', await askOptionalSecret(rl, 'SLACK_SIGNING_SECRET: '));
|
|
509
|
+
}
|
|
510
|
+
if (options.telegramBotToken) {
|
|
511
|
+
values.set('TELEGRAM_BOT_TOKEN', options.telegramBotToken);
|
|
512
|
+
}
|
|
513
|
+
else if (await askYesNo(rl, 'Configure Telegram?', Boolean(values.get('TELEGRAM_BOT_TOKEN')))) {
|
|
514
|
+
values.set('TELEGRAM_BOT_TOKEN', await askRequiredSecret(rl, 'TELEGRAM_BOT_TOKEN: '));
|
|
515
|
+
}
|
|
516
|
+
const imessageEnabled = options.imessageEnabled !== undefined ? normalizeBooleanOption(options.imessageEnabled) : undefined;
|
|
517
|
+
if (imessageEnabled !== undefined) {
|
|
518
|
+
values.set('CHAT_IMESSAGE_ENABLED', imessageEnabled ? 'true' : 'false');
|
|
519
|
+
if (options.imessageLocal !== undefined)
|
|
520
|
+
values.set('IMESSAGE_LOCAL', normalizeBooleanOption(options.imessageLocal) ? 'true' : 'false');
|
|
521
|
+
}
|
|
522
|
+
else if (await askYesNo(rl, 'Configure iMessage?', values.get('CHAT_IMESSAGE_ENABLED') === 'true')) {
|
|
523
|
+
values.set('CHAT_IMESSAGE_ENABLED', 'true');
|
|
524
|
+
if (process.platform === 'darwin') {
|
|
525
|
+
values.set('IMESSAGE_LOCAL', (await askYesNo(rl, 'Use local iMessage database (macOS only)?', true)) ? 'true' : 'false');
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
values.set('IMESSAGE_LOCAL', 'false');
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
await writeEnvValues(envPath, values, {
|
|
532
|
+
merge: existsSync(envPath),
|
|
533
|
+
touchedKeys: new Set(['CHAT_INTEGRATION_ENABLED', 'SLACK_BOT_TOKEN', 'SLACK_SIGNING_SECRET', 'TELEGRAM_BOT_TOKEN', 'CHAT_IMESSAGE_ENABLED', 'IMESSAGE_LOCAL']),
|
|
534
|
+
});
|
|
535
|
+
console.log(chalk.green(`Saved chat settings to ${envPath}.`));
|
|
536
|
+
printPanel('Chat summary', [
|
|
537
|
+
['Status', 'enabled'],
|
|
538
|
+
['Slack', values.get('SLACK_BOT_TOKEN') ? 'configured' : 'missing'],
|
|
539
|
+
['Telegram', values.get('TELEGRAM_BOT_TOKEN') ? 'configured' : 'missing'],
|
|
540
|
+
['iMessage', values.get('CHAT_IMESSAGE_ENABLED') === 'true' ? values.get('IMESSAGE_LOCAL') === 'true' ? 'enabled (Local)' : 'enabled (Remote)' : 'disabled'],
|
|
541
|
+
]);
|
|
542
|
+
}
|
|
543
|
+
finally {
|
|
544
|
+
rl.close();
|
|
545
|
+
}
|
|
546
|
+
}
|
|
700
547
|
//# sourceMappingURL=setup.js.map
|