pi-freeflow 1.4.2 → 1.4.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.
- package/README.md +323 -279
- package/package.json +1 -1
- package/src/catalog.ts +250 -207
- package/src/commands.ts +670 -618
- package/src/config.ts +144 -145
- package/src/deploy.ts +496 -126
- package/src/index.ts +301 -277
- package/src/models.ts +355 -399
- package/src/proxy.ts +493 -500
- package/src/relay.ts +213 -210
- package/src/stream-pipe.ts +265 -263
package/src/commands.ts
CHANGED
|
@@ -1,618 +1,670 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Interactive CLI slash commands and status bar manager for pi-freeflow
|
|
3
|
-
*
|
|
4
|
-
* log viewing, debug level configuration, and live catalog refreshing.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { refreshCatalog, setAliveCatalog } from "./catalog.ts";
|
|
8
|
-
import { DEBUG_STATE_FILE, DEFAULT_RELAY_URL, LOG_FILE } from "./config.ts";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (!
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
"
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
);
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (!
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if (
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
)
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
)
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
} else if (
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
);
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
} else {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
await
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
relayState.
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Interactive CLI slash commands and status bar manager for pi-freeflow
|
|
3
|
+
*
|
|
4
|
+
* log viewing, debug level configuration, and live catalog refreshing.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { refreshCatalog, setAliveCatalog } from "./catalog.ts";
|
|
8
|
+
import { DEBUG_STATE_FILE, DEFAULT_RELAY_URL, LOG_FILE } from "./config.ts";
|
|
9
|
+
import {
|
|
10
|
+
deployCloudflareWorker,
|
|
11
|
+
deployDenoRelay,
|
|
12
|
+
deployVercelRelay,
|
|
13
|
+
type DeployPlatform,
|
|
14
|
+
} from "./deploy.ts";
|
|
15
|
+
import {
|
|
16
|
+
LOG_LEVEL_ORDER,
|
|
17
|
+
getMinLogLevel,
|
|
18
|
+
isDebugEnabled,
|
|
19
|
+
loadDebugState,
|
|
20
|
+
readRecentLogs,
|
|
21
|
+
saveDebugState,
|
|
22
|
+
} from "./logger.ts";
|
|
23
|
+
import {
|
|
24
|
+
ensureRelay,
|
|
25
|
+
findRelay,
|
|
26
|
+
getActiveRelayState,
|
|
27
|
+
getRelayHealth,
|
|
28
|
+
isRelayHealthy,
|
|
29
|
+
removeRelay,
|
|
30
|
+
saveRelayState,
|
|
31
|
+
setActiveRelayState,
|
|
32
|
+
setRelayLabel,
|
|
33
|
+
setStatusUi,
|
|
34
|
+
shortRelayLabel,
|
|
35
|
+
} from "./relay-state.ts";
|
|
36
|
+
import type {
|
|
37
|
+
ExtensionAPI,
|
|
38
|
+
ExtensionContext,
|
|
39
|
+
ExtensionUIContext,
|
|
40
|
+
KnownRelay,
|
|
41
|
+
LogLevel,
|
|
42
|
+
RegisteredCommand,
|
|
43
|
+
RegisteredModel,
|
|
44
|
+
RelayState,
|
|
45
|
+
} from "./types.ts";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Update the TUI status bar widget with active relay state.
|
|
49
|
+
*/
|
|
50
|
+
export function updateStatusBar(ui?: ExtensionUIContext): void {
|
|
51
|
+
if (!ui) return;
|
|
52
|
+
const relayState = getActiveRelayState();
|
|
53
|
+
if (relayState.enabled && relayState.relays.length > 0) {
|
|
54
|
+
const label = shortRelayLabel(relayState.url);
|
|
55
|
+
const idx = Math.max(
|
|
56
|
+
1,
|
|
57
|
+
relayState.relays.findIndex((r) => r.url === relayState.url) + 1,
|
|
58
|
+
);
|
|
59
|
+
const total = relayState.relays.length;
|
|
60
|
+
const modeLabel = relayState.mode === "on" ? "ON" : "AUTO (ON)";
|
|
61
|
+
ui.setStatus("freeflow", `relay: ${modeLabel} | ${label} ${idx}/${total}`);
|
|
62
|
+
} else if (relayState.mode === "off" || !relayState.enabled) {
|
|
63
|
+
ui.setStatus("freeflow", "relay: OFF (direct)");
|
|
64
|
+
} else {
|
|
65
|
+
ui.setStatus("freeflow", undefined);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function createCommandSpec(
|
|
70
|
+
_pi: ExtensionAPI,
|
|
71
|
+
onCatalogRefreshed?: (models: RegisteredModel[]) => void,
|
|
72
|
+
): Omit<RegisteredCommand, "name"> {
|
|
73
|
+
return {
|
|
74
|
+
description:
|
|
75
|
+
"Relay egress: auto | on | off | status | add <URL> [name] | list | use <URL|name|index> [name] | label <target> <name> | remove <target> | logs [level] [n] | debug on|off | refresh | deploy vercel | deploy cloudflare | deploy deno",
|
|
76
|
+
getArgumentCompletions: (prefix: string) =>
|
|
77
|
+
[
|
|
78
|
+
"auto",
|
|
79
|
+
"on",
|
|
80
|
+
"off",
|
|
81
|
+
"status",
|
|
82
|
+
"add",
|
|
83
|
+
"list",
|
|
84
|
+
"use",
|
|
85
|
+
"label",
|
|
86
|
+
"rename",
|
|
87
|
+
"remove",
|
|
88
|
+
"url",
|
|
89
|
+
"deploy",
|
|
90
|
+
"deploy vercel",
|
|
91
|
+
"deploy cloudflare",
|
|
92
|
+
"deploy deno",
|
|
93
|
+
"refresh",
|
|
94
|
+
"models",
|
|
95
|
+
"logs",
|
|
96
|
+
"debug",
|
|
97
|
+
"trace",
|
|
98
|
+
]
|
|
99
|
+
.filter((s) => s.startsWith(prefix))
|
|
100
|
+
.map((s) => ({ value: s, label: s })),
|
|
101
|
+
handler: async (args: string, ctx: ExtensionContext) => {
|
|
102
|
+
const parts = String(args || "")
|
|
103
|
+
.trim()
|
|
104
|
+
.split(/\s+/);
|
|
105
|
+
const sub = parts[0] || "";
|
|
106
|
+
const rest = parts.slice(1).join(" ");
|
|
107
|
+
const relayState = getActiveRelayState();
|
|
108
|
+
|
|
109
|
+
const flash = () => {
|
|
110
|
+
const activeLabel = shortRelayLabel(relayState.url, relayState.relays);
|
|
111
|
+
const activeIdx = Math.max(
|
|
112
|
+
1,
|
|
113
|
+
relayState.relays.findIndex((r) => r.url === relayState.url) + 1,
|
|
114
|
+
);
|
|
115
|
+
const total = relayState.relays.length || 1;
|
|
116
|
+
const modeStr = (relayState.mode || "auto").toUpperCase();
|
|
117
|
+
ctx.ui.notify(
|
|
118
|
+
`Relay mode: ${modeStr} (${relayState.enabled ? "ON" : "OFF"})${relayState.enabled ? ` → ${activeLabel} (${activeIdx}/${total})` : " (direct)"} | saved=${relayState.relays.length} (auto-fallback rolling)`,
|
|
119
|
+
"info",
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const persist = () => {
|
|
124
|
+
setActiveRelayState(relayState, true);
|
|
125
|
+
setStatusUi(ctx.ui);
|
|
126
|
+
updateStatusBar(ctx.ui);
|
|
127
|
+
};
|
|
128
|
+
const setRelay = (
|
|
129
|
+
enabled: boolean,
|
|
130
|
+
url: string,
|
|
131
|
+
addLabel?: string,
|
|
132
|
+
) => {
|
|
133
|
+
relayState.enabled = enabled;
|
|
134
|
+
const cleanUrl = (url || "").trim() || DEFAULT_RELAY_URL;
|
|
135
|
+
relayState.url = cleanUrl;
|
|
136
|
+
if (cleanUrl) {
|
|
137
|
+
ensureRelay(relayState, cleanUrl, addLabel);
|
|
138
|
+
}
|
|
139
|
+
setActiveRelayState(relayState);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const addRelayInteractive = async () => {
|
|
143
|
+
const inputUrl = (
|
|
144
|
+
await ctx.ui.input(
|
|
145
|
+
"Custom relay URL (e.g. https://my-relay.workers.dev):",
|
|
146
|
+
"",
|
|
147
|
+
)
|
|
148
|
+
)?.trim();
|
|
149
|
+
if (!inputUrl) {
|
|
150
|
+
ctx.ui.notify("Cancelled — no URL provided", "warning");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const defaultLabel = shortRelayLabel(inputUrl, relayState.relays);
|
|
154
|
+
const inputLabel = (
|
|
155
|
+
await ctx.ui.input(
|
|
156
|
+
"Short name / alias for TUI (e.g. cf-sg, vercel-1):",
|
|
157
|
+
defaultLabel,
|
|
158
|
+
)
|
|
159
|
+
)?.trim() || defaultLabel;
|
|
160
|
+
|
|
161
|
+
const added = ensureRelay(relayState, inputUrl, inputLabel);
|
|
162
|
+
setRelay(true, added.url, added.label);
|
|
163
|
+
persist();
|
|
164
|
+
flash();
|
|
165
|
+
ctx.ui.notify(
|
|
166
|
+
`✓ Added & activated relay [${added.label || shortRelayLabel(added.url)}]: ${added.url}`,
|
|
167
|
+
"info",
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const editRelayLabelMenu = async () => {
|
|
172
|
+
if (!relayState.relays.length) {
|
|
173
|
+
ctx.ui.notify("No saved relays to rename", "warning");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const fmt = (r: KnownRelay, idx: number) =>
|
|
177
|
+
`[${idx + 1}] ${r.label ? `[${r.label}] ` : ""}${r.url}`;
|
|
178
|
+
const opts = relayState.relays.map(fmt);
|
|
179
|
+
const choice = await ctx.ui.select("Select relay to rename / set short name", opts);
|
|
180
|
+
if (!choice) return;
|
|
181
|
+
const match = relayState.relays.find((r, idx) => fmt(r, idx) === choice);
|
|
182
|
+
if (!match) return;
|
|
183
|
+
|
|
184
|
+
const curLabel = match.label || shortRelayLabel(match.url, relayState.relays);
|
|
185
|
+
const newLabel = (
|
|
186
|
+
await ctx.ui.input(
|
|
187
|
+
`New short name for ${match.url}:`,
|
|
188
|
+
curLabel,
|
|
189
|
+
)
|
|
190
|
+
)?.trim();
|
|
191
|
+
if (!newLabel) {
|
|
192
|
+
ctx.ui.notify("Cancelled — short name not changed", "warning");
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
setRelayLabel(relayState, match.url, newLabel);
|
|
196
|
+
persist();
|
|
197
|
+
flash();
|
|
198
|
+
ctx.ui.notify(
|
|
199
|
+
`✓ Relay short name updated to [${newLabel}] for ${match.url}`,
|
|
200
|
+
"info",
|
|
201
|
+
);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const parseDeployPlatform = (raw?: string): DeployPlatform | null => {
|
|
205
|
+
const v = (raw || "").trim().toLowerCase();
|
|
206
|
+
if (!v || v === "vercel") return "vercel";
|
|
207
|
+
if (v === "cloudflare" || v === "cf") return "cloudflare";
|
|
208
|
+
if (v === "deno") return "deno";
|
|
209
|
+
return null;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const doDeploy = async (platform: DeployPlatform = "vercel") => {
|
|
213
|
+
const defaultName = `relay-${Date.now().toString(36)}`;
|
|
214
|
+
const label =
|
|
215
|
+
platform === "cloudflare"
|
|
216
|
+
? "Cloudflare"
|
|
217
|
+
: platform === "deno"
|
|
218
|
+
? "Deno Deploy"
|
|
219
|
+
: "Vercel";
|
|
220
|
+
const token = (
|
|
221
|
+
await ctx.ui.input(`${label} API token:`, "")
|
|
222
|
+
)?.trim();
|
|
223
|
+
if (!token) {
|
|
224
|
+
ctx.ui.notify("Deploy cancelled: no token", "warning");
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const name =
|
|
228
|
+
(
|
|
229
|
+
await ctx.ui.input(
|
|
230
|
+
"Project name (empty = auto):",
|
|
231
|
+
defaultName,
|
|
232
|
+
)
|
|
233
|
+
)?.trim() || defaultName;
|
|
234
|
+
|
|
235
|
+
ctx.ui.setStatus("freeflow", `deploying ${platform} relay…`);
|
|
236
|
+
try {
|
|
237
|
+
const deployer =
|
|
238
|
+
platform === "cloudflare"
|
|
239
|
+
? deployCloudflareWorker
|
|
240
|
+
: platform === "deno"
|
|
241
|
+
? deployDenoRelay
|
|
242
|
+
: deployVercelRelay;
|
|
243
|
+
const url = await deployer(token, name, (m) =>
|
|
244
|
+
ctx.ui.notify(m, "info"),
|
|
245
|
+
);
|
|
246
|
+
setRelay(true, url, `deployed ${name}`);
|
|
247
|
+
persist();
|
|
248
|
+
ctx.ui.notify(`✓ Deployed & active: ${url}`, "info");
|
|
249
|
+
} catch (e) {
|
|
250
|
+
updateStatusBar(ctx.ui);
|
|
251
|
+
ctx.ui.notify(
|
|
252
|
+
`Deploy failed: ${(e as Error).message}`,
|
|
253
|
+
"error",
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const switchRelay = async () => {
|
|
259
|
+
if (!relayState.relays.length) {
|
|
260
|
+
ctx.ui.notify("No saved relays yet", "warning");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const fmt = (r: KnownRelay, idx: number) => {
|
|
264
|
+
const isAct = r.url === relayState.url ? "★ " : " ";
|
|
265
|
+
const lbl = r.label ? `[${r.label}] ` : `[${shortRelayLabel(r.url, relayState.relays)}] `;
|
|
266
|
+
return `${isAct}[${idx + 1}] ${lbl}→ ${r.url}`;
|
|
267
|
+
};
|
|
268
|
+
const opts = relayState.relays.map(fmt);
|
|
269
|
+
const choice = await ctx.ui.select("Switch active relay", opts);
|
|
270
|
+
if (!choice) return;
|
|
271
|
+
const match = relayState.relays.find((r, idx) => fmt(r, idx) === choice);
|
|
272
|
+
if (!match) return;
|
|
273
|
+
setRelay(true, match.url, match.label);
|
|
274
|
+
persist();
|
|
275
|
+
flash();
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const showList = () => {
|
|
279
|
+
if (!relayState.relays.length) {
|
|
280
|
+
ctx.ui.notify("No saved relays (direct upstream mode)", "info");
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const lines = relayState.relays.map((r, idx) => {
|
|
284
|
+
const star = r.url === relayState.url ? "★" : " ";
|
|
285
|
+
const shortName = r.label ? `[${r.label}]` : `[${shortRelayLabel(r.url, relayState.relays)}]`;
|
|
286
|
+
const paddedName = shortName.padEnd(16, " ");
|
|
287
|
+
const health = getRelayHealth(r.url);
|
|
288
|
+
const isCooling = health && Date.now() < health.cooldownUntil;
|
|
289
|
+
const remainingSec = isCooling ? Math.ceil((health.cooldownUntil - Date.now()) / 1000) : 0;
|
|
290
|
+
const healthBadge = isCooling
|
|
291
|
+
? ` ⚠️ [cooling ${remainingSec}s: ${health.lastStatus ? `HTTP ${health.lastStatus}` : "error"}]`
|
|
292
|
+
: " ✓";
|
|
293
|
+
return `${star} [${idx + 1}] ${paddedName} → ${r.url}${healthBadge}`;
|
|
294
|
+
});
|
|
295
|
+
const activeLabel = shortRelayLabel(relayState.url, relayState.relays);
|
|
296
|
+
const activeIdx = Math.max(
|
|
297
|
+
1,
|
|
298
|
+
relayState.relays.findIndex((r) => r.url === relayState.url) + 1,
|
|
299
|
+
);
|
|
300
|
+
const modeStr = (relayState.mode || "auto").toUpperCase();
|
|
301
|
+
const header = `Saved Relays (${relayState.relays.length}):\n${lines.join("\n")}\n\nActive: [${activeIdx}] ${activeLabel} | Mode: ${modeStr} (${relayState.enabled ? "ON" : "OFF"})`;
|
|
302
|
+
ctx.ui.notify(header, "info");
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const removeRelayMenu = async () => {
|
|
306
|
+
const removable = relayState.relays.filter(
|
|
307
|
+
(r) => r.url !== relayState.url,
|
|
308
|
+
);
|
|
309
|
+
if (!removable.length) {
|
|
310
|
+
ctx.ui.notify(
|
|
311
|
+
"Nothing to remove — the active relay cannot be removed (switch first)",
|
|
312
|
+
"warning",
|
|
313
|
+
);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const fmt = (r: KnownRelay, idx: number) => {
|
|
317
|
+
const lbl = r.label ? `[${r.label}] ` : `[${shortRelayLabel(r.url, relayState.relays)}] `;
|
|
318
|
+
return `[${idx + 1}] ${lbl}→ ${r.url}`;
|
|
319
|
+
};
|
|
320
|
+
const choice = await ctx.ui.select(
|
|
321
|
+
"Remove relay",
|
|
322
|
+
removable.map(fmt),
|
|
323
|
+
);
|
|
324
|
+
if (!choice) return;
|
|
325
|
+
const match = removable.find((r, idx) => fmt(r, idx) === choice);
|
|
326
|
+
if (!match) return;
|
|
327
|
+
removeRelay(relayState, match.url);
|
|
328
|
+
persist();
|
|
329
|
+
ctx.ui.notify(`Removed: [${shortRelayLabel(match.url, relayState.relays)}] ${match.url}`, "info");
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
if (sub === "auto") {
|
|
333
|
+
relayState.mode = "auto";
|
|
334
|
+
relayState.enabled = true;
|
|
335
|
+
setRelay(true, relayState.url || DEFAULT_RELAY_URL);
|
|
336
|
+
persist();
|
|
337
|
+
flash();
|
|
338
|
+
} else if (sub === "on") {
|
|
339
|
+
relayState.mode = "on";
|
|
340
|
+
setRelay(true, relayState.url || DEFAULT_RELAY_URL);
|
|
341
|
+
persist();
|
|
342
|
+
flash();
|
|
343
|
+
} else if (sub === "off") {
|
|
344
|
+
relayState.mode = "off";
|
|
345
|
+
relayState.enabled = false;
|
|
346
|
+
setActiveRelayState(relayState);
|
|
347
|
+
persist();
|
|
348
|
+
flash();
|
|
349
|
+
} else if (sub === "status") {
|
|
350
|
+
flash();
|
|
351
|
+
} else if (sub === "list") {
|
|
352
|
+
showList();
|
|
353
|
+
} else if (sub === "add") {
|
|
354
|
+
if (!rest.trim()) {
|
|
355
|
+
await addRelayInteractive();
|
|
356
|
+
} else {
|
|
357
|
+
const tokens = rest.trim().split(/\s+/);
|
|
358
|
+
const targetUrl = tokens[0];
|
|
359
|
+
const customLabel = tokens.slice(1).join(" ").trim() || undefined;
|
|
360
|
+
const added = ensureRelay(relayState, targetUrl, customLabel);
|
|
361
|
+
setRelay(true, added.url, added.label);
|
|
362
|
+
persist();
|
|
363
|
+
flash();
|
|
364
|
+
ctx.ui.notify(
|
|
365
|
+
`✓ Added & activated relay [${added.label || shortRelayLabel(added.url)}]: ${added.url}`,
|
|
366
|
+
"info",
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
} else if (sub === "use") {
|
|
370
|
+
if (!rest.trim()) {
|
|
371
|
+
await switchRelay();
|
|
372
|
+
} else {
|
|
373
|
+
const tokens = rest.trim().split(/\s+/);
|
|
374
|
+
const targetToken = tokens[0];
|
|
375
|
+
const customLabel = tokens.slice(1).join(" ").trim() || undefined;
|
|
376
|
+
const matched = findRelay(relayState, targetToken);
|
|
377
|
+
if (matched) {
|
|
378
|
+
if (customLabel) {
|
|
379
|
+
matched.label = customLabel;
|
|
380
|
+
}
|
|
381
|
+
setRelay(true, matched.url, matched.label);
|
|
382
|
+
persist();
|
|
383
|
+
flash();
|
|
384
|
+
} else {
|
|
385
|
+
// Only treat unmatched tokens as new relays when they are absolute
|
|
386
|
+
// http(s) URLs — typos or bad indices must not become saved junk relays.
|
|
387
|
+
let parsedUrl = null;
|
|
388
|
+
try {
|
|
389
|
+
parsedUrl = new URL(targetToken);
|
|
390
|
+
} catch {}
|
|
391
|
+
const looksLikeUrl = parsedUrl && (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:");
|
|
392
|
+
if (looksLikeUrl) {
|
|
393
|
+
const added = ensureRelay(relayState, targetToken, customLabel);
|
|
394
|
+
setRelay(true, added.url, added.label);
|
|
395
|
+
persist();
|
|
396
|
+
flash();
|
|
397
|
+
} else {
|
|
398
|
+
ctx.ui.notify(`Relay '${targetToken}' not found in saved list`, "warning");
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
} else if (sub === "label" || sub === "rename") {
|
|
403
|
+
if (!rest.trim()) {
|
|
404
|
+
await editRelayLabelMenu();
|
|
405
|
+
} else {
|
|
406
|
+
const tokens = rest.trim().split(/\s+/);
|
|
407
|
+
const targetToken = tokens[0];
|
|
408
|
+
const newLabel = tokens.slice(1).join(" ").trim();
|
|
409
|
+
const matched = findRelay(relayState, targetToken);
|
|
410
|
+
if (!matched) {
|
|
411
|
+
ctx.ui.notify(`Relay '${targetToken}' not found in saved list`, "warning");
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
let finalLabel = newLabel;
|
|
415
|
+
if (!finalLabel) {
|
|
416
|
+
finalLabel = (
|
|
417
|
+
await ctx.ui.input(
|
|
418
|
+
`New short name / alias for ${matched.url}:`,
|
|
419
|
+
matched.label || shortRelayLabel(matched.url, relayState.relays),
|
|
420
|
+
)
|
|
421
|
+
)?.trim() || "";
|
|
422
|
+
}
|
|
423
|
+
setRelayLabel(relayState, matched.url, finalLabel);
|
|
424
|
+
persist();
|
|
425
|
+
flash();
|
|
426
|
+
ctx.ui.notify(
|
|
427
|
+
`✓ Relay short name updated to [${shortRelayLabel(matched.url, relayState.relays)}] for ${matched.url}`,
|
|
428
|
+
"info",
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
} else if (sub === "debug") {
|
|
432
|
+
const arg = rest.trim().toLowerCase();
|
|
433
|
+
if (arg === "on" || arg === "enable" || arg === "true") {
|
|
434
|
+
saveDebugState({ debug: true });
|
|
435
|
+
ctx.ui.notify(
|
|
436
|
+
"🔍 Debug ON — verbose trace enabled (level=debug). Logs now include request IDs, thinking sniffing, and payload normalize details.",
|
|
437
|
+
"info",
|
|
438
|
+
);
|
|
439
|
+
} else if (
|
|
440
|
+
arg === "off" ||
|
|
441
|
+
arg === "disable" ||
|
|
442
|
+
arg === "false"
|
|
443
|
+
) {
|
|
444
|
+
saveDebugState({ debug: false });
|
|
445
|
+
ctx.ui.notify(
|
|
446
|
+
`🔇 Debug OFF — level restored to info. File: ${DEBUG_STATE_FILE}`,
|
|
447
|
+
"info",
|
|
448
|
+
);
|
|
449
|
+
} else if (arg.startsWith("level")) {
|
|
450
|
+
const lvl = arg.split(/\s+/)[1] as LogLevel | undefined;
|
|
451
|
+
// Allowlist only levels that actually have emitters. 'audit' exists in
|
|
452
|
+
// LOG_LEVEL_ORDER but nothing ever logs at it, so accepting it here
|
|
453
|
+
// silently suppressed ALL log output until manually reset.
|
|
454
|
+
const EMITTED_LEVELS: readonly LogLevel[] = [
|
|
455
|
+
"debug",
|
|
456
|
+
"info",
|
|
457
|
+
"warn",
|
|
458
|
+
"error",
|
|
459
|
+
];
|
|
460
|
+
if (
|
|
461
|
+
lvl &&
|
|
462
|
+
(EMITTED_LEVELS as readonly string[]).includes(lvl)
|
|
463
|
+
) {
|
|
464
|
+
saveDebugState({ debug: false, level: lvl });
|
|
465
|
+
ctx.ui.notify(
|
|
466
|
+
`Log level set to ${lvl} (persisted to ${DEBUG_STATE_FILE})`,
|
|
467
|
+
"info",
|
|
468
|
+
);
|
|
469
|
+
} else {
|
|
470
|
+
ctx.ui.notify(
|
|
471
|
+
`Unknown level: ${lvl} (use debug/info/warn/error)`,
|
|
472
|
+
"warning",
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
} else {
|
|
476
|
+
const st = loadDebugState();
|
|
477
|
+
const cur = st?.debug
|
|
478
|
+
? "debug (ON)"
|
|
479
|
+
: st?.level ||
|
|
480
|
+
process.env.FREEFLOW_LOG_LEVEL ||
|
|
481
|
+
"info";
|
|
482
|
+
ctx.ui.notify(
|
|
483
|
+
`Debug status: ${cur}\nFile: ${DEBUG_STATE_FILE}\nMinLevel: ${getMinLogLevel()} | isDebug=${isDebugEnabled()}\nUsage: /freeflow debug on|off | /freeflow debug level debug`,
|
|
484
|
+
"info",
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
} else if (sub === "logs" || sub === "log" || sub === "trace") {
|
|
488
|
+
try {
|
|
489
|
+
const rawRest = rest.trim();
|
|
490
|
+
let filterLevel: LogLevel | null = null;
|
|
491
|
+
let filterReqId: string | null = null;
|
|
492
|
+
let count = 25;
|
|
493
|
+
|
|
494
|
+
if (sub === "trace" && rawRest) {
|
|
495
|
+
filterReqId = rawRest.split(/\s+/)[0];
|
|
496
|
+
} else if (rawRest) {
|
|
497
|
+
const tokens = rawRest.split(/\s+/);
|
|
498
|
+
for (const t of tokens) {
|
|
499
|
+
const lower = t.toLowerCase();
|
|
500
|
+
if (lower in LOG_LEVEL_ORDER) {
|
|
501
|
+
filterLevel = lower as LogLevel;
|
|
502
|
+
} else if (/^\d+$/.test(t)) {
|
|
503
|
+
count = Math.min(
|
|
504
|
+
200,
|
|
505
|
+
Math.max(5, Number.parseInt(t, 10)),
|
|
506
|
+
);
|
|
507
|
+
} else if (
|
|
508
|
+
/^[a-f0-9]{6,8}$/i.test(t) ||
|
|
509
|
+
t.startsWith("req=")
|
|
510
|
+
) {
|
|
511
|
+
filterReqId = t.replace(/^req=/, "");
|
|
512
|
+
} else if (lower === "trace" || lower === "req") {
|
|
513
|
+
continue;
|
|
514
|
+
} else {
|
|
515
|
+
filterReqId = t;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const result = readRecentLogs(
|
|
521
|
+
filterLevel,
|
|
522
|
+
filterReqId,
|
|
523
|
+
count,
|
|
524
|
+
);
|
|
525
|
+
if (result.lines.length === 0) {
|
|
526
|
+
if (result.totalLines === 0) {
|
|
527
|
+
ctx.ui.notify(
|
|
528
|
+
"Log file is empty (no logs yet)",
|
|
529
|
+
"info",
|
|
530
|
+
);
|
|
531
|
+
} else {
|
|
532
|
+
ctx.ui.notify(
|
|
533
|
+
`No logs matched (level=${filterLevel || "any"} reqId=${filterReqId || "any"} count=${count})`,
|
|
534
|
+
"warning",
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const header = `pi-freeflow logs (last ${result.lines.length}/${result.totalMatched} matched, total ${result.totalLines} lines, file: ${LOG_FILE}${filterLevel ? ` level=${filterLevel}` : ""}${filterReqId ? ` req=${filterReqId}` : ""}):`;
|
|
541
|
+
ctx.ui.notify(
|
|
542
|
+
`${header}\n\n${result.lines.join("\n")}`,
|
|
543
|
+
"info",
|
|
544
|
+
);
|
|
545
|
+
} catch (e) {
|
|
546
|
+
ctx.ui.notify(
|
|
547
|
+
`Could not read log file: ${(e as Error).message}`,
|
|
548
|
+
"error",
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
} else if (
|
|
552
|
+
sub === "refresh" ||
|
|
553
|
+
sub === "reload" ||
|
|
554
|
+
sub === "models"
|
|
555
|
+
) {
|
|
556
|
+
ctx.ui.notify(
|
|
557
|
+
"Refreshing model catalog from live upstreams…",
|
|
558
|
+
"info",
|
|
559
|
+
);
|
|
560
|
+
const updated = await refreshCatalog(true);
|
|
561
|
+
setAliveCatalog(updated);
|
|
562
|
+
persist();
|
|
563
|
+
onCatalogRefreshed?.(updated);
|
|
564
|
+
ctx.ui.notify(
|
|
565
|
+
`✓ Refreshed ${updated.length} models with full-spec metadata!`,
|
|
566
|
+
"info",
|
|
567
|
+
);
|
|
568
|
+
} else if (sub === "remove") {
|
|
569
|
+
if (!rest.trim()) {
|
|
570
|
+
await removeRelayMenu();
|
|
571
|
+
} else {
|
|
572
|
+
const target = rest.trim();
|
|
573
|
+
const matched = findRelay(relayState, target);
|
|
574
|
+
if (!matched) {
|
|
575
|
+
ctx.ui.notify(`Relay '${target}' not in saved list`, "warning");
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (matched.url === relayState.url) {
|
|
579
|
+
ctx.ui.notify(
|
|
580
|
+
"Cannot remove the active relay — switch to another relay first",
|
|
581
|
+
"warning",
|
|
582
|
+
);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
removeRelay(relayState, matched.url);
|
|
586
|
+
persist();
|
|
587
|
+
ctx.ui.notify(`Removed: [${shortRelayLabel(matched.url, relayState.relays)}] ${matched.url}`, "info");
|
|
588
|
+
}
|
|
589
|
+
} else if (sub === "url") {
|
|
590
|
+
const input =
|
|
591
|
+
rest ||
|
|
592
|
+
(await ctx.ui.input(
|
|
593
|
+
"Relay URL (empty = default):",
|
|
594
|
+
relayState.url || DEFAULT_RELAY_URL,
|
|
595
|
+
));
|
|
596
|
+
const cleanInput = (input || "").trim() || DEFAULT_RELAY_URL;
|
|
597
|
+
setRelay(
|
|
598
|
+
relayState.enabled,
|
|
599
|
+
cleanInput,
|
|
600
|
+
shortRelayLabel(cleanInput, relayState.relays),
|
|
601
|
+
);
|
|
602
|
+
persist();
|
|
603
|
+
flash();
|
|
604
|
+
} else if (sub === "deploy") {
|
|
605
|
+
const pf = parseDeployPlatform(rest);
|
|
606
|
+
if (!pf) {
|
|
607
|
+
ctx.ui.notify(
|
|
608
|
+
"Unknown platform. Use: vercel | cloudflare | deno",
|
|
609
|
+
"warning",
|
|
610
|
+
);
|
|
611
|
+
} else {
|
|
612
|
+
await doDeploy(pf);
|
|
613
|
+
}
|
|
614
|
+
} else {
|
|
615
|
+
const currentMode = (relayState.mode || "auto").toUpperCase();
|
|
616
|
+
const activeLabel = shortRelayLabel(relayState.url, relayState.relays);
|
|
617
|
+
const choice = await ctx.ui.select("freeflow relay", [
|
|
618
|
+
`Mode: ${currentMode} (${relayState.enabled ? "ON" : "OFF"}) → ${activeLabel}`,
|
|
619
|
+
"Add custom relay (URL + Short name)…",
|
|
620
|
+
"Switch active relay…",
|
|
621
|
+
"Rename / Set relay short name…",
|
|
622
|
+
"Remove relay…",
|
|
623
|
+
"List saved relays",
|
|
624
|
+
"Deploy Vercel relay…",
|
|
625
|
+
"Deploy Cloudflare relay…",
|
|
626
|
+
"Deploy Deno relay…",
|
|
627
|
+
"Mode: AUTO (auto-detect on model select)",
|
|
628
|
+
"Mode: ON (always relay)",
|
|
629
|
+
"Mode: OFF (always direct)",
|
|
630
|
+
]);
|
|
631
|
+
if (choice === `Mode: ${currentMode} (${relayState.enabled ? "ON" : "OFF"}) → ${activeLabel}`) {
|
|
632
|
+
flash();
|
|
633
|
+
} else if (choice === "Add custom relay (URL + Short name)…") {
|
|
634
|
+
await addRelayInteractive();
|
|
635
|
+
} else if (choice === "Switch active relay…") {
|
|
636
|
+
await switchRelay();
|
|
637
|
+
} else if (choice === "Rename / Set relay short name…") {
|
|
638
|
+
await editRelayLabelMenu();
|
|
639
|
+
} else if (choice === "Remove relay…") {
|
|
640
|
+
await removeRelayMenu();
|
|
641
|
+
} else if (choice === "List saved relays") {
|
|
642
|
+
showList();
|
|
643
|
+
} else if (choice === "Deploy Vercel relay…") {
|
|
644
|
+
await doDeploy("vercel");
|
|
645
|
+
} else if (choice === "Deploy Cloudflare relay…") {
|
|
646
|
+
await doDeploy("cloudflare");
|
|
647
|
+
} else if (choice === "Deploy Deno relay…") {
|
|
648
|
+
await doDeploy("deno");
|
|
649
|
+
} else if (choice === "Mode: AUTO (auto-detect on model select)") {
|
|
650
|
+
relayState.mode = "auto";
|
|
651
|
+
relayState.enabled = true;
|
|
652
|
+
setRelay(true, relayState.url || DEFAULT_RELAY_URL);
|
|
653
|
+
persist();
|
|
654
|
+
flash();
|
|
655
|
+
} else if (choice === "Mode: ON (always relay)") {
|
|
656
|
+
relayState.mode = "on";
|
|
657
|
+
setRelay(true, relayState.url || DEFAULT_RELAY_URL);
|
|
658
|
+
persist();
|
|
659
|
+
flash();
|
|
660
|
+
} else if (choice === "Mode: OFF (always direct)") {
|
|
661
|
+
relayState.mode = "off";
|
|
662
|
+
relayState.enabled = false;
|
|
663
|
+
setActiveRelayState(relayState);
|
|
664
|
+
persist();
|
|
665
|
+
flash();
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
};
|
|
670
|
+
}
|