@synergenius/flow-weaver 0.23.1 → 0.23.4

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.
@@ -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
@@ -1,2 +1,4 @@
1
+ import { DeviceConnection } from '../../agent/device-connection.js';
2
+ export declare function loadPackDeviceHandlers(conn: DeviceConnection, projectDir: string): Promise<string[]>;
1
3
  export declare function handleConnect(projectDir: string): Promise<void>;
2
4
  //# sourceMappingURL=connect.d.ts.map
@@ -1,7 +1,42 @@
1
1
  import * as fs from 'node:fs';
2
2
  import * as path from 'node:path';
3
3
  import * as os from 'node:os';
4
+ import * as readline from 'node:readline';
4
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
+ }
5
40
  export async function handleConnect(projectDir) {
6
41
  // Load credentials
7
42
  const credPath = path.join(os.homedir(), '.fw', 'credentials.json');
@@ -46,13 +81,72 @@ export async function handleConnect(projectDir) {
46
81
  const entries = fs.readdirSync(dirPath, { withFileTypes: true });
47
82
  return entries
48
83
  .filter(e => !e.name.startsWith('.') && e.name !== 'node_modules' && e.name !== 'dist')
49
- .map(e => ({ name: e.name, type: e.isDirectory() ? 'directory' : 'file', path: path.relative(projectDir, path.join(dirPath, e.name)) }));
84
+ .map(e => ({ name: e.name, type: e.isDirectory() ? 'directory' : 'file', path: path.relative(projectDir, path.join(dirPath, e.name)), hasUnfetchedChildren: e.isDirectory() }));
50
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
+ }
51
92
  console.log('');
52
93
  console.log(' \x1b[1mflow-weaver connect\x1b[0m');
53
94
  console.log(` \x1b[2mProject: ${path.basename(projectDir)}\x1b[0m`);
54
95
  console.log(` \x1b[2mPlatform: ${creds.platformUrl}\x1b[0m`);
55
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
+ }
56
150
  try {
57
151
  await conn.connect();
58
152
  console.log(' \x1b[2mPress Ctrl+C to disconnect.\x1b[0m\n');