wispy-ai 1.7.0 → 1.7.2
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/program.js +4 -4
- package/dist/cli/program.js.map +1 -1
- package/dist/cli/setup/wizard.d.ts +9 -16
- package/dist/cli/setup/wizard.d.ts.map +1 -1
- package/dist/cli/setup/wizard.js +918 -653
- package/dist/cli/setup/wizard.js.map +1 -1
- package/dist/cli/ui/banner.d.ts +1 -1
- package/dist/cli/ui/banner.js +1 -1
- package/package.json +1 -1
package/dist/cli/setup/wizard.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Interactive setup wizard — first-run onboarding
|
|
2
|
+
* Interactive setup wizard — comprehensive first-run onboarding.
|
|
3
3
|
*
|
|
4
4
|
* Steps:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* 9. Summary
|
|
5
|
+
* 1. Welcome
|
|
6
|
+
* 2. AI Engine + API Keys
|
|
7
|
+
* 3. Channels (Telegram, Discord, Slack, WhatsApp, Matrix, Signal, Email, Phone)
|
|
8
|
+
* 4. Integrations (Google, Productivity, Social, Smart Home, Commerce, Security)
|
|
9
|
+
* 5. Wallet & Commerce (x402)
|
|
10
|
+
* 6. Security & Mode
|
|
11
|
+
* 7. Extras (Voice, MCP, Theme, Agents, Memory)
|
|
12
|
+
* 8. Summary
|
|
14
13
|
*/
|
|
15
14
|
import * as readline from "readline";
|
|
16
15
|
import * as fs from "fs";
|
|
@@ -18,14 +17,15 @@ import * as path from "path";
|
|
|
18
17
|
import chalk from "chalk";
|
|
19
18
|
import gradient from "gradient-string";
|
|
20
19
|
import { writeYAML } from "../../utils/file.js";
|
|
21
|
-
// ── Brand
|
|
20
|
+
// ── Brand ────────────────────────────────────────────────
|
|
22
21
|
const sky = chalk.rgb(49, 204, 255);
|
|
23
22
|
const skyBold = chalk.rgb(49, 204, 255).bold;
|
|
24
23
|
const dim = chalk.dim;
|
|
25
24
|
const bold = chalk.bold;
|
|
26
25
|
const green = chalk.green;
|
|
27
|
-
const
|
|
28
|
-
|
|
26
|
+
const yellow = chalk.yellowBright;
|
|
27
|
+
const accent = chalk.hex("#00FFA3");
|
|
28
|
+
const wizardGradient = gradient(["#31CCFF", "#7B61FF", "#31CCFF"]);
|
|
29
29
|
const WISPY_ASCII_RAW = [
|
|
30
30
|
"\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557",
|
|
31
31
|
"\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D",
|
|
@@ -34,48 +34,9 @@ const WISPY_ASCII_RAW = [
|
|
|
34
34
|
"\u255A\u2588\u2588\u2588\u2554\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ",
|
|
35
35
|
" \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ",
|
|
36
36
|
];
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const THEMES = [
|
|
41
|
-
{ key: "dawn", label: `${chalk.yellow("◉")} Dawn — Warm sunrise tones` },
|
|
42
|
-
{ key: "day", label: `${chalk.rgb(49, 204, 255)("◉")} Day — Clean sky blue (default)` },
|
|
43
|
-
{ key: "dusk", label: `${chalk.magenta("◉")} Dusk — Twilight purple` },
|
|
44
|
-
{ key: "night", label: `${chalk.blue("◉")} Night — Deep dark mode` },
|
|
45
|
-
];
|
|
46
|
-
const THEME_ICON = {
|
|
47
|
-
dawn: chalk.yellow("◉"),
|
|
48
|
-
day: chalk.rgb(49, 204, 255)("◉"),
|
|
49
|
-
dusk: chalk.magenta("◉"),
|
|
50
|
-
night: chalk.blue("◉"),
|
|
51
|
-
};
|
|
52
|
-
// ── Agent definitions ────────────────────────────────────
|
|
53
|
-
const AGENTS = [
|
|
54
|
-
{ id: "coder", label: "Coder — Code generation, debugging, refactoring", defaultOn: true },
|
|
55
|
-
{ id: "researcher", label: "Researcher — Web search, analysis, summarization", defaultOn: true },
|
|
56
|
-
{ id: "writer", label: "Writer — Content creation, copywriting, docs", defaultOn: false },
|
|
57
|
-
{ id: "devops", label: "DevOps — CI/CD, deployment, infrastructure", defaultOn: false },
|
|
58
|
-
{ id: "designer", label: "Designer — UI/UX design, accessibility", defaultOn: false },
|
|
59
|
-
{ id: "data", label: "Data — SQL, data analysis, visualization", defaultOn: false },
|
|
60
|
-
{ id: "security", label: "Security — Audit, vulnerability scanning", defaultOn: false },
|
|
61
|
-
{ id: "planner", label: "Planner — Task breakdown, project planning", defaultOn: false },
|
|
62
|
-
];
|
|
63
|
-
// ── Integration definitions ──────────────────────────────
|
|
64
|
-
const INTEGRATIONS = [
|
|
65
|
-
{ id: "google-calendar", label: "Google Calendar" },
|
|
66
|
-
{ id: "gmail", label: "Gmail" },
|
|
67
|
-
{ id: "google-drive", label: "Google Drive" },
|
|
68
|
-
{ id: "discord", label: "Discord" },
|
|
69
|
-
{ id: "github", label: "GitHub" },
|
|
70
|
-
{ id: "notion", label: "Notion" },
|
|
71
|
-
{ id: "slack", label: "Slack" },
|
|
72
|
-
{ id: "spotify", label: "Spotify" },
|
|
73
|
-
{ id: "zoho-email", label: "Zoho Mail" },
|
|
74
|
-
{ id: "anthropic", label: "Anthropic (Claude)" },
|
|
75
|
-
{ id: "groq", label: "Groq" },
|
|
76
|
-
{ id: "openrouter", label: "OpenRouter" },
|
|
77
|
-
{ id: "kimi", label: "Kimi (Moonshot)" },
|
|
78
|
-
];
|
|
37
|
+
const WISPY_ASCII = "\n" +
|
|
38
|
+
WISPY_ASCII_RAW.map((line) => ` ${wizardGradient(line)}`).join("\n") +
|
|
39
|
+
"\n";
|
|
79
40
|
// ── Helpers ──────────────────────────────────────────────
|
|
80
41
|
function ask(rl, question) {
|
|
81
42
|
return new Promise((resolve, reject) => {
|
|
@@ -93,637 +54,891 @@ function parseNumberList(input, max) {
|
|
|
93
54
|
function capitalize(s) {
|
|
94
55
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
95
56
|
}
|
|
57
|
+
function masked(key, prefixLen = 8, suffixLen = 4) {
|
|
58
|
+
if (key.length < prefixLen + suffixLen + 4)
|
|
59
|
+
return key.slice(0, 4) + "...";
|
|
60
|
+
return key.slice(0, prefixLen) + "..." + key.slice(-suffixLen);
|
|
61
|
+
}
|
|
62
|
+
function stepHeader(step, total, title, icon) {
|
|
63
|
+
console.clear();
|
|
64
|
+
console.log(WISPY_ASCII);
|
|
65
|
+
const progress = dim(`[${step}/${total}]`);
|
|
66
|
+
console.log(skyBold(` ${icon} Step ${step} of ${total}: ${title} ${progress}\n`));
|
|
67
|
+
}
|
|
68
|
+
function envOrEmpty(name) {
|
|
69
|
+
return process.env[name] || "";
|
|
70
|
+
}
|
|
71
|
+
// ── Definitions ──────────────────────────────────────────
|
|
72
|
+
const THEMES = [
|
|
73
|
+
{ key: "dawn", label: `${chalk.yellow("\u25C9")} Dawn -- Warm sunrise tones` },
|
|
74
|
+
{ key: "day", label: `${sky("\u25C9")} Day -- Clean sky blue (default)` },
|
|
75
|
+
{ key: "dusk", label: `${chalk.magenta("\u25C9")} Dusk -- Twilight purple` },
|
|
76
|
+
{ key: "night", label: `${chalk.blue("\u25C9")} Night -- Deep dark mode` },
|
|
77
|
+
];
|
|
78
|
+
const THEME_ICON = {
|
|
79
|
+
dawn: chalk.yellow("\u25C9"),
|
|
80
|
+
day: sky("\u25C9"),
|
|
81
|
+
dusk: chalk.magenta("\u25C9"),
|
|
82
|
+
night: chalk.blue("\u25C9"),
|
|
83
|
+
};
|
|
84
|
+
const AGENTS = [
|
|
85
|
+
{ id: "coder", label: "Coder -- Code generation, debugging, refactoring", defaultOn: true },
|
|
86
|
+
{ id: "researcher", label: "Researcher -- Web search, analysis, summarization", defaultOn: true },
|
|
87
|
+
{ id: "writer", label: "Writer -- Content creation, copywriting, docs", defaultOn: false },
|
|
88
|
+
{ id: "devops", label: "DevOps -- CI/CD, deployment, infrastructure", defaultOn: false },
|
|
89
|
+
{ id: "designer", label: "Designer -- UI/UX design, accessibility", defaultOn: false },
|
|
90
|
+
{ id: "data", label: "Data -- SQL, data analysis, visualization", defaultOn: false },
|
|
91
|
+
{ id: "security", label: "Security -- Audit, vulnerability scanning", defaultOn: false },
|
|
92
|
+
{ id: "planner", label: "Planner -- Task breakdown, project planning", defaultOn: false },
|
|
93
|
+
];
|
|
94
|
+
const CHANNELS = [
|
|
95
|
+
{
|
|
96
|
+
id: "telegram",
|
|
97
|
+
label: "Telegram",
|
|
98
|
+
configKey: "telegram",
|
|
99
|
+
envVars: [{ name: "TELEGRAM_BOT_TOKEN", prompt: "Bot token", hint: "123456:ABC-DEF..." }],
|
|
100
|
+
instructions: [
|
|
101
|
+
`Open Telegram, search ${bold("@BotFather")}`,
|
|
102
|
+
`Send ${sky("/newbot")}, pick a name and username`,
|
|
103
|
+
"Copy the token BotFather gives you",
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "discord",
|
|
108
|
+
label: "Discord",
|
|
109
|
+
configKey: "discord",
|
|
110
|
+
envVars: [{ name: "DISCORD_BOT_TOKEN", prompt: "Bot token", hint: "MTIz..." }],
|
|
111
|
+
instructions: [
|
|
112
|
+
`Go to ${sky("https://discord.com/developers/applications")}`,
|
|
113
|
+
"Create app, go to Bot tab, click Reset Token",
|
|
114
|
+
"Enable Message Content Intent under Privileged Gateway Intents",
|
|
115
|
+
"Invite bot to your server with bot + applications.commands scope",
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: "slack",
|
|
120
|
+
label: "Slack",
|
|
121
|
+
configKey: "slack",
|
|
122
|
+
envVars: [
|
|
123
|
+
{ name: "SLACK_BOT_TOKEN", prompt: "Bot token", hint: "xoxb-..." },
|
|
124
|
+
{ name: "SLACK_SIGNING_SECRET", prompt: "Signing secret", hint: "abc123..." },
|
|
125
|
+
{ name: "SLACK_APP_TOKEN", prompt: "App-level token", hint: "xapp-..." },
|
|
126
|
+
],
|
|
127
|
+
instructions: [
|
|
128
|
+
`Go to ${sky("https://api.slack.com/apps")} and create a new app`,
|
|
129
|
+
"Enable Socket Mode, add chat:write + app_mentions:read scopes",
|
|
130
|
+
"Install to your workspace and copy the tokens",
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "whatsapp",
|
|
135
|
+
label: "WhatsApp",
|
|
136
|
+
configKey: "whatsapp",
|
|
137
|
+
envVars: [],
|
|
138
|
+
instructions: [
|
|
139
|
+
"WhatsApp uses QR code authentication (Baileys)",
|
|
140
|
+
`Run ${sky("wispy gateway")} and scan the QR code with your phone`,
|
|
141
|
+
"No API key needed -- connects directly to WhatsApp Web",
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: "matrix",
|
|
146
|
+
label: "Matrix",
|
|
147
|
+
configKey: "matrix",
|
|
148
|
+
envVars: [
|
|
149
|
+
{ name: "MATRIX_HOMESERVER", prompt: "Homeserver URL", hint: "https://matrix.org" },
|
|
150
|
+
{ name: "MATRIX_ACCESS_TOKEN", prompt: "Access token", hint: "syt_..." },
|
|
151
|
+
{ name: "MATRIX_USER_ID", prompt: "User ID", hint: "@wispy:matrix.org" },
|
|
152
|
+
],
|
|
153
|
+
instructions: [
|
|
154
|
+
"Create a Matrix account on your homeserver",
|
|
155
|
+
`Get an access token from ${sky("Element > Settings > Help & About")}`,
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: "signal",
|
|
160
|
+
label: "Signal",
|
|
161
|
+
configKey: "signal",
|
|
162
|
+
envVars: [
|
|
163
|
+
{ name: "SIGNAL_CLI_URL", prompt: "signal-cli REST URL", hint: "http://localhost:8080" },
|
|
164
|
+
{ name: "SIGNAL_PHONE_NUMBER", prompt: "Phone number", hint: "+1234567890" },
|
|
165
|
+
],
|
|
166
|
+
instructions: [
|
|
167
|
+
`Run signal-cli REST API: ${sky("docker run -p 8080:8080 bbernhard/signal-cli-rest-api")}`,
|
|
168
|
+
"Register your phone number with signal-cli first",
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: "email",
|
|
173
|
+
label: "Email (AgentMail)",
|
|
174
|
+
configKey: "email",
|
|
175
|
+
envVars: [{ name: "AGENTMAIL_API_KEY", prompt: "AgentMail API key", hint: "am_..." }],
|
|
176
|
+
instructions: [
|
|
177
|
+
`Get your API key at ${sky("https://agentmail.to")}`,
|
|
178
|
+
"Wispy gets its own email address to send and receive",
|
|
179
|
+
],
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: "phone",
|
|
183
|
+
label: "Phone / SMS (Telnyx)",
|
|
184
|
+
configKey: "phone",
|
|
185
|
+
envVars: [
|
|
186
|
+
{ name: "TELNYX_API_KEY", prompt: "Telnyx API key", hint: "KEY..." },
|
|
187
|
+
{ name: "TELNYX_CONNECTION_ID", prompt: "Connection ID", hint: "123456789" },
|
|
188
|
+
],
|
|
189
|
+
instructions: [
|
|
190
|
+
`Sign up at ${sky("https://telnyx.com")} and buy a phone number`,
|
|
191
|
+
"Create a TeXML application and get credentials",
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
];
|
|
195
|
+
const INTEGRATION_CATEGORIES = [
|
|
196
|
+
{
|
|
197
|
+
name: "Google Workspace",
|
|
198
|
+
icon: "\uD83D\uDD0D",
|
|
199
|
+
note: "All Google integrations share one OAuth setup",
|
|
200
|
+
items: [
|
|
201
|
+
{ id: "google-calendar", label: "Google Calendar" },
|
|
202
|
+
{ id: "gmail", label: "Gmail" },
|
|
203
|
+
{ id: "google-drive", label: "Google Drive" },
|
|
204
|
+
{ id: "google-docs", label: "Google Docs" },
|
|
205
|
+
{ id: "google-sheets", label: "Google Sheets" },
|
|
206
|
+
{ id: "google-meet", label: "Google Meet" },
|
|
207
|
+
{ id: "google-maps", label: "Google Maps" },
|
|
208
|
+
{ id: "google-search", label: "Google Search" },
|
|
209
|
+
{ id: "youtube", label: "YouTube" },
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: "Productivity",
|
|
214
|
+
icon: "\uD83D\uDCCB",
|
|
215
|
+
items: [
|
|
216
|
+
{ id: "github", label: "GitHub", envVars: ["GITHUB_TOKEN"], link: "https://github.com/settings/tokens" },
|
|
217
|
+
{ id: "notion", label: "Notion", envVars: ["NOTION_TOKEN"], link: "https://www.notion.so/my-integrations" },
|
|
218
|
+
{ id: "linear", label: "Linear", envVars: ["LINEAR_API_KEY"], link: "https://linear.app/settings/api" },
|
|
219
|
+
{ id: "obsidian", label: "Obsidian (local)" },
|
|
220
|
+
{ id: "trello", label: "Trello", envVars: ["TRELLO_API_KEY", "TRELLO_TOKEN"] },
|
|
221
|
+
{ id: "asana", label: "Asana", envVars: ["ASANA_ACCESS_TOKEN"] },
|
|
222
|
+
{ id: "calendly", label: "Calendly", envVars: ["CALENDLY_API_KEY"] },
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: "Social Media",
|
|
227
|
+
icon: "\uD83D\uDCF1",
|
|
228
|
+
items: [
|
|
229
|
+
{ id: "twitter", label: "Twitter / X", envVars: ["TWITTER_API_KEY", "TWITTER_API_SECRET", "TWITTER_ACCESS_TOKEN", "TWITTER_ACCESS_SECRET"], link: "https://developer.x.com/portal" },
|
|
230
|
+
{ id: "linkedin", label: "LinkedIn", envVars: ["LINKEDIN_ACCESS_TOKEN"] },
|
|
231
|
+
{ id: "instagram", label: "Instagram", envVars: ["INSTAGRAM_ACCESS_TOKEN"] },
|
|
232
|
+
{ id: "reddit", label: "Reddit", envVars: ["REDDIT_CLIENT_ID", "REDDIT_CLIENT_SECRET"] },
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
name: "Smart Home",
|
|
237
|
+
icon: "\uD83C\uDFE0",
|
|
238
|
+
items: [
|
|
239
|
+
{ id: "hue", label: "Philips Hue", envVars: ["HUE_BRIDGE_IP", "HUE_USERNAME"] },
|
|
240
|
+
{ id: "home-assistant", label: "Home Assistant", envVars: ["HA_URL", "HA_TOKEN"] },
|
|
241
|
+
{ id: "sonos", label: "Sonos", envVars: ["SONOS_API_KEY", "SONOS_HOUSEHOLD_ID"] },
|
|
242
|
+
],
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: "Commerce & Security",
|
|
246
|
+
icon: "\uD83D\uDD12",
|
|
247
|
+
items: [
|
|
248
|
+
{ id: "stripe", label: "Stripe", envVars: ["STRIPE_SECRET_KEY"], link: "https://dashboard.stripe.com/apikeys" },
|
|
249
|
+
{ id: "1password", label: "1Password", envVars: ["OP_SERVICE_ACCOUNT_TOKEN"] },
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: "Music & Media",
|
|
254
|
+
icon: "\uD83C\uDFB5",
|
|
255
|
+
items: [
|
|
256
|
+
{ id: "spotify", label: "Spotify", envVars: ["SPOTIFY_ACCESS_TOKEN"] },
|
|
257
|
+
{ id: "image-generation", label: "Image Generation (DALL-E / Stability)", envVars: ["IMAGE_GEN_API_KEY"] },
|
|
258
|
+
],
|
|
259
|
+
},
|
|
260
|
+
];
|
|
261
|
+
const MCP_SERVERS = [
|
|
262
|
+
{ id: "filesystem", label: "File System -- Read/write files anywhere", command: "npx", args: ["-y", "@anthropic/mcp-server-filesystem", "--allow-write", "/"], default: true },
|
|
263
|
+
{ id: "fetch", label: "Web Fetch -- HTTP requests with auth", command: "npx", args: ["-y", "@anthropic/mcp-server-fetch"], default: true },
|
|
264
|
+
{ id: "memory", label: "Memory -- Persistent key-value storage", command: "npx", args: ["-y", "@anthropic/mcp-server-memory"], default: false },
|
|
265
|
+
{ id: "brave", label: "Brave Search-- Web search via Brave API", command: "npx", args: ["-y", "@anthropic/mcp-server-brave-search"], default: false, envRequired: "BRAVE_API_KEY" },
|
|
266
|
+
{ id: "github", label: "GitHub -- Access repos, issues, PRs", command: "npx", args: ["-y", "@modelcontextprotocol/server-github"], default: false, envRequired: "GITHUB_TOKEN" },
|
|
267
|
+
{ id: "postgres", label: "PostgreSQL -- Query databases directly", command: "npx", args: ["-y", "@modelcontextprotocol/server-postgres"], default: false, envRequired: "DATABASE_URL" },
|
|
268
|
+
];
|
|
96
269
|
// ── Public API ───────────────────────────────────────────
|
|
97
|
-
/**
|
|
98
|
-
* Check whether this is the first run (no config.yaml exists).
|
|
99
|
-
*/
|
|
100
270
|
export function isFirstRun(runtimeDir) {
|
|
101
271
|
const configPath = path.resolve(runtimeDir, "config.yaml");
|
|
102
272
|
return !fs.existsSync(configPath);
|
|
103
273
|
}
|
|
104
|
-
/**
|
|
105
|
-
* Run the interactive setup wizard.
|
|
106
|
-
*/
|
|
107
274
|
export async function runSetupWizard(opts) {
|
|
108
275
|
const { rootDir, runtimeDir } = opts;
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
output: process.stdout,
|
|
112
|
-
});
|
|
113
|
-
// Graceful Ctrl+C
|
|
276
|
+
const TOTAL_STEPS = 8;
|
|
277
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
114
278
|
rl.on("SIGINT", () => {
|
|
115
279
|
console.log(dim("\n\n Setup cancelled.\n"));
|
|
116
280
|
rl.close();
|
|
117
281
|
process.exit(0);
|
|
118
282
|
});
|
|
283
|
+
// Collected state
|
|
284
|
+
const env = {};
|
|
285
|
+
let engine = "gemini";
|
|
286
|
+
let apiKey = "";
|
|
287
|
+
let useVertexAI = false;
|
|
288
|
+
let anthropicKey = "";
|
|
289
|
+
let openaiKey = "";
|
|
290
|
+
let groqKey = "";
|
|
291
|
+
let openrouterKey = "";
|
|
292
|
+
let kimiKey = "";
|
|
293
|
+
let ollamaUrl = "";
|
|
294
|
+
let elevenlabsKey = "";
|
|
295
|
+
const enabledChannels = {};
|
|
296
|
+
let selectedIntegrations = [];
|
|
297
|
+
let autonomousMode = true;
|
|
298
|
+
let fullFilesystemAccess = true;
|
|
299
|
+
let selectedTheme = "day";
|
|
300
|
+
let selectedAgents = ["coder", "researcher"];
|
|
301
|
+
let commerceEnabled = false;
|
|
302
|
+
let agentPrivateKey = "";
|
|
303
|
+
let cdpKeyName = "";
|
|
304
|
+
let cdpPrivateKey = "";
|
|
305
|
+
let voiceModel = "edge-tts";
|
|
119
306
|
try {
|
|
120
|
-
//
|
|
307
|
+
// ════════════════════════════════════════════════════════
|
|
308
|
+
// STEP 1: Welcome
|
|
309
|
+
// ════════════════════════════════════════════════════════
|
|
121
310
|
console.clear();
|
|
122
311
|
console.log(WISPY_ASCII);
|
|
123
|
-
console.log(skyBold(`
|
|
124
|
-
console.log(dim("
|
|
125
|
-
console.log(dim("
|
|
126
|
-
console.log(
|
|
127
|
-
console.log(`
|
|
128
|
-
console.log(`
|
|
129
|
-
console.log(`
|
|
312
|
+
console.log(skyBold(` * Welcome to Wispy Setup!\n`));
|
|
313
|
+
console.log(dim(" This wizard sets up everything your AI agent needs."));
|
|
314
|
+
console.log(dim(" Every step has smart defaults -- just press Enter to skip.\n"));
|
|
315
|
+
console.log(` ${sky("What we will configure:")}`);
|
|
316
|
+
console.log(` ${dim("1.")} AI engine and API keys`);
|
|
317
|
+
console.log(` ${dim("2.")} Messaging channels (Telegram, Discord, Slack, etc.)`);
|
|
318
|
+
console.log(` ${dim("3.")} App integrations (GitHub, Notion, Google, etc.)`);
|
|
319
|
+
console.log(` ${dim("4.")} Blockchain wallet (optional)`);
|
|
320
|
+
console.log(` ${dim("5.")} Security and permissions`);
|
|
321
|
+
console.log(` ${dim("6.")} Voice, tools, theme, and more`);
|
|
130
322
|
console.log("");
|
|
131
|
-
console.log(dim("
|
|
323
|
+
console.log(dim(" Takes about 3 minutes. You can reconfigure anytime with:"));
|
|
324
|
+
console.log(` ${sky("wispy setup")}\n`);
|
|
132
325
|
await ask(rl, dim(" Press Enter to start..."));
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
console.log(dim("
|
|
138
|
-
console.log(` ${sky("1)")} ${bold("Gemini
|
|
139
|
-
console.log(dim(`
|
|
140
|
-
console.log(`
|
|
326
|
+
// ════════════════════════════════════════════════════════
|
|
327
|
+
// STEP 2: AI Engine + Keys
|
|
328
|
+
// ════════════════════════════════════════════════════════
|
|
329
|
+
stepHeader(2, TOTAL_STEPS, "AI Engine & API Keys", chalk.yellow("\u25B8"));
|
|
330
|
+
console.log(dim(" Choose your primary AI engine:\n"));
|
|
331
|
+
console.log(` ${sky("1)")} ${bold("Gemini")} ${dim("(Google -- recommended, free tier available)")}`);
|
|
332
|
+
console.log(dim(` Get key: ${sky("https://aistudio.google.com/apikey")}`));
|
|
333
|
+
console.log(` ${sky("2)")} ${bold("Claude")} ${dim("(Anthropic -- advanced reasoning)")}`);
|
|
334
|
+
console.log(dim(` Get key: ${sky("https://console.anthropic.com/keys")}`));
|
|
335
|
+
console.log(` ${sky("3)")} ${bold("Vertex AI")} ${dim("(Google Cloud -- production / high quota)")}`);
|
|
141
336
|
console.log("");
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
337
|
+
const engineChoice = await ask(rl, sky(" Select [1/2/3] (default: 1): "));
|
|
338
|
+
if (engineChoice.trim() === "2") {
|
|
339
|
+
engine = "claude";
|
|
340
|
+
console.log("");
|
|
341
|
+
const existing = envOrEmpty("ANTHROPIC_API_KEY");
|
|
342
|
+
if (existing) {
|
|
343
|
+
console.log(green(` Found existing key: ${masked(existing, 10)}`));
|
|
344
|
+
anthropicKey = existing;
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
anthropicKey = (await ask(rl, sky(" Anthropic API key (sk-ant-...): "))).trim();
|
|
348
|
+
if (anthropicKey) {
|
|
349
|
+
console.log(green(" Configured."));
|
|
350
|
+
env.ANTHROPIC_API_KEY = anthropicKey;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Still need Gemini for embeddings
|
|
354
|
+
console.log(dim("\n Note: Gemini is still used for embeddings. Adding Gemini key is recommended.\n"));
|
|
355
|
+
const geminiKey = envOrEmpty("GEMINI_API_KEY");
|
|
356
|
+
if (geminiKey) {
|
|
357
|
+
console.log(green(` Found Gemini key: ${masked(geminiKey)}`));
|
|
358
|
+
apiKey = geminiKey;
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
const gk = (await ask(rl, sky(" Gemini API key (optional, for embeddings): "))).trim();
|
|
362
|
+
if (gk) {
|
|
363
|
+
apiKey = gk;
|
|
364
|
+
env.GEMINI_API_KEY = gk;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
154
367
|
}
|
|
155
|
-
else if (
|
|
156
|
-
|
|
157
|
-
console.log(
|
|
158
|
-
|
|
368
|
+
else if (engineChoice.trim() === "3") {
|
|
369
|
+
useVertexAI = true;
|
|
370
|
+
console.log("");
|
|
371
|
+
const existing = envOrEmpty("GOOGLE_CLOUD_PROJECT");
|
|
372
|
+
if (existing) {
|
|
373
|
+
console.log(green(` Found Vertex project: ${existing}`));
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
console.log(dim(" Set these in .env:"));
|
|
377
|
+
console.log(sky(" GOOGLE_CLOUD_PROJECT=your-project-id"));
|
|
378
|
+
console.log(sky(" GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json"));
|
|
379
|
+
const proj = (await ask(rl, sky("\n Project ID (or Enter to skip): "))).trim();
|
|
380
|
+
if (proj)
|
|
381
|
+
env.GOOGLE_CLOUD_PROJECT = proj;
|
|
382
|
+
}
|
|
159
383
|
}
|
|
160
384
|
else {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
console.log(
|
|
167
|
-
|
|
168
|
-
console.log("");
|
|
169
|
-
console.log(sky(" GOOGLE_CLOUD_PROJECT=your-project-id"));
|
|
170
|
-
console.log(sky(" GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json"));
|
|
171
|
-
console.log("");
|
|
172
|
-
console.log(dim(" Quick setup guide:"));
|
|
173
|
-
console.log(dim(` ${sky("https://cloud.google.com/vertex-ai/docs/authentication")}`));
|
|
174
|
-
console.log("");
|
|
175
|
-
const vertexProject = await ask(rl, sky(" Enter your project ID (or press Enter to skip): "));
|
|
176
|
-
if (vertexProject.trim()) {
|
|
177
|
-
useVertexAI = true;
|
|
178
|
-
// Will save to .env later
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
console.log(dim(" Skipping Vertex AI. You can set it up later in .env\n"));
|
|
182
|
-
}
|
|
385
|
+
// Gemini (default)
|
|
386
|
+
engine = "gemini";
|
|
387
|
+
console.log("");
|
|
388
|
+
const existing = envOrEmpty("GEMINI_API_KEY");
|
|
389
|
+
if (existing) {
|
|
390
|
+
console.log(green(` Found existing key: ${masked(existing)}`));
|
|
391
|
+
apiKey = existing;
|
|
183
392
|
}
|
|
184
393
|
else {
|
|
185
|
-
console.log("");
|
|
186
394
|
console.log(dim(" How to get your key:"));
|
|
187
395
|
console.log(` ${sky("1.")} Open ${sky("https://aistudio.google.com/apikey")}`);
|
|
188
|
-
console.log(` ${sky("2.")} Sign in
|
|
189
|
-
console.log(` ${sky("3.")}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
396
|
+
console.log(` ${sky("2.")} Sign in and click ${bold('"Create API key"')}`);
|
|
397
|
+
console.log(` ${sky("3.")} Copy the key (starts with ${bold("AIza")}...)\n`);
|
|
398
|
+
apiKey = (await ask(rl, sky(" Paste your Gemini API key: "))).trim();
|
|
399
|
+
if (apiKey) {
|
|
400
|
+
if (!apiKey.startsWith("AIza")) {
|
|
401
|
+
console.log(yellow("\n Key does not start with 'AIza'. Double-check it."));
|
|
402
|
+
const ok = await ask(rl, dim(" Continue anyway? [y/N]: "));
|
|
403
|
+
if (!ok.trim().toLowerCase().startsWith("y"))
|
|
404
|
+
apiKey = "";
|
|
405
|
+
}
|
|
406
|
+
if (apiKey) {
|
|
407
|
+
env.GEMINI_API_KEY = apiKey;
|
|
408
|
+
console.log(green(" Configured."));
|
|
200
409
|
}
|
|
201
|
-
}
|
|
202
|
-
else if (!apiKey) {
|
|
203
|
-
console.log(dim(" Skipped. Set GEMINI_API_KEY in .env later.\n"));
|
|
204
410
|
}
|
|
205
411
|
}
|
|
206
412
|
}
|
|
207
|
-
//
|
|
208
|
-
console.clear();
|
|
209
|
-
console.log(WISPY_ASCII);
|
|
210
|
-
console.log(skyBold(` ${chalk.cyan("▸")} Additional AI Providers (Optional)\n`));
|
|
211
|
-
console.log(dim(" Wispy's core engine runs on Gemini. You can also configure"));
|
|
212
|
-
console.log(dim(" other providers as tools the agent can delegate tasks to.\n"));
|
|
213
|
-
console.log(` ${sky("1)")} ${bold("Anthropic")} ${dim("(Claude Opus, Sonnet)")}`);
|
|
214
|
-
console.log(` ${sky("2)")} ${bold("OpenAI")} ${dim("(GPT-4o, Codex, o3)")}`);
|
|
215
|
-
console.log(` ${sky("3)")} ${bold("Groq")} ${dim("(Ultra-fast inference, free tier)")}`);
|
|
216
|
-
console.log(` ${sky("4)")} ${bold("OpenRouter")} ${dim("(200+ models, unified API)")}`);
|
|
217
|
-
console.log(` ${sky("5)")} ${bold("Kimi / Moonshot")} ${dim("(128K context, long docs)")}`);
|
|
218
|
-
console.log(` ${sky("6)")} ${bold("Ollama")} ${dim("(Local, free, offline)")}`);
|
|
413
|
+
// Additional providers
|
|
219
414
|
console.log("");
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
if (
|
|
228
|
-
const picks = parseNumberList(
|
|
415
|
+
console.log(dim(" ── Additional AI Providers (optional) ──\n"));
|
|
416
|
+
console.log(` ${sky("1)")} Anthropic ${dim("(Claude)")} ${sky("4)")} OpenRouter ${dim("(200+ models)")}`);
|
|
417
|
+
console.log(` ${sky("2)")} OpenAI ${dim("(GPT-4o)")} ${sky("5)")} Kimi ${dim("(128K context)")}`);
|
|
418
|
+
console.log(` ${sky("3)")} Groq ${dim("(fast inference)")} ${sky("6)")} Ollama ${dim("(local, free)")}`);
|
|
419
|
+
console.log(` ${sky("7)")} ElevenLabs ${dim("(voice AI)")}`);
|
|
420
|
+
console.log("");
|
|
421
|
+
const providerInput = await ask(rl, sky(" Add providers? Numbers (e.g. 1,3,7) or Enter to skip: "));
|
|
422
|
+
if (providerInput.trim()) {
|
|
423
|
+
const picks = parseNumberList(providerInput, 7);
|
|
229
424
|
console.log("");
|
|
230
425
|
for (const idx of picks) {
|
|
231
426
|
switch (idx) {
|
|
232
427
|
case 0: { // Anthropic
|
|
233
|
-
if (anthropicKey) {
|
|
234
|
-
const
|
|
235
|
-
|
|
428
|
+
if (!anthropicKey) {
|
|
429
|
+
const existing = envOrEmpty("ANTHROPIC_API_KEY");
|
|
430
|
+
if (existing) {
|
|
431
|
+
anthropicKey = existing;
|
|
432
|
+
console.log(green(` Anthropic: ${masked(existing, 10)}`));
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
console.log(dim(` Get key: ${sky("https://console.anthropic.com/keys")}`));
|
|
436
|
+
anthropicKey = (await ask(rl, sky(" Anthropic key (sk-ant-...): "))).trim();
|
|
437
|
+
if (anthropicKey) {
|
|
438
|
+
env.ANTHROPIC_API_KEY = anthropicKey;
|
|
439
|
+
console.log(green(" Anthropic configured."));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
236
442
|
}
|
|
237
443
|
else {
|
|
238
|
-
console.log(
|
|
239
|
-
const key = await ask(rl, sky(" Anthropic API key (sk-ant-...): "));
|
|
240
|
-
anthropicKey = key.trim();
|
|
241
|
-
if (anthropicKey)
|
|
242
|
-
console.log(green(" ✓ Anthropic configured"));
|
|
243
|
-
else
|
|
244
|
-
console.log(dim(" Skipped."));
|
|
444
|
+
console.log(green(` Anthropic: already set`));
|
|
245
445
|
}
|
|
246
|
-
console.log("");
|
|
247
446
|
break;
|
|
248
447
|
}
|
|
249
448
|
case 1: { // OpenAI
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
449
|
+
const existing = envOrEmpty("OPENAI_API_KEY");
|
|
450
|
+
if (existing) {
|
|
451
|
+
openaiKey = existing;
|
|
452
|
+
console.log(green(` OpenAI: ${masked(existing)}`));
|
|
253
453
|
}
|
|
254
454
|
else {
|
|
255
|
-
console.log(dim(` Get
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
console.log(green("
|
|
260
|
-
|
|
261
|
-
console.log(dim(" Skipped."));
|
|
455
|
+
console.log(dim(` Get key: ${sky("https://platform.openai.com/api-keys")}`));
|
|
456
|
+
openaiKey = (await ask(rl, sky(" OpenAI key (sk-...): "))).trim();
|
|
457
|
+
if (openaiKey) {
|
|
458
|
+
env.OPENAI_API_KEY = openaiKey;
|
|
459
|
+
console.log(green(" OpenAI configured."));
|
|
460
|
+
}
|
|
262
461
|
}
|
|
263
|
-
console.log("");
|
|
264
462
|
break;
|
|
265
463
|
}
|
|
266
464
|
case 2: { // Groq
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
465
|
+
const existing = envOrEmpty("GROQ_API_KEY");
|
|
466
|
+
if (existing) {
|
|
467
|
+
groqKey = existing;
|
|
468
|
+
console.log(green(` Groq: ${masked(existing)}`));
|
|
270
469
|
}
|
|
271
470
|
else {
|
|
272
|
-
console.log(dim(` Get
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
console.log(green("
|
|
277
|
-
|
|
278
|
-
console.log(dim(" Skipped."));
|
|
471
|
+
console.log(dim(` Get key: ${sky("https://console.groq.com/keys")}`));
|
|
472
|
+
groqKey = (await ask(rl, sky(" Groq key (gsk_...): "))).trim();
|
|
473
|
+
if (groqKey) {
|
|
474
|
+
env.GROQ_API_KEY = groqKey;
|
|
475
|
+
console.log(green(" Groq configured."));
|
|
476
|
+
}
|
|
279
477
|
}
|
|
280
|
-
console.log("");
|
|
281
478
|
break;
|
|
282
479
|
}
|
|
283
480
|
case 3: { // OpenRouter
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
481
|
+
const existing = envOrEmpty("OPENROUTER_API_KEY");
|
|
482
|
+
if (existing) {
|
|
483
|
+
openrouterKey = existing;
|
|
484
|
+
console.log(green(` OpenRouter: ${masked(existing, 10)}`));
|
|
287
485
|
}
|
|
288
486
|
else {
|
|
289
|
-
console.log(dim(` Get
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
console.log(green("
|
|
294
|
-
|
|
295
|
-
console.log(dim(" Skipped."));
|
|
487
|
+
console.log(dim(` Get key: ${sky("https://openrouter.ai/keys")}`));
|
|
488
|
+
openrouterKey = (await ask(rl, sky(" OpenRouter key (sk-or-...): "))).trim();
|
|
489
|
+
if (openrouterKey) {
|
|
490
|
+
env.OPENROUTER_API_KEY = openrouterKey;
|
|
491
|
+
console.log(green(" OpenRouter configured."));
|
|
492
|
+
}
|
|
296
493
|
}
|
|
297
|
-
console.log("");
|
|
298
494
|
break;
|
|
299
495
|
}
|
|
300
496
|
case 4: { // Kimi
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
497
|
+
const existing = envOrEmpty("MOONSHOT_API_KEY");
|
|
498
|
+
if (existing) {
|
|
499
|
+
kimiKey = existing;
|
|
500
|
+
console.log(green(` Kimi: ${masked(existing)}`));
|
|
304
501
|
}
|
|
305
502
|
else {
|
|
306
|
-
console.log(dim(` Get
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
console.log(green("
|
|
311
|
-
|
|
312
|
-
console.log(dim(" Skipped."));
|
|
503
|
+
console.log(dim(` Get key: ${sky("https://platform.moonshot.cn/console/api-keys")}`));
|
|
504
|
+
kimiKey = (await ask(rl, sky(" Moonshot key: "))).trim();
|
|
505
|
+
if (kimiKey) {
|
|
506
|
+
env.MOONSHOT_API_KEY = kimiKey;
|
|
507
|
+
console.log(green(" Kimi configured."));
|
|
508
|
+
}
|
|
313
509
|
}
|
|
314
|
-
console.log("");
|
|
315
510
|
break;
|
|
316
511
|
}
|
|
317
512
|
case 5: { // Ollama
|
|
318
|
-
console.log(dim(
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
513
|
+
console.log(dim(` Make sure Ollama is running. Install: ${sky("https://ollama.com/download")}`));
|
|
514
|
+
const url = (await ask(rl, sky(" Ollama URL (default: http://localhost:11434): "))).trim();
|
|
515
|
+
ollamaUrl = url || "http://localhost:11434";
|
|
516
|
+
console.log(green(` Ollama: ${ollamaUrl}`));
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
case 6: { // ElevenLabs
|
|
520
|
+
const existing = envOrEmpty("ELEVENLABS_API_KEY");
|
|
521
|
+
if (existing) {
|
|
522
|
+
elevenlabsKey = existing;
|
|
523
|
+
console.log(green(` ElevenLabs: ${masked(existing)}`));
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
console.log(dim(` Get key: ${sky("https://elevenlabs.io/app/settings/api-keys")}`));
|
|
527
|
+
elevenlabsKey = (await ask(rl, sky(" ElevenLabs key: "))).trim();
|
|
528
|
+
if (elevenlabsKey) {
|
|
529
|
+
env.ELEVENLABS_API_KEY = elevenlabsKey;
|
|
530
|
+
console.log(green(" ElevenLabs configured."));
|
|
531
|
+
}
|
|
532
|
+
}
|
|
324
533
|
break;
|
|
325
534
|
}
|
|
326
535
|
}
|
|
536
|
+
console.log("");
|
|
327
537
|
}
|
|
328
538
|
}
|
|
329
|
-
|
|
330
|
-
|
|
539
|
+
// ════════════════════════════════════════════════════════
|
|
540
|
+
// STEP 3: Channels
|
|
541
|
+
// ════════════════════════════════════════════════════════
|
|
542
|
+
stepHeader(3, TOTAL_STEPS, "Messaging Channels", chalk.blue("\u25B8"));
|
|
543
|
+
console.log(dim(" Where should Wispy be available? Pick one or more.\n"));
|
|
544
|
+
for (let i = 0; i < CHANNELS.length; i++) {
|
|
545
|
+
const ch = CHANNELS[i];
|
|
546
|
+
const hasKey = ch.envVars.length > 0 && ch.envVars.some((v) => !!envOrEmpty(v.name));
|
|
547
|
+
const status = hasKey ? green(" [key found]") : "";
|
|
548
|
+
console.log(` ${sky(`${i + 1})`)} ${bold(ch.label)}${status}`);
|
|
331
549
|
}
|
|
332
|
-
// ── Step 2b: Telegram Bot Token (optional) ──────────
|
|
333
|
-
console.clear();
|
|
334
|
-
console.log(WISPY_ASCII);
|
|
335
|
-
console.log(skyBold(` ${chalk.blue("▸")} Step 2 of 4: Telegram Bot (Optional)\n`));
|
|
336
|
-
console.log(dim(" Connect Telegram to chat with Wispy from your phone."));
|
|
337
|
-
console.log(dim(" Skip this if you only want to use the terminal.\n"));
|
|
338
|
-
console.log(dim(" How to create a Telegram bot (takes 1 minute):"));
|
|
339
|
-
console.log(` ${sky("1.")} Open Telegram and search for ${bold("@BotFather")}`);
|
|
340
|
-
console.log(` ${sky("2.")} Send the message: ${sky("/newbot")}`);
|
|
341
|
-
console.log(` ${sky("3.")} Choose a name for your bot (e.g. "My Wispy")`);
|
|
342
|
-
console.log(` ${sky("4.")} Choose a username (e.g. "my_wispy_bot")`);
|
|
343
|
-
console.log(` ${sky("5.")} BotFather will reply with a ${bold("token")} like: ${dim("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")}`);
|
|
344
|
-
console.log(` ${sky("6.")} Copy that token`);
|
|
345
550
|
console.log("");
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
551
|
+
console.log(dim(" CLI is always available. REST + WebSocket start with the gateway."));
|
|
552
|
+
console.log(dim(" WhatsApp uses QR code scan -- no token needed.\n"));
|
|
553
|
+
const channelInput = await ask(rl, sky(" Enter numbers (e.g. 1,2,4) or Enter to skip: "));
|
|
554
|
+
if (channelInput.trim()) {
|
|
555
|
+
const picks = parseNumberList(channelInput, CHANNELS.length);
|
|
556
|
+
for (const idx of picks) {
|
|
557
|
+
const ch = CHANNELS[idx];
|
|
558
|
+
enabledChannels[ch.configKey] = true;
|
|
559
|
+
console.log("");
|
|
560
|
+
console.log(skyBold(` -- ${ch.label} Setup --\n`));
|
|
561
|
+
// Show instructions
|
|
562
|
+
for (const step of ch.instructions) {
|
|
563
|
+
console.log(` ${dim("\u2022")} ${step}`);
|
|
564
|
+
}
|
|
565
|
+
console.log("");
|
|
566
|
+
// Collect env vars
|
|
567
|
+
for (const v of ch.envVars) {
|
|
568
|
+
const existing = envOrEmpty(v.name);
|
|
569
|
+
if (existing) {
|
|
570
|
+
console.log(green(` ${v.name}: ${masked(existing)}`));
|
|
571
|
+
env[v.name] = existing;
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
const value = (await ask(rl, sky(` ${v.prompt} (${dim(v.hint)}): `))).trim();
|
|
575
|
+
if (value) {
|
|
576
|
+
env[v.name] = value;
|
|
577
|
+
console.log(green(` Set.`));
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
console.log(dim(` Skipped. Add ${v.name} to .env later.`));
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
358
584
|
}
|
|
359
585
|
}
|
|
360
|
-
//
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
console.log(dim("
|
|
365
|
-
console.log(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const modeChoice = await ask(rl, sky(" Select [1/2] (default: 2 for Autonomous): "));
|
|
372
|
-
const autonomousMode = modeChoice.trim() !== "1";
|
|
373
|
-
// ── Step 2d: File System Access ──────────────────────
|
|
374
|
-
let fullFilesystemAccess = false;
|
|
375
|
-
if (autonomousMode) {
|
|
376
|
-
console.clear();
|
|
377
|
-
console.log(WISPY_ASCII);
|
|
378
|
-
console.log(skyBold(` ${chalk.yellow("▸")} File System Access\n`));
|
|
379
|
-
console.log(dim(" Autonomous mode is enabled. Wispy can already write files in its workspace."));
|
|
380
|
-
console.log(dim(" Enable full filesystem access to let Wispy work on projects anywhere.\n"));
|
|
381
|
-
console.log(` ${sky("Examples:")}`);
|
|
382
|
-
console.log(dim(" - Edit files across your projects directories"));
|
|
383
|
-
console.log(dim(" - Read/write anywhere on your drive"));
|
|
384
|
-
console.log(dim(" - Manage files in your Downloads folder\n"));
|
|
385
|
-
const fsChoice = await ask(rl, sky(" Enable full filesystem access? [Y/n]: "));
|
|
386
|
-
fullFilesystemAccess = !fsChoice.trim().toLowerCase().startsWith("n");
|
|
387
|
-
}
|
|
388
|
-
// ── Step 3: Theme ────────────────────────────────────
|
|
389
|
-
console.clear();
|
|
390
|
-
console.log(WISPY_ASCII);
|
|
391
|
-
console.log(skyBold(` ${chalk.magenta("▸")} Pick a Color Theme\n`));
|
|
392
|
-
console.log(dim(" This changes how Wispy looks in your terminal.\n"));
|
|
393
|
-
for (let i = 0; i < THEMES.length; i++) {
|
|
394
|
-
const num = sky(`${i + 1})`);
|
|
395
|
-
console.log(` ${num} ${THEMES[i].label}`);
|
|
396
|
-
}
|
|
397
|
-
const themeInput = await ask(rl, sky("\n Select [1-4] (default: 2): "));
|
|
398
|
-
const themeIdx = parseInt(themeInput.trim(), 10);
|
|
399
|
-
const selectedTheme = themeIdx >= 1 && themeIdx <= 4
|
|
400
|
-
? THEMES[themeIdx - 1].key
|
|
401
|
-
: "day";
|
|
402
|
-
// ── Step 4: Agents ───────────────────────────────────
|
|
403
|
-
console.clear();
|
|
404
|
-
console.log(WISPY_ASCII);
|
|
405
|
-
console.log(skyBold(` ${chalk.cyan("▸")} Choose Agent Roles\n`));
|
|
406
|
-
console.log(dim(" These are Wispy's specializations. Coder + Researcher are pre-selected."));
|
|
407
|
-
console.log(dim(" Just press Enter to keep the defaults, or pick more.\n"));
|
|
408
|
-
for (let i = 0; i < AGENTS.length; i++) {
|
|
409
|
-
const mark = AGENTS[i].defaultOn ? green("[x]") : dim("[ ]");
|
|
410
|
-
const num = sky(`${i + 1})`);
|
|
411
|
-
console.log(` ${mark} ${num} ${AGENTS[i].label}`);
|
|
412
|
-
}
|
|
413
|
-
const agentInput = await ask(rl, sky("\n Enter numbers separated by commas (default: 1,2): "));
|
|
414
|
-
let selectedAgents;
|
|
415
|
-
if (!agentInput.trim()) {
|
|
416
|
-
selectedAgents = AGENTS.filter((a) => a.defaultOn).map((a) => a.id);
|
|
417
|
-
}
|
|
418
|
-
else {
|
|
419
|
-
const indices = parseNumberList(agentInput, AGENTS.length);
|
|
420
|
-
selectedAgents = indices.length > 0
|
|
421
|
-
? indices.map((i) => AGENTS[i].id)
|
|
422
|
-
: AGENTS.filter((a) => a.defaultOn).map((a) => a.id);
|
|
423
|
-
}
|
|
424
|
-
// ── Step 5: Integrations ─────────────────────────────
|
|
425
|
-
console.clear();
|
|
426
|
-
console.log(WISPY_ASCII);
|
|
427
|
-
console.log(skyBold(` ${chalk.blue("▸")} Connect Apps (Optional)\n`));
|
|
428
|
-
console.log(dim(" Connect Wispy to services you use. Skip for now if unsure."));
|
|
429
|
-
console.log(dim(" You can always enable these later: wispy integrations enable <name>\n"));
|
|
430
|
-
// Display in two columns
|
|
431
|
-
const half = Math.ceil(INTEGRATIONS.length / 2);
|
|
432
|
-
for (let i = 0; i < half; i++) {
|
|
433
|
-
const left = `${dim("[ ]")} ${sky(`${i + 1})`)} ${INTEGRATIONS[i].label}`;
|
|
434
|
-
const rightIdx = i + half;
|
|
435
|
-
const right = rightIdx < INTEGRATIONS.length
|
|
436
|
-
? `${dim("[ ]")} ${sky(`${rightIdx + 1})`)} ${INTEGRATIONS[rightIdx].label}`
|
|
437
|
-
: "";
|
|
438
|
-
console.log(` ${left.padEnd(38)}${right}`);
|
|
439
|
-
}
|
|
440
|
-
const intInput = await ask(rl, sky("\n Enter numbers to enable (or press Enter to skip): "));
|
|
441
|
-
let selectedIntegrations;
|
|
442
|
-
if (!intInput.trim()) {
|
|
443
|
-
selectedIntegrations = [];
|
|
444
|
-
}
|
|
445
|
-
else {
|
|
446
|
-
const indices = parseNumberList(intInput, INTEGRATIONS.length);
|
|
447
|
-
selectedIntegrations = indices.map((i) => INTEGRATIONS[i].id);
|
|
448
|
-
}
|
|
449
|
-
// ── Step 6: MCP Servers ─────────────────────────────
|
|
450
|
-
console.clear();
|
|
451
|
-
console.log(WISPY_ASCII);
|
|
452
|
-
console.log(skyBold(` ${chalk.green("▸")} Extra Tools (MCP Servers)\n`));
|
|
453
|
-
console.log(dim(" These give Wispy extra abilities like reading files or browsing the web."));
|
|
454
|
-
console.log(dim(" The defaults (File System + Web Fetch) are recommended. Just press Enter.\n"));
|
|
455
|
-
const MCP_SERVERS = [
|
|
456
|
-
{
|
|
457
|
-
id: "filesystem",
|
|
458
|
-
label: "File System — Read/write files anywhere (Recommended)",
|
|
459
|
-
command: "npx",
|
|
460
|
-
args: ["-y", "@anthropic/mcp-server-filesystem", "--allow-write", "/"],
|
|
461
|
-
default: true,
|
|
462
|
-
},
|
|
463
|
-
{
|
|
464
|
-
id: "fetch",
|
|
465
|
-
label: "Web Fetch — Make HTTP requests with headers & auth",
|
|
466
|
-
command: "npx",
|
|
467
|
-
args: ["-y", "@anthropic/mcp-server-fetch"],
|
|
468
|
-
default: true,
|
|
469
|
-
},
|
|
470
|
-
{
|
|
471
|
-
id: "memory",
|
|
472
|
-
label: "Memory — Persistent key-value storage",
|
|
473
|
-
command: "npx",
|
|
474
|
-
args: ["-y", "@anthropic/mcp-server-memory"],
|
|
475
|
-
default: false,
|
|
476
|
-
},
|
|
477
|
-
{
|
|
478
|
-
id: "brave",
|
|
479
|
-
label: "Brave Search— Web search via Brave API",
|
|
480
|
-
command: "npx",
|
|
481
|
-
args: ["-y", "@anthropic/mcp-server-brave-search"],
|
|
482
|
-
default: false,
|
|
483
|
-
envRequired: "BRAVE_API_KEY",
|
|
484
|
-
},
|
|
485
|
-
{
|
|
486
|
-
id: "github",
|
|
487
|
-
label: "GitHub — Access repos, issues, PRs",
|
|
488
|
-
command: "npx",
|
|
489
|
-
args: ["-y", "@modelcontextprotocol/server-github"],
|
|
490
|
-
default: false,
|
|
491
|
-
envRequired: "GITHUB_TOKEN",
|
|
492
|
-
},
|
|
493
|
-
{
|
|
494
|
-
id: "postgres",
|
|
495
|
-
label: "PostgreSQL — Query databases directly",
|
|
496
|
-
command: "npx",
|
|
497
|
-
args: ["-y", "@modelcontextprotocol/server-postgres"],
|
|
498
|
-
default: false,
|
|
499
|
-
envRequired: "DATABASE_URL",
|
|
500
|
-
},
|
|
501
|
-
];
|
|
502
|
-
for (let i = 0; i < MCP_SERVERS.length; i++) {
|
|
503
|
-
const mark = MCP_SERVERS[i].default ? green("[x]") : dim("[ ]");
|
|
504
|
-
const num = sky(`${i + 1})`);
|
|
505
|
-
const envNote = MCP_SERVERS[i].envRequired ? dim(` (needs ${MCP_SERVERS[i].envRequired})`) : "";
|
|
506
|
-
console.log(` ${mark} ${num} ${MCP_SERVERS[i].label}${envNote}`);
|
|
586
|
+
// ════════════════════════════════════════════════════════
|
|
587
|
+
// STEP 4: Integrations (by category)
|
|
588
|
+
// ════════════════════════════════════════════════════════
|
|
589
|
+
stepHeader(4, TOTAL_STEPS, "App Integrations", chalk.cyan("\u25B8"));
|
|
590
|
+
console.log(dim(" Connect Wispy to apps and services. Pick categories to configure."));
|
|
591
|
+
console.log(dim(" You can enable individual integrations later: wispy setup <name>\n"));
|
|
592
|
+
for (let i = 0; i < INTEGRATION_CATEGORIES.length; i++) {
|
|
593
|
+
const cat = INTEGRATION_CATEGORIES[i];
|
|
594
|
+
const items = cat.items.map((it) => it.label).join(", ");
|
|
595
|
+
console.log(` ${sky(`${i + 1})`)} ${bold(cat.name)} ${dim(`(${cat.items.length})`)} ${cat.icon}`);
|
|
596
|
+
console.log(dim(` ${items}`));
|
|
507
597
|
}
|
|
508
598
|
console.log("");
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
599
|
+
const catInput = await ask(rl, sky(" Enter category numbers (e.g. 1,2) or Enter to skip: "));
|
|
600
|
+
if (catInput.trim()) {
|
|
601
|
+
const catPicks = parseNumberList(catInput, INTEGRATION_CATEGORIES.length);
|
|
602
|
+
for (const catIdx of catPicks) {
|
|
603
|
+
const cat = INTEGRATION_CATEGORIES[catIdx];
|
|
604
|
+
console.log("");
|
|
605
|
+
console.log(skyBold(` -- ${cat.icon} ${cat.name} --\n`));
|
|
606
|
+
if (cat.note) {
|
|
607
|
+
console.log(dim(` Note: ${cat.note}\n`));
|
|
608
|
+
}
|
|
609
|
+
// If Google category, prompt for OAuth credentials once
|
|
610
|
+
if (cat.name === "Google Workspace") {
|
|
611
|
+
console.log(dim(" Google integrations use OAuth2. You need:"));
|
|
612
|
+
console.log(` ${dim("1.")} Go to ${sky("https://console.cloud.google.com/apis/credentials")}`);
|
|
613
|
+
console.log(` ${dim("2.")} Create OAuth 2.0 Client ID`);
|
|
614
|
+
console.log(` ${dim("3.")} Enable the APIs you want (Calendar, Gmail, Drive, etc.)\n`);
|
|
615
|
+
const googleClientId = envOrEmpty("GOOGLE_CLIENT_ID");
|
|
616
|
+
const googleClientSecret = envOrEmpty("GOOGLE_CLIENT_SECRET");
|
|
617
|
+
if (googleClientId) {
|
|
618
|
+
console.log(green(` Google OAuth: configured`));
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
const clientId = (await ask(rl, sky(" Google Client ID (or Enter to skip): "))).trim();
|
|
622
|
+
if (clientId) {
|
|
623
|
+
env.GOOGLE_CLIENT_ID = clientId;
|
|
624
|
+
const secret = (await ask(rl, sky(" Google Client Secret: "))).trim();
|
|
625
|
+
if (secret)
|
|
626
|
+
env.GOOGLE_CLIENT_SECRET = secret;
|
|
627
|
+
const redirect = (await ask(rl, sky(" Redirect URI (default: http://localhost:3000/auth/callback): "))).trim();
|
|
628
|
+
env.GOOGLE_REDIRECT_URI = redirect || "http://localhost:3000/auth/callback";
|
|
629
|
+
console.log(green(" Google OAuth configured."));
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
console.log("");
|
|
633
|
+
}
|
|
634
|
+
// Show items and let user pick
|
|
635
|
+
for (let i = 0; i < cat.items.length; i++) {
|
|
636
|
+
const item = cat.items[i];
|
|
637
|
+
const hasEnv = item.envVars?.some((v) => !!envOrEmpty(v)) || false;
|
|
638
|
+
const status = hasEnv ? green(" [configured]") : "";
|
|
639
|
+
console.log(` ${sky(`${i + 1})`)} ${item.label}${status}`);
|
|
640
|
+
}
|
|
641
|
+
console.log("");
|
|
642
|
+
const itemInput = await ask(rl, sky(` Enable which? Numbers (e.g. 1,2,3) or Enter for all: `));
|
|
643
|
+
let picked;
|
|
644
|
+
if (!itemInput.trim()) {
|
|
645
|
+
picked = cat.items.map((_, i) => i);
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
picked = parseNumberList(itemInput, cat.items.length);
|
|
649
|
+
}
|
|
650
|
+
for (const itemIdx of picked) {
|
|
651
|
+
const item = cat.items[itemIdx];
|
|
652
|
+
selectedIntegrations.push(item.id);
|
|
653
|
+
// Prompt for env vars if needed
|
|
654
|
+
if (item.envVars && item.envVars.length > 0) {
|
|
655
|
+
const allSet = item.envVars.every((v) => !!envOrEmpty(v));
|
|
656
|
+
if (!allSet) {
|
|
657
|
+
if (item.link)
|
|
658
|
+
console.log(dim(`\n ${item.label}: ${sky(item.link)}`));
|
|
659
|
+
for (const varName of item.envVars) {
|
|
660
|
+
const existing = envOrEmpty(varName);
|
|
661
|
+
if (existing) {
|
|
662
|
+
env[varName] = existing;
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
const val = (await ask(rl, sky(` ${varName}: `))).trim();
|
|
666
|
+
if (val)
|
|
667
|
+
env[varName] = val;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (picked.length > 0) {
|
|
674
|
+
const names = picked.map((i) => cat.items[i].label).join(", ");
|
|
675
|
+
console.log(green(`\n Enabled: ${names}`));
|
|
676
|
+
}
|
|
677
|
+
}
|
|
521
678
|
}
|
|
522
|
-
//
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
console.log(dim(" Give Wispy a wallet
|
|
527
|
-
console.log(dim(" Uses USDC on SKALE
|
|
528
|
-
console.log(
|
|
529
|
-
console.log(` ${
|
|
530
|
-
console.log(` ${
|
|
531
|
-
console.log(
|
|
532
|
-
|
|
533
|
-
console.log(dim(" Skip this if you just want to use Wispy for chat and coding.\n"));
|
|
534
|
-
const existingAgentKey = process.env.AGENT_PRIVATE_KEY;
|
|
535
|
-
let agentPrivateKey = "";
|
|
536
|
-
let commerceEnabled = false;
|
|
679
|
+
// ════════════════════════════════════════════════════════
|
|
680
|
+
// STEP 5: Wallet & Commerce
|
|
681
|
+
// ════════════════════════════════════════════════════════
|
|
682
|
+
stepHeader(5, TOTAL_STEPS, "Blockchain Wallet", accent("\u25B8"));
|
|
683
|
+
console.log(dim(" Give Wispy a wallet for autonomous payments (x402 protocol)."));
|
|
684
|
+
console.log(dim(" Uses USDC on SKALE -- gasless, no transaction fees.\n"));
|
|
685
|
+
console.log(` ${accent("\u2022")} Agent pays for APIs on its own (micro-payments)`);
|
|
686
|
+
console.log(` ${accent("\u2022")} DeFi trading with built-in risk controls`);
|
|
687
|
+
console.log(` ${accent("\u2022")} Encrypted conditional payments (BITE v2)\n`);
|
|
688
|
+
console.log(dim(" Skip this if you only want chat and coding.\n"));
|
|
689
|
+
const existingAgentKey = envOrEmpty("AGENT_PRIVATE_KEY");
|
|
537
690
|
if (existingAgentKey) {
|
|
538
|
-
|
|
539
|
-
console.log(green(` ✓ Found wallet key: ${masked}\n`));
|
|
691
|
+
console.log(green(` Found wallet key: ${masked(existingAgentKey, 6)}`));
|
|
540
692
|
agentPrivateKey = existingAgentKey;
|
|
541
693
|
commerceEnabled = true;
|
|
542
694
|
}
|
|
543
695
|
else {
|
|
544
|
-
const
|
|
545
|
-
if (
|
|
696
|
+
const walletChoice = await ask(rl, sky(" Enable x402 commerce? [y/N]: "));
|
|
697
|
+
if (walletChoice.trim().toLowerCase().startsWith("y")) {
|
|
546
698
|
console.log("");
|
|
547
|
-
console.log(` ${sky("1)")} ${bold("Generate
|
|
548
|
-
console.log(` ${sky("2)")} ${bold("Use
|
|
699
|
+
console.log(` ${sky("1)")} ${bold("Generate new wallet")} ${dim("(recommended)")}`);
|
|
700
|
+
console.log(` ${sky("2)")} ${bold("Use existing wallet")} ${dim("(paste private key)")}`);
|
|
549
701
|
console.log("");
|
|
550
702
|
const keyChoice = await ask(rl, sky(" Select [1/2] (default: 1): "));
|
|
551
703
|
if (keyChoice.trim() === "2") {
|
|
552
|
-
|
|
553
|
-
agentPrivateKey
|
|
554
|
-
if (agentPrivateKey && !agentPrivateKey.startsWith("0x")) {
|
|
704
|
+
agentPrivateKey = (await ask(rl, sky(" Private key (0x...): "))).trim();
|
|
705
|
+
if (agentPrivateKey && !agentPrivateKey.startsWith("0x"))
|
|
555
706
|
agentPrivateKey = "0x" + agentPrivateKey;
|
|
556
|
-
}
|
|
557
707
|
}
|
|
558
708
|
else {
|
|
559
|
-
// Generate a new wallet with recovery phrase
|
|
560
709
|
try {
|
|
561
710
|
const { ethers } = await import("ethers");
|
|
562
711
|
const wallet = ethers.Wallet.createRandom();
|
|
563
712
|
agentPrivateKey = wallet.privateKey;
|
|
564
713
|
const mnemonic = wallet.mnemonic?.phrase || "";
|
|
565
714
|
console.log("");
|
|
566
|
-
console.log(green(`
|
|
715
|
+
console.log(green(` Generated new wallet!\n`));
|
|
567
716
|
console.log(` ${dim("Address:")} ${sky(wallet.address)}`);
|
|
568
717
|
if (mnemonic) {
|
|
569
718
|
console.log("");
|
|
570
|
-
console.log(
|
|
571
|
-
console.log(
|
|
572
|
-
console.log(
|
|
719
|
+
console.log(yellow(" +-------------------------------------------------+"));
|
|
720
|
+
console.log(yellow(" | RECOVERY PHRASE -- Write this down on paper! |"));
|
|
721
|
+
console.log(yellow(" +-------------------------------------------------+"));
|
|
573
722
|
console.log("");
|
|
574
723
|
const words = mnemonic.split(" ");
|
|
575
724
|
for (let i = 0; i < words.length; i += 3) {
|
|
576
|
-
const row = words
|
|
725
|
+
const row = words
|
|
726
|
+
.slice(i, i + 3)
|
|
727
|
+
.map((w, j) => {
|
|
577
728
|
const num = String(i + j + 1).padStart(2, " ");
|
|
578
729
|
return `${dim(num + ".")} ${bold(w.padEnd(10))}`;
|
|
579
|
-
})
|
|
730
|
+
})
|
|
731
|
+
.join(" ");
|
|
580
732
|
console.log(` ${row}`);
|
|
581
733
|
}
|
|
582
734
|
console.log("");
|
|
583
|
-
console.log(
|
|
584
|
-
console.log(dim("
|
|
585
|
-
|
|
586
|
-
await ask(rl, dim(" Press Enter once you have saved your recovery phrase..."));
|
|
735
|
+
console.log(yellow(" This is the ONLY time you will see this phrase."));
|
|
736
|
+
console.log(dim(" Never share it. Never type it online.\n"));
|
|
737
|
+
await ask(rl, dim(" Press Enter once saved..."));
|
|
587
738
|
}
|
|
588
739
|
}
|
|
589
740
|
catch {
|
|
590
|
-
console.log(
|
|
591
|
-
console.log(dim(" Add AGENT_PRIVATE_KEY=0x... to .env manually.\n"));
|
|
741
|
+
console.log(yellow(" Could not generate wallet. Add AGENT_PRIVATE_KEY to .env later."));
|
|
592
742
|
}
|
|
593
743
|
}
|
|
594
744
|
if (agentPrivateKey) {
|
|
595
745
|
commerceEnabled = true;
|
|
746
|
+
env.AGENT_PRIVATE_KEY = agentPrivateKey;
|
|
747
|
+
}
|
|
748
|
+
// Optional CDP wallet
|
|
749
|
+
if (commerceEnabled) {
|
|
750
|
+
console.log("");
|
|
751
|
+
console.log(dim(" Optional: Coinbase Developer Platform (CDP) custodial wallet"));
|
|
752
|
+
const cdpChoice = await ask(rl, sky(" Configure CDP? [y/N]: "));
|
|
753
|
+
if (cdpChoice.trim().toLowerCase().startsWith("y")) {
|
|
754
|
+
console.log(dim(`\n Get credentials: ${sky("https://portal.cdp.coinbase.com/")}\n`));
|
|
755
|
+
cdpKeyName = (await ask(rl, sky(" CDP API Key Name: "))).trim();
|
|
756
|
+
cdpPrivateKey = (await ask(rl, sky(" CDP Private Key: "))).trim();
|
|
757
|
+
if (cdpKeyName)
|
|
758
|
+
env.CDP_API_KEY_NAME = cdpKeyName;
|
|
759
|
+
if (cdpPrivateKey)
|
|
760
|
+
env.CDP_PRIVATE_KEY = cdpPrivateKey;
|
|
761
|
+
}
|
|
596
762
|
}
|
|
597
|
-
}
|
|
598
|
-
else {
|
|
599
|
-
console.log(dim("\n Skipped. Enable later by adding AGENT_PRIVATE_KEY to .env\n"));
|
|
600
763
|
}
|
|
601
764
|
}
|
|
602
|
-
//
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
765
|
+
// ════════════════════════════════════════════════════════
|
|
766
|
+
// STEP 6: Security & Mode
|
|
767
|
+
// ════════════════════════════════════════════════════════
|
|
768
|
+
stepHeader(6, TOTAL_STEPS, "Security & Permissions", chalk.yellow("\u25B8"));
|
|
769
|
+
console.log(dim(" Choose how much freedom Wispy gets:\n"));
|
|
770
|
+
console.log(` ${sky("1)")} ${bold("Autonomous Mode")} ${dim("(recommended)")}`);
|
|
771
|
+
console.log(dim(" Wispy writes files, runs code, and manages projects on its own."));
|
|
772
|
+
console.log(dim(" Best for: personal computer, development, getting things done.\n"));
|
|
773
|
+
console.log(` ${sky("2)")} ${bold("Standard Mode")} ${dim("(ask before acting)")}`);
|
|
774
|
+
console.log(dim(" Wispy asks permission before writing files or running code."));
|
|
775
|
+
console.log(dim(" Best for: shared computers, production servers.\n"));
|
|
776
|
+
const modeChoice = await ask(rl, sky(" Select [1/2] (default: 1): "));
|
|
777
|
+
autonomousMode = modeChoice.trim() !== "2";
|
|
778
|
+
if (autonomousMode) {
|
|
606
779
|
console.log("");
|
|
607
|
-
console.log(dim("
|
|
608
|
-
console.log(dim("
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
cdpKeyName = (await ask(rl, sky(" CDP API Key Name: "))).trim();
|
|
613
|
-
cdpPrivateKey = (await ask(rl, sky(" CDP Private Key: "))).trim();
|
|
614
|
-
}
|
|
780
|
+
console.log(dim(" File system access:\n"));
|
|
781
|
+
console.log(` ${sky("1)")} ${bold("Full access")} ${dim("-- read/write anywhere on your drive (recommended)")}`);
|
|
782
|
+
console.log(` ${sky("2)")} ${bold("Workspace only")} ${dim("-- restricted to Wispy's workspace directory")}\n`);
|
|
783
|
+
const fsChoice = await ask(rl, sky(" Select [1/2] (default: 1): "));
|
|
784
|
+
fullFilesystemAccess = fsChoice.trim() !== "2";
|
|
615
785
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
console.log(
|
|
625
|
-
console.log(
|
|
626
|
-
console.log(` ${sky("
|
|
627
|
-
console.log(` ${sky("
|
|
786
|
+
else {
|
|
787
|
+
fullFilesystemAccess = false;
|
|
788
|
+
}
|
|
789
|
+
// ════════════════════════════════════════════════════════
|
|
790
|
+
// STEP 7: Extras (Voice, MCP, Theme, Agents, Memory)
|
|
791
|
+
// ════════════════════════════════════════════════════════
|
|
792
|
+
stepHeader(7, TOTAL_STEPS, "Extras", chalk.hex("#FF6B6B")("\u25B8"));
|
|
793
|
+
// ── Voice/TTS ────────────────────────────────────────
|
|
794
|
+
console.log(skyBold(" Voice / Text-to-Speech\n"));
|
|
795
|
+
console.log(dim(" Let Wispy speak aloud in voice messages.\n"));
|
|
796
|
+
console.log(` ${sky("1)")} ${bold("Edge TTS")} ${dim("-- free, fast, no API key (default)")}`);
|
|
797
|
+
console.log(` ${sky("2)")} ${bold("ElevenLabs")} ${dim("-- premium quality, requires API key")}`);
|
|
798
|
+
console.log(` ${sky("3)")} ${bold("Qwen3-TTS")} ${dim("-- multilingual via HuggingFace (free)")}`);
|
|
799
|
+
console.log(` ${sky("4)")} ${dim("Skip voice")}`);
|
|
628
800
|
console.log("");
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
801
|
+
const voiceChoice = await ask(rl, sky(" Select [1/2/3/4] (default: 1): "));
|
|
802
|
+
if (voiceChoice.trim() === "2") {
|
|
803
|
+
voiceModel = "elevenlabs";
|
|
804
|
+
if (!elevenlabsKey) {
|
|
805
|
+
const existing = envOrEmpty("ELEVENLABS_API_KEY");
|
|
806
|
+
if (existing) {
|
|
807
|
+
elevenlabsKey = existing;
|
|
808
|
+
console.log(green(` ElevenLabs: ${masked(existing)}`));
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
console.log(dim(`\n Get key: ${sky("https://elevenlabs.io/app/settings/api-keys")}`));
|
|
812
|
+
elevenlabsKey = (await ask(rl, sky(" ElevenLabs key: "))).trim();
|
|
813
|
+
if (elevenlabsKey)
|
|
814
|
+
env.ELEVENLABS_API_KEY = elevenlabsKey;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
635
817
|
}
|
|
636
|
-
|
|
637
|
-
|
|
818
|
+
else if (voiceChoice.trim() === "3") {
|
|
819
|
+
voiceModel = "qwen3-tts";
|
|
820
|
+
// Check if gradio_client is installed
|
|
821
|
+
let qwen3Ready = false;
|
|
822
|
+
try {
|
|
823
|
+
const { execSync } = await import("child_process");
|
|
824
|
+
execSync('python -c "from gradio_client import Client"', { stdio: "pipe", timeout: 10000 });
|
|
825
|
+
qwen3Ready = true;
|
|
826
|
+
}
|
|
827
|
+
catch { /* not installed */ }
|
|
828
|
+
if (!qwen3Ready) {
|
|
829
|
+
console.log(dim("\n Qwen3-TTS needs the gradio_client Python package."));
|
|
830
|
+
const install = await ask(rl, sky(" Install now? [Y/n]: "));
|
|
831
|
+
if (!install.trim() || install.trim().toLowerCase().startsWith("y")) {
|
|
832
|
+
try {
|
|
833
|
+
const { execSync } = await import("child_process");
|
|
834
|
+
execSync("pip install gradio_client -q", { stdio: "inherit", timeout: 120000 });
|
|
835
|
+
console.log(green(" Installed."));
|
|
836
|
+
}
|
|
837
|
+
catch {
|
|
838
|
+
console.log(yellow(" Install failed. Run: pip install gradio_client"));
|
|
839
|
+
voiceModel = "edge-tts";
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
voiceModel = "edge-tts";
|
|
844
|
+
}
|
|
845
|
+
}
|
|
638
846
|
}
|
|
639
|
-
if (
|
|
640
|
-
|
|
847
|
+
else if (voiceChoice.trim() === "4") {
|
|
848
|
+
voiceModel = "";
|
|
641
849
|
}
|
|
642
850
|
else {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
851
|
+
voiceModel = "edge-tts";
|
|
852
|
+
// Check if Edge TTS is installed
|
|
853
|
+
let edgeReady = false;
|
|
854
|
+
try {
|
|
855
|
+
const { execSync } = await import("child_process");
|
|
856
|
+
execSync("python -m edge_tts --version", { stdio: "pipe", timeout: 5000 });
|
|
857
|
+
edgeReady = true;
|
|
858
|
+
}
|
|
859
|
+
catch { /* not installed */ }
|
|
860
|
+
if (!edgeReady) {
|
|
861
|
+
console.log(dim("\n Edge TTS needs the edge-tts Python package."));
|
|
862
|
+
const install = await ask(rl, sky(" Install now? [Y/n]: "));
|
|
863
|
+
if (!install.trim() || install.trim().toLowerCase().startsWith("y")) {
|
|
864
|
+
try {
|
|
865
|
+
const { execSync } = await import("child_process");
|
|
866
|
+
execSync("pip install edge-tts", { stdio: "inherit", timeout: 120000 });
|
|
867
|
+
console.log(green(" Installed."));
|
|
868
|
+
}
|
|
869
|
+
catch {
|
|
870
|
+
console.log(yellow(" Install failed. Run: pip install edge-tts"));
|
|
871
|
+
}
|
|
663
872
|
}
|
|
664
873
|
}
|
|
665
874
|
else {
|
|
666
|
-
console.log(
|
|
875
|
+
console.log(green(" Edge TTS is ready."));
|
|
667
876
|
}
|
|
668
877
|
}
|
|
669
|
-
//
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
878
|
+
// ── MCP Servers ──────────────────────────────────────
|
|
879
|
+
console.log("");
|
|
880
|
+
console.log(skyBold(" MCP Servers (Extra Tools)\n"));
|
|
881
|
+
console.log(dim(" MCP servers give Wispy additional capabilities."));
|
|
882
|
+
console.log(dim(" Defaults (File System + Web Fetch) are recommended.\n"));
|
|
883
|
+
for (let i = 0; i < MCP_SERVERS.length; i++) {
|
|
884
|
+
const m = MCP_SERVERS[i];
|
|
885
|
+
const mark = m.default ? green("[x]") : dim("[ ]");
|
|
886
|
+
const envNote = m.envRequired ? dim(` (needs ${m.envRequired})`) : "";
|
|
887
|
+
console.log(` ${mark} ${sky(`${i + 1})`)} ${m.label}${envNote}`);
|
|
888
|
+
}
|
|
889
|
+
console.log("");
|
|
890
|
+
const mcpInput = await ask(rl, sky(" Numbers to enable (default: 1,2): "));
|
|
891
|
+
let selectedMCPs;
|
|
892
|
+
if (!mcpInput.trim()) {
|
|
893
|
+
selectedMCPs = MCP_SERVERS.filter((m) => m.default);
|
|
677
894
|
}
|
|
678
|
-
|
|
679
|
-
|
|
895
|
+
else {
|
|
896
|
+
const indices = parseNumberList(mcpInput, MCP_SERVERS.length);
|
|
897
|
+
selectedMCPs = indices.length > 0 ? indices.map((i) => MCP_SERVERS[i]) : MCP_SERVERS.filter((m) => m.default);
|
|
680
898
|
}
|
|
899
|
+
// ── Theme ────────────────────────────────────────────
|
|
681
900
|
console.log("");
|
|
682
|
-
console.log(
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
901
|
+
console.log(skyBold(" Color Theme\n"));
|
|
902
|
+
for (let i = 0; i < THEMES.length; i++) {
|
|
903
|
+
console.log(` ${sky(`${i + 1})`)} ${THEMES[i].label}`);
|
|
904
|
+
}
|
|
905
|
+
const themeInput = await ask(rl, sky("\n Select [1-4] (default: 2): "));
|
|
906
|
+
const themeIdx = parseInt(themeInput.trim(), 10);
|
|
907
|
+
selectedTheme = themeIdx >= 1 && themeIdx <= 4 ? THEMES[themeIdx - 1].key : "day";
|
|
908
|
+
// ── Agents ───────────────────────────────────────────
|
|
689
909
|
console.log("");
|
|
690
|
-
|
|
691
|
-
|
|
910
|
+
console.log(skyBold(" Agent Roles\n"));
|
|
911
|
+
console.log(dim(" Wispy's specializations. Coder + Researcher are pre-selected.\n"));
|
|
912
|
+
for (let i = 0; i < AGENTS.length; i++) {
|
|
913
|
+
const mark = AGENTS[i].defaultOn ? green("[x]") : dim("[ ]");
|
|
914
|
+
console.log(` ${mark} ${sky(`${i + 1})`)} ${AGENTS[i].label}`);
|
|
692
915
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
console.log("");
|
|
699
|
-
console.log(dim(" Installing gradio_client..."));
|
|
700
|
-
try {
|
|
701
|
-
const { execSync } = await import("child_process");
|
|
702
|
-
execSync("pip install gradio_client -q", {
|
|
703
|
-
stdio: "inherit",
|
|
704
|
-
timeout: 120000,
|
|
705
|
-
});
|
|
706
|
-
qwen3Installed = true;
|
|
707
|
-
console.log("");
|
|
708
|
-
console.log(green(" ✓ Qwen3-TTS client installed successfully!"));
|
|
709
|
-
}
|
|
710
|
-
catch (installErr) {
|
|
711
|
-
console.log("");
|
|
712
|
-
console.log(yellowBright(" ⚠ Installation failed. You can install manually later:"));
|
|
713
|
-
console.log(sky(" pip install gradio_client"));
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
else {
|
|
717
|
-
console.log(dim("\n Skipped. Edge TTS will be used (still sounds great!).\n"));
|
|
718
|
-
}
|
|
916
|
+
const agentInput = await ask(rl, sky("\n Numbers (default: 1,2): "));
|
|
917
|
+
if (agentInput.trim()) {
|
|
918
|
+
const indices = parseNumberList(agentInput, AGENTS.length);
|
|
919
|
+
if (indices.length > 0)
|
|
920
|
+
selectedAgents = indices.map((i) => AGENTS[i].id);
|
|
719
921
|
}
|
|
720
|
-
|
|
922
|
+
// ── Memory Bank ──────────────────────────────────────
|
|
923
|
+
console.log("");
|
|
924
|
+
console.log(skyBold(" Memory Bank\n"));
|
|
925
|
+
console.log(dim(" Wispy can remember things across conversations using a structured memory"));
|
|
926
|
+
console.log(dim(" system with 4 types: episodic, semantic, procedural, and preference.\n"));
|
|
927
|
+
console.log(` ${sky("\u2022")} Memories decay over time if not accessed`);
|
|
928
|
+
console.log(` ${sky("\u2022")} Frequently used memories get reinforced`);
|
|
929
|
+
console.log(` ${sky("\u2022")} AI-powered reflection merges similar memories\n`);
|
|
930
|
+
console.log(green(" Memory Bank is enabled by default. No extra configuration needed."));
|
|
931
|
+
console.log("");
|
|
932
|
+
await ask(rl, dim(" Press Enter to continue..."));
|
|
721
933
|
rl.close();
|
|
722
|
-
//
|
|
723
|
-
//
|
|
934
|
+
// ════════════════════════════════════════════════════════
|
|
935
|
+
// SAVE CONFIGURATION
|
|
936
|
+
// ════════════════════════════════════════════════════════
|
|
724
937
|
fs.mkdirSync(runtimeDir, { recursive: true });
|
|
938
|
+
// Build config object
|
|
725
939
|
const config = {
|
|
726
940
|
agent: { name: "wispy", id: "main" },
|
|
941
|
+
engine,
|
|
727
942
|
gemini: {
|
|
728
943
|
models: {
|
|
729
944
|
pro: "gemini-2.5-pro-preview-05-06",
|
|
@@ -732,11 +947,39 @@ export async function runSetupWizard(opts) {
|
|
|
732
947
|
embedding: "text-embedding-004",
|
|
733
948
|
},
|
|
734
949
|
},
|
|
950
|
+
...(engine === "claude" || anthropicKey
|
|
951
|
+
? {
|
|
952
|
+
claude: {
|
|
953
|
+
...(anthropicKey ? { apiKey: anthropicKey } : {}),
|
|
954
|
+
models: { reasoning: "claude-sonnet-4-20250514", fast: "claude-haiku-4-5-20251001" },
|
|
955
|
+
},
|
|
956
|
+
}
|
|
957
|
+
: {}),
|
|
735
958
|
channels: {
|
|
736
959
|
web: { enabled: true, port: 4000 },
|
|
737
960
|
rest: { enabled: true, port: 4001 },
|
|
738
|
-
telegram: {
|
|
739
|
-
|
|
961
|
+
telegram: {
|
|
962
|
+
enabled: !!enabledChannels.telegram && !!(env.TELEGRAM_BOT_TOKEN || envOrEmpty("TELEGRAM_BOT_TOKEN")),
|
|
963
|
+
},
|
|
964
|
+
discord: {
|
|
965
|
+
enabled: !!enabledChannels.discord && !!(env.DISCORD_BOT_TOKEN || envOrEmpty("DISCORD_BOT_TOKEN")),
|
|
966
|
+
},
|
|
967
|
+
slack: {
|
|
968
|
+
enabled: !!enabledChannels.slack && !!(env.SLACK_BOT_TOKEN || envOrEmpty("SLACK_BOT_TOKEN")),
|
|
969
|
+
},
|
|
970
|
+
whatsapp: { enabled: !!enabledChannels.whatsapp },
|
|
971
|
+
matrix: {
|
|
972
|
+
enabled: !!enabledChannels.matrix && !!(env.MATRIX_ACCESS_TOKEN || envOrEmpty("MATRIX_ACCESS_TOKEN")),
|
|
973
|
+
},
|
|
974
|
+
signal: {
|
|
975
|
+
enabled: !!enabledChannels.signal && !!(env.SIGNAL_PHONE_NUMBER || envOrEmpty("SIGNAL_PHONE_NUMBER")),
|
|
976
|
+
},
|
|
977
|
+
email: {
|
|
978
|
+
enabled: !!enabledChannels.email && !!(env.AGENTMAIL_API_KEY || envOrEmpty("AGENTMAIL_API_KEY")),
|
|
979
|
+
},
|
|
980
|
+
phone: {
|
|
981
|
+
enabled: !!enabledChannels.phone && !!(env.TELNYX_API_KEY || envOrEmpty("TELNYX_API_KEY")),
|
|
982
|
+
},
|
|
740
983
|
},
|
|
741
984
|
providers: {
|
|
742
985
|
...(anthropicKey ? { anthropic: { apiKey: anthropicKey } } : {}),
|
|
@@ -749,15 +992,21 @@ export async function runSetupWizard(opts) {
|
|
|
749
992
|
browser: { enabled: true },
|
|
750
993
|
wallet: { enabled: commerceEnabled, chain: "skale-bite-sandbox" },
|
|
751
994
|
memory: { embeddingDimensions: 768, heartbeatIntervalMinutes: 30, hybridSearch: true },
|
|
752
|
-
security: {
|
|
995
|
+
security: {
|
|
996
|
+
requireApprovalForExternal: !autonomousMode,
|
|
997
|
+
allowedGroups: [],
|
|
998
|
+
autonomousMode,
|
|
999
|
+
fullFilesystemAccess,
|
|
1000
|
+
},
|
|
753
1001
|
thinking: { defaultLevel: "medium", costAware: true },
|
|
1002
|
+
voice: voiceModel ? { enabled: true, model: voiceModel, replyWithVoice: true } : { enabled: false },
|
|
754
1003
|
theme: selectedTheme,
|
|
755
1004
|
agents: selectedAgents,
|
|
756
1005
|
integrations: selectedIntegrations,
|
|
757
1006
|
};
|
|
758
1007
|
const configPath = path.resolve(runtimeDir, "config.yaml");
|
|
759
1008
|
writeYAML(configPath, config);
|
|
760
|
-
//
|
|
1009
|
+
// Save MCP config
|
|
761
1010
|
const mcpDir = path.resolve(runtimeDir, "mcp");
|
|
762
1011
|
fs.mkdirSync(mcpDir, { recursive: true });
|
|
763
1012
|
const mcpConfig = {
|
|
@@ -769,83 +1018,85 @@ export async function runSetupWizard(opts) {
|
|
|
769
1018
|
env: {},
|
|
770
1019
|
})),
|
|
771
1020
|
};
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
// Save credentials to .env
|
|
1021
|
+
fs.writeFileSync(path.resolve(mcpDir, "servers.json"), JSON.stringify(mcpConfig, null, 2));
|
|
1022
|
+
// Save .env
|
|
775
1023
|
const envPath = path.resolve(rootDir, ".env");
|
|
776
1024
|
let envContent = fs.existsSync(envPath) ? fs.readFileSync(envPath, "utf-8") : "";
|
|
777
|
-
//
|
|
778
|
-
|
|
779
|
-
if (!
|
|
780
|
-
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
if (!envContent.includes("TELEGRAM_BOT_TOKEN=")) {
|
|
786
|
-
envContent += `\nTELEGRAM_BOT_TOKEN=${telegramToken}`;
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
// Add additional provider keys
|
|
790
|
-
if (anthropicKey && !process.env.ANTHROPIC_API_KEY) {
|
|
791
|
-
if (!envContent.includes("ANTHROPIC_API_KEY=")) {
|
|
792
|
-
envContent += `\nANTHROPIC_API_KEY=${anthropicKey}`;
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
if (openaiKey && !process.env.OPENAI_API_KEY) {
|
|
796
|
-
if (!envContent.includes("OPENAI_API_KEY=")) {
|
|
797
|
-
envContent += `\nOPENAI_API_KEY=${openaiKey}`;
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
if (groqKey && !process.env.GROQ_API_KEY) {
|
|
801
|
-
if (!envContent.includes("GROQ_API_KEY=")) {
|
|
802
|
-
envContent += `\nGROQ_API_KEY=${groqKey}`;
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
if (openrouterKey && !process.env.OPENROUTER_API_KEY) {
|
|
806
|
-
if (!envContent.includes("OPENROUTER_API_KEY=")) {
|
|
807
|
-
envContent += `\nOPENROUTER_API_KEY=${openrouterKey}`;
|
|
1025
|
+
// Helper: add env var if not already present
|
|
1026
|
+
const addEnv = (key, value, section) => {
|
|
1027
|
+
if (!value)
|
|
1028
|
+
return;
|
|
1029
|
+
if (envContent.includes(`${key}=`))
|
|
1030
|
+
return;
|
|
1031
|
+
if (section && !envContent.includes(`# ${section}`)) {
|
|
1032
|
+
envContent += `\n\n# ${section}`;
|
|
808
1033
|
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
1034
|
+
envContent += `\n${key}=${value}`;
|
|
1035
|
+
};
|
|
1036
|
+
// Engine
|
|
1037
|
+
addEnv("ENGINE", engine, "AI Engine");
|
|
1038
|
+
// Primary keys
|
|
1039
|
+
if (apiKey && !envOrEmpty("GEMINI_API_KEY"))
|
|
1040
|
+
addEnv("GEMINI_API_KEY", apiKey);
|
|
1041
|
+
if (anthropicKey && !envOrEmpty("ANTHROPIC_API_KEY"))
|
|
1042
|
+
addEnv("ANTHROPIC_API_KEY", anthropicKey);
|
|
1043
|
+
// Additional providers
|
|
1044
|
+
if (openaiKey && !envOrEmpty("OPENAI_API_KEY"))
|
|
1045
|
+
addEnv("OPENAI_API_KEY", openaiKey, "Providers");
|
|
1046
|
+
if (groqKey && !envOrEmpty("GROQ_API_KEY"))
|
|
1047
|
+
addEnv("GROQ_API_KEY", groqKey);
|
|
1048
|
+
if (openrouterKey && !envOrEmpty("OPENROUTER_API_KEY"))
|
|
1049
|
+
addEnv("OPENROUTER_API_KEY", openrouterKey);
|
|
1050
|
+
if (kimiKey && !envOrEmpty("MOONSHOT_API_KEY"))
|
|
1051
|
+
addEnv("MOONSHOT_API_KEY", kimiKey);
|
|
1052
|
+
if (elevenlabsKey && !envOrEmpty("ELEVENLABS_API_KEY"))
|
|
1053
|
+
addEnv("ELEVENLABS_API_KEY", elevenlabsKey);
|
|
1054
|
+
// Channel tokens
|
|
1055
|
+
for (const [key, value] of Object.entries(env)) {
|
|
1056
|
+
if (key.startsWith("TELEGRAM_") || key.startsWith("DISCORD_") || key.startsWith("SLACK_") ||
|
|
1057
|
+
key.startsWith("MATRIX_") || key.startsWith("SIGNAL_") || key.startsWith("AGENTMAIL_") ||
|
|
1058
|
+
key.startsWith("TELNYX_")) {
|
|
1059
|
+
addEnv(key, value, "Channels");
|
|
813
1060
|
}
|
|
814
1061
|
}
|
|
815
|
-
//
|
|
816
|
-
|
|
817
|
-
if (
|
|
818
|
-
|
|
1062
|
+
// Integration keys
|
|
1063
|
+
for (const [key, value] of Object.entries(env)) {
|
|
1064
|
+
if (key.startsWith("GOOGLE_") || key.startsWith("GITHUB_") || key.startsWith("NOTION_") ||
|
|
1065
|
+
key.startsWith("LINEAR_") || key.startsWith("TWITTER_") || key.startsWith("LINKEDIN_") ||
|
|
1066
|
+
key.startsWith("INSTAGRAM_") || key.startsWith("REDDIT_") || key.startsWith("SPOTIFY_") ||
|
|
1067
|
+
key.startsWith("STRIPE_") || key.startsWith("OP_") || key.startsWith("HUE_") ||
|
|
1068
|
+
key.startsWith("HA_") || key.startsWith("SONOS_") || key.startsWith("TRELLO_") ||
|
|
1069
|
+
key.startsWith("ASANA_") || key.startsWith("CALENDLY_") || key.startsWith("IMAGE_GEN_")) {
|
|
1070
|
+
addEnv(key, value, "Integrations");
|
|
819
1071
|
}
|
|
820
1072
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
if (
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1073
|
+
// Commerce
|
|
1074
|
+
if (agentPrivateKey && !envOrEmpty("AGENT_PRIVATE_KEY"))
|
|
1075
|
+
addEnv("AGENT_PRIVATE_KEY", agentPrivateKey, "x402 Commerce");
|
|
1076
|
+
if (cdpKeyName)
|
|
1077
|
+
addEnv("CDP_API_KEY_NAME", cdpKeyName);
|
|
1078
|
+
if (cdpPrivateKey)
|
|
1079
|
+
addEnv("CDP_PRIVATE_KEY", cdpPrivateKey);
|
|
828
1080
|
if (envContent.trim()) {
|
|
829
1081
|
fs.writeFileSync(envPath, envContent.trim() + "\n");
|
|
830
1082
|
}
|
|
831
|
-
//
|
|
1083
|
+
// ════════════════════════════════════════════════════════
|
|
1084
|
+
// STEP 8: Summary
|
|
1085
|
+
// ════════════════════════════════════════════════════════
|
|
832
1086
|
console.clear();
|
|
833
1087
|
console.log(WISPY_ASCII);
|
|
834
|
-
console.log(skyBold(` ${
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
? selectedMCPs.map((m) => capitalize(m.id)).join(", ")
|
|
845
|
-
: "None";
|
|
846
|
-
const aiDisplay = useVertexAI ? "Vertex AI (high quota)" : apiKey ? "Gemini API" : "Not configured";
|
|
1088
|
+
console.log(skyBold(` ${green("\u2713")} Setup complete!\n`));
|
|
1089
|
+
// Engine
|
|
1090
|
+
const engineDisplay = engine === "claude"
|
|
1091
|
+
? `Claude ${dim("(Anthropic)")}`
|
|
1092
|
+
: useVertexAI
|
|
1093
|
+
? `Gemini ${dim("(Vertex AI)")}`
|
|
1094
|
+
: apiKey
|
|
1095
|
+
? `Gemini ${dim("(API key)")}`
|
|
1096
|
+
: yellow("Not configured");
|
|
1097
|
+
// Providers
|
|
847
1098
|
const extraProviders = [];
|
|
848
|
-
if (anthropicKey)
|
|
1099
|
+
if (engine !== "claude" && anthropicKey)
|
|
849
1100
|
extraProviders.push("Anthropic");
|
|
850
1101
|
if (openaiKey)
|
|
851
1102
|
extraProviders.push("OpenAI");
|
|
@@ -857,50 +1108,64 @@ export async function runSetupWizard(opts) {
|
|
|
857
1108
|
extraProviders.push("Kimi");
|
|
858
1109
|
if (ollamaUrl)
|
|
859
1110
|
extraProviders.push("Ollama");
|
|
860
|
-
|
|
861
|
-
|
|
1111
|
+
if (elevenlabsKey)
|
|
1112
|
+
extraProviders.push("ElevenLabs");
|
|
1113
|
+
// Channels
|
|
1114
|
+
const activeChannels = Object.entries(enabledChannels)
|
|
1115
|
+
.filter(([, v]) => v)
|
|
1116
|
+
.map(([k]) => capitalize(k));
|
|
1117
|
+
// Display
|
|
1118
|
+
console.log(` ${dim("Engine:")} ${engineDisplay}`);
|
|
1119
|
+
console.log(` ${dim("Providers:")} ${extraProviders.length > 0 ? extraProviders.join(", ") : dim("None")}`);
|
|
1120
|
+
console.log(` ${dim("Channels:")} ${activeChannels.length > 0 ? activeChannels.join(", ") : dim("CLI only")}`);
|
|
1121
|
+
console.log(` ${dim("Integrations:")} ${selectedIntegrations.length > 0 ? `${selectedIntegrations.length} enabled` : dim("None")}`);
|
|
1122
|
+
if (selectedIntegrations.length > 0) {
|
|
1123
|
+
// Show first 6 then "and X more"
|
|
1124
|
+
const show = selectedIntegrations.slice(0, 6);
|
|
1125
|
+
const rest = selectedIntegrations.length - show.length;
|
|
1126
|
+
const names = show.map((id) => {
|
|
1127
|
+
for (const cat of INTEGRATION_CATEGORIES) {
|
|
1128
|
+
const found = cat.items.find((i) => i.id === id);
|
|
1129
|
+
if (found)
|
|
1130
|
+
return found.label;
|
|
1131
|
+
}
|
|
1132
|
+
return id;
|
|
1133
|
+
});
|
|
1134
|
+
console.log(` ${dim(" ")}${dim(names.join(", "))}${rest > 0 ? dim(` +${rest} more`) : ""}`);
|
|
1135
|
+
}
|
|
862
1136
|
const modeDisplay = autonomousMode
|
|
863
|
-
? chalk.yellow("
|
|
864
|
-
: green("
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
: dim("Not configured");
|
|
873
|
-
const fsDisplay = fullFilesystemAccess
|
|
874
|
-
? chalk.yellow("⚡ Full Access") + dim(" (entire drive)")
|
|
875
|
-
: dim("Workspace only");
|
|
876
|
-
console.log(` ${dim("AI Provider:")} ${aiDisplay}`);
|
|
877
|
-
console.log(` ${dim("Providers:")} ${providersDisplay}`);
|
|
878
|
-
console.log(` ${dim("Mode:")} ${modeDisplay}`);
|
|
879
|
-
console.log(` ${dim("File System:")} ${fsDisplay}`);
|
|
880
|
-
console.log(` ${dim("Theme:")} ${themeDisplay}`);
|
|
881
|
-
console.log(` ${dim("Agents:")} ${agentsDisplay}`);
|
|
882
|
-
console.log(` ${dim("Telegram:")} ${telegramDisplay}`);
|
|
883
|
-
console.log(` ${dim("x402 Commerce:")} ${commerceDisplay}`);
|
|
884
|
-
console.log(` ${dim("Voice:")} ${voiceDisplay}`);
|
|
885
|
-
console.log(` ${dim("MCP Servers:")} ${mcpDisplay}`);
|
|
886
|
-
console.log(` ${dim("Integrations:")} ${intDisplay}`);
|
|
1137
|
+
? chalk.yellow("\u26A1 Autonomous") + (fullFilesystemAccess ? dim(" (full access)") : dim(" (workspace only)"))
|
|
1138
|
+
: green("Shield Standard") + dim(" (ask before acting)");
|
|
1139
|
+
console.log(` ${dim("Mode:")} ${modeDisplay}`);
|
|
1140
|
+
console.log(` ${dim("Wallet:")} ${commerceEnabled ? green("\u2713 Enabled") + dim(` (${cdpKeyName ? "CDP" : "raw key"})`) : dim("Not configured")}`);
|
|
1141
|
+
console.log(` ${dim("Voice:")} ${voiceModel ? green(`\u2713 ${voiceModel}`) : dim("Disabled")}`);
|
|
1142
|
+
console.log(` ${dim("Memory Bank:")} ${green("\u2713 Enabled")} ${dim("(episodic, semantic, procedural, preference)")}`);
|
|
1143
|
+
console.log(` ${dim("MCP Servers:")} ${selectedMCPs.map((m) => capitalize(m.id)).join(", ") || dim("None")}`);
|
|
1144
|
+
console.log(` ${dim("Theme:")} ${capitalize(selectedTheme)} ${THEME_ICON[selectedTheme] || ""}`);
|
|
1145
|
+
console.log(` ${dim("Agents:")} ${selectedAgents.map(capitalize).join(", ")}`);
|
|
887
1146
|
console.log("");
|
|
888
|
-
if (!apiKey && !useVertexAI) {
|
|
889
|
-
console.log(
|
|
890
|
-
console.log(dim(
|
|
891
|
-
console.log(
|
|
1147
|
+
if (!apiKey && !useVertexAI && engine === "gemini") {
|
|
1148
|
+
console.log(yellow(" No Gemini API key configured!"));
|
|
1149
|
+
console.log(dim(` Add GEMINI_API_KEY to .env. Free: ${sky("https://aistudio.google.com/apikey")}`));
|
|
1150
|
+
console.log("");
|
|
1151
|
+
}
|
|
1152
|
+
if (engine === "claude" && !anthropicKey) {
|
|
1153
|
+
console.log(yellow(" No Anthropic API key configured!"));
|
|
1154
|
+
console.log(dim(` Add ANTHROPIC_API_KEY to .env: ${sky("https://console.anthropic.com/keys")}`));
|
|
892
1155
|
console.log("");
|
|
893
1156
|
}
|
|
894
1157
|
console.log(skyBold(" What to do next:\n"));
|
|
895
|
-
console.log(` ${sky("wispy chat")}
|
|
896
|
-
console.log(` ${sky("wispy gateway")} Start
|
|
1158
|
+
console.log(` ${sky("wispy chat")} Chat with Wispy in the terminal`);
|
|
1159
|
+
console.log(` ${sky("wispy gateway")} Start all channels (Telegram, Discord, etc.)`);
|
|
897
1160
|
console.log(` ${sky("wispy doctor")} Check if everything is working`);
|
|
898
|
-
console.log(` ${sky("wispy
|
|
1161
|
+
console.log(` ${sky("wispy setup")} Re-run this wizard anytime`);
|
|
1162
|
+
console.log(` ${sky("wispy setup github")} Configure a specific integration`);
|
|
899
1163
|
console.log("");
|
|
900
|
-
console.log(dim(
|
|
1164
|
+
console.log(dim(` Config saved to: ${sky(path.resolve(runtimeDir, "config.yaml"))}`));
|
|
1165
|
+
console.log(dim(` Env saved to: ${sky(path.resolve(rootDir, ".env"))}`));
|
|
901
1166
|
console.log("");
|
|
902
|
-
console.log(dim(" Docs:
|
|
903
|
-
console.log(dim(" Help:
|
|
1167
|
+
console.log(dim(" Docs: https://docs.wispy.cc"));
|
|
1168
|
+
console.log(dim(" Help: https://github.com/hausorlabs/wispy/issues"));
|
|
904
1169
|
console.log("");
|
|
905
1170
|
}
|
|
906
1171
|
catch (err) {
|