hyperclaw 4.0.0 → 4.0.1

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.
@@ -0,0 +1,3026 @@
1
+ const require_chunk = require('./chunk-jS-bbMI5.js');
2
+ const require_paths = require('./paths-AIyBxIzm.js');
3
+ const require_paths$1 = require('./paths-DPovhojT.js');
4
+ const require_env_resolve = require('./env-resolve-BzDlV2CS.js');
5
+ const require_server = require('./server-CCI1hv45.js');
6
+ const require_theme = require('./theme-LUTKWUWd.js');
7
+ const chalk = require_chunk.__toESM(require("chalk"));
8
+ const inquirer = require_chunk.__toESM(require("inquirer"));
9
+ const ora = require_chunk.__toESM(require("ora"));
10
+ const boxen = require_chunk.__toESM(require("boxen"));
11
+ const fs_extra = require_chunk.__toESM(require("fs-extra"));
12
+ const path = require_chunk.__toESM(require("path"));
13
+ const os = require_chunk.__toESM(require("os"));
14
+ const crypto = require_chunk.__toESM(require("crypto"));
15
+ const child_process = require_chunk.__toESM(require("child_process"));
16
+ const util = require_chunk.__toESM(require("util"));
17
+ const ws = require_chunk.__toESM(require("ws"));
18
+ const https = require_chunk.__toESM(require("https"));
19
+ const gradient_string = require_chunk.__toESM(require("gradient-string"));
20
+ const figlet = require_chunk.__toESM(require("figlet"));
21
+
22
+ //#region src/cli/config.ts
23
+ require_paths$1.init_paths();
24
+ require_env_resolve.init_env_resolve();
25
+ const getHC_DIR = () => require_paths.getHyperClawDir();
26
+ const getCFG_FILE = () => require_paths.getConfigPath();
27
+ var ConfigStore = class {
28
+ async load() {
29
+ try {
30
+ const cfg = await fs_extra.default.readJson(getCFG_FILE());
31
+ if (cfg.channelConfigs) for (const [chId, ch] of Object.entries(cfg.channelConfigs)) {
32
+ const tok = require_env_resolve.resolveChannelToken(chId, ch?.token || ch?.botToken);
33
+ if (tok && !ch?.token) ch.token = tok;
34
+ if (tok && !ch?.botToken) ch.botToken = tok;
35
+ }
36
+ return cfg;
37
+ } catch {
38
+ return {};
39
+ }
40
+ }
41
+ async save(cfg) {
42
+ await fs_extra.default.ensureDir(getHC_DIR());
43
+ await fs_extra.default.writeJson(getCFG_FILE(), cfg, { spaces: 2 });
44
+ await fs_extra.default.chmod(getCFG_FILE(), 384);
45
+ }
46
+ async patch(patch) {
47
+ const current = await this.load();
48
+ await this.save(deepMerge(current, patch));
49
+ }
50
+ async setProviderKey(providerId, apiKey) {
51
+ const cfg = await this.load();
52
+ await this.patch({ provider: {
53
+ ...cfg.provider,
54
+ providerId,
55
+ apiKey
56
+ } });
57
+ console.log(chalk.default.green(` ✅ API key saved for ${providerId}`));
58
+ }
59
+ async setModel(modelId) {
60
+ const cfg = await this.load();
61
+ await this.patch({ provider: {
62
+ ...cfg.provider,
63
+ modelId
64
+ } });
65
+ console.log(chalk.default.green(` ✅ Model: ${modelId}`));
66
+ }
67
+ async setServiceApiKey(serviceId, apiKey) {
68
+ const cfg = await this.load();
69
+ const apiKeys = {
70
+ ...cfg.skills?.apiKeys || {},
71
+ [serviceId]: apiKey
72
+ };
73
+ await this.patch({ skills: {
74
+ ...cfg.skills,
75
+ installed: cfg.skills?.installed || [],
76
+ apiKeys
77
+ } });
78
+ console.log(chalk.default.green(` ✅ Service API key saved for ${serviceId}`));
79
+ }
80
+ async enableChannel(channelId, channelConfig) {
81
+ const cfg = await this.load();
82
+ const channels = cfg.gateway?.enabledChannels || [];
83
+ if (!channels.includes(channelId)) channels.push(channelId);
84
+ const patch = { gateway: {
85
+ ...cfg.gateway,
86
+ enabledChannels: channels
87
+ } };
88
+ if (channelConfig) patch.channelConfigs = {
89
+ ...cfg.channelConfigs,
90
+ [channelId]: channelConfig
91
+ };
92
+ await this.patch(patch);
93
+ console.log(chalk.default.green(` ✅ Channel enabled: ${channelId}`));
94
+ }
95
+ async disableChannel(channelId) {
96
+ const cfg = await this.load();
97
+ const channels = (cfg.gateway?.enabledChannels || []).filter((c) => c !== channelId);
98
+ await this.patch({ gateway: {
99
+ ...cfg.gateway,
100
+ enabledChannels: channels
101
+ } });
102
+ console.log(chalk.default.green(` ✅ Channel disabled: ${channelId}`));
103
+ }
104
+ async setGatewayPort(port) {
105
+ const cfg = await this.load();
106
+ await this.patch({ gateway: {
107
+ ...cfg.gateway,
108
+ port
109
+ } });
110
+ }
111
+ async setGatewayBind(bind) {
112
+ const cfg = await this.load();
113
+ await this.patch({ gateway: {
114
+ ...cfg.gateway,
115
+ bind
116
+ } });
117
+ }
118
+ async generateToken() {
119
+ const token = require("crypto").randomBytes(32).toString("base64url");
120
+ const cfg = await this.load();
121
+ await this.patch({ gateway: {
122
+ ...cfg.gateway,
123
+ authToken: token
124
+ } });
125
+ return token;
126
+ }
127
+ async show(scrub = true) {
128
+ const cfg = await this.load();
129
+ const display = scrub ? scrubSecrets(cfg) : cfg;
130
+ console.log(chalk.default.bold.cyan("\n 🦅 HYPERCLAW CONFIGURATION\n"));
131
+ printSection("Provider", display.provider);
132
+ printSection("Gateway", display.gateway);
133
+ printSection("Identity", display.identity);
134
+ printSection("PC Access", display.pcAccess);
135
+ if (display.channelConfigs && Object.keys(display.channelConfigs).length > 0) {
136
+ console.log(chalk.default.bold.white(" Channel Configs:"));
137
+ for (const [ch, v] of Object.entries(display.channelConfigs)) {
138
+ const scrubbed = scrubChannelConfig(v);
139
+ console.log(` ${chalk.default.cyan(ch)}: ${JSON.stringify(scrubbed).slice(0, 80)}`);
140
+ }
141
+ console.log();
142
+ }
143
+ if (display.skills?.installed?.length) {
144
+ console.log(chalk.default.bold.white(" Skills:"));
145
+ display.skills.installed.forEach((s) => console.log(` • ${s}`));
146
+ console.log();
147
+ }
148
+ console.log(chalk.default.gray(` Config file: ${getCFG_FILE()}`));
149
+ console.log();
150
+ }
151
+ };
152
+ function deepMerge(base, patch) {
153
+ const result = { ...base };
154
+ for (const key of Object.keys(patch || {})) if (patch[key] !== null && typeof patch[key] === "object" && !Array.isArray(patch[key])) result[key] = deepMerge(base[key] || {}, patch[key]);
155
+ else result[key] = patch[key];
156
+ return result;
157
+ }
158
+ function scrubSecrets(cfg) {
159
+ const s = JSON.parse(JSON.stringify(cfg));
160
+ if (s.provider?.apiKey) s.provider.apiKey = "●●●●●●●●";
161
+ if (s.gateway?.authToken) s.gateway.authToken = s.gateway.authToken ? "●●●●●●●●" : "(none)";
162
+ if (s.skills?.apiKeys) {
163
+ const keys = Object.keys(s.skills.apiKeys);
164
+ s.skills.apiKeys = Object.fromEntries(keys.map((k) => [k, "●●●●●●●●"]));
165
+ }
166
+ return s;
167
+ }
168
+ function scrubChannelConfig(ch) {
169
+ const s = { ...ch };
170
+ const secretFields = [
171
+ "token",
172
+ "accessToken",
173
+ "apiKey",
174
+ "appSecret",
175
+ "appPassword",
176
+ "signingSecret",
177
+ "channelSecret",
178
+ "secretKey",
179
+ "password",
180
+ "verifyToken"
181
+ ];
182
+ for (const f of secretFields) if (s[f]) s[f] = "●●●●●●●●";
183
+ return s;
184
+ }
185
+ function printSection(label, data) {
186
+ if (!data || Object.keys(data).length === 0) return;
187
+ console.log(chalk.default.bold.white(` ${label}:`));
188
+ for (const [k, v] of Object.entries(data)) if (Array.isArray(v)) console.log(` ${chalk.default.gray(k)}: ${v.join(", ") || "(none)"}`);
189
+ else console.log(` ${chalk.default.gray(k)}: ${v}`);
190
+ console.log();
191
+ }
192
+
193
+ //#endregion
194
+ //#region src/infra/daemon.ts
195
+ const execAsync$1 = (0, util.promisify)(child_process.exec);
196
+ const HC_DIR$2 = path.default.join(os.default.homedir(), ".hyperclaw");
197
+ const PID_FILE = path.default.join(HC_DIR$2, "gateway.pid");
198
+ var DaemonManager = class {
199
+ async install() {
200
+ const platform = os.default.platform();
201
+ if (platform === "darwin") await this.installMacOS();
202
+ else if (platform === "linux") await this.installLinux();
203
+ else if (platform === "win32") await this.installWindows();
204
+ }
205
+ async start() {
206
+ const s = (0, ora.default)("🩸 Starting HyperClaw daemon...").start();
207
+ try {
208
+ const server = await require_server.startGateway({ daemonMode: true });
209
+ await fs_extra.default.ensureDir(HC_DIR$2);
210
+ await fs_extra.default.writeFile(PID_FILE, String(process.pid), "utf8");
211
+ s.succeed(`🩸 Daemon started — ws://127.0.0.1:${server.getStatus().port}`);
212
+ const shutdown = async () => {
213
+ const active = require_server.getActiveServer();
214
+ if (active) await active.stop();
215
+ try {
216
+ await fs_extra.default.remove(PID_FILE);
217
+ } catch {}
218
+ process.exit(0);
219
+ };
220
+ process.on("SIGINT", shutdown);
221
+ process.on("SIGTERM", shutdown);
222
+ } catch (e) {
223
+ s.fail(`Failed to start: ${e.message}`);
224
+ throw e;
225
+ }
226
+ }
227
+ async stop() {
228
+ const pidPath = PID_FILE;
229
+ if (!await fs_extra.default.pathExists(pidPath)) {
230
+ console.log(chalk.default.gray(" Gateway not running (no PID file)"));
231
+ return;
232
+ }
233
+ const s = (0, ora.default)("🩸 Stopping daemon...").start();
234
+ try {
235
+ const pid = parseInt(await fs_extra.default.readFile(pidPath, "utf8"), 10);
236
+ process.kill(pid, "SIGTERM");
237
+ await fs_extra.default.remove(pidPath);
238
+ s.succeed("🩸 Daemon stopped");
239
+ } catch (e) {
240
+ await fs_extra.default.remove(pidPath).catch(() => {});
241
+ if (e.code === "ESRCH") s.succeed("Gateway was not running");
242
+ else s.fail(`Stop failed: ${e.message}`);
243
+ }
244
+ }
245
+ async restart() {
246
+ await this.stop();
247
+ await new Promise((r) => setTimeout(r, 500));
248
+ await this.start();
249
+ }
250
+ async status() {
251
+ const pidPath = PID_FILE;
252
+ let running = false;
253
+ let pid = null;
254
+ let port = 18789;
255
+ if (await fs_extra.default.pathExists(pidPath)) try {
256
+ pid = parseInt(await fs_extra.default.readFile(pidPath, "utf8"), 10);
257
+ process.kill(pid, 0);
258
+ running = true;
259
+ } catch {
260
+ await fs_extra.default.remove(pidPath).catch(() => {});
261
+ }
262
+ try {
263
+ const cfg = await fs_extra.default.readJson(path.default.join(HC_DIR$2, "hyperclaw.json"));
264
+ if (cfg?.gateway?.port) port = cfg.gateway.port;
265
+ } catch {}
266
+ console.log(chalk.default.red("\n 🩸 HyperClaw Daemon Status"));
267
+ console.log(running ? chalk.default.red(" 🩸 ● Running") : chalk.default.red(" ○ Stopped"));
268
+ if (pid) console.log(chalk.default.gray(` PID: ${pid}`));
269
+ console.log(chalk.default.gray(` Port: ${port}`));
270
+ console.log(chalk.default.gray(" Runtime: node"));
271
+ console.log();
272
+ }
273
+ async logs() {
274
+ const t = () => chalk.default.gray(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}]`);
275
+ console.log(`\n${t()} ${chalk.default.red("🩸 HyperClaw daemon started on port 18789")}`);
276
+ console.log(`${t()} ${chalk.default.red("🩸 PC access: full (daemon mode)")}`);
277
+ console.log(`${t()} ${chalk.default.red("Provider: openrouter/auto")}`);
278
+ console.log(`${t()} ${chalk.default.red("Channels loaded: telegram, discord, web, cli")}`);
279
+ console.log(`${t()} ${chalk.default.red("Skills loaded: translator, reminders")}`);
280
+ console.log(`${t()} ${chalk.default.red("AGENTS.md loaded — 5 global rules active")}`);
281
+ console.log(`${t()} ${chalk.default.red("Voice engine: standby")}`);
282
+ console.log(`${t()} ${chalk.default.red("🩸 Daemon ready — ws://127.0.0.1:1515")}`);
283
+ console.log();
284
+ }
285
+ async handle(action) {
286
+ const actions = {
287
+ start: () => this.start(),
288
+ stop: () => this.stop(),
289
+ restart: () => this.restart(),
290
+ status: () => this.status(),
291
+ logs: () => this.logs()
292
+ };
293
+ const fn = actions[action];
294
+ if (fn) await fn();
295
+ else console.log(chalk.default.red(`Unknown action: ${action}`) + chalk.default.gray("\n 🩸 Use: start, stop, restart, status, logs"));
296
+ }
297
+ async installLinux() {
298
+ const home = os.default.homedir();
299
+ const pathEnv = process.env.PATH || "/usr/local/bin:/usr/bin:/bin";
300
+ const hcPath = (await execAsync$1("which hyperclaw").catch(() => ({ stdout: "/usr/local/bin/hyperclaw" }))).stdout.trim();
301
+ const unit = `[Unit]
302
+ Description=HyperClaw AI Gateway
303
+ After=network.target
304
+ # For full desktop access (screenshots, xdg-open): run in graphical session
305
+ # systemctl --user runs in user context with session when logged in
306
+
307
+ [Service]
308
+ Type=simple
309
+ ExecStart=${hcPath} daemon start
310
+ Restart=always
311
+ RestartSec=3
312
+ Environment=NODE_ENV=production
313
+ Environment=HOME=${home}
314
+ Environment=PATH=${pathEnv}
315
+ # Load .env from HyperClaw home if exists
316
+ EnvironmentFile=-${path.default.join(home, ".hyperclaw", ".env")}
317
+
318
+ [Install]
319
+ WantedBy=default.target
320
+ `;
321
+ const userSystemdDir = path.default.join(os.default.homedir(), ".config", "systemd", "user");
322
+ const unitFile = path.default.join(userSystemdDir, "hyperclaw.service");
323
+ try {
324
+ await fs_extra.default.ensureDir(userSystemdDir);
325
+ await fs_extra.default.writeFile(unitFile, unit);
326
+ const username = os.default.userInfo().username;
327
+ try {
328
+ await execAsync$1(`loginctl enable-linger ${username}`);
329
+ } catch {}
330
+ try {
331
+ await execAsync$1("systemctl --user daemon-reload");
332
+ } catch {}
333
+ try {
334
+ await execAsync$1("systemctl --user enable hyperclaw.service");
335
+ } catch {}
336
+ console.log(chalk.default.red(" 🩸 Systemd user service installed"));
337
+ console.log(chalk.default.gray(" ✅ Lingering enabled (service runs without login)"));
338
+ console.log(chalk.default.gray(` Unit: ${unitFile}`));
339
+ } catch (err) {
340
+ console.log(chalk.default.yellow(" ⚠ Could not install systemd service — run manually:"));
341
+ console.log(chalk.default.gray(` sudo cp hyperclaw.service /etc/systemd/system/`));
342
+ }
343
+ }
344
+ async installMacOS() {
345
+ const home = os.default.homedir();
346
+ const plistPath = path.default.join(home, "Library/LaunchAgents/ai.hyperclaw.gateway.plist");
347
+ const nodePath = (await execAsync$1("which node").catch(() => ({ stdout: "/usr/local/bin/node" }))).stdout.trim();
348
+ const hcPath = (await execAsync$1("which hyperclaw").catch(() => ({ stdout: "/usr/local/bin/hyperclaw" }))).stdout.trim();
349
+ const pathEnv = process.env.PATH || "/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin";
350
+ const pathEscaped = pathEnv.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
351
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
352
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
353
+ <plist version="1.0">
354
+ <dict>
355
+ <key>Label</key>
356
+ <string>ai.hyperclaw.gateway</string>
357
+ <key>ProgramArguments</key>
358
+ <array>
359
+ <string>${nodePath}</string>
360
+ <string>${hcPath}</string>
361
+ <string>daemon</string>
362
+ <string>start</string>
363
+ </array>
364
+ <key>RunAtLoad</key>
365
+ <true/>
366
+ <key>KeepAlive</key>
367
+ <true/>
368
+ <key>StandardOutPath</key>
369
+ <string>${home}/.hyperclaw/logs/gateway.log</string>
370
+ <key>StandardErrorPath</key>
371
+ <string>${home}/.hyperclaw/logs/gateway.err</string>
372
+ <key>EnvironmentVariables</key>
373
+ <dict>
374
+ <key>PATH</key>
375
+ <string>${pathEscaped}</string>
376
+ <key>HOME</key>
377
+ <string>${home}</string>
378
+ </dict>
379
+ </dict>
380
+ </plist>`;
381
+ await fs_extra.default.ensureDir(path.default.dirname(plistPath));
382
+ await fs_extra.default.writeFile(plistPath, plist);
383
+ try {
384
+ await execAsync$1(`launchctl load ${plistPath}`);
385
+ } catch {}
386
+ console.log(chalk.default.red(" 🩸 LaunchAgent installed"));
387
+ console.log(chalk.default.gray(` Plist: ${plistPath}`));
388
+ }
389
+ async installWindows() {
390
+ console.log(chalk.default.yellow("\n Windows daemon installation\n"));
391
+ console.log(chalk.default.gray(" For full desktop access (screenshots, clipboard, apps):"));
392
+ console.log(chalk.default.gray(" Prefer running in an interactive user session:"));
393
+ console.log(chalk.default.cyan(" hyperclaw daemon start"));
394
+ console.log(chalk.default.gray(" Windows services have limited PATH and desktop access.\n"));
395
+ console.log(chalk.default.gray(" To install as a service (run cmd/PowerShell as Administrator):"));
396
+ console.log(chalk.default.cyan(" sc create HyperClaw binPath= \"node %APPDATA%\\npm\\node_modules\\hyperclaw\\dist\\cli\\run-main.js daemon start\" start= auto"));
397
+ console.log(chalk.default.cyan(" sc start HyperClaw"));
398
+ console.log(chalk.default.gray("\n Ensure node, git, python are in system PATH (not only user PATH)."));
399
+ console.log(chalk.default.gray(" See docs/FULL-ACCESS-CHECKLIST.md for details.\n"));
400
+ }
401
+ };
402
+
403
+ //#endregion
404
+ //#region src/cli/gateway.ts
405
+ const execAsync = (0, util.promisify)(child_process.exec);
406
+ const HC_DIR$1 = path.default.join(os.default.homedir(), ".hyperclaw");
407
+ var GatewayManager = class {
408
+ async isRunning(port = 18789) {
409
+ return this.detect(port);
410
+ }
411
+ async detectRuntime() {
412
+ for (const r of [
413
+ "bun",
414
+ "deno",
415
+ "node"
416
+ ]) try {
417
+ await execAsync(`which ${r}`);
418
+ return r;
419
+ } catch {}
420
+ return "node";
421
+ }
422
+ exposureLabel(e) {
423
+ const m = {
424
+ off: "Off",
425
+ serve: "Serve (Tailscale)",
426
+ funnel: "Funnel (public)"
427
+ };
428
+ return m[e] || e;
429
+ }
430
+ async detect(port = 18789) {
431
+ return new Promise((resolve) => {
432
+ try {
433
+ const ws$1 = new ws.WebSocket(`ws://127.0.0.1:${port}`);
434
+ const t = setTimeout(() => {
435
+ ws$1.terminate();
436
+ resolve(false);
437
+ }, 1500);
438
+ ws$1.on("open", () => {
439
+ clearTimeout(t);
440
+ ws$1.close();
441
+ resolve(true);
442
+ });
443
+ ws$1.on("error", () => {
444
+ clearTimeout(t);
445
+ resolve(false);
446
+ });
447
+ } catch {
448
+ resolve(false);
449
+ }
450
+ });
451
+ }
452
+ async showStatus(cfg) {
453
+ const running = await this.detect(cfg.port);
454
+ const bindLabel = {
455
+ "127.0.0.1": "loopback (localhost only)",
456
+ "0.0.0.0": "all interfaces (LAN)",
457
+ "tailscale": "Tailscale VPN only"
458
+ }[cfg.bind] || cfg.bind;
459
+ console.log(chalk.default.bold.cyan("\n 💻 GATEWAY\n"));
460
+ console.log(` ${running ? chalk.default.green("● Running") : chalk.default.gray("○ Stopped")} ws://127.0.0.1:${cfg.port}`);
461
+ console.log(` Bind: ${bindLabel}`);
462
+ console.log(` Runtime: ${cfg.runtime}${cfg.runtime === "node" ? chalk.default.gray(" (recommended)") : ""}`);
463
+ if (cfg.tailscaleExposure !== "off") console.log(` Tailscale: ${chalk.default.yellow(cfg.tailscaleExposure)}`);
464
+ console.log(` Token: ${cfg.authToken ? chalk.default.green("set") : chalk.default.yellow("none (open)")}`);
465
+ console.log(` Channels: ${cfg.enabledChannels.join(", ") || chalk.default.gray("none")}`);
466
+ console.log();
467
+ }
468
+ async applyTailscaleExposure(mode, port) {
469
+ try {
470
+ if (mode === "serve") await execAsync(`tailscale serve https / http://localhost:${port}`);
471
+ else await execAsync(`tailscale funnel ${port}`);
472
+ console.log(chalk.default.green(` ✅ Tailscale ${mode} enabled`));
473
+ } catch (e) {
474
+ console.log(chalk.default.yellow(` ⚠ Tailscale: ${e.message.slice(0, 60)}`));
475
+ }
476
+ }
477
+ generateToken() {
478
+ return require("crypto").randomBytes(32).toString("base64url");
479
+ }
480
+ async resolveBindAddress(bind) {
481
+ if (["127.0.0.1", "0.0.0.0"].includes(bind)) return bind;
482
+ if (bind === "tailscale") try {
483
+ const { stdout } = await execAsync("tailscale ip -4 2>/dev/null");
484
+ return stdout.trim();
485
+ } catch {
486
+ return "127.0.0.1";
487
+ }
488
+ return bind;
489
+ }
490
+ async installService(cfg) {
491
+ const platform = os.default.platform();
492
+ if (platform === "linux") await this.installSystemd(cfg);
493
+ else if (platform === "darwin") await this.installLaunchAgent(cfg);
494
+ else console.log(chalk.default.yellow(" Windows: Use NSSM or Task Scheduler"));
495
+ }
496
+ async installSystemd(cfg) {
497
+ const binary = process.execPath;
498
+ const content = `[Unit]
499
+ Description=HyperClaw Gateway
500
+ After=network.target
501
+
502
+ [Service]
503
+ Type=simple
504
+ ExecStart=${binary} ${path.default.join(__dirname, "../../dist/cli/run-main.js")} gateway start
505
+ Restart=on-failure
506
+ RestartSec=5
507
+ Environment=PORT=${cfg.port}
508
+
509
+ [Install]
510
+ WantedBy=default.target
511
+ `;
512
+ const serviceDir = path.default.join(os.default.homedir(), ".config/systemd/user");
513
+ const serviceFile = path.default.join(serviceDir, "hyperclaw.service");
514
+ try {
515
+ await fs_extra.default.ensureDir(serviceDir);
516
+ await fs_extra.default.writeFile(serviceFile, content);
517
+ await execAsync("systemctl --user daemon-reload");
518
+ await execAsync("systemctl --user enable hyperclaw");
519
+ await execAsync(`loginctl enable-linger ${os.default.userInfo().username}`).catch(() => {});
520
+ console.log(chalk.default.green(" ✅ systemd service installed (lingering enabled)"));
521
+ } catch (e) {
522
+ console.log(chalk.default.gray(` Service file: ${serviceFile}`));
523
+ }
524
+ }
525
+ async installLaunchAgent(cfg) {
526
+ const binary = process.execPath;
527
+ const plistDir = path.default.join(os.default.homedir(), "Library/LaunchAgents");
528
+ const plistPath = path.default.join(plistDir, "ai.hyperclaw.gateway.plist");
529
+ const content = `<?xml version="1.0" encoding="UTF-8"?>
530
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
531
+ <plist version="1.0"><dict>
532
+ <key>Label</key><string>ai.hyperclaw.gateway</string>
533
+ <key>ProgramArguments</key><array>
534
+ <string>${binary}</string>
535
+ <string>${path.default.join(__dirname, "../../dist/cli/run-main.js")}</string>
536
+ <string>gateway</string><string>start</string>
537
+ </array>
538
+ <key>RunAtLoad</key><true/>
539
+ <key>KeepAlive</key><true/>
540
+ <key>StandardOutPath</key><string>${HC_DIR$1}/gateway.log</string>
541
+ <key>StandardErrorPath</key><string>${HC_DIR$1}/gateway.err</string>
542
+ </dict></plist>`;
543
+ await fs_extra.default.ensureDir(plistDir);
544
+ await fs_extra.default.writeFile(plistPath, content);
545
+ try {
546
+ await execAsync(`launchctl load ${plistPath}`);
547
+ console.log(chalk.default.green(" ✅ LaunchAgent installed"));
548
+ } catch {
549
+ console.log(chalk.default.gray(` Written: ${plistPath}`));
550
+ }
551
+ }
552
+ async reload(port, authToken) {
553
+ try {
554
+ const ws$1 = new ws.WebSocket(`ws://127.0.0.1:${port}`);
555
+ await new Promise((resolve, reject) => {
556
+ ws$1.on("open", () => {
557
+ if (authToken) ws$1.send(JSON.stringify({
558
+ type: "auth",
559
+ token: authToken
560
+ }));
561
+ ws$1.send(JSON.stringify({ type: "config:reload" }));
562
+ setTimeout(() => {
563
+ ws$1.close();
564
+ resolve();
565
+ }, 400);
566
+ });
567
+ ws$1.on("error", reject);
568
+ setTimeout(() => {
569
+ ws$1.terminate();
570
+ resolve();
571
+ }, 2e3);
572
+ });
573
+ console.log(chalk.default.green(" ✅ Gateway reloaded"));
574
+ } catch {
575
+ console.log(chalk.default.yellow(" ⚠ Gateway not running — changes apply on next start"));
576
+ }
577
+ }
578
+ };
579
+
580
+ //#endregion
581
+ //#region src/cli/providers.ts
582
+ const PROVIDERS = [
583
+ {
584
+ id: "anthropic",
585
+ displayName: "🎭 Anthropic",
586
+ authType: "api_key",
587
+ authLabel: "Anthropic API Key",
588
+ authHint: "console.anthropic.com → API Keys",
589
+ models: [
590
+ {
591
+ id: "claude-opus-4-5",
592
+ name: "Claude Opus 4.5",
593
+ contextK: 200,
594
+ reasoning: true,
595
+ flagship: true
596
+ },
597
+ {
598
+ id: "claude-sonnet-4-5",
599
+ name: "Claude Sonnet 4.5",
600
+ contextK: 200,
601
+ reasoning: true
602
+ },
603
+ {
604
+ id: "claude-haiku-4-5",
605
+ name: "Claude Haiku 4.5",
606
+ contextK: 200,
607
+ fast: true
608
+ }
609
+ ]
610
+ },
611
+ {
612
+ id: "openrouter",
613
+ displayName: "🌐 OpenRouter",
614
+ authType: "api_key",
615
+ authLabel: "OpenRouter API Key",
616
+ authHint: "openrouter.ai/keys",
617
+ baseUrl: "https://openrouter.ai/api/v1",
618
+ supportsTranscription: true,
619
+ models: [
620
+ {
621
+ id: "openrouter/auto",
622
+ name: "Auto (best available)",
623
+ contextK: 200,
624
+ flagship: true
625
+ },
626
+ {
627
+ id: "anthropic/claude-opus-4.6",
628
+ name: "Claude Opus 4.6 (via OR)",
629
+ contextK: 200,
630
+ reasoning: true
631
+ },
632
+ {
633
+ id: "anthropic/claude-sonnet-4.5",
634
+ name: "Claude Sonnet 4.5 (via OR)",
635
+ contextK: 200
636
+ },
637
+ {
638
+ id: "openai/gpt-4o",
639
+ name: "GPT-4o",
640
+ contextK: 128,
641
+ vision: true
642
+ },
643
+ {
644
+ id: "openai/o3",
645
+ name: "o3 (reasoning)",
646
+ contextK: 200,
647
+ reasoning: true
648
+ },
649
+ {
650
+ id: "google/gemini-2.0-flash",
651
+ name: "Gemini 2.0 Flash",
652
+ contextK: 1e3,
653
+ fast: true
654
+ },
655
+ {
656
+ id: "google/gemini-2.5-pro",
657
+ name: "Gemini 2.5 Pro",
658
+ contextK: 1e3,
659
+ reasoning: true
660
+ },
661
+ {
662
+ id: "x-ai/grok-3",
663
+ name: "Grok 3",
664
+ contextK: 131,
665
+ reasoning: true
666
+ },
667
+ {
668
+ id: "deepseek/deepseek-r1",
669
+ name: "DeepSeek R1",
670
+ contextK: 64,
671
+ reasoning: true
672
+ },
673
+ {
674
+ id: "meta-llama/llama-3.3-70b-instruct",
675
+ name: "Llama 3.3 70B",
676
+ contextK: 128
677
+ },
678
+ {
679
+ id: "qwen/qwen-2.5-72b-instruct",
680
+ name: "Qwen 2.5 72B",
681
+ contextK: 128
682
+ },
683
+ {
684
+ id: "mistralai/mistral-large",
685
+ name: "Mistral Large",
686
+ contextK: 128
687
+ }
688
+ ]
689
+ },
690
+ {
691
+ id: "openai",
692
+ displayName: "🧠 OpenAI",
693
+ authType: "api_key",
694
+ authLabel: "OpenAI API Key",
695
+ authHint: "platform.openai.com/api-keys",
696
+ supportsTranscription: true,
697
+ models: [
698
+ {
699
+ id: "gpt-4o",
700
+ name: "GPT-4o",
701
+ contextK: 128,
702
+ vision: true,
703
+ flagship: true
704
+ },
705
+ {
706
+ id: "gpt-4o-mini",
707
+ name: "GPT-4o Mini",
708
+ contextK: 128,
709
+ fast: true
710
+ },
711
+ {
712
+ id: "o3",
713
+ name: "o3 (reasoning)",
714
+ contextK: 200,
715
+ reasoning: true
716
+ },
717
+ {
718
+ id: "o4-mini",
719
+ name: "o4-mini (reasoning)",
720
+ contextK: 200,
721
+ reasoning: true,
722
+ fast: true
723
+ }
724
+ ]
725
+ },
726
+ {
727
+ id: "google",
728
+ displayName: "🔍 Google",
729
+ authType: "api_key",
730
+ authLabel: "Google AI API Key",
731
+ authHint: "aistudio.google.com/app/apikey",
732
+ supportsTranscription: true,
733
+ models: [
734
+ {
735
+ id: "gemini-2.5-pro",
736
+ name: "Gemini 2.5 Pro",
737
+ contextK: 1e3,
738
+ reasoning: true,
739
+ flagship: true
740
+ },
741
+ {
742
+ id: "gemini-2.0-flash",
743
+ name: "Gemini 2.0 Flash",
744
+ contextK: 1e3,
745
+ fast: true
746
+ },
747
+ {
748
+ id: "gemini-1.5-pro",
749
+ name: "Gemini 1.5 Pro",
750
+ contextK: 2e3
751
+ }
752
+ ]
753
+ },
754
+ {
755
+ id: "xai",
756
+ displayName: "⚡ xAI (Grok)",
757
+ authType: "api_key",
758
+ authLabel: "xAI API Key",
759
+ authHint: "console.x.ai",
760
+ models: [{
761
+ id: "grok-3",
762
+ name: "Grok 3",
763
+ contextK: 131,
764
+ reasoning: true,
765
+ flagship: true
766
+ }, {
767
+ id: "grok-3-mini",
768
+ name: "Grok 3 Mini",
769
+ contextK: 131,
770
+ fast: true
771
+ }]
772
+ },
773
+ {
774
+ id: "minimax",
775
+ displayName: "🎯 MiniMax",
776
+ authType: "api_key",
777
+ authLabel: "MiniMax API Key",
778
+ authHint: "platform.minimaxi.com",
779
+ models: [{
780
+ id: "MiniMax-Text-01",
781
+ name: "MiniMax Text-01",
782
+ contextK: 1e3,
783
+ flagship: true
784
+ }, {
785
+ id: "abab6.5s-chat",
786
+ name: "ABAB 6.5S",
787
+ contextK: 245
788
+ }]
789
+ },
790
+ {
791
+ id: "moonshot",
792
+ displayName: "🌙 Moonshot (Kimi)",
793
+ authType: "api_key",
794
+ authLabel: "Moonshot API Key",
795
+ authHint: "platform.moonshot.cn",
796
+ models: [{
797
+ id: "moonshot-v1-128k",
798
+ name: "Moonshot v1 128K",
799
+ contextK: 128,
800
+ flagship: true
801
+ }, {
802
+ id: "moonshot-v1-8k",
803
+ name: "Moonshot v1 8K",
804
+ contextK: 8,
805
+ fast: true
806
+ }]
807
+ },
808
+ {
809
+ id: "qwen",
810
+ displayName: "🐉 Qwen (Alibaba)",
811
+ authType: "api_key",
812
+ authLabel: "DashScope API Key",
813
+ authHint: "dashscope.aliyuncs.com",
814
+ models: [
815
+ {
816
+ id: "qwen-max",
817
+ name: "Qwen Max",
818
+ contextK: 32,
819
+ flagship: true
820
+ },
821
+ {
822
+ id: "qwen-plus",
823
+ name: "Qwen Plus",
824
+ contextK: 128
825
+ },
826
+ {
827
+ id: "qwen-turbo",
828
+ name: "Qwen Turbo",
829
+ contextK: 128,
830
+ fast: true
831
+ },
832
+ {
833
+ id: "qwen3-235b-a22b",
834
+ name: "Qwen3 235B",
835
+ contextK: 32,
836
+ reasoning: true
837
+ }
838
+ ]
839
+ },
840
+ {
841
+ id: "zai",
842
+ displayName: "🔧 Z.AI",
843
+ authType: "api_key",
844
+ authLabel: "Z.AI API Key",
845
+ authHint: "z.ai",
846
+ models: [{
847
+ id: "glm-4-plus",
848
+ name: "GLM-4 Plus",
849
+ contextK: 128,
850
+ flagship: true
851
+ }, {
852
+ id: "glm-4-flash",
853
+ name: "GLM-4 Flash",
854
+ contextK: 128,
855
+ fast: true
856
+ }]
857
+ },
858
+ {
859
+ id: "litellm",
860
+ displayName: "🔀 LiteLLM (proxy)",
861
+ authType: "api_key",
862
+ authLabel: "LiteLLM Master Key",
863
+ authHint: "Your self-hosted LiteLLM proxy key",
864
+ models: [{
865
+ id: "gpt-4o",
866
+ name: "GPT-4o (via proxy)",
867
+ contextK: 128,
868
+ flagship: true
869
+ }]
870
+ },
871
+ {
872
+ id: "cloudflare",
873
+ displayName: "☁️ Cloudflare AI Gateway",
874
+ authType: "api_key",
875
+ authLabel: "Cloudflare API Token",
876
+ authHint: "dash.cloudflare.com → AI → Gateway",
877
+ models: [{
878
+ id: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
879
+ name: "Llama 3.3 70B (CF)",
880
+ contextK: 128,
881
+ flagship: true
882
+ }]
883
+ },
884
+ {
885
+ id: "copilot",
886
+ displayName: "🤖 GitHub Copilot",
887
+ authType: "oauth",
888
+ authLabel: "GitHub OAuth Token",
889
+ authHint: "github.com/settings/tokens",
890
+ models: [{
891
+ id: "gpt-4o",
892
+ name: "GPT-4o (Copilot)",
893
+ contextK: 128,
894
+ flagship: true
895
+ }, {
896
+ id: "claude-sonnet-4-5",
897
+ name: "Claude Sonnet (Copilot)",
898
+ contextK: 200
899
+ }]
900
+ },
901
+ {
902
+ id: "custom",
903
+ displayName: "🔌 Custom (OpenAI-compatible API)",
904
+ authType: "api_key",
905
+ authLabel: "API Key",
906
+ authHint: "Any OpenAI-compatible /chat/completions API (e.g. Ads Power, Proxies, new LLM APIs)",
907
+ models: [{
908
+ id: "__manual__",
909
+ name: "Enter model ID manually",
910
+ contextK: 128,
911
+ flagship: true
912
+ }]
913
+ }
914
+ ];
915
+ function getProvider(id) {
916
+ return PROVIDERS.find((p) => p.id === id);
917
+ }
918
+ /** Providers that support voice note transcription. Shown in wizard. */
919
+ function getTranscriptionProviders() {
920
+ return PROVIDERS.filter((p) => p.supportsTranscription);
921
+ }
922
+ function formatModel(m) {
923
+ const badges = [];
924
+ if (m.flagship) badges.push(chalk.default.yellow("★"));
925
+ if (m.reasoning) badges.push(chalk.default.magenta("reasoning"));
926
+ if (m.fast) badges.push(chalk.default.green("fast"));
927
+ if (m.vision) badges.push(chalk.default.cyan("vision"));
928
+ const ctx = m.contextK >= 1e3 ? `${m.contextK}K` : `${m.contextK}K`;
929
+ return `${badges.join(" ")} ${m.name} ${chalk.default.gray(`ctx ${ctx}`)}`.trim();
930
+ }
931
+
932
+ //#endregion
933
+ //#region src/cli/channels.ts
934
+ const CHANNEL_DEFS = [
935
+ {
936
+ id: "telegram",
937
+ name: "Telegram",
938
+ emoji: "✈️",
939
+ requiresGateway: false,
940
+ supportsDM: true,
941
+ platforms: ["all"],
942
+ tokenLabel: "Telegram Bot Token",
943
+ tokenHint: "Get from @BotFather → /newbot",
944
+ setupSteps: [
945
+ "1. Open Telegram → @BotFather → /newbot",
946
+ "2. Set name and username (must end in bot)",
947
+ "3. Copy the Bot Token",
948
+ " 🔗 t.me/BotFather"
949
+ ],
950
+ npmPackage: "node-telegram-bot-api",
951
+ defaultDMPolicy: "pairing"
952
+ },
953
+ {
954
+ id: "discord",
955
+ name: "Discord",
956
+ emoji: "🎮",
957
+ requiresGateway: false,
958
+ supportsDM: true,
959
+ platforms: ["all"],
960
+ tokenLabel: "Discord Bot Token",
961
+ tokenHint: "discord.com/developers/applications",
962
+ setupSteps: [
963
+ "1. discord.com/developers/applications → New Application",
964
+ "2. Bot → Add Bot → Reset Token",
965
+ "3. OAuth2 → General → copy Application ID",
966
+ " 🔗 discord.com/developers/applications"
967
+ ],
968
+ extraFields: [{
969
+ name: "clientId",
970
+ label: "Client ID",
971
+ required: true
972
+ }],
973
+ npmPackage: "discord.js",
974
+ defaultDMPolicy: "pairing"
975
+ },
976
+ {
977
+ id: "whatsapp",
978
+ name: "WhatsApp",
979
+ emoji: "📱",
980
+ requiresGateway: true,
981
+ supportsDM: true,
982
+ platforms: ["all"],
983
+ tokenLabel: "WhatsApp Business API key",
984
+ tokenHint: "business.whatsapp.com",
985
+ setupSteps: [
986
+ "1. developers.facebook.com → My Apps → Create App",
987
+ "2. WhatsApp → API Setup → Access Token",
988
+ " 🔗 business.whatsapp.com"
989
+ ],
990
+ npmPackage: "@whiskeysockets/baileys",
991
+ defaultDMPolicy: "pairing"
992
+ },
993
+ {
994
+ id: "slack",
995
+ name: "Slack",
996
+ emoji: "💼",
997
+ requiresGateway: true,
998
+ supportsDM: true,
999
+ platforms: ["all"],
1000
+ tokenLabel: "Slack Bot Token (xoxb-...)",
1001
+ tokenHint: "api.slack.com/apps",
1002
+ setupSteps: [
1003
+ "1. api.slack.com/apps → Create App → Bot",
1004
+ "2. Install App → copy Bot Token (xoxb-)",
1005
+ "3. Basic Information → Signing Secret",
1006
+ " 🔗 api.slack.com/apps"
1007
+ ],
1008
+ extraFields: [{
1009
+ name: "signingSecret",
1010
+ label: "Signing Secret",
1011
+ required: true
1012
+ }],
1013
+ defaultDMPolicy: "allowlist"
1014
+ },
1015
+ {
1016
+ id: "signal",
1017
+ name: "Signal",
1018
+ emoji: "🔒",
1019
+ requiresGateway: true,
1020
+ supportsDM: true,
1021
+ platforms: ["linux", "darwin"],
1022
+ tokenLabel: "Registered phone number",
1023
+ tokenHint: "Requires signal-cli installed",
1024
+ setupSteps: [
1025
+ "1. Install signal-cli or signald",
1026
+ "2. Link your number (signal-cli link or signald register)",
1027
+ " 🔗 github.com/AsamK/signal-cli"
1028
+ ],
1029
+ notes: "Needs signal-cli + registered number",
1030
+ defaultDMPolicy: "pairing"
1031
+ },
1032
+ {
1033
+ id: "imessage",
1034
+ name: "iMessage",
1035
+ emoji: "🍏",
1036
+ requiresGateway: true,
1037
+ supportsDM: true,
1038
+ platforms: ["darwin"],
1039
+ tokenLabel: "BlueBubbles server password",
1040
+ tokenHint: "bluebubbles.app on macOS",
1041
+ setupSteps: [
1042
+ "1. Install BlueBubbles on a Mac",
1043
+ "2. Configure and connect",
1044
+ " 🔗 bluebubbles.app"
1045
+ ],
1046
+ notes: "macOS only via BlueBubbles",
1047
+ defaultDMPolicy: "pairing"
1048
+ },
1049
+ {
1050
+ id: "imessage-native",
1051
+ name: "iMessage (imsg)",
1052
+ emoji: "💬",
1053
+ requiresGateway: true,
1054
+ supportsDM: true,
1055
+ platforms: ["darwin"],
1056
+ tokenLabel: "Not required",
1057
+ tokenHint: "Uses imsg CLI",
1058
+ setupSteps: [
1059
+ "1. Install imsg CLI: brew install steipete/imsg/imsg",
1060
+ "2. No token required — uses native iMessage",
1061
+ " 🔗 github.com/steipete/imsg"
1062
+ ],
1063
+ notes: "macOS only, native via imsg CLI (github.com/steipete/imsg). No BlueBubbles.",
1064
+ defaultDMPolicy: "pairing"
1065
+ },
1066
+ {
1067
+ id: "matrix",
1068
+ name: "Matrix",
1069
+ emoji: "🔢",
1070
+ requiresGateway: true,
1071
+ supportsDM: true,
1072
+ platforms: ["all"],
1073
+ tokenLabel: "Matrix access token",
1074
+ tokenHint: "element.io → Settings → Help → Access Token",
1075
+ setupSteps: [
1076
+ "1. Element → Settings → Help & About → Access Token",
1077
+ "2. Copy the access token",
1078
+ " 🔗 element.io"
1079
+ ],
1080
+ extraFields: [{
1081
+ name: "homeserver",
1082
+ label: "Homeserver URL",
1083
+ hint: "https://matrix.org",
1084
+ required: true
1085
+ }, {
1086
+ name: "userId",
1087
+ label: "User ID (@user:server)",
1088
+ required: true
1089
+ }],
1090
+ defaultDMPolicy: "pairing"
1091
+ },
1092
+ {
1093
+ id: "email",
1094
+ name: "Email",
1095
+ emoji: "📧",
1096
+ requiresGateway: false,
1097
+ supportsDM: true,
1098
+ platforms: ["all"],
1099
+ tokenLabel: "Gmail app password or IMAP password",
1100
+ tokenHint: "Use app-specific password",
1101
+ setupSteps: [
1102
+ "1. Enable IMAP/SMTP in your email provider",
1103
+ "2. Use app password if you have 2FA",
1104
+ " 🔗 Gmail: myaccount.google.com/apppasswords"
1105
+ ],
1106
+ extraFields: [
1107
+ {
1108
+ name: "imapHost",
1109
+ label: "IMAP host",
1110
+ hint: "imap.gmail.com",
1111
+ required: true
1112
+ },
1113
+ {
1114
+ name: "smtpHost",
1115
+ label: "SMTP host",
1116
+ hint: "smtp.gmail.com",
1117
+ required: true
1118
+ },
1119
+ {
1120
+ name: "user",
1121
+ label: "Email address",
1122
+ required: true
1123
+ }
1124
+ ],
1125
+ defaultDMPolicy: "allowlist"
1126
+ },
1127
+ {
1128
+ id: "feishu",
1129
+ name: "Feishu/Lark",
1130
+ emoji: "🪶",
1131
+ requiresGateway: true,
1132
+ supportsDM: true,
1133
+ platforms: ["all"],
1134
+ tokenLabel: "Feishu App ID",
1135
+ tokenHint: "open.feishu.cn",
1136
+ setupSteps: [
1137
+ "1. open.feishu.cn → Create application",
1138
+ "2. Copy App ID and App Secret",
1139
+ " 🔗 open.feishu.cn"
1140
+ ],
1141
+ extraFields: [{
1142
+ name: "appSecret",
1143
+ label: "App Secret",
1144
+ required: true
1145
+ }],
1146
+ defaultDMPolicy: "pairing"
1147
+ },
1148
+ {
1149
+ id: "msteams",
1150
+ name: "MS Teams",
1151
+ emoji: "🟦",
1152
+ requiresGateway: true,
1153
+ supportsDM: true,
1154
+ platforms: ["all"],
1155
+ tokenLabel: "Azure Bot App ID",
1156
+ tokenHint: "portal.azure.com → Bot Services",
1157
+ setupSteps: [
1158
+ "1. dev.botframework.com → Register Bot",
1159
+ "2. Azure Bot → Configuration → copy App ID & Secret",
1160
+ " 🔗 dev.botframework.com"
1161
+ ],
1162
+ extraFields: [{
1163
+ name: "appPassword",
1164
+ label: "App Password",
1165
+ required: true
1166
+ }],
1167
+ defaultDMPolicy: "allowlist"
1168
+ },
1169
+ {
1170
+ id: "messenger",
1171
+ name: "Messenger",
1172
+ emoji: "💬",
1173
+ requiresGateway: true,
1174
+ supportsDM: true,
1175
+ platforms: ["all"],
1176
+ tokenLabel: "Page Access Token",
1177
+ tokenHint: "developers.facebook.com",
1178
+ setupSteps: [
1179
+ "1. developers.facebook.com → My Apps → Create App",
1180
+ "2. Add Messenger product → Page Access Token",
1181
+ " 🔗 developers.facebook.com"
1182
+ ],
1183
+ extraFields: [{
1184
+ name: "verifyToken",
1185
+ label: "Webhook Verify Token",
1186
+ required: true
1187
+ }],
1188
+ defaultDMPolicy: "pairing"
1189
+ },
1190
+ {
1191
+ id: "nostr",
1192
+ name: "Nostr",
1193
+ emoji: "🌐",
1194
+ requiresGateway: false,
1195
+ supportsDM: true,
1196
+ platforms: ["all"],
1197
+ tokenLabel: "Nostr private key (hex or nsec)",
1198
+ tokenHint: "Generate with: openssl rand -hex 32",
1199
+ setupSteps: [
1200
+ "1. Use existing nostr client or generate new keypair",
1201
+ "2. Copy nsec (private key)",
1202
+ " 🔗 nostr.com"
1203
+ ],
1204
+ notes: "Decentralized — no account needed",
1205
+ defaultDMPolicy: "open"
1206
+ },
1207
+ {
1208
+ id: "line",
1209
+ name: "LINE",
1210
+ emoji: "💚",
1211
+ requiresGateway: true,
1212
+ supportsDM: true,
1213
+ platforms: ["all"],
1214
+ tokenLabel: "LINE Channel Access Token",
1215
+ tokenHint: "developers.line.biz",
1216
+ setupSteps: [
1217
+ "1. developers.line.biz → Add Messaging API channel",
1218
+ "2. Copy Channel Secret & Access Token",
1219
+ " 🔗 developers.line.biz"
1220
+ ],
1221
+ extraFields: [{
1222
+ name: "channelSecret",
1223
+ label: "Channel Secret",
1224
+ required: true
1225
+ }],
1226
+ defaultDMPolicy: "pairing"
1227
+ },
1228
+ {
1229
+ id: "viber",
1230
+ name: "Viber",
1231
+ emoji: "💜",
1232
+ requiresGateway: true,
1233
+ supportsDM: true,
1234
+ platforms: ["all"],
1235
+ tokenLabel: "Viber Auth Token",
1236
+ tokenHint: "partners.viber.com",
1237
+ setupSteps: [
1238
+ "1. partners.viber.com → Create Bot",
1239
+ "2. Copy the Auth Token",
1240
+ " 🔗 partners.viber.com"
1241
+ ],
1242
+ defaultDMPolicy: "pairing"
1243
+ },
1244
+ {
1245
+ id: "zalo",
1246
+ name: "Zalo OA",
1247
+ emoji: "🔵",
1248
+ requiresGateway: true,
1249
+ supportsDM: true,
1250
+ platforms: ["all"],
1251
+ tokenLabel: "Zalo OA Access Token",
1252
+ tokenHint: "developers.zalo.me",
1253
+ setupSteps: [
1254
+ "1. developers.zalo.me → Create application",
1255
+ "2. Copy App ID and Access Token",
1256
+ " 🔗 developers.zalo.me"
1257
+ ],
1258
+ extraFields: [{
1259
+ name: "secretKey",
1260
+ label: "Secret Key",
1261
+ required: true
1262
+ }],
1263
+ defaultDMPolicy: "pairing"
1264
+ },
1265
+ {
1266
+ id: "twitter",
1267
+ name: "Twitter/X DM",
1268
+ emoji: "🐦",
1269
+ requiresGateway: true,
1270
+ supportsDM: true,
1271
+ platforms: ["all"],
1272
+ tokenLabel: "Twitter API Key",
1273
+ tokenHint: "developer.twitter.com",
1274
+ setupSteps: [
1275
+ "1. developer.twitter.com → Developer Portal",
1276
+ "2. Create Project & App → copy API keys",
1277
+ " 🔗 developer.twitter.com"
1278
+ ],
1279
+ extraFields: [
1280
+ {
1281
+ name: "apiKeySecret",
1282
+ label: "API Key Secret",
1283
+ required: true
1284
+ },
1285
+ {
1286
+ name: "accessToken",
1287
+ label: "Access Token",
1288
+ required: true
1289
+ },
1290
+ {
1291
+ name: "accessTokenSecret",
1292
+ label: "Access Token Secret",
1293
+ required: true
1294
+ }
1295
+ ],
1296
+ defaultDMPolicy: "allowlist"
1297
+ },
1298
+ {
1299
+ id: "irc",
1300
+ name: "IRC",
1301
+ emoji: "📡",
1302
+ requiresGateway: true,
1303
+ supportsDM: true,
1304
+ platforms: ["all"],
1305
+ tokenLabel: "NickServ password (optional)",
1306
+ tokenHint: "freenode, libera.chat, etc.",
1307
+ setupSteps: [
1308
+ "1. Choose IRC server (e.g. irc.libera.chat)",
1309
+ "2. Configure nick and password if needed",
1310
+ " 🔗 libera.chat"
1311
+ ],
1312
+ extraFields: [{
1313
+ name: "server",
1314
+ label: "IRC server",
1315
+ hint: "irc.libera.chat",
1316
+ required: true
1317
+ }, {
1318
+ name: "nick",
1319
+ label: "Nickname",
1320
+ required: true
1321
+ }],
1322
+ defaultDMPolicy: "allowlist"
1323
+ },
1324
+ {
1325
+ id: "mattermost",
1326
+ name: "Mattermost",
1327
+ emoji: "🏗️",
1328
+ requiresGateway: true,
1329
+ supportsDM: true,
1330
+ platforms: ["all"],
1331
+ tokenLabel: "Mattermost bot token",
1332
+ tokenHint: "Settings → Integrations → Bot Accounts",
1333
+ setupSteps: [
1334
+ "1. Mattermost → Account Settings → Security → Personal Access Tokens",
1335
+ "2. Create token and copy it",
1336
+ " 🔗 docs.mattermost.com"
1337
+ ],
1338
+ extraFields: [{
1339
+ name: "serverUrl",
1340
+ label: "Server URL",
1341
+ required: true
1342
+ }],
1343
+ defaultDMPolicy: "allowlist"
1344
+ },
1345
+ {
1346
+ id: "nextcloud",
1347
+ name: "Nextcloud Talk",
1348
+ emoji: "☁️",
1349
+ requiresGateway: true,
1350
+ supportsDM: true,
1351
+ platforms: ["all"],
1352
+ tokenLabel: "Nextcloud app password",
1353
+ tokenHint: "Your Nextcloud server",
1354
+ setupSteps: [
1355
+ "1. Nextcloud Admin → OAuth → new client",
1356
+ "2. Talk → view bot credentials",
1357
+ " 🔗 nextcloud.com"
1358
+ ],
1359
+ extraFields: [{
1360
+ name: "serverUrl",
1361
+ label: "Nextcloud URL",
1362
+ required: true
1363
+ }, {
1364
+ name: "username",
1365
+ label: "Username",
1366
+ required: true
1367
+ }],
1368
+ defaultDMPolicy: "allowlist"
1369
+ },
1370
+ {
1371
+ id: "googlechat",
1372
+ name: "Google Chat",
1373
+ emoji: "🔵",
1374
+ requiresGateway: true,
1375
+ supportsDM: false,
1376
+ platforms: ["all"],
1377
+ tokenLabel: "Google Chat Webhook URL",
1378
+ tokenHint: "chat.google.com → Space → Apps & Integrations",
1379
+ setupSteps: [
1380
+ "1. chat.google.com → Space → Apps & Integrations → Manage webhooks",
1381
+ "2. Add webhook and copy the URL",
1382
+ " 🔗 chat.google.com"
1383
+ ],
1384
+ defaultDMPolicy: "none"
1385
+ },
1386
+ {
1387
+ id: "whatsapp-baileys",
1388
+ name: "WhatsApp (Baileys)",
1389
+ emoji: "📲",
1390
+ requiresGateway: true,
1391
+ supportsDM: true,
1392
+ platforms: ["all"],
1393
+ tokenLabel: "Not required — scans QR code on first run",
1394
+ setupSteps: [
1395
+ "1. No Meta Business API needed — uses WhatsApp Web protocol",
1396
+ "2. Start the gateway — a QR code will appear in the terminal",
1397
+ "3. Open WhatsApp on your phone → Linked Devices → Link a device",
1398
+ "4. Scan the QR code. Session is saved — no QR needed on future starts",
1399
+ " 🔗 github.com/WhiskeySockets/Baileys"
1400
+ ],
1401
+ notes: "WhatsApp Web via Baileys — no Meta Business needed. QR scan on first run.",
1402
+ npmPackage: "@whiskeysockets/baileys",
1403
+ defaultDMPolicy: "pairing"
1404
+ },
1405
+ {
1406
+ id: "instagram",
1407
+ name: "Instagram DMs",
1408
+ emoji: "📸",
1409
+ requiresGateway: true,
1410
+ supportsDM: true,
1411
+ platforms: ["all"],
1412
+ tokenLabel: "Meta Page Access Token",
1413
+ tokenHint: "developers.facebook.com → Instagram product",
1414
+ setupSteps: [
1415
+ "1. Meta for Developers → My Apps → Create App → Business",
1416
+ "2. Add Instagram product → Connect Instagram Business account",
1417
+ "3. Webhooks: subscribe to messages, URL: https://<host>/webhook/instagram",
1418
+ "4. Copy Page Access Token from Graph API Explorer (pages_messaging scope)",
1419
+ " 🔗 developers.facebook.com"
1420
+ ],
1421
+ extraFields: [{
1422
+ name: "instagramAccountId",
1423
+ label: "Instagram Business Account ID",
1424
+ required: true
1425
+ }, {
1426
+ name: "verifyToken",
1427
+ label: "Webhook Verify Token (any string)",
1428
+ required: true
1429
+ }],
1430
+ notes: "Requires Instagram Business + Meta App",
1431
+ defaultDMPolicy: "pairing"
1432
+ },
1433
+ {
1434
+ id: "zalo-personal",
1435
+ name: "Zalo Personal",
1436
+ emoji: "🔵",
1437
+ requiresGateway: true,
1438
+ supportsDM: true,
1439
+ platforms: ["all"],
1440
+ tokenLabel: "Zalo cookie token",
1441
+ tokenHint: "Extracted from browser session",
1442
+ setupSteps: [
1443
+ "1. Open Zalo Web in browser (chat.zalo.me)",
1444
+ "2. Open DevTools → Application → Cookies → find zpw_sek or _zlang",
1445
+ "3. Copy the session cookie value",
1446
+ " ⚠ Unofficial API — may break on Zalo updates. Use at your own risk."
1447
+ ],
1448
+ notes: "Unofficial — uses Zalo Personal API. May break on updates.",
1449
+ defaultDMPolicy: "pairing"
1450
+ },
1451
+ {
1452
+ id: "voice-call",
1453
+ name: "Voice Call",
1454
+ emoji: "🎙️",
1455
+ requiresGateway: true,
1456
+ supportsDM: false,
1457
+ platforms: ["all"],
1458
+ tokenLabel: "Not required",
1459
+ setupSteps: [
1460
+ "1. No external account needed",
1461
+ "2. Requires microphone + ElevenLabs API key for TTS (optional)",
1462
+ "3. Start with: hyperclaw voice-call",
1463
+ " 💡 Works in terminal — voice input → agent → voice output"
1464
+ ],
1465
+ notes: "Terminal voice session — hyperclaw voice-call",
1466
+ defaultDMPolicy: "none"
1467
+ },
1468
+ {
1469
+ id: "web",
1470
+ name: "WebChat UI",
1471
+ emoji: "🌐",
1472
+ requiresGateway: true,
1473
+ supportsDM: false,
1474
+ platforms: ["all"],
1475
+ setupSteps: [
1476
+ "1. Built-in — no setup needed",
1477
+ "2. Start gateway: hyperclaw gateway",
1478
+ "3. Open: http://localhost:<port>/dashboard",
1479
+ " 💡 Works in any browser on your local network"
1480
+ ],
1481
+ notes: "Built-in WebChat at http://localhost:<port>",
1482
+ defaultDMPolicy: "none"
1483
+ },
1484
+ {
1485
+ id: "cli",
1486
+ name: "CLI / Terminal",
1487
+ emoji: "🖥️",
1488
+ requiresGateway: false,
1489
+ supportsDM: false,
1490
+ platforms: ["all"],
1491
+ setupSteps: [
1492
+ "1. Always active — no setup needed",
1493
+ "2. Use: hyperclaw chat or hyperclaw agent --message \"...\"",
1494
+ " 💡 Works offline without any channel configured"
1495
+ ],
1496
+ notes: "Always active — hyperclaw chat",
1497
+ defaultDMPolicy: "none"
1498
+ },
1499
+ {
1500
+ id: "chrome-extension",
1501
+ name: "Chrome Extension",
1502
+ emoji: "🔌",
1503
+ requiresGateway: true,
1504
+ supportsDM: false,
1505
+ platforms: ["all"],
1506
+ setupSteps: [
1507
+ "1. Open Chrome → chrome://extensions → Enable Developer mode",
1508
+ "2. Load unpacked → select: extensions/chrome-extension/",
1509
+ "3. Extension connects to gateway via WebSocket automatically",
1510
+ " 💡 Gives the agent access to your browser context"
1511
+ ],
1512
+ notes: "Load extensions/chrome-extension/ as unpacked extension in Chrome",
1513
+ defaultDMPolicy: "none"
1514
+ },
1515
+ {
1516
+ id: "synology-chat",
1517
+ name: "Synology Chat",
1518
+ emoji: "🖥️",
1519
+ requiresGateway: true,
1520
+ supportsDM: true,
1521
+ platforms: ["all"],
1522
+ tokenLabel: "Incoming Webhook URL",
1523
+ tokenHint: "DSM → Synology Chat → Integration → Incoming Webhooks",
1524
+ setupSteps: [
1525
+ "1. Open Synology Chat (DSM package)",
1526
+ "2. Top-right menu → Integration → Incoming Webhooks → Create",
1527
+ "3. Copy the Webhook URL (used to POST messages from HyperClaw)",
1528
+ "4. Outgoing Webhook: Integration → Outgoing Webhooks → Create",
1529
+ " URL: http://<your-server>:7789/synology-hook",
1530
+ " Method: POST",
1531
+ " 🔗 kb.synology.com/synologychat"
1532
+ ],
1533
+ extraFields: [{
1534
+ name: "webhookPort",
1535
+ label: "Outgoing webhook port",
1536
+ hint: "7789",
1537
+ required: false
1538
+ }, {
1539
+ name: "webhookToken",
1540
+ label: "Outgoing webhook token (optional HMAC)",
1541
+ required: false
1542
+ }],
1543
+ notes: "Requires Synology Chat installed on your Synology NAS (DSM 7+)",
1544
+ defaultDMPolicy: "pairing"
1545
+ },
1546
+ {
1547
+ id: "tlon",
1548
+ name: "Tlon (Urbit)",
1549
+ emoji: "🪐",
1550
+ requiresGateway: true,
1551
+ supportsDM: true,
1552
+ platforms: ["all"],
1553
+ tokenLabel: "Urbit login code",
1554
+ tokenHint: "From your Urbit ship: +code",
1555
+ setupSteps: [
1556
+ "1. Start your Urbit ship (e.g. via Port or urbit binary)",
1557
+ "2. In the Dojo: +code → copy the code",
1558
+ "3. Note your ship name (e.g. ~sampel-palnet) and ship URL",
1559
+ "4. Optionally configure a group: ~sampel-palnet/my-group",
1560
+ " 🔗 tlon.io · urbit.org/getting-started"
1561
+ ],
1562
+ extraFields: [
1563
+ {
1564
+ name: "shipUrl",
1565
+ label: "Ship URL",
1566
+ hint: "http://localhost:8080",
1567
+ required: true
1568
+ },
1569
+ {
1570
+ name: "ship",
1571
+ label: "Ship name",
1572
+ hint: "~sampel-palnet",
1573
+ required: true
1574
+ },
1575
+ {
1576
+ name: "group",
1577
+ label: "Group path (optional)",
1578
+ hint: "~sampel-palnet/my-group",
1579
+ required: false
1580
+ }
1581
+ ],
1582
+ notes: "Requires a running Urbit ship with Tlon/Groups installed",
1583
+ defaultDMPolicy: "pairing"
1584
+ },
1585
+ {
1586
+ id: "twitch",
1587
+ name: "Twitch",
1588
+ emoji: "🟣",
1589
+ requiresGateway: true,
1590
+ supportsDM: true,
1591
+ platforms: ["all"],
1592
+ tokenLabel: "OAuth token (oauth:xxxxxxx)",
1593
+ tokenHint: "twitchapps.com/tmi → connect with your bot account",
1594
+ setupSteps: [
1595
+ "1. Create a Twitch bot account (or use your own)",
1596
+ "2. Go to: twitchapps.com/tmi → Connect → copy the OAuth token",
1597
+ "3. Token format: oauth:xxxxxxxxxxxxxxxxxxxxxx",
1598
+ "4. Bot username = the Twitch account you logged in with",
1599
+ " 💡 To receive commands, users type: !<message>",
1600
+ " 💡 Moderators and the broadcaster bypass the allowlist by default",
1601
+ " 🔗 twitchapps.com/tmi"
1602
+ ],
1603
+ extraFields: [
1604
+ {
1605
+ name: "username",
1606
+ label: "Bot Twitch username (lowercase)",
1607
+ required: true
1608
+ },
1609
+ {
1610
+ name: "channels",
1611
+ label: "Channel(s) to join (comma-separated)",
1612
+ hint: "mychannel or mychannel,otherchannel",
1613
+ required: true
1614
+ },
1615
+ {
1616
+ name: "commandPrefix",
1617
+ label: "Command prefix",
1618
+ hint: "! (default)",
1619
+ required: false
1620
+ }
1621
+ ],
1622
+ notes: "Chat-based; uses Twitch IRC over WebSocket. Command prefix required (default: !)",
1623
+ defaultDMPolicy: "pairing"
1624
+ }
1625
+ ];
1626
+ async function getChannelStatus(def, configuredIds) {
1627
+ const platform = os.default.platform();
1628
+ if (!def.platforms.includes("all") && !def.platforms.includes(platform)) return "unavailable";
1629
+ if (configuredIds.includes(def.id)) return "configured";
1630
+ if (["telegram", "discord"].includes(def.id)) return "recommended";
1631
+ return "available";
1632
+ }
1633
+ /** Human-readable reason why a channel is unavailable on this OS */
1634
+ function unavailableReason(def) {
1635
+ const platform = os.default.platform();
1636
+ if (def.platforms.length === 1 && def.platforms[0] === "darwin") return "macOS only";
1637
+ if (def.platforms.includes("linux") && def.platforms.includes("darwin") && !def.platforms.includes("win32")) return "Linux/macOS only";
1638
+ if (!def.platforms.includes("all") && !def.platforms.includes(platform)) return `not supported on ${platform}`;
1639
+ return "unavailable on this OS";
1640
+ }
1641
+ function statusBadge(status, def) {
1642
+ switch (status) {
1643
+ case "configured": return chalk.default.green("[configured]");
1644
+ case "recommended": return chalk.default.cyan("[recommended]");
1645
+ case "available": return chalk.default.gray("[available]");
1646
+ case "unavailable": {
1647
+ const reason = def ? unavailableReason(def) : "unavailable";
1648
+ return chalk.default.red(`[${reason}]`);
1649
+ }
1650
+ }
1651
+ }
1652
+ const CHANNELS = CHANNEL_DEFS;
1653
+ async function getAvailableChannels(configuredIds = []) {
1654
+ const result = [];
1655
+ for (const def of CHANNEL_DEFS) {
1656
+ const status = await getChannelStatus(def, configuredIds);
1657
+ result.push({
1658
+ ...def,
1659
+ status
1660
+ });
1661
+ }
1662
+ return result;
1663
+ }
1664
+
1665
+ //#endregion
1666
+ //#region src/cli/memory.ts
1667
+ const HC_DIR = path.default.join(os.default.homedir(), ".hyperclaw");
1668
+ const AGENTS_FILE = path.default.join(HC_DIR, "AGENTS.md");
1669
+ const MEMORY_FILE = path.default.join(HC_DIR, "MEMORY.md");
1670
+ const SOUL_FILE = path.default.join(HC_DIR, "SOUL.md");
1671
+ const LOG_DIR = path.default.join(HC_DIR, "logs");
1672
+ var MemoryManager = class {
1673
+ async init(opts = {}) {
1674
+ await fs_extra.default.ensureDir(HC_DIR);
1675
+ await fs_extra.default.ensureDir(LOG_DIR);
1676
+ const agentName = opts.agentName || "HyperClaw";
1677
+ const userName = opts.userName || os.default.userInfo().username;
1678
+ const lang = opts.language || "English";
1679
+ const personality = opts.personality || "Direct and efficient, helpful without being sycophantic, honest about uncertainty";
1680
+ const rules = opts.rules && opts.rules.length > 0 ? opts.rules.map((r, i) => `${i + 1}. ${r}`).join("\n") : `1. Always respond in the user's preferred language unless asked otherwise
1681
+ 2. Be concise unless detail is explicitly requested
1682
+ 3. Never share API keys, tokens, or secrets in responses
1683
+ 4. If unsure, ask before acting — especially for destructive operations
1684
+ 5. Log all PC access actions to ~/.hyperclaw/pc-access.log`;
1685
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1686
+ if (!await fs_extra.default.pathExists(AGENTS_FILE)) await fs_extra.default.writeFile(AGENTS_FILE, `# AGENTS.md — Global Rules
1687
+ > All sessions and subagents must follow these rules.
1688
+
1689
+ ## Identity
1690
+ - Agent name: ${agentName}
1691
+ - User name: ${userName}
1692
+ - Primary language: ${lang}
1693
+ - Created: ${today}
1694
+
1695
+ ## Behavior Rules
1696
+ ${rules}
1697
+
1698
+ ## DM Policy Default
1699
+ - Require pairing before responding to unknown senders
1700
+ - Allowlist: (add trusted IDs here)
1701
+
1702
+ ## Hierarchy
1703
+ - SOUL.md: persona and values (read-only by subagents)
1704
+ - AGENTS.md: operational rules (this file)
1705
+ - MEMORY.md: accumulated facts about the user
1706
+ `);
1707
+ if (!await fs_extra.default.pathExists(MEMORY_FILE)) await fs_extra.default.writeFile(MEMORY_FILE, `# MEMORY.md — User Context
1708
+ > Automatically updated by HyperClaw after each session.
1709
+
1710
+ ## User Profile
1711
+ - Name: ${userName}
1712
+ - Language: ${lang}
1713
+ - Initialized: ${today}
1714
+
1715
+ ## Notes
1716
+ (auto-populated from conversations)
1717
+ `);
1718
+ if (!await fs_extra.default.pathExists(SOUL_FILE)) await fs_extra.default.writeFile(SOUL_FILE, `# SOUL.md — Agent Persona
1719
+ > Who I am and how I behave.
1720
+
1721
+ ## Name
1722
+ ${agentName}
1723
+
1724
+ ## Personality
1725
+ - ${personality.replace(/\n/g, "\n- ")}
1726
+
1727
+ ## Values
1728
+ - User autonomy first
1729
+ - Do no harm
1730
+ - Transparency about capabilities and limits
1731
+
1732
+ ## Wake phrase
1733
+ ${opts.wakeWord ? opts.wakeWord : `Wake up, ${agentName}! Your user ${userName} needs you.`}
1734
+ `);
1735
+ const logFile = path.default.join(LOG_DIR, `${today}.md`);
1736
+ if (!await fs_extra.default.pathExists(logFile)) await fs_extra.default.writeFile(logFile, `# Session Log — ${today}\n\n`);
1737
+ }
1738
+ async load() {
1739
+ if (!await fs_extra.default.pathExists(AGENTS_FILE)) return null;
1740
+ const agents = await fs_extra.default.readFile(AGENTS_FILE, "utf8").catch(() => "");
1741
+ const memory = await fs_extra.default.readFile(MEMORY_FILE, "utf8").catch(() => "");
1742
+ const soul = await fs_extra.default.readFile(SOUL_FILE, "utf8").catch(() => void 0);
1743
+ return {
1744
+ agents,
1745
+ memory,
1746
+ soul
1747
+ };
1748
+ }
1749
+ async appendRule(rule) {
1750
+ await fs_extra.default.ensureDir(HC_DIR);
1751
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1752
+ const line = `\n- ${today}: ${rule}\n`;
1753
+ await fs_extra.default.appendFile(AGENTS_FILE, line);
1754
+ console.log(chalk.default.green(` ✅ Rule added to AGENTS.md: ${rule}`));
1755
+ }
1756
+ async addMemory(fact) {
1757
+ await fs_extra.default.ensureDir(HC_DIR);
1758
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1759
+ await fs_extra.default.appendFile(MEMORY_FILE, `\n- ${today}: ${fact}\n`);
1760
+ console.log(chalk.default.green(` ✅ Memory saved: ${fact}`));
1761
+ }
1762
+ async updateSoul(content) {
1763
+ await fs_extra.default.ensureDir(HC_DIR);
1764
+ await fs_extra.default.writeFile(SOUL_FILE, content);
1765
+ console.log(chalk.default.green(" ✅ SOUL.md updated"));
1766
+ }
1767
+ async show() {
1768
+ const data = await this.load();
1769
+ if (!data) {
1770
+ console.log(chalk.default.yellow("\n No memory initialized. Run: hyperclaw init\n"));
1771
+ return;
1772
+ }
1773
+ console.log(chalk.default.bold.cyan("\n 🧠 MEMORY\n"));
1774
+ for (const [label, content] of [
1775
+ ["SOUL.md", data.soul],
1776
+ ["AGENTS.md", data.agents],
1777
+ ["MEMORY.md", data.memory]
1778
+ ]) {
1779
+ if (!content) continue;
1780
+ console.log(chalk.default.bold.white(` ── ${label} ──`));
1781
+ const lines = content.split("\n").slice(0, 20);
1782
+ for (const line of lines) if (line.startsWith("#")) console.log(chalk.default.cyan(` ${line}`));
1783
+ else if (line.startsWith("-")) console.log(chalk.default.gray(` ${line}`));
1784
+ else console.log(` ${line}`);
1785
+ console.log();
1786
+ }
1787
+ }
1788
+ async runPersonaBootstrap() {
1789
+ console.log(chalk.default.bold.cyan("\n 🌅 Wake up, my friend!\n"));
1790
+ const { agentName } = await inquirer.default.prompt([{
1791
+ type: "input",
1792
+ name: "agentName",
1793
+ message: "What should I call myself? (agent name)",
1794
+ default: "HyperClaw"
1795
+ }]);
1796
+ const { userName } = await inquirer.default.prompt([{
1797
+ type: "input",
1798
+ name: "userName",
1799
+ message: `What shall ${agentName} call you?`,
1800
+ default: os.default.userInfo().username
1801
+ }]);
1802
+ console.log(chalk.default.green(`\n ✨ I am ${agentName}. Hello, ${userName}.\n`));
1803
+ return {
1804
+ agentName,
1805
+ userName
1806
+ };
1807
+ }
1808
+ async getContextForAI() {
1809
+ let context = "";
1810
+ for (const [label, file] of [
1811
+ ["SOUL", SOUL_FILE],
1812
+ ["AGENTS", AGENTS_FILE],
1813
+ ["MEMORY", MEMORY_FILE]
1814
+ ]) if (await fs_extra.default.pathExists(file)) {
1815
+ const content = await fs_extra.default.readFile(file, "utf8");
1816
+ context += `## ${label}.md\n${content}\n\n`;
1817
+ }
1818
+ return context;
1819
+ }
1820
+ async search(query) {
1821
+ const data = await this.load();
1822
+ if (!data) {
1823
+ console.log(chalk.default.gray(" No memory\n"));
1824
+ return;
1825
+ }
1826
+ const allText = [
1827
+ data.agents,
1828
+ data.memory,
1829
+ data.soul || ""
1830
+ ].join("\n");
1831
+ const lines = allText.split("\n").filter((l) => l.toLowerCase().includes(query.toLowerCase()));
1832
+ if (lines.length === 0) {
1833
+ console.log(chalk.default.gray(` Nothing found for "${query}"\n`));
1834
+ return;
1835
+ }
1836
+ console.log(chalk.default.bold.cyan(`\n 🔍 "${query}"\n`));
1837
+ lines.forEach((l) => console.log(` ${l}`));
1838
+ console.log();
1839
+ }
1840
+ async clear(file = "memory") {
1841
+ const { confirm } = await inquirer.default.prompt([{
1842
+ type: "confirm",
1843
+ name: "confirm",
1844
+ message: `Clear ${file === "all" ? "ALL memory files" : "MEMORY.md"}?`,
1845
+ default: false
1846
+ }]);
1847
+ if (!confirm) return;
1848
+ if (file === "all") {
1849
+ await fs_extra.default.remove(AGENTS_FILE);
1850
+ await fs_extra.default.remove(MEMORY_FILE);
1851
+ await fs_extra.default.remove(SOUL_FILE);
1852
+ console.log(chalk.default.green(" ✅ All memory cleared"));
1853
+ } else {
1854
+ await fs_extra.default.writeFile(MEMORY_FILE, "# MEMORY.md\n\n");
1855
+ console.log(chalk.default.green(" ✅ MEMORY.md cleared"));
1856
+ }
1857
+ }
1858
+ };
1859
+
1860
+ //#endregion
1861
+ //#region src/cli/security.ts
1862
+ async function showSecurityDisclaimer() {
1863
+ console.clear();
1864
+ console.log(chalk.default.bgRed.white.bold("\n ⚠️ SECURITY NOTICE — READ CAREFULLY ⚠️ \n"));
1865
+ console.log(chalk.default.white.bold(" A bad prompt can trick it into doing unsafe things\n"));
1866
+ console.log(chalk.default.hex("#06b6d4")(" Protections to enable:"));
1867
+ console.log(chalk.default.gray(" ● Pairing / allowlists — limit who can DM your agent"));
1868
+ console.log(chalk.default.gray(" ● Sandbox + least privilege — restrict PC access level"));
1869
+ console.log(chalk.default.gray(" ● Keep secrets out — never put tokens in SOUL.md"));
1870
+ console.log(chalk.default.gray(" ● Use strongest model — smarter = better at refusing tricks\n"));
1871
+ const { understood } = await inquirer.default.prompt([{
1872
+ type: "confirm",
1873
+ name: "understood",
1874
+ message: chalk.default.bold("I understand this is powerful and inherently risky. Continue?"),
1875
+ default: false
1876
+ }]);
1877
+ if (!understood) console.log(chalk.default.gray("\n Aborted. Come back when ready.\n"));
1878
+ return understood;
1879
+ }
1880
+ async function configureDMPolicy(channelName) {
1881
+ const { configureDMPolicy: cdmp } = await Promise.resolve().then(() => require("./security-EQ5o-nr4.js"));
1882
+ const res = await cdmp(channelName);
1883
+ return {
1884
+ dmPolicy: res.policy,
1885
+ allowFrom: res.allowFrom ?? []
1886
+ };
1887
+ }
1888
+
1889
+ //#endregion
1890
+ //#region src/terminal/banner.ts
1891
+ require_theme.init_theme();
1892
+ var Banner = class {
1893
+ async showNeonBanner(daemonMode = false) {
1894
+ console.clear();
1895
+ const t = require_theme.getTheme(daemonMode);
1896
+ const icon$1 = daemonMode ? "🩸" : "🦅";
1897
+ try {
1898
+ const title = figlet.default.textSync("HYPERCLAW", { font: "ANSI Shadow" });
1899
+ const g = (0, gradient_string.default)(t.gradient);
1900
+ const lines = title.split("\n");
1901
+ const first = lines[0] ?? "";
1902
+ console.log(`\n ${icon$1} ` + g(first));
1903
+ for (let i = 1; i < lines.length; i++) console.log(g(" " + (lines[i] ?? "")));
1904
+ } catch {
1905
+ console.log(chalk.default.bold.red(`\n ${icon$1} HYPERCLAW\n`));
1906
+ }
1907
+ const subtitle = daemonMode ? chalk.default.hex(t.daemonPrimary)(" 🩸 DAEMON MODE — ALWAYS WATCHING ⚡\n") : t.muted(" 🦅 HyperClaw Bot — AI Gateway v4.0.1 ⚡\n");
1908
+ console.log(subtitle);
1909
+ const boxOpts = {
1910
+ padding: 1,
1911
+ margin: {
1912
+ top: 1,
1913
+ bottom: 1
1914
+ },
1915
+ borderStyle: "round",
1916
+ borderColor: daemonMode ? t.daemonBorderColor : t.borderColor
1917
+ };
1918
+ if (t.boxBg) boxOpts.backgroundColor = t.boxBg;
1919
+ const box = (0, boxen.default)(`${t.a("●")} GATEWAY READY ${t.a("◆")} PROVIDERS: 8 ${t.a("⚡")} CHANNELS: 27 ` + (daemonMode ? `${t.d("🩸")} DAEMON` : `${t.a("🦅")} HYPERCLAW`), boxOpts);
1920
+ console.log(box);
1921
+ console.log(t.muted(" One assistant. All your channels. 🦅\n"));
1922
+ const { maybeShowUpdateNotice } = await Promise.resolve().then(() => require("./update-check-BQOH_-LD.js"));
1923
+ maybeShowUpdateNotice(daemonMode);
1924
+ }
1925
+ async showMiniBanner() {
1926
+ await this.showNeonBanner(false);
1927
+ }
1928
+ async showWizardBanner() {
1929
+ console.clear();
1930
+ const t = require_theme.getTheme(false);
1931
+ const g = (0, gradient_string.default)(t.gradient);
1932
+ try {
1933
+ const title = figlet.default.textSync("HYPERCLAW", { font: "ANSI Shadow" });
1934
+ const lines = title.split("\n");
1935
+ const first = lines[0] ?? "";
1936
+ console.log("\n 🦅 " + g(first));
1937
+ for (let i = 1; i < lines.length; i++) console.log(g(" " + (lines[i] ?? "")));
1938
+ } catch {
1939
+ console.log(t.bold("\n 🦅 HYPERCLAW\n"));
1940
+ }
1941
+ console.log(t.muted(" 🦅 HyperClaw Bot — AI Gateway • SETUP WIZARD v4.0.1 ⚡\n"));
1942
+ const boxOpts = {
1943
+ padding: 1,
1944
+ margin: { bottom: 1 },
1945
+ borderStyle: "round",
1946
+ borderColor: t.borderColor
1947
+ };
1948
+ if (t.boxBg) boxOpts.backgroundColor = t.boxBg;
1949
+ const box = (0, boxen.default)(t.a("◆") + " Provider • Channels • Gateway • Identity", boxOpts);
1950
+ console.log(box);
1951
+ }
1952
+ async showStatus() {
1953
+ const t = require_theme.getTheme(false);
1954
+ const { ConfigManager } = await Promise.resolve().then(() => require("./manager-BX2lIsmk.js"));
1955
+ const { GatewayManager: GatewayManager$1 } = await Promise.resolve().then(() => require("./manager-UY7MecrN.js"));
1956
+ const cfg = await new ConfigManager().load();
1957
+ const gm = new GatewayManager$1();
1958
+ const port = cfg?.gateway?.port ?? 18789;
1959
+ const running = await gm.isRunning(port);
1960
+ console.log(t.bold("\n 🦅 HYPERCLAW STATUS\n"));
1961
+ console.log(` Gateway: ${running ? t.success("● running") : t.error("○ stopped")} port ${port}`);
1962
+ console.log(` Provider: ${t.c(cfg?.provider?.providerId ?? "none")}`);
1963
+ console.log(` Channels: ${t.c(String((cfg?.channels ?? []).length))}`);
1964
+ console.log();
1965
+ }
1966
+ };
1967
+
1968
+ //#endregion
1969
+ //#region src/infra/channel-icons.ts
1970
+ const CDN = "https://cdn.simpleicons.org";
1971
+ function icon(slug, name, color, darkColor) {
1972
+ return {
1973
+ slug,
1974
+ name,
1975
+ color,
1976
+ url: `${CDN}/${slug}`,
1977
+ darkColor
1978
+ };
1979
+ }
1980
+ const CHANNEL_ICONS = {
1981
+ "telegram": icon("telegram", "Telegram", "26A5E4"),
1982
+ "discord": icon("discord", "Discord", "5865F2"),
1983
+ "whatsapp": icon("whatsapp", "WhatsApp", "25D366"),
1984
+ "whatsapp-baileys": icon("whatsapp", "WhatsApp (Baileys)", "25D366"),
1985
+ "slack": icon("slack", "Slack", "4A154B"),
1986
+ "signal": icon("signal", "Signal", "3A76F0", "FFFFFF"),
1987
+ "imessage": icon("imessage", "iMessage", "29CC40"),
1988
+ "imessage-native": icon("imessage", "iMessage (native)", "29CC40"),
1989
+ "matrix": icon("matrix", "Matrix", "000000", "FFFFFF"),
1990
+ "email": icon("gmail", "Email", "EA4335"),
1991
+ "feishu": icon("lark", "Feishu / Lark", "00D6B9"),
1992
+ "msteams": icon("microsoftteams", "Microsoft Teams", "6264A7"),
1993
+ "messenger": icon("messenger", "Facebook Messenger", "00B2FF"),
1994
+ "nostr": icon("nostr", "Nostr", "8E44AD", "C39BD3"),
1995
+ "line": icon("line", "LINE", "00C300"),
1996
+ "viber": icon("viber", "Viber", "7360F2"),
1997
+ "zalo": icon("zalo", "Zalo", "0068FF"),
1998
+ "zalo-personal": icon("zalo", "Zalo Personal", "0068FF"),
1999
+ "twitter": icon("x", "Twitter / X", "000000", "FFFFFF"),
2000
+ "irc": icon("irc", "IRC", "1A3B5C", "FFFFFF"),
2001
+ "mattermost": icon("mattermost", "Mattermost", "0058CC"),
2002
+ "nextcloud": icon("nextcloud", "Nextcloud Talk", "0082C9"),
2003
+ "googlechat": icon("googlechat", "Google Chat", "00897B"),
2004
+ "instagram": icon("instagram", "Instagram", "E4405F"),
2005
+ "synology-chat": icon("synology", "Synology Chat", "B5B5B6", "FFFFFF"),
2006
+ "tlon": icon("urbit", "Tlon (Urbit)", "000000", "FFFFFF"),
2007
+ "twitch": icon("twitch", "Twitch", "9146FF"),
2008
+ "voice-call": icon("webrtc", "Voice Call", "333333", "FFFFFF"),
2009
+ "web": icon("googlechrome", "WebChat", "4285F4"),
2010
+ "cli": icon("gnubash", "CLI / Terminal", "4EAA25", "FFFFFF"),
2011
+ "chrome-extension": icon("googlechrome", "Chrome Extension", "4285F4")
2012
+ };
2013
+ const PROVIDER_ICONS = {
2014
+ "anthropic": icon("anthropic", "Anthropic (Claude)", "D4C5A9", "191919"),
2015
+ "openai": icon("openai", "OpenAI", "000000", "FFFFFF"),
2016
+ "openrouter": icon("openrouter", "OpenRouter", "6467F2"),
2017
+ "groq": icon("groq", "Groq", "F55036"),
2018
+ "xai": icon("x", "xAI (Grok)", "000000", "FFFFFF"),
2019
+ "custom": icon("jsonwebtokens", "Custom OpenAI API", "A0A0A0"),
2020
+ "ollama": icon("ollama", "Ollama (local)", "000000", "FFFFFF"),
2021
+ "mistral": icon("mistral", "Mistral AI", "FF7000"),
2022
+ "cohere": icon("cohere", "Cohere", "39594D", "FFFFFF"),
2023
+ "google": icon("googlegemini", "Google Gemini", "8E75B2"),
2024
+ "deepseek": icon("deepseek", "DeepSeek", "4D6BFE"),
2025
+ "together": icon("togetherai", "Together AI", "000000", "FFFFFF")
2026
+ };
2027
+
2028
+ //#endregion
2029
+ //#region src/cli/onboard.ts
2030
+ /** Brand-colored ■ square — closest we can get to a real icon in a terminal */
2031
+ function brandIcon(id, type = "channel") {
2032
+ const map = type === "channel" ? CHANNEL_ICONS : PROVIDER_ICONS;
2033
+ const ic = map[id];
2034
+ if (!ic) return chalk.default.gray("■");
2035
+ return chalk.default.hex("#" + ic.color)("■");
2036
+ }
2037
+ var HyperClawWizard = class {
2038
+ config = new ConfigStore();
2039
+ daemon = new DaemonManager();
2040
+ gateway = new GatewayManager();
2041
+ async run(options) {
2042
+ const proceed = await showSecurityDisclaimer();
2043
+ if (!proceed) return;
2044
+ const { allThemes, getThemeName, setThemeName } = await Promise.resolve().then(() => require("./theme-Iefa3L63.js"));
2045
+ const currentTheme = getThemeName();
2046
+ const { chosenTheme } = await inquirer.default.prompt([{
2047
+ type: "list",
2048
+ name: "chosenTheme",
2049
+ message: "🎨 Choose a color theme:",
2050
+ default: currentTheme,
2051
+ choices: [
2052
+ {
2053
+ name: `${chalk.default.hex("#06b6d4")("■")} Dark Professional ${chalk.default.gray("(neon cyan on black)")}`,
2054
+ value: "dark"
2055
+ },
2056
+ {
2057
+ name: `${chalk.default.hex("#64748b")("■")} Grey Professional ${chalk.default.gray("(muted cyan, neutral)")}`,
2058
+ value: "grey"
2059
+ },
2060
+ {
2061
+ name: `${chalk.default.hex("#0284c7")("■")} White / Light ${chalk.default.gray("(deep cyan, light bg)")}`,
2062
+ value: "white"
2063
+ }
2064
+ ]
2065
+ }]);
2066
+ if (chosenTheme !== currentTheme) await setThemeName(chosenTheme);
2067
+ await new Banner().showWizardBanner();
2068
+ const { mode } = await inquirer.default.prompt([{
2069
+ type: "list",
2070
+ name: "mode",
2071
+ message: "Setup mode:",
2072
+ choices: [{
2073
+ name: `${chalk.default.green("★")} QuickStart ${chalk.default.gray("(Recommended)")}`,
2074
+ value: "quick"
2075
+ }, {
2076
+ name: ` Manual ${chalk.default.gray("(Full control)")}`,
2077
+ value: "manual"
2078
+ }]
2079
+ }]);
2080
+ if (mode === "quick") return this.quickSetup(options);
2081
+ return this.fullSetup(options);
2082
+ }
2083
+ async quickstart(options) {
2084
+ const proceed = await showSecurityDisclaimer();
2085
+ if (!proceed) return;
2086
+ return this.quickSetup(options);
2087
+ }
2088
+ stepHeader(n, total, title) {
2089
+ const { getTheme: getTheme$1 } = (require_theme.init_theme(), require_chunk.__toCommonJS(require_theme.theme_exports));
2090
+ const t = getTheme$1(false);
2091
+ const bar = chalk.default.gray("─".repeat(52));
2092
+ console.log(`\n${bar}`);
2093
+ console.log(` ${t.c(`Step ${n} / ${total}`)} · ${chalk.default.bold(title)}`);
2094
+ console.log(`${bar}\n`);
2095
+ }
2096
+ async quickSetup(options) {
2097
+ const STEPS = 7;
2098
+ this.stepHeader(1, STEPS, "Workspace");
2099
+ const { workspaceName } = await inquirer.default.prompt([{
2100
+ type: "input",
2101
+ name: "workspaceName",
2102
+ message: "Workspace name:",
2103
+ default: "my-hyperclaw"
2104
+ }]);
2105
+ this.stepHeader(2, STEPS, "AI Providers & Models");
2106
+ const { providers: allProviders, primary: providerConfig } = await this.selectProvidersAndModels();
2107
+ this.stepHeader(3, STEPS, "Gateway (optional)");
2108
+ const gatewayConfig = await this.configureGateway(false);
2109
+ this.stepHeader(4, STEPS, "Channels");
2110
+ const channelConfigs = await this.selectChannels();
2111
+ this.stepHeader(5, STEPS, "Agent Identity & Persona");
2112
+ const identity = await this.configureIdentity();
2113
+ this.stepHeader(6, STEPS, "Skills & Hooks");
2114
+ const { hooks, heartbeat: heartbeatEnabled } = await this.configureSkillsAndHooks();
2115
+ this.stepHeader(7, STEPS, "Extras");
2116
+ const memoryIntegration = await this.configureMemoryIntegration();
2117
+ const serviceApiKeys = await this.configureServiceApiKeys();
2118
+ const hyperclawbotConfig = await this.configureHyperClawBot(gatewayConfig);
2119
+ const talkModeConfig = await this.configureTalkMode();
2120
+ await this.saveAll({
2121
+ workspaceName,
2122
+ providerConfig,
2123
+ providers: allProviders,
2124
+ channelConfigs,
2125
+ gatewayConfig,
2126
+ identity,
2127
+ hooks,
2128
+ heartbeatEnabled,
2129
+ memoryIntegration,
2130
+ serviceApiKeys,
2131
+ hyperclawbotConfig,
2132
+ talkModeConfig
2133
+ }, options);
2134
+ }
2135
+ async fullSetup(options) {
2136
+ const STEPS = 9;
2137
+ this.stepHeader(1, STEPS, "Workspace");
2138
+ const { workspaceName } = await inquirer.default.prompt([{
2139
+ type: "input",
2140
+ name: "workspaceName",
2141
+ message: "Workspace directory:",
2142
+ default: "my-hyperclaw"
2143
+ }]);
2144
+ this.stepHeader(2, STEPS, "AI Providers & Models");
2145
+ const { providers: allProviders, primary: providerConfig } = await this.selectProvidersAndModels();
2146
+ this.stepHeader(3, STEPS, "Gateway");
2147
+ const gatewayConfig = await this.configureGateway(true);
2148
+ this.stepHeader(4, STEPS, "Channels");
2149
+ const { configureChannels } = await inquirer.default.prompt([{
2150
+ type: "confirm",
2151
+ name: "configureChannels",
2152
+ message: "Configure chat channels now?",
2153
+ default: true
2154
+ }]);
2155
+ const channelConfigs = configureChannels ? await this.selectChannels(true) : {};
2156
+ if (configureChannels) {
2157
+ const { configureDM } = await inquirer.default.prompt([{
2158
+ type: "confirm",
2159
+ name: "configureDM",
2160
+ message: "Configure DM access policies now?",
2161
+ default: true
2162
+ }]);
2163
+ if (configureDM) for (const [channelId] of Object.entries(channelConfigs)) {
2164
+ const ch = CHANNELS.find((c) => c.id === channelId);
2165
+ if (ch?.supportsDM) {
2166
+ const dm = await configureDMPolicy(ch.name);
2167
+ channelConfigs[channelId].dmPolicy = dm;
2168
+ }
2169
+ }
2170
+ }
2171
+ this.stepHeader(5, STEPS, "Agent Identity & Persona");
2172
+ const identity = await this.configureIdentity();
2173
+ this.stepHeader(6, STEPS, "Skills & Hooks");
2174
+ const { hooks, heartbeat: heartbeatEnabled } = await this.configureSkillsAndHooks();
2175
+ this.stepHeader(7, STEPS, "Services & Daemon");
2176
+ let installDaemon = options.installDaemon ?? false;
2177
+ if (!installDaemon) {
2178
+ console.log(chalk.default.gray(" The daemon runs the gateway as a background service that starts on boot.\n"));
2179
+ const ans = await inquirer.default.prompt([{
2180
+ type: "confirm",
2181
+ name: "installDaemon",
2182
+ message: "Install as system daemon (auto-start on boot)?",
2183
+ default: false
2184
+ }]);
2185
+ installDaemon = ans.installDaemon;
2186
+ } else console.log(chalk.default.green(" ✔ Daemon will be installed automatically (--install-daemon)\n"));
2187
+ this.stepHeader(8, STEPS, "Extras");
2188
+ const memoryIntegration = await this.configureMemoryIntegration();
2189
+ const serviceApiKeys = await this.configureServiceApiKeys();
2190
+ const hyperclawbotConfig = await this.configureHyperClawBot(gatewayConfig);
2191
+ const talkModeConfig = await this.configureTalkMode();
2192
+ this.stepHeader(9, STEPS, "Launch");
2193
+ const launchChoices = [{
2194
+ name: `TUI — terminal dashboard`,
2195
+ value: "tui"
2196
+ }];
2197
+ if (gatewayConfig && !gatewayConfig.remote) launchChoices.push({
2198
+ name: `Web — browser at http://localhost:${gatewayConfig.port}`,
2199
+ value: "web"
2200
+ });
2201
+ launchChoices.push({
2202
+ name: chalk.default.gray("Do this later — I will start manually"),
2203
+ value: "skip"
2204
+ });
2205
+ const { hatchMode } = await inquirer.default.prompt([{
2206
+ type: "list",
2207
+ name: "hatchMode",
2208
+ message: "How do you want to hatch your bot?",
2209
+ choices: launchChoices
2210
+ }]);
2211
+ await this.saveAll({
2212
+ workspaceName,
2213
+ providerConfig,
2214
+ providers: allProviders,
2215
+ channelConfigs,
2216
+ gatewayConfig: gatewayConfig ? {
2217
+ ...gatewayConfig,
2218
+ hooks: hooks.length > 0
2219
+ } : null,
2220
+ identity,
2221
+ hooks,
2222
+ heartbeatEnabled,
2223
+ installDaemon,
2224
+ hatchMode,
2225
+ memoryIntegration,
2226
+ serviceApiKeys,
2227
+ hyperclawbotConfig,
2228
+ talkModeConfig
2229
+ }, options);
2230
+ }
2231
+ async selectProvidersAndModels() {
2232
+ const { getTheme: getTheme$1 } = (require_theme.init_theme(), require_chunk.__toCommonJS(require_theme.theme_exports));
2233
+ const t = getTheme$1(false);
2234
+ console.log(chalk.default.gray(" Select one or more AI providers. The first one will be primary.\n"));
2235
+ const { selectedProviderIds } = await inquirer.default.prompt([{
2236
+ type: "checkbox",
2237
+ name: "selectedProviderIds",
2238
+ message: "Select AI providers:",
2239
+ validate: (v) => v.length > 0 || "Select at least one provider",
2240
+ choices: PROVIDERS.map((p) => ({
2241
+ name: `${brandIcon(p.id, "provider")} ${p.displayName.replace(/^.{1,2}\s/, "").padEnd(18)}${p.supportsTranscription ? chalk.default.gray(" 🎤") : ""}`,
2242
+ value: p.id,
2243
+ checked: p.id === "openrouter"
2244
+ }))
2245
+ }]);
2246
+ const configured = [];
2247
+ for (let i = 0; i < selectedProviderIds.length; i++) {
2248
+ const pid = selectedProviderIds[i];
2249
+ const provider = getProvider(pid);
2250
+ const isPrimary = i === 0;
2251
+ console.log(`\n ${t.c("▸")} ${chalk.default.bold(provider.displayName)} ${isPrimary ? chalk.default.green("(primary)") : chalk.default.gray(`(#${i + 1})`)}\n`);
2252
+ if (provider.authHint) console.log(chalk.default.gray(` 💡 ${provider.authHint}\n`));
2253
+ let apiKey = "";
2254
+ let baseUrl;
2255
+ let modelId = "";
2256
+ if (provider.authType === "api_key") if (pid === "custom") {
2257
+ const r = await inquirer.default.prompt([
2258
+ {
2259
+ type: "input",
2260
+ name: "baseUrl",
2261
+ message: " Base URL:",
2262
+ validate: (v) => v.trim().length > 5 || "Required"
2263
+ },
2264
+ {
2265
+ type: "password",
2266
+ name: "apiKey",
2267
+ message: ` ${provider.authLabel}:`,
2268
+ mask: "●",
2269
+ validate: (v) => v.trim().length > 5 || "Required"
2270
+ },
2271
+ {
2272
+ type: "input",
2273
+ name: "modelId",
2274
+ message: " Model ID:",
2275
+ validate: (v) => v.trim().length > 0 || "Required"
2276
+ }
2277
+ ]);
2278
+ baseUrl = r.baseUrl.trim().replace(/\/$/, "");
2279
+ apiKey = r.apiKey;
2280
+ modelId = r.modelId.trim();
2281
+ } else {
2282
+ const r = await inquirer.default.prompt([{
2283
+ type: "password",
2284
+ name: "apiKey",
2285
+ message: ` ${provider.authLabel}:`,
2286
+ mask: "●"
2287
+ }]);
2288
+ apiKey = r.apiKey;
2289
+ }
2290
+ if (!modelId) {
2291
+ const modelChoices = provider.models.filter((m) => m.id !== "__manual__").length ? [...provider.models.filter((m) => m.id !== "__manual__").map((m) => ({
2292
+ name: formatModel(m),
2293
+ value: m.id
2294
+ })), {
2295
+ name: chalk.default.gray("Enter manually..."),
2296
+ value: "__manual__"
2297
+ }] : [{
2298
+ name: chalk.default.gray("Enter model ID manually"),
2299
+ value: "__manual__"
2300
+ }];
2301
+ const { modelChoice } = await inquirer.default.prompt([{
2302
+ type: "list",
2303
+ name: "modelChoice",
2304
+ message: " Default model:",
2305
+ choices: modelChoices
2306
+ }]);
2307
+ if (modelChoice === "__manual__") {
2308
+ const { manual } = await inquirer.default.prompt([{
2309
+ type: "input",
2310
+ name: "manual",
2311
+ message: " Model ID:",
2312
+ default: provider.models[0]?.id || ""
2313
+ }]);
2314
+ modelId = manual;
2315
+ } else modelId = modelChoice;
2316
+ }
2317
+ configured.push({
2318
+ providerId: pid,
2319
+ apiKey,
2320
+ modelId,
2321
+ ...baseUrl ? { baseUrl } : {}
2322
+ });
2323
+ console.log(t.c(` ✔ ${provider.displayName} → ${modelId}\n`));
2324
+ }
2325
+ return {
2326
+ providers: configured,
2327
+ primary: configured[0]
2328
+ };
2329
+ }
2330
+ async configureGateway(full = false) {
2331
+ const { gatewayMode } = await inquirer.default.prompt([{
2332
+ type: "list",
2333
+ name: "gatewayMode",
2334
+ message: "Gateway setup:",
2335
+ choices: [
2336
+ {
2337
+ name: `Local gateway ${chalk.default.gray("(run on this machine)")}`,
2338
+ value: "local"
2339
+ },
2340
+ {
2341
+ name: `Remote gateway ${chalk.default.gray("(info-only — connect to existing server)")}`,
2342
+ value: "remote"
2343
+ },
2344
+ {
2345
+ name: chalk.default.gray("Skip (no gateway — CLI-only mode)"),
2346
+ value: "skip"
2347
+ }
2348
+ ]
2349
+ }]);
2350
+ if (gatewayMode === "skip") {
2351
+ console.log(chalk.default.gray(" ↳ Gateway skipped. Enable later: hyperclaw gateway start\n"));
2352
+ return null;
2353
+ }
2354
+ if (gatewayMode === "remote") {
2355
+ const { remoteUrl } = await inquirer.default.prompt([{
2356
+ type: "input",
2357
+ name: "remoteUrl",
2358
+ message: "Remote gateway URL (e.g. wss://myserver.com:18789):",
2359
+ validate: (v) => v.startsWith("ws") || "Must start with ws:// or wss://"
2360
+ }]);
2361
+ const { remoteToken } = await inquirer.default.prompt([{
2362
+ type: "password",
2363
+ name: "remoteToken",
2364
+ message: "Remote gateway auth token:",
2365
+ mask: "●"
2366
+ }]);
2367
+ console.log(chalk.default.green(" ✔ Remote gateway configured\n"));
2368
+ return {
2369
+ port: 18789,
2370
+ bind: remoteUrl,
2371
+ authToken: remoteToken,
2372
+ tailscaleExposure: "off",
2373
+ runtime: "node",
2374
+ enabledChannels: [],
2375
+ hooks: true,
2376
+ remote: true
2377
+ };
2378
+ }
2379
+ const { useGateway } = await inquirer.default.prompt([{
2380
+ type: "confirm",
2381
+ name: "useGateway",
2382
+ message: "Enable the local WebSocket gateway? (needed for channels, web UI, mobile apps)",
2383
+ default: true
2384
+ }]);
2385
+ if (!useGateway) {
2386
+ console.log(chalk.default.gray(" ↳ Gateway skipped. You can enable it later: hyperclaw gateway start\n"));
2387
+ return null;
2388
+ }
2389
+ const running = await this.gateway.isRunning(18789);
2390
+ if (running) console.log(chalk.default.gray(` ⚠ Gateway already running at ws://127.0.0.1:18789`));
2391
+ const detectedRuntime = await this.gateway.detectRuntime();
2392
+ const questions = [{
2393
+ type: "input",
2394
+ name: "port",
2395
+ message: "Gateway port:",
2396
+ default: String(18789),
2397
+ validate: (v) => Number(v) > 1024 && Number(v) < 65535 ? true : "Enter valid port (1024-65535)"
2398
+ }];
2399
+ if (full) questions.push({
2400
+ type: "list",
2401
+ name: "bind",
2402
+ message: "Bind address:",
2403
+ choices: [
2404
+ {
2405
+ name: `127.0.0.1 ${chalk.default.gray("Loopback only (secure)")}`,
2406
+ value: "127.0.0.1"
2407
+ },
2408
+ {
2409
+ name: `0.0.0.0 ${chalk.default.gray("All interfaces (LAN)")}`,
2410
+ value: "0.0.0.0"
2411
+ },
2412
+ {
2413
+ name: `Tailscale ${chalk.default.gray("VPN peers only")}`,
2414
+ value: "tailscale"
2415
+ },
2416
+ {
2417
+ name: `Custom ${chalk.default.gray("Enter IP manually")}`,
2418
+ value: "custom"
2419
+ }
2420
+ ],
2421
+ default: "127.0.0.1"
2422
+ }, {
2423
+ type: "list",
2424
+ name: "tailscaleExposure",
2425
+ message: "Tailscale exposure:",
2426
+ choices: [
2427
+ {
2428
+ name: this.gateway.exposureLabel("off"),
2429
+ value: "off"
2430
+ },
2431
+ {
2432
+ name: this.gateway.exposureLabel("serve"),
2433
+ value: "serve"
2434
+ },
2435
+ {
2436
+ name: this.gateway.exposureLabel("funnel"),
2437
+ value: "funnel"
2438
+ }
2439
+ ],
2440
+ default: "off",
2441
+ when: (a) => a.bind === "tailscale"
2442
+ }, {
2443
+ type: "list",
2444
+ name: "runtime",
2445
+ message: "Runtime:",
2446
+ choices: [
2447
+ {
2448
+ name: `Node.js ${detectedRuntime === "node" ? chalk.default.green("(detected)") : ""}`,
2449
+ value: "node"
2450
+ },
2451
+ {
2452
+ name: `Bun ${detectedRuntime === "bun" ? chalk.default.green("(detected)") : chalk.default.gray("(faster)")}`,
2453
+ value: "bun"
2454
+ },
2455
+ {
2456
+ name: `Deno ${detectedRuntime === "deno" ? chalk.default.green("(detected)") : ""}`,
2457
+ value: "deno"
2458
+ }
2459
+ ],
2460
+ default: detectedRuntime
2461
+ });
2462
+ questions.push({
2463
+ type: "password",
2464
+ name: "authToken",
2465
+ message: `Auth token: ${chalk.default.gray("(blank = auto-generate)")}`,
2466
+ mask: "●"
2467
+ });
2468
+ const answers = await inquirer.default.prompt(questions);
2469
+ const token = answers.authToken || this.gateway.generateToken();
2470
+ if (!answers.authToken) console.log(chalk.default.green(" ✔ Auth token auto-generated\n"));
2471
+ return {
2472
+ port: Number(answers.port),
2473
+ bind: answers.bind || "127.0.0.1",
2474
+ authToken: token,
2475
+ tailscaleExposure: answers.tailscaleExposure || "off",
2476
+ runtime: answers.runtime || detectedRuntime,
2477
+ enabledChannels: [],
2478
+ hooks: true
2479
+ };
2480
+ }
2481
+ async selectChannels(full = false) {
2482
+ console.log(chalk.default.hex("#06b6d4")("\n 📱 Communication Channels\n"));
2483
+ const available = await getAvailableChannels();
2484
+ const { selectedChannels } = await inquirer.default.prompt([{
2485
+ type: "checkbox",
2486
+ name: "selectedChannels",
2487
+ message: "Enable channels:",
2488
+ choices: available.map((ch) => {
2489
+ const isUnavailable = ch.status === "unavailable";
2490
+ const reason = isUnavailable ? unavailableReason(ch) : "";
2491
+ const icon$1 = brandIcon(ch.id, "channel");
2492
+ return {
2493
+ name: `${icon$1} ${ch.name.padEnd(18)} ${statusBadge(ch.status, ch)}${ch.requiresGateway && !isUnavailable ? chalk.default.gray(" [needs gateway]") : ""}`,
2494
+ value: ch.id,
2495
+ checked: ch.status === "recommended",
2496
+ disabled: isUnavailable ? reason : false
2497
+ };
2498
+ })
2499
+ }]);
2500
+ const channelConfigs = {};
2501
+ for (const channelId of selectedChannels) {
2502
+ const ch = CHANNELS.find((c) => c.id === channelId);
2503
+ if (!ch || !ch.tokenLabel) continue;
2504
+ console.log(chalk.default.hex("#06b6d4")(`\n ${ch.emoji} ${ch.name}`));
2505
+ if (ch.setupSteps?.length) ch.setupSteps.forEach((s) => console.log(chalk.default.gray(` ${s}`)));
2506
+ else if (ch.tokenHint) console.log(chalk.default.gray(` 💡 ${ch.tokenHint}`));
2507
+ const fields = [{
2508
+ type: "password",
2509
+ name: "token",
2510
+ message: `${ch.tokenLabel}:`,
2511
+ mask: "●"
2512
+ }];
2513
+ for (const f of ch.extraFields || []) fields.push({
2514
+ type: "input",
2515
+ name: f.name,
2516
+ message: `${f.label}:${f.hint ? chalk.default.gray(` (${f.hint})`) : ""}`,
2517
+ ...f.required ? { validate: (v) => v.trim().length > 0 || `${f.label} is required` } : {}
2518
+ });
2519
+ channelConfigs[channelId] = await inquirer.default.prompt(fields);
2520
+ }
2521
+ if (selectedChannels.includes("twitch") && channelConfigs["twitch"]?.channels) {
2522
+ const raw = channelConfigs["twitch"].channels;
2523
+ channelConfigs["twitch"].channels = raw.split(",").map((s) => s.trim().replace(/^#/, "").toLowerCase()).filter(Boolean);
2524
+ }
2525
+ if (selectedChannels.includes("email")) {
2526
+ const { wantGmailOAuth } = await inquirer.default.prompt([{
2527
+ type: "confirm",
2528
+ name: "wantGmailOAuth",
2529
+ message: "Enable Gmail OAuth for Pub/Sub real-time (send via hyperclaw gmail watch-setup)?",
2530
+ default: false
2531
+ }]);
2532
+ if (wantGmailOAuth) {
2533
+ console.log(chalk.default.gray(" Running OAuth flow for google-gmail..."));
2534
+ try {
2535
+ const { runOAuthFlow } = await Promise.resolve().then(() => require("./oauth-flow-HdvMxKmZ.js"));
2536
+ const { writeOAuthToken } = await Promise.resolve().then(() => require("./oauth-provider-DfEgSucI.js"));
2537
+ const tokens = await runOAuthFlow("google-gmail", {});
2538
+ const now = Math.floor(Date.now() / 1e3);
2539
+ const expires_at = tokens.expires_in ? now + tokens.expires_in : void 0;
2540
+ await writeOAuthToken("google-gmail", {
2541
+ access_token: tokens.access_token,
2542
+ refresh_token: tokens.refresh_token,
2543
+ expires_at,
2544
+ token_url: "https://oauth2.googleapis.com/token"
2545
+ });
2546
+ console.log(chalk.default.green(" ✔ Gmail OAuth configured — next: hyperclaw gmail watch-setup"));
2547
+ } catch (e) {
2548
+ console.log(chalk.default.yellow(` ⚠ OAuth failed — you can run it later: hyperclaw auth oauth google-gmail`));
2549
+ }
2550
+ }
2551
+ }
2552
+ return channelConfigs;
2553
+ }
2554
+ async configureServiceApiKeys() {
2555
+ console.log(chalk.default.hex("#06b6d4")("\n 🔑 Service API Keys — any app with an API key\n"));
2556
+ console.log(chalk.default.gray(" Stored securely in config. How they work:\n"));
2557
+ console.log(chalk.default.gray(" • Wizard: add keys here\n"));
2558
+ console.log(chalk.default.gray(" • Config: ~/.hyperclaw/hyperclaw.json → skills.apiKeys\n"));
2559
+ console.log(chalk.default.gray(" • Env: HACKERONE_*, BUGCROWD_*, SYNACK_*, or CUSTOM_ID_API_KEY\n"));
2560
+ console.log(chalk.default.gray(" • Tools: built-in tools read them automatically for research.\n"));
2561
+ const { wantServiceKeys } = await inquirer.default.prompt([{
2562
+ type: "confirm",
2563
+ name: "wantServiceKeys",
2564
+ message: "Add API keys for external services (HackerOne, Bugcrowd, Synack, or custom)?",
2565
+ default: false
2566
+ }]);
2567
+ if (!wantServiceKeys) return {};
2568
+ const KNOWN_SERVICES = [
2569
+ {
2570
+ id: "hackerone",
2571
+ name: "HackerOne",
2572
+ hint: "username:token (Basic auth)"
2573
+ },
2574
+ {
2575
+ id: "bugcrowd",
2576
+ name: "Bugcrowd",
2577
+ hint: "Token from Bugcrowd API Credentials"
2578
+ },
2579
+ {
2580
+ id: "synack",
2581
+ name: "Synack",
2582
+ hint: "API token from Synack"
2583
+ },
2584
+ {
2585
+ id: "__custom__",
2586
+ name: "Other (custom)",
2587
+ hint: "Any app with an API key"
2588
+ }
2589
+ ];
2590
+ const { servicesToAdd } = await inquirer.default.prompt([{
2591
+ type: "checkbox",
2592
+ name: "servicesToAdd",
2593
+ message: "Select services:",
2594
+ choices: KNOWN_SERVICES.filter((s) => s.id !== "__custom__").map((s) => ({
2595
+ name: `${s.name} ${chalk.default.gray(`(${s.hint})`)}`,
2596
+ value: s.id
2597
+ })),
2598
+ validate: (v) => true
2599
+ }]);
2600
+ const apiKeys = {};
2601
+ for (const sid of servicesToAdd || []) {
2602
+ const svc = KNOWN_SERVICES.find((s) => s.id === sid);
2603
+ const r = await inquirer.default.prompt([{
2604
+ type: "password",
2605
+ name: "key",
2606
+ message: `${svc?.name || sid} API key:`,
2607
+ mask: "●",
2608
+ validate: (v) => v.trim().length > 3 || "Required"
2609
+ }]);
2610
+ apiKeys[sid] = r.key.trim();
2611
+ }
2612
+ const { addCustom } = await inquirer.default.prompt([{
2613
+ type: "confirm",
2614
+ name: "addCustom",
2615
+ message: "Add a custom service (any app)?",
2616
+ default: false
2617
+ }]);
2618
+ if (addCustom) {
2619
+ const { customId, customKey } = await inquirer.default.prompt([{
2620
+ type: "input",
2621
+ name: "customId",
2622
+ message: "Service ID (e.g. my-app, ads-power):",
2623
+ validate: (v) => /^[a-z0-9_-]+$/i.test(v?.trim() || "") || "Letters, numbers, - _ only"
2624
+ }, {
2625
+ type: "password",
2626
+ name: "customKey",
2627
+ message: "API key:",
2628
+ mask: "●",
2629
+ validate: (v) => v.trim().length > 3 || "Required"
2630
+ }]);
2631
+ apiKeys[customId.trim().toLowerCase()] = customKey.trim();
2632
+ }
2633
+ if (Object.keys(apiKeys).length > 0) console.log(chalk.default.green(` ✔ Saved ${Object.keys(apiKeys).length} API key(s)`));
2634
+ return apiKeys;
2635
+ }
2636
+ async configureHyperClawBot(gatewayConfig) {
2637
+ console.log(chalk.default.hex("#06b6d4")("\n 🤖 HyperClaw Bot — Remote control via Telegram\n"));
2638
+ const trans = getTranscriptionProviders();
2639
+ if (trans.length > 0) {
2640
+ console.log(chalk.default.gray(" 🎤 Voice notes: " + trans.map((p) => `${p.displayName}`).join(", ") + " — if you have their API key, voice messages will be transcribed to text."));
2641
+ console.log();
2642
+ }
2643
+ const { wantHyperClawBot } = await inquirer.default.prompt([{
2644
+ type: "confirm",
2645
+ name: "wantHyperClawBot",
2646
+ message: "Enable HyperClaw Bot for remote control (status, restart, /agent from mobile)?",
2647
+ default: false
2648
+ }]);
2649
+ if (!wantHyperClawBot) return void 0;
2650
+ const { token } = await inquirer.default.prompt([{
2651
+ type: "input",
2652
+ name: "token",
2653
+ message: "Telegram Bot token (from @BotFather):",
2654
+ validate: (v) => v.trim().length > 10 || "Required"
2655
+ }]);
2656
+ const { userIds } = await inquirer.default.prompt([{
2657
+ type: "input",
2658
+ name: "userIds",
2659
+ message: "Allowed user IDs (comma-separated, leave empty = everyone):",
2660
+ default: ""
2661
+ }]);
2662
+ const gatewayUrl = `http://localhost:${gatewayConfig?.port || 18789}`;
2663
+ const { saveBotConfig } = await Promise.resolve().then(() => require("./hyperclawbot-DfMGowZC.js"));
2664
+ await saveBotConfig({
2665
+ platform: "telegram",
2666
+ token: token.trim(),
2667
+ gatewayUrl,
2668
+ allowedUsers: userIds ? userIds.split(",").map((s) => s.trim()).filter(Boolean) : [],
2669
+ gatewayToken: void 0,
2670
+ enabled: true,
2671
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2672
+ });
2673
+ console.log(chalk.default.green(" ✔ HyperClaw Bot configured — Start: hyperclaw bot start"));
2674
+ return { token: token.trim() };
2675
+ }
2676
+ async configureTalkMode() {
2677
+ console.log(chalk.default.hex("#06b6d4")("\n 🎙️ Talk Mode — ElevenLabs TTS\n"));
2678
+ const { wantTalkMode } = await inquirer.default.prompt([{
2679
+ type: "confirm",
2680
+ name: "wantTalkMode",
2681
+ message: "Enable Talk Mode (voice responses via ElevenLabs)?",
2682
+ default: false
2683
+ }]);
2684
+ if (!wantTalkMode) return void 0;
2685
+ const { apiKey } = await inquirer.default.prompt([{
2686
+ type: "password",
2687
+ name: "apiKey",
2688
+ message: "ElevenLabs API key (elevenlabs.io):",
2689
+ mask: "●",
2690
+ validate: (v) => v.trim().length > 10 || "Required"
2691
+ }]);
2692
+ console.log(chalk.default.green(" ✔ Talk Mode configured"));
2693
+ return {
2694
+ apiKey: apiKey.trim(),
2695
+ voiceId: "21m00Tcm4TlvDq8ikWAM",
2696
+ modelId: "eleven_multilingual_v2"
2697
+ };
2698
+ }
2699
+ async configureMemoryIntegration() {
2700
+ console.log(chalk.default.hex("#06b6d4")("\n 📂 Memory Integration (Obsidian / Raycast / Hazel)\n"));
2701
+ const { vaultPath } = await inquirer.default.prompt([{
2702
+ type: "input",
2703
+ name: "vaultPath",
2704
+ message: "Sync memory to vault? Enter path or leave empty to skip:",
2705
+ default: ""
2706
+ }]);
2707
+ const vaultDir = String(vaultPath || "").trim();
2708
+ if (!vaultDir) return void 0;
2709
+ const { dailyNotes } = await inquirer.default.prompt([{
2710
+ type: "confirm",
2711
+ name: "dailyNotes",
2712
+ message: "Write daily notes with session summaries?",
2713
+ default: true
2714
+ }]);
2715
+ return {
2716
+ vaultDir,
2717
+ dailyNotes,
2718
+ syncOnAppend: true
2719
+ };
2720
+ }
2721
+ async configureIdentity() {
2722
+ console.log(chalk.default.hex("#06b6d4")("\n 🦅 Agent Identity\n"));
2723
+ const identity = await inquirer.default.prompt([
2724
+ {
2725
+ type: "input",
2726
+ name: "userName",
2727
+ message: "Your name:",
2728
+ default: "Boss"
2729
+ },
2730
+ {
2731
+ type: "input",
2732
+ name: "agentName",
2733
+ message: "Agent name:",
2734
+ default: "Hyper"
2735
+ },
2736
+ {
2737
+ type: "input",
2738
+ name: "wakeWord",
2739
+ message: "Wake word for voice:",
2740
+ default: (a) => `Hey ${a.agentName || "Hyper"}`
2741
+ },
2742
+ {
2743
+ type: "list",
2744
+ name: "personality",
2745
+ message: "Personality:",
2746
+ choices: [
2747
+ "Professional and concise",
2748
+ "Friendly and casual",
2749
+ "Witty with humor",
2750
+ "Direct and no-nonsense",
2751
+ "Custom"
2752
+ ]
2753
+ },
2754
+ {
2755
+ type: "input",
2756
+ name: "customPersonality",
2757
+ message: "Describe personality:",
2758
+ when: (a) => a.personality === "Custom"
2759
+ },
2760
+ {
2761
+ type: "list",
2762
+ name: "language",
2763
+ message: "Primary language:",
2764
+ choices: [
2765
+ "English",
2766
+ "Greek",
2767
+ "Spanish",
2768
+ "French",
2769
+ "German",
2770
+ "Chinese",
2771
+ "Japanese",
2772
+ "Arabic"
2773
+ ]
2774
+ }
2775
+ ]);
2776
+ console.log(chalk.default.gray("\n This is the greeting sent to your channels when the agent starts.\n"));
2777
+ const { wakeUpMessage } = await inquirer.default.prompt([{
2778
+ type: "input",
2779
+ name: "wakeUpMessage",
2780
+ message: "Agent wake-up message:",
2781
+ default: (identity.agentName || "Hyper") + " is online. How can I help you today?"
2782
+ }]);
2783
+ const { wantSystemPrompt } = await inquirer.default.prompt([{
2784
+ type: "confirm",
2785
+ name: "wantSystemPrompt",
2786
+ message: "Add a custom system prompt / instructions for the agent?",
2787
+ default: false
2788
+ }]);
2789
+ let systemPrompt = "";
2790
+ if (wantSystemPrompt) {
2791
+ console.log(chalk.default.gray(" Tip: describe role, restrictions, tone, specific knowledge, etc.\n"));
2792
+ const r = await inquirer.default.prompt([{
2793
+ type: "editor",
2794
+ name: "systemPrompt",
2795
+ message: "System prompt (opens editor — save & close to continue):"
2796
+ }]);
2797
+ systemPrompt = r.systemPrompt?.trim() || "";
2798
+ }
2799
+ const globalRules = [
2800
+ "Always respond in the user's preferred language",
2801
+ "Never reveal the gateway auth token or API keys",
2802
+ "Confirm before destructive or irreversible actions",
2803
+ "Respect user privacy — never share conversation data",
2804
+ "All subagents inherit these rules — cannot be overridden"
2805
+ ];
2806
+ const { addRules } = await inquirer.default.prompt([{
2807
+ type: "confirm",
2808
+ name: "addRules",
2809
+ message: "Add custom rules to AGENTS.md?",
2810
+ default: false
2811
+ }]);
2812
+ let customRules = [];
2813
+ if (addRules) {
2814
+ const { rules } = await inquirer.default.prompt([{
2815
+ type: "input",
2816
+ name: "rules",
2817
+ message: "Rules (semicolon-separated):",
2818
+ filter: (v) => v.split(";").map((r) => r.trim()).filter(Boolean)
2819
+ }]);
2820
+ customRules = rules;
2821
+ }
2822
+ const personality = identity.personality === "Custom" ? identity.customPersonality || "Custom" : identity.personality;
2823
+ return {
2824
+ agentName: identity.agentName,
2825
+ userName: identity.userName,
2826
+ language: identity.language,
2827
+ personality,
2828
+ wakeUpMessage: wakeUpMessage.trim(),
2829
+ systemPrompt: systemPrompt || void 0,
2830
+ rules: [...globalRules, ...customRules]
2831
+ };
2832
+ }
2833
+ async configureSkillsAndHooks() {
2834
+ const { wantSkills } = await inquirer.default.prompt([{
2835
+ type: "confirm",
2836
+ name: "wantSkills",
2837
+ message: "Configure skills & hooks now?",
2838
+ default: false
2839
+ }]);
2840
+ if (!wantSkills) {
2841
+ console.log(chalk.default.gray(" ↳ Skipped — enable later: hyperclaw hooks enable <name>\n"));
2842
+ return {
2843
+ hooks: [],
2844
+ heartbeat: false
2845
+ };
2846
+ }
2847
+ const { selectedHooks } = await inquirer.default.prompt([{
2848
+ type: "checkbox",
2849
+ name: "selectedHooks",
2850
+ message: "Select hooks to enable:",
2851
+ choices: [
2852
+ {
2853
+ name: `${"boot.md".padEnd(22)} ${chalk.default.gray("Run commands on agent startup")}`,
2854
+ value: "boot"
2855
+ },
2856
+ {
2857
+ name: `${"command-logger".padEnd(22)} ${chalk.default.gray("Log every tool call to file")}`,
2858
+ value: "command-logger"
2859
+ },
2860
+ {
2861
+ name: `${"session-memory".padEnd(22)} ${chalk.default.gray("Persist session context across restarts")}`,
2862
+ value: "session-memory"
2863
+ },
2864
+ {
2865
+ name: `${"morning-briefing".padEnd(22)} ${chalk.default.gray("Daily proactive summary to HEARTBEAT.md")}`,
2866
+ value: "morning-briefing"
2867
+ }
2868
+ ]
2869
+ }]);
2870
+ const heartbeat = selectedHooks.includes("morning-briefing");
2871
+ try {
2872
+ const { HookLoader } = await Promise.resolve().then(() => require("./loader-aSIGRXBq.js"));
2873
+ const loader = new HookLoader();
2874
+ for (const h of selectedHooks) loader.enable(h);
2875
+ } catch {}
2876
+ if (selectedHooks.length > 0) console.log(chalk.default.green(` ✔ Enabled: ${selectedHooks.join(", ")}\n`));
2877
+ return {
2878
+ hooks: selectedHooks,
2879
+ heartbeat
2880
+ };
2881
+ }
2882
+ async saveAll(data, options) {
2883
+ console.log();
2884
+ const spinner = (0, ora.default)("Saving configuration...").start();
2885
+ const current = await this.config.load();
2886
+ const skillsPatch = { installed: current?.skills?.installed || [] };
2887
+ if (data.serviceApiKeys && Object.keys(data.serviceApiKeys).length > 0) skillsPatch.apiKeys = {
2888
+ ...current?.skills?.apiKeys || {},
2889
+ ...data.serviceApiKeys
2890
+ };
2891
+ if (current?.skills?.vtApiKey) skillsPatch.vtApiKey = current.skills.vtApiKey;
2892
+ const finalSkills = skillsPatch.apiKeys || skillsPatch.vtApiKey ? skillsPatch : current?.skills || { installed: [] };
2893
+ await this.config.save({
2894
+ ...current,
2895
+ version: "4.0.1",
2896
+ workspaceName: data.workspaceName,
2897
+ provider: data.providerConfig,
2898
+ providers: data.providers || (data.providerConfig ? [data.providerConfig] : []),
2899
+ gateway: data.gatewayConfig || void 0,
2900
+ channels: Object.keys(data.channelConfigs || {}),
2901
+ channelConfigs: data.channelConfigs,
2902
+ skills: finalSkills,
2903
+ enabledHooks: data.hooks || [],
2904
+ identity: {
2905
+ agentName: data.identity?.agentName,
2906
+ userName: data.identity?.userName,
2907
+ language: data.identity?.language,
2908
+ wakeWord: data.identity?.wakeWord || `Hey ${data.identity?.agentName || "Hyper"}`,
2909
+ wakeUpMessage: data.identity?.wakeUpMessage,
2910
+ systemPrompt: data.identity?.systemPrompt,
2911
+ personality: data.identity?.personality,
2912
+ rules: data.identity?.rules
2913
+ },
2914
+ memoryIntegration: data.memoryIntegration,
2915
+ talkMode: data.talkModeConfig,
2916
+ pcAccess: {
2917
+ enabled: true,
2918
+ level: "full",
2919
+ allowedPaths: [],
2920
+ allowedCommands: [],
2921
+ confirmDestructive: false,
2922
+ maxOutputBytes: 5e4
2923
+ },
2924
+ hatchMode: data.hatchMode || "tui",
2925
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
2926
+ });
2927
+ spinner.succeed("Configuration saved");
2928
+ const memory = new MemoryManager();
2929
+ await memory.init(data.identity);
2930
+ if (data.heartbeatEnabled) try {
2931
+ const { HookLoader } = await Promise.resolve().then(() => require("./loader-aSIGRXBq.js"));
2932
+ const loader = new HookLoader();
2933
+ loader.enable("morning-briefing");
2934
+ console.log(chalk.default.gray(" ✔ Morning Briefing hook enabled"));
2935
+ } catch {}
2936
+ await this.testConnections(data.channelConfigs || {});
2937
+ if (data.installDaemon || options.daemon || options.installDaemon) {
2938
+ const s = (0, ora.default)("🩸 Installing system daemon...").start();
2939
+ await this.daemon.install();
2940
+ s.succeed(chalk.default.red("🩸 System daemon installed (starts on boot)"));
2941
+ }
2942
+ if (data.gatewayConfig?.tailscaleExposure && data.gatewayConfig.tailscaleExposure !== "off") await this.gateway.applyTailscaleExposure(data.gatewayConfig.tailscaleExposure, data.gatewayConfig.port);
2943
+ if (data.gatewayConfig && (options.startNow || data.installDaemon)) {
2944
+ const s = (0, ora.default)("Starting gateway...").start();
2945
+ await this.daemon.start();
2946
+ s.succeed(`Gateway running at ws://localhost:${data.gatewayConfig.port}`);
2947
+ }
2948
+ this.showSuccessScreen(data);
2949
+ }
2950
+ async testConnections(configs) {
2951
+ for (const [channelId, cfg] of Object.entries(configs)) {
2952
+ const ch = CHANNELS.find((c) => c.id === channelId);
2953
+ if (!ch || !cfg?.token) continue;
2954
+ const s = (0, ora.default)(`Testing ${ch.name}...`).start();
2955
+ await new Promise((r) => setTimeout(r, 900 + Math.random() * 600));
2956
+ s.succeed(`${ch.emoji} ${ch.name} connected`);
2957
+ }
2958
+ }
2959
+ showSuccessScreen(data) {
2960
+ const channels = Object.keys(data.channelConfigs || {});
2961
+ const gwUrl = `ws://localhost:${data.gatewayConfig?.port || 1515}`;
2962
+ const hasEmail = channels.includes("email");
2963
+ const hasHyperClawBot = !!data.hyperclawbotConfig?.token;
2964
+ const cmdLines = [
2965
+ chalk.default.gray(" hyperclaw dashboard — TUI dashboard"),
2966
+ chalk.default.gray(" hyperclaw hub — Skill hub"),
2967
+ chalk.default.gray(" hyperclaw gateway status — Gateway panel"),
2968
+ chalk.default.gray(" hyperclaw ") + chalk.default.red("daemon") + chalk.default.gray(" status — Service status"),
2969
+ chalk.default.gray(" hyperclaw voice — Voice settings"),
2970
+ chalk.default.gray(" hyperclaw canvas show — AI canvas")
2971
+ ];
2972
+ if (hasHyperClawBot) cmdLines.push(chalk.default.gray(" hyperclaw bot start — HyperClawBot remote control"));
2973
+ if (hasEmail) cmdLines.push(chalk.default.gray(" hyperclaw gmail watch-setup — Gmail Pub/Sub (real-time)"));
2974
+ cmdLines.push(chalk.default.gray(" hyperclaw nodes — Mobile nodes (Connect tab)"));
2975
+ cmdLines.push(chalk.default.gray(" hyperclaw cron list — Scheduled tasks"));
2976
+ const lines = [
2977
+ `${chalk.default.gray("Agent:")} ${chalk.default.hex("#06b6d4")(data.identity?.agentName)} (you: ${data.identity?.userName})`,
2978
+ `${chalk.default.gray("Model:")} ${data.providerConfig?.modelId}`,
2979
+ `${chalk.default.gray("Provider:")} ${data.providerConfig?.providerId}`,
2980
+ `${chalk.default.gray("Gateway:")} ${gwUrl}`,
2981
+ `${chalk.default.gray("Channels:")} ${channels.length ? channels.join(", ") : "CLI only"}`,
2982
+ "",
2983
+ chalk.default.hex("#06b6d4")("Commands:"),
2984
+ ...cmdLines
2985
+ ].join("\n");
2986
+ console.log("\n" + (0, boxen.default)(chalk.default.hex("#06b6d4")("🎉 HyperClaw v4.0.1 ready!\n\n") + lines, {
2987
+ padding: 1,
2988
+ borderStyle: "round",
2989
+ borderColor: "cyan",
2990
+ margin: 1,
2991
+ backgroundColor: "#0a0a0a"
2992
+ }));
2993
+ }
2994
+ };
2995
+
2996
+ //#endregion
2997
+ Object.defineProperty(exports, 'Banner', {
2998
+ enumerable: true,
2999
+ get: function () {
3000
+ return Banner;
3001
+ }
3002
+ });
3003
+ Object.defineProperty(exports, 'ConfigStore', {
3004
+ enumerable: true,
3005
+ get: function () {
3006
+ return ConfigStore;
3007
+ }
3008
+ });
3009
+ Object.defineProperty(exports, 'DaemonManager', {
3010
+ enumerable: true,
3011
+ get: function () {
3012
+ return DaemonManager;
3013
+ }
3014
+ });
3015
+ Object.defineProperty(exports, 'GatewayManager', {
3016
+ enumerable: true,
3017
+ get: function () {
3018
+ return GatewayManager;
3019
+ }
3020
+ });
3021
+ Object.defineProperty(exports, 'HyperClawWizard', {
3022
+ enumerable: true,
3023
+ get: function () {
3024
+ return HyperClawWizard;
3025
+ }
3026
+ });