@synergenius/flow-weaver 0.23.0 → 0.23.3

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,184 @@
1
+ /**
2
+ * Device Connection — WebSocket client for connecting a local machine
3
+ * to the Flow Weaver platform as a mounted device.
4
+ *
5
+ * This is the transport layer. Packs register their own request handlers
6
+ * on top of this connection (e.g., improve status, bot management).
7
+ *
8
+ * The platform relays Studio requests to connected devices and forwards
9
+ * device events to Studio subscribers.
10
+ */
11
+ export class DeviceConnection {
12
+ ws = null;
13
+ heartbeatInterval = null;
14
+ reconnectTimeout = null;
15
+ requestHandlers = new Map();
16
+ connected = false;
17
+ shouldReconnect = true;
18
+ options;
19
+ deviceInfo;
20
+ log;
21
+ constructor(options) {
22
+ this.options = options;
23
+ this.log = options.logger ?? (() => { });
24
+ const os = require('node:os');
25
+ this.deviceInfo = {
26
+ name: options.deviceName ?? os.hostname(),
27
+ hostname: os.hostname(),
28
+ projectDir: options.projectDir,
29
+ platform: process.platform,
30
+ capabilities: [],
31
+ };
32
+ }
33
+ /**
34
+ * Add a capability to advertise to the platform.
35
+ */
36
+ addCapability(capability) {
37
+ if (!this.deviceInfo.capabilities.includes(capability)) {
38
+ this.deviceInfo.capabilities.push(capability);
39
+ }
40
+ }
41
+ /**
42
+ * Set the list of packs that contributed device handlers.
43
+ * The platform uses this to auto-install packs in the user's workspace.
44
+ */
45
+ setPacks(packs) {
46
+ this.deviceInfo.packs = packs;
47
+ }
48
+ /**
49
+ * Register a handler for incoming requests from the platform.
50
+ */
51
+ onRequest(method, handler) {
52
+ this.requestHandlers.set(method, handler);
53
+ }
54
+ /**
55
+ * Connect to the platform. Reconnects automatically on disconnect.
56
+ */
57
+ async connect() {
58
+ const wsUrl = this.options.platformUrl
59
+ .replace(/^http/, 'ws')
60
+ .replace(/\/$/, '') + '/ws/device';
61
+ this.log(`Connecting to ${wsUrl}...`);
62
+ this.shouldReconnect = true;
63
+ return new Promise((resolve, reject) => {
64
+ try {
65
+ this.ws = new WebSocket(`${wsUrl}?token=${encodeURIComponent(this.options.token)}`);
66
+ }
67
+ catch (err) {
68
+ reject(err);
69
+ return;
70
+ }
71
+ this.ws.addEventListener('open', () => {
72
+ this.connected = true;
73
+ this.log(`Connected as "${this.deviceInfo.name}"`);
74
+ // Send device registration
75
+ this.send({ type: 'device:register', device: this.deviceInfo });
76
+ // Start heartbeat
77
+ this.heartbeatInterval = setInterval(() => {
78
+ if (this.ws?.readyState === WebSocket.OPEN) {
79
+ this.send({ type: 'heartbeat', timestamp: Date.now() });
80
+ }
81
+ }, 30_000);
82
+ this.options.onConnect?.();
83
+ resolve();
84
+ });
85
+ this.ws.addEventListener('message', async (event) => {
86
+ try {
87
+ const msg = JSON.parse(typeof event.data === 'string' ? event.data : String(event.data));
88
+ await this.handleMessage(msg);
89
+ }
90
+ catch (err) {
91
+ this.log(`Parse error: ${err instanceof Error ? err.message : err}`);
92
+ }
93
+ });
94
+ this.ws.addEventListener('close', (event) => {
95
+ this.connected = false;
96
+ if (this.heartbeatInterval)
97
+ clearInterval(this.heartbeatInterval);
98
+ this.options.onDisconnect?.(event.code);
99
+ if (this.shouldReconnect) {
100
+ this.log(`Disconnected (${event.code}). Reconnecting in 5s...`);
101
+ this.scheduleReconnect();
102
+ }
103
+ });
104
+ this.ws.addEventListener('error', () => {
105
+ if (!this.connected) {
106
+ reject(new Error('WebSocket connection failed'));
107
+ }
108
+ else {
109
+ this.log('Connection error');
110
+ }
111
+ });
112
+ });
113
+ }
114
+ /**
115
+ * Emit an event to the platform.
116
+ */
117
+ emit(event) {
118
+ if (!this.connected)
119
+ return;
120
+ this.send({ type: 'device:event', event });
121
+ this.options.onEvent?.(event);
122
+ }
123
+ /**
124
+ * Disconnect from the platform. No auto-reconnect.
125
+ */
126
+ disconnect() {
127
+ this.shouldReconnect = false;
128
+ if (this.heartbeatInterval)
129
+ clearInterval(this.heartbeatInterval);
130
+ if (this.reconnectTimeout)
131
+ clearTimeout(this.reconnectTimeout);
132
+ if (this.ws) {
133
+ this.ws.close(1000, 'Device disconnecting');
134
+ this.ws = null;
135
+ }
136
+ this.connected = false;
137
+ }
138
+ isConnected() {
139
+ return this.connected;
140
+ }
141
+ getDeviceInfo() {
142
+ return this.deviceInfo;
143
+ }
144
+ // --- Private ---
145
+ send(msg) {
146
+ if (this.ws?.readyState === WebSocket.OPEN) {
147
+ this.ws.send(JSON.stringify(msg));
148
+ }
149
+ }
150
+ async handleMessage(msg) {
151
+ const type = String(msg.type ?? '');
152
+ const requestId = String(msg.requestId ?? '');
153
+ if (type === 'request') {
154
+ const method = String(msg.method ?? '');
155
+ const params = msg.params ?? {};
156
+ const handler = this.requestHandlers.get(method);
157
+ if (!handler) {
158
+ this.send({ type: 'response', requestId, success: false, error: `Unknown method: ${method}` });
159
+ return;
160
+ }
161
+ try {
162
+ const result = await handler(method, params);
163
+ this.send({ type: 'response', requestId, success: true, result });
164
+ }
165
+ catch (err) {
166
+ this.send({ type: 'response', requestId, success: false, error: err instanceof Error ? err.message : String(err) });
167
+ }
168
+ }
169
+ }
170
+ scheduleReconnect() {
171
+ if (this.reconnectTimeout)
172
+ clearTimeout(this.reconnectTimeout);
173
+ this.reconnectTimeout = setTimeout(async () => {
174
+ try {
175
+ await this.connect();
176
+ }
177
+ catch {
178
+ this.log('Reconnect failed. Retrying in 10s...');
179
+ this.reconnectTimeout = setTimeout(() => this.scheduleReconnect(), 10_000);
180
+ }
181
+ }, 5_000);
182
+ }
183
+ }
184
+ //# sourceMappingURL=device-connection.js.map
@@ -17,4 +17,6 @@ export { createMcpBridge } from './mcp-bridge.js';
17
17
  export { CliSession, getOrCreateCliSession, killCliSession, killAllCliSessions, } from './cli-session.js';
18
18
  export { buildSafeEnv, buildSafeSpawnOpts, MINIMAL_PATH, ENV_ALLOWLIST } from './env-allowlist.js';
19
19
  export { StreamJsonParser } from './streaming.js';
20
+ export { DeviceConnection } from './device-connection.js';
21
+ export type { DeviceConnectionOptions, DeviceInfo, DeviceEvent } from './device-connection.js';
20
22
  //# sourceMappingURL=index.d.ts.map
@@ -19,4 +19,6 @@ export { CliSession, getOrCreateCliSession, killCliSession, killAllCliSessions,
19
19
  export { buildSafeEnv, buildSafeSpawnOpts, MINIMAL_PATH, ENV_ALLOWLIST } from './env-allowlist.js';
20
20
  // Stream parser (for custom providers)
21
21
  export { StreamJsonParser } from './streaming.js';
22
+ // Device connection (mount local machine into platform Studio)
23
+ export { DeviceConnection } from './device-connection.js';
22
24
  //# sourceMappingURL=index.js.map
@@ -14,6 +14,7 @@ export declare class ClaudeCliProvider implements AgentProvider {
14
14
  private mcpConfigPath;
15
15
  private spawnFn;
16
16
  private timeout;
17
+ private disallowedTools;
17
18
  constructor(options?: ClaudeCliProviderOptions);
18
19
  stream(messages: AgentMessage[], tools: ToolDefinition[], options?: StreamOptions): AsyncGenerator<StreamEvent>;
19
20
  }
@@ -16,12 +16,14 @@ export class ClaudeCliProvider {
16
16
  mcpConfigPath;
17
17
  spawnFn;
18
18
  timeout;
19
+ disallowedTools;
19
20
  constructor(options = {}) {
20
21
  this.binPath = options.binPath ?? 'claude';
21
22
  this.cwd = options.cwd ?? process.cwd();
22
23
  this.env = options.env ?? process.env;
23
24
  this.model = options.model;
24
25
  this.mcpConfigPath = options.mcpConfigPath;
26
+ this.disallowedTools = options.disallowedTools ?? [];
25
27
  this.spawnFn = options.spawnFn ?? ((cmd, args, opts) => nodeSpawn(cmd, args, { ...opts, stdio: opts.stdio }));
26
28
  this.timeout = options.timeout ?? 600_000;
27
29
  }
@@ -53,6 +55,7 @@ export class ClaudeCliProvider {
53
55
  'bypassPermissions',
54
56
  ...(systemPrompt ? ['--system-prompt', systemPrompt] : []),
55
57
  ...(mcpConfigPath ? ['--mcp-config', mcpConfigPath, '--strict-mcp-config'] : []),
58
+ ...(this.disallowedTools.length > 0 ? ['--disallowed-tools', this.disallowedTools.join(' ')] : []),
56
59
  ...(model ? ['--model', model] : []),
57
60
  ];
58
61
  // Spawn the CLI process
@@ -131,6 +131,8 @@ export interface ClaudeCliProviderOptions {
131
131
  spawnFn?: SpawnFn;
132
132
  /** CLI timeout in milliseconds. Defaults to 120000. */
133
133
  timeout?: number;
134
+ /** Disable specific built-in tools (e.g. ['Read', 'Edit', 'Write', 'Bash'] to force MCP tools). */
135
+ disallowedTools?: string[];
134
136
  }
135
137
  export interface CliSessionOptions {
136
138
  /** Absolute path to the claude binary. */
@@ -3,8 +3,9 @@ import { loadCredentials, saveCredentials, clearCredentials, getPlatformUrl } fr
3
3
  import { PlatformClient } from '../config/platform-client.js';
4
4
  export async function loginCommand(options) {
5
5
  const platformUrl = options.platformUrl ?? getPlatformUrl();
6
+ const displayUrl = platformUrl.replace(/^https?:\/\//, '');
6
7
  console.log('');
7
- console.log(' \x1b[1mFlow Weaver Cloud\x1b[0m \x1b[2m(flowweaver.ai)\x1b[0m');
8
+ console.log(` \x1b[1mFlow Weaver\x1b[0m \x1b[2m(${displayUrl})\x1b[0m`);
8
9
  console.log('');
9
10
  // API key mode (for CI/headless)
10
11
  if (options.apiKey) {
@@ -42,8 +43,8 @@ async function loginWithBrowser(platformUrl) {
42
43
  interval = data.interval ?? 5;
43
44
  }
44
45
  catch {
45
- console.error(' \x1b[31m✗\x1b[0m Cannot connect to flowweaver.ai');
46
- console.error(' Check your internet connection or set FW_PLATFORM_URL');
46
+ console.error(` \x1b[31m✗\x1b[0m Cannot connect to ${platformUrl}`);
47
+ console.error(' Check the URL or set FW_PLATFORM_URL');
47
48
  process.exit(1);
48
49
  return;
49
50
  }
@@ -65,20 +66,24 @@ async function loginWithBrowser(platformUrl) {
65
66
  }
66
67
  console.log('');
67
68
  // Step 3: Poll for completion
68
- process.stdout.write(' Waiting for authentication...');
69
+ process.stderr.write(' Waiting for approval');
69
70
  let cancelled = false;
70
71
  const sigHandler = () => { cancelled = true; };
71
72
  process.on('SIGINT', sigHandler);
72
73
  const maxAttempts = 120; // 10 minutes at 5s intervals
74
+ let networkErrors = 0;
73
75
  for (let i = 0; i < maxAttempts && !cancelled; i++) {
74
76
  await new Promise(r => setTimeout(r, interval * 1000));
75
77
  try {
76
78
  const resp = await fetch(`${platformUrl}/auth/device/poll?deviceCode=${deviceCode}`);
77
- if (!resp.ok)
79
+ networkErrors = 0; // reset on successful connection
80
+ if (!resp.ok) {
81
+ process.stderr.write('.');
78
82
  continue;
83
+ }
79
84
  const data = await resp.json();
80
85
  if (data.status === 'approved' && data.token && data.user) {
81
- process.stdout.write(' \x1b[32m✓\x1b[0m\n\n');
86
+ process.stderr.write(' \x1b[32m✓\x1b[0m\n\n');
82
87
  saveCredentials({
83
88
  token: data.token,
84
89
  email: data.user.email,
@@ -95,7 +100,7 @@ async function loginWithBrowser(platformUrl) {
95
100
  return;
96
101
  }
97
102
  if (data.status === 'expired') {
98
- process.stdout.write(' \x1b[31mtimed out\x1b[0m\n\n');
103
+ process.stderr.write(' \x1b[31mtimed out\x1b[0m\n\n');
99
104
  console.log(' Code expired. Run \x1b[36mfw login\x1b[0m again.');
100
105
  console.log('');
101
106
  process.removeListener('SIGINT', sigHandler);
@@ -103,27 +108,30 @@ async function loginWithBrowser(platformUrl) {
103
108
  return;
104
109
  }
105
110
  if (data.status === 'denied') {
106
- process.stdout.write(' \x1b[31mdenied\x1b[0m\n\n');
111
+ process.stderr.write(' \x1b[31mdenied\x1b[0m\n\n');
107
112
  console.log(' Access denied.');
108
113
  console.log('');
109
114
  process.removeListener('SIGINT', sigHandler);
110
115
  process.exit(1);
111
116
  return;
112
117
  }
113
- // Still pending — show a dot for progress
114
- if (i % 4 === 3)
115
- process.stdout.write('.');
118
+ // Still pending — show a dot every poll
119
+ process.stderr.write('.');
116
120
  }
117
121
  catch {
118
- // Network error — keep trying
122
+ networkErrors++;
123
+ if (networkErrors >= 3) {
124
+ process.stderr.write(' \x1b[33m!\x1b[0m');
125
+ networkErrors = 0;
126
+ }
119
127
  }
120
128
  }
121
129
  process.removeListener('SIGINT', sigHandler);
122
130
  if (cancelled) {
123
- process.stdout.write(' \x1b[33mcancelled\x1b[0m\n\n');
131
+ process.stderr.write(' \x1b[33mcancelled\x1b[0m\n\n');
124
132
  }
125
133
  else {
126
- process.stdout.write(' \x1b[31mtimed out\x1b[0m\n\n');
134
+ process.stderr.write(' \x1b[31mtimed out\x1b[0m\n\n');
127
135
  console.log(' Authentication timed out. Run \x1b[36mfw login\x1b[0m again.');
128
136
  console.log('');
129
137
  }
@@ -209,23 +217,24 @@ export async function authStatusCommand() {
209
217
  console.log('');
210
218
  }
211
219
  function prompt(message, hidden = false) {
212
- return new Promise((resolve) => {
213
- const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
214
- if (hidden && process.stdin.isTTY) {
215
- // Hide password input
220
+ if (hidden && process.stdin.isTTY) {
221
+ // Raw-mode password input — no readline (it competes for stdin)
222
+ return new Promise((resolve) => {
216
223
  process.stderr.write(message);
217
224
  process.stdin.setRawMode(true);
225
+ process.stdin.resume();
218
226
  let input = '';
219
227
  const handler = (key) => {
220
228
  const ch = key.toString();
221
229
  if (ch === '\r' || ch === '\n') {
222
230
  process.stdin.setRawMode(false);
231
+ process.stdin.pause();
223
232
  process.stdin.removeListener('data', handler);
224
233
  process.stderr.write('\n');
225
- rl.close();
226
234
  resolve(input);
227
235
  }
228
236
  else if (ch === '\x03') { // Ctrl+C
237
+ process.stdin.setRawMode(false);
229
238
  process.exit(1);
230
239
  }
231
240
  else if (ch === '\x7f') { // Backspace
@@ -236,10 +245,11 @@ function prompt(message, hidden = false) {
236
245
  }
237
246
  };
238
247
  process.stdin.on('data', handler);
239
- }
240
- else {
241
- rl.question(message, (answer) => { rl.close(); resolve(answer); });
242
- }
248
+ });
249
+ }
250
+ return new Promise((resolve) => {
251
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
252
+ rl.question(message, (answer) => { rl.close(); resolve(answer); });
243
253
  });
244
254
  }
245
255
  //# sourceMappingURL=auth.js.map
@@ -0,0 +1,4 @@
1
+ import { DeviceConnection } from '../../agent/device-connection.js';
2
+ export declare function loadPackDeviceHandlers(conn: DeviceConnection, projectDir: string): Promise<string[]>;
3
+ export declare function handleConnect(projectDir: string): Promise<void>;
4
+ //# sourceMappingURL=connect.d.ts.map
@@ -0,0 +1,163 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ import * as readline from 'node:readline';
5
+ import { DeviceConnection } from '../../agent/device-connection.js';
6
+ import { discoverDeviceHandlers } from '../../marketplace/registry.js';
7
+ function promptYesNo(message) {
8
+ return new Promise((resolve) => {
9
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
10
+ rl.question(message, (answer) => {
11
+ rl.close();
12
+ const normalized = answer.trim().toLowerCase();
13
+ resolve(normalized === '' || normalized === 'y' || normalized === 'yes');
14
+ });
15
+ });
16
+ }
17
+ export async function loadPackDeviceHandlers(conn, projectDir) {
18
+ const loadedPacks = [];
19
+ try {
20
+ const handlers = await discoverDeviceHandlers(projectDir);
21
+ for (const handler of handlers) {
22
+ try {
23
+ const mod = await import(handler.entrypoint);
24
+ if (typeof mod.register === 'function') {
25
+ await mod.register(conn, { projectDir });
26
+ loadedPacks.push(handler.packageName);
27
+ process.stderr.write(` \x1b[2m+ ${handler.packageName} handlers\x1b[0m\n`);
28
+ }
29
+ }
30
+ catch (err) {
31
+ process.stderr.write(` \x1b[33m⚠\x1b[0m Failed to load handlers from ${handler.packageName}: ${err instanceof Error ? err.message : err}\n`);
32
+ }
33
+ }
34
+ }
35
+ catch {
36
+ // Discovery failed — non-fatal
37
+ }
38
+ return loadedPacks;
39
+ }
40
+ export async function handleConnect(projectDir) {
41
+ // Load credentials
42
+ const credPath = path.join(os.homedir(), '.fw', 'credentials.json');
43
+ if (!fs.existsSync(credPath)) {
44
+ console.error('\n Not logged in. Run: fw login\n');
45
+ process.exit(1);
46
+ }
47
+ const creds = JSON.parse(fs.readFileSync(credPath, 'utf-8'));
48
+ if (!creds.token || !creds.platformUrl || creds.expiresAt < Date.now()) {
49
+ console.error('\n Credentials expired. Run: fw login\n');
50
+ process.exit(1);
51
+ }
52
+ const conn = new DeviceConnection({
53
+ platformUrl: creds.platformUrl,
54
+ token: creds.token,
55
+ projectDir,
56
+ deviceName: path.basename(projectDir),
57
+ logger: (msg) => process.stderr.write(` \x1b[2m${msg}\x1b[0m\n`),
58
+ });
59
+ // Register basic file handlers (any project can use these)
60
+ conn.addCapability('file_read');
61
+ conn.addCapability('file_list');
62
+ conn.onRequest('file:read', async (_method, params) => {
63
+ const filePath = path.resolve(projectDir, String(params.path ?? ''));
64
+ if (!filePath.startsWith(projectDir))
65
+ throw new Error('Path outside project directory');
66
+ if (!fs.existsSync(filePath))
67
+ throw new Error('File not found');
68
+ const stat = fs.statSync(filePath);
69
+ if (stat.isDirectory())
70
+ return { type: 'directory', entries: fs.readdirSync(filePath) };
71
+ if (stat.size > 1_048_576)
72
+ throw new Error('File too large (>1MB)');
73
+ return { type: 'file', content: fs.readFileSync(filePath, 'utf-8') };
74
+ });
75
+ conn.onRequest('file:list', async (_method, params) => {
76
+ const dirPath = path.resolve(projectDir, String(params.path ?? '.'));
77
+ if (!dirPath.startsWith(projectDir))
78
+ throw new Error('Path outside project directory');
79
+ if (!fs.existsSync(dirPath))
80
+ throw new Error('Directory not found');
81
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
82
+ return entries
83
+ .filter(e => !e.name.startsWith('.') && e.name !== 'node_modules' && e.name !== 'dist')
84
+ .map(e => ({ name: e.name, type: e.isDirectory() ? 'directory' : 'file', path: path.relative(projectDir, path.join(dirPath, e.name)), hasUnfetchedChildren: e.isDirectory() }));
85
+ });
86
+ // Load pack device handlers (if any installed packs provide them)
87
+ const loadedPacks = await loadPackDeviceHandlers(conn, projectDir);
88
+ // Tell the platform which packs contributed handlers (for auto-install)
89
+ if (loadedPacks.length > 0) {
90
+ conn.setPacks(loadedPacks);
91
+ }
92
+ console.log('');
93
+ console.log(' \x1b[1mflow-weaver connect\x1b[0m');
94
+ console.log(` \x1b[2mProject: ${path.basename(projectDir)}\x1b[0m`);
95
+ console.log(` \x1b[2mPlatform: ${creds.platformUrl}\x1b[0m`);
96
+ console.log('');
97
+ // Check if packs need to be installed in the Studio workspace
98
+ if (loadedPacks.length > 0) {
99
+ try {
100
+ const checkRes = await fetch(`${creds.platformUrl}/devices/check-packs`, {
101
+ method: 'POST',
102
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${creds.token}` },
103
+ body: JSON.stringify({ packs: loadedPacks }),
104
+ });
105
+ if (checkRes.ok) {
106
+ const { missing } = await checkRes.json();
107
+ if (missing.length > 0) {
108
+ console.log(' The following packs need to be installed in Studio:');
109
+ for (const p of missing) {
110
+ console.log(` \x1b[36m${p}\x1b[0m`);
111
+ }
112
+ console.log('');
113
+ const answer = await promptYesNo(' Install now? (Y/n) ');
114
+ if (answer) {
115
+ process.stderr.write(' Installing...');
116
+ const installRes = await fetch(`${creds.platformUrl}/devices/install-packs`, {
117
+ method: 'POST',
118
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${creds.token}` },
119
+ body: JSON.stringify({ packs: missing }),
120
+ });
121
+ if (installRes.ok) {
122
+ const { results } = await installRes.json();
123
+ const allOk = results.every(r => r.ok);
124
+ if (allOk) {
125
+ process.stderr.write(' \x1b[32m✓\x1b[0m\n\n');
126
+ }
127
+ else {
128
+ process.stderr.write(' \x1b[33mpartial\x1b[0m\n');
129
+ for (const r of results) {
130
+ if (!r.ok)
131
+ console.log(` \x1b[31m✗\x1b[0m ${r.pack}: ${r.error}`);
132
+ }
133
+ console.log('');
134
+ }
135
+ }
136
+ else {
137
+ process.stderr.write(' \x1b[31mfailed\x1b[0m\n\n');
138
+ }
139
+ }
140
+ else {
141
+ console.log(' \x1b[2mSkipped. Install manually via Studio marketplace.\x1b[0m\n');
142
+ }
143
+ }
144
+ }
145
+ }
146
+ catch {
147
+ // Check failed — non-fatal, continue connecting
148
+ }
149
+ }
150
+ try {
151
+ await conn.connect();
152
+ console.log(' \x1b[2mPress Ctrl+C to disconnect.\x1b[0m\n');
153
+ await new Promise((resolve) => {
154
+ process.on('SIGINT', () => { console.log('\n \x1b[2mDisconnecting...\x1b[0m'); conn.disconnect(); resolve(); });
155
+ process.on('SIGTERM', () => { conn.disconnect(); resolve(); });
156
+ });
157
+ }
158
+ catch (err) {
159
+ console.error(` \x1b[31m✗\x1b[0m Connection failed: ${err instanceof Error ? err.message : err}`);
160
+ process.exit(1);
161
+ }
162
+ }
163
+ //# sourceMappingURL=connect.js.map