pi-protonmail 0.1.0
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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/biome.json +25 -0
- package/package.json +63 -0
- package/src/constants.ts +1 -0
- package/src/hub.ts +559 -0
- package/src/index.ts +7 -0
- package/src/proton-bridge.ts +444 -0
- package/src/protonmail.ts +624 -0
- package/src/secret-refs.ts +81 -0
- package/src/types.ts +90 -0
- package/src/workspace.ts +96 -0
package/src/hub.ts
ADDED
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { Theme } from "@earendil-works/pi-tui";
|
|
4
|
+
import {
|
|
5
|
+
type Component,
|
|
6
|
+
type Focusable,
|
|
7
|
+
Input,
|
|
8
|
+
Markdown,
|
|
9
|
+
matchesKey,
|
|
10
|
+
SelectList,
|
|
11
|
+
Text,
|
|
12
|
+
truncateToWidth,
|
|
13
|
+
visibleWidth,
|
|
14
|
+
} from "@earendil-works/pi-tui";
|
|
15
|
+
|
|
16
|
+
import type { ProtonMailProfilePolicy, ProtonMailWorkingProfile } from "./types.ts";
|
|
17
|
+
|
|
18
|
+
export type ProtonMailHubResult =
|
|
19
|
+
| {
|
|
20
|
+
kind: "save";
|
|
21
|
+
profile: string;
|
|
22
|
+
policy: ProtonMailProfilePolicy;
|
|
23
|
+
}
|
|
24
|
+
| {
|
|
25
|
+
kind: "delete";
|
|
26
|
+
profile: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export interface ProtonMailHubArgs {
|
|
30
|
+
profile?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const MONTH_RE = /^\d{4}-\d{2}$/;
|
|
34
|
+
|
|
35
|
+
function currentMonth(): string {
|
|
36
|
+
const now = new Date();
|
|
37
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
38
|
+
return `${now.getFullYear()}-${month}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseHubArgs(raw: string): ProtonMailHubArgs {
|
|
42
|
+
const profile = raw.trim().split(/\s+/).filter(Boolean)[0];
|
|
43
|
+
return { profile: profile || undefined };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function selectTheme(theme: Theme) {
|
|
47
|
+
return {
|
|
48
|
+
selectedPrefix: (text: string) => theme.fg("accent", text),
|
|
49
|
+
selectedText: (text: string) => theme.fg("accent", theme.bold(text)),
|
|
50
|
+
description: (text: string) => theme.fg("muted", text),
|
|
51
|
+
scrollInfo: (text: string) => theme.fg("muted", text),
|
|
52
|
+
noMatch: (text: string) => theme.fg("dim", text),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function policySummary(policy: ProtonMailProfilePolicy): string {
|
|
57
|
+
const parts = [
|
|
58
|
+
policy.default_mailbox ? `mailbox:${policy.default_mailbox}` : "mailbox:—",
|
|
59
|
+
policy.mailbox_filter ? `filter:${policy.mailbox_filter}` : "filter:—",
|
|
60
|
+
policy.default_period ? `period:${policy.default_period}` : "period:current",
|
|
61
|
+
];
|
|
62
|
+
return parts.join(" • ");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function profileLabel(profile: ProtonMailWorkingProfile): string {
|
|
66
|
+
return profile.profile;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function makeSelectItem(value: string, label: string, description?: string) {
|
|
70
|
+
return { value, label, description };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function replaceSelectItems<T extends { value: string; label: string; description?: string }>(
|
|
74
|
+
list: SelectList,
|
|
75
|
+
items: T[],
|
|
76
|
+
) {
|
|
77
|
+
const mutable = list as SelectList & {
|
|
78
|
+
items: T[];
|
|
79
|
+
filteredItems: T[];
|
|
80
|
+
selectedIndex: number;
|
|
81
|
+
};
|
|
82
|
+
mutable.items = items;
|
|
83
|
+
mutable.filteredItems = items;
|
|
84
|
+
mutable.selectedIndex = items.length > 0 ? 0 : 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function selectedItemValue(list: SelectList): string | undefined {
|
|
88
|
+
return list.getSelectedItem()?.value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function normalized(text: string): string {
|
|
92
|
+
return text.trim().toLowerCase();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
class ProtonMailHubComponent implements Component, Focusable {
|
|
96
|
+
private readonly mdTheme = getMarkdownTheme();
|
|
97
|
+
private readonly profileFilterInput = new Input();
|
|
98
|
+
private readonly defaultMailboxInput = new Input();
|
|
99
|
+
private readonly mailboxFilterInput = new Input();
|
|
100
|
+
private readonly periodInput = new Input();
|
|
101
|
+
private readonly profilesList: SelectList;
|
|
102
|
+
private readonly titleText: Markdown;
|
|
103
|
+
private readonly helpText: Markdown;
|
|
104
|
+
private readonly footerText: Text;
|
|
105
|
+
private readonly theme: Theme;
|
|
106
|
+
private _focused = false;
|
|
107
|
+
get focused(): boolean {
|
|
108
|
+
return this._focused;
|
|
109
|
+
}
|
|
110
|
+
set focused(value: boolean) {
|
|
111
|
+
this._focused = value;
|
|
112
|
+
this.updateFocusState();
|
|
113
|
+
}
|
|
114
|
+
private focusMode:
|
|
115
|
+
| "profile-filter"
|
|
116
|
+
| "profiles"
|
|
117
|
+
| "default-mailbox"
|
|
118
|
+
| "mailbox-filter"
|
|
119
|
+
| "period" = "profile-filter";
|
|
120
|
+
private profiles: ProtonMailWorkingProfile[];
|
|
121
|
+
private activeProfile: string;
|
|
122
|
+
private errorMessage = "";
|
|
123
|
+
|
|
124
|
+
constructor(
|
|
125
|
+
private readonly tui: { requestRender(): void },
|
|
126
|
+
theme: Theme,
|
|
127
|
+
private readonly done: (value: ProtonMailHubResult | null) => void,
|
|
128
|
+
profiles: ProtonMailWorkingProfile[],
|
|
129
|
+
initialProfile?: string,
|
|
130
|
+
) {
|
|
131
|
+
this.theme = theme;
|
|
132
|
+
this.profiles = profiles.length > 0 ? [...profiles] : [this.createSyntheticDefaultProfile()];
|
|
133
|
+
this.activeProfile = this.resolveInitialProfile(initialProfile);
|
|
134
|
+
this.profilesList = new SelectList([], 6, selectTheme(theme));
|
|
135
|
+
this.titleText = new Markdown(
|
|
136
|
+
[
|
|
137
|
+
"# Proton Mail Settings Hub",
|
|
138
|
+
"",
|
|
139
|
+
"Configure profiles for later LLM workflows instead of browsing mail here.",
|
|
140
|
+
].join("\n"),
|
|
141
|
+
0,
|
|
142
|
+
0,
|
|
143
|
+
this.mdTheme,
|
|
144
|
+
);
|
|
145
|
+
this.helpText = new Markdown(
|
|
146
|
+
[
|
|
147
|
+
"# What this saves",
|
|
148
|
+
"",
|
|
149
|
+
"- active profile",
|
|
150
|
+
"- default mailbox",
|
|
151
|
+
"- mailbox filter",
|
|
152
|
+
"- default period",
|
|
153
|
+
].join("\n"),
|
|
154
|
+
0,
|
|
155
|
+
0,
|
|
156
|
+
this.mdTheme,
|
|
157
|
+
);
|
|
158
|
+
this.footerText = new Text(
|
|
159
|
+
"Tab cycles • Enter creates/saves • Ctrl+S saves • Ctrl+D deletes • Esc clears/back",
|
|
160
|
+
0,
|
|
161
|
+
0,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
this.profileFilterInput.setValue(initialProfile ?? this.activeProfile);
|
|
165
|
+
this.loadProfileIntoInputs(this.currentProfile());
|
|
166
|
+
|
|
167
|
+
this.profileFilterInput.onSubmit = () => {
|
|
168
|
+
const next = this.profileFilterInput.getValue().trim();
|
|
169
|
+
if (!next) {
|
|
170
|
+
this.focusMode = "profiles";
|
|
171
|
+
this.updateFocusState();
|
|
172
|
+
this.refresh();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
this.selectOrCreateProfile(next);
|
|
176
|
+
this.focusMode = "default-mailbox";
|
|
177
|
+
this.updateFocusState();
|
|
178
|
+
this.refresh();
|
|
179
|
+
};
|
|
180
|
+
this.profileFilterInput.onEscape = () => {
|
|
181
|
+
if (this.profileFilterInput.getValue()) {
|
|
182
|
+
this.profileFilterInput.setValue("");
|
|
183
|
+
this.applyProfileFilter();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
this.done(null);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
this.defaultMailboxInput.onSubmit = () => {
|
|
190
|
+
this.focusMode = "mailbox-filter";
|
|
191
|
+
this.updateFocusState();
|
|
192
|
+
this.refresh();
|
|
193
|
+
};
|
|
194
|
+
this.defaultMailboxInput.onEscape = () => {
|
|
195
|
+
if (this.defaultMailboxInput.getValue() !== this.currentPolicy().default_mailbox) {
|
|
196
|
+
this.defaultMailboxInput.setValue(this.currentPolicy().default_mailbox ?? "");
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
this.focusMode = "profiles";
|
|
200
|
+
this.updateFocusState();
|
|
201
|
+
this.refresh();
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
this.mailboxFilterInput.onSubmit = () => {
|
|
205
|
+
this.focusMode = "period";
|
|
206
|
+
this.updateFocusState();
|
|
207
|
+
this.refresh();
|
|
208
|
+
};
|
|
209
|
+
this.mailboxFilterInput.onEscape = () => {
|
|
210
|
+
if (this.mailboxFilterInput.getValue() !== this.currentPolicy().mailbox_filter) {
|
|
211
|
+
this.mailboxFilterInput.setValue(this.currentPolicy().mailbox_filter ?? "");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
this.focusMode = "default-mailbox";
|
|
215
|
+
this.updateFocusState();
|
|
216
|
+
this.refresh();
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
this.periodInput.onSubmit = () => {
|
|
220
|
+
this.saveCurrentProfile();
|
|
221
|
+
};
|
|
222
|
+
this.periodInput.onEscape = () => {
|
|
223
|
+
if (this.periodInput.getValue() !== this.currentPolicy().default_period) {
|
|
224
|
+
this.periodInput.setValue(this.currentPolicy().default_period ?? currentMonth());
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
this.focusMode = "mailbox-filter";
|
|
228
|
+
this.updateFocusState();
|
|
229
|
+
this.refresh();
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
this.profilesList.onSelectionChange = (item) => {
|
|
233
|
+
this.selectProfile(item.value);
|
|
234
|
+
};
|
|
235
|
+
this.profilesList.onSelect = (item) => {
|
|
236
|
+
this.selectProfile(item.value);
|
|
237
|
+
this.focusMode = "default-mailbox";
|
|
238
|
+
this.updateFocusState();
|
|
239
|
+
this.refresh();
|
|
240
|
+
};
|
|
241
|
+
this.profilesList.onCancel = () => {
|
|
242
|
+
this.focusMode = "profile-filter";
|
|
243
|
+
this.updateFocusState();
|
|
244
|
+
this.refresh();
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
this.applyProfileFilter();
|
|
248
|
+
this.updateFocusState();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private createSyntheticDefaultProfile(): ProtonMailWorkingProfile {
|
|
252
|
+
return {
|
|
253
|
+
profile: "default",
|
|
254
|
+
policy: {},
|
|
255
|
+
policyPath: ".pi/protonmail/profiles/default/policy.json",
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private resolveInitialProfile(initialProfile?: string): string {
|
|
260
|
+
if (initialProfile?.trim()) {
|
|
261
|
+
const normalizedInitial = normalized(initialProfile);
|
|
262
|
+
if (this.profiles.some((profile) => profile.profile === normalizedInitial))
|
|
263
|
+
return normalizedInitial;
|
|
264
|
+
return normalizedInitial;
|
|
265
|
+
}
|
|
266
|
+
return this.profiles[0]?.profile ?? "default";
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private currentProfile(): ProtonMailWorkingProfile {
|
|
270
|
+
return (
|
|
271
|
+
this.profiles.find((profile) => profile.profile === this.activeProfile) ?? this.profiles[0]
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private currentPolicy(): ProtonMailProfilePolicy {
|
|
276
|
+
return this.currentProfile()?.policy ?? {};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private loadProfileIntoInputs(profile?: ProtonMailWorkingProfile): void {
|
|
280
|
+
const current = profile ?? this.currentProfile();
|
|
281
|
+
this.defaultMailboxInput.setValue(current.policy.default_mailbox ?? "");
|
|
282
|
+
this.mailboxFilterInput.setValue(current.policy.mailbox_filter ?? "");
|
|
283
|
+
this.periodInput.setValue(current.policy.default_period ?? currentMonth());
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private profileMatchesQuery(profile: ProtonMailWorkingProfile, query: string): boolean {
|
|
287
|
+
const needle = normalized(query);
|
|
288
|
+
if (!needle) return true;
|
|
289
|
+
return [
|
|
290
|
+
profile.profile,
|
|
291
|
+
profile.policy.default_mailbox ?? "",
|
|
292
|
+
profile.policy.mailbox_filter ?? "",
|
|
293
|
+
profile.policy.default_period ?? "",
|
|
294
|
+
].some((value) => normalized(value).includes(needle));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private filteredProfiles(): ProtonMailWorkingProfile[] {
|
|
298
|
+
const query = this.profileFilterInput.getValue().trim();
|
|
299
|
+
if (!query) return this.profiles;
|
|
300
|
+
return this.profiles.filter((profile) => this.profileMatchesQuery(profile, query));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private applyProfileFilter(): void {
|
|
304
|
+
const filter = this.profileFilterInput.getValue().trim().toLowerCase();
|
|
305
|
+
const visible = this.filteredProfiles();
|
|
306
|
+
replaceSelectItems(
|
|
307
|
+
this.profilesList,
|
|
308
|
+
visible.map((profile) =>
|
|
309
|
+
makeSelectItem(profile.profile, profileLabel(profile), policySummary(profile.policy)),
|
|
310
|
+
),
|
|
311
|
+
);
|
|
312
|
+
this.profilesList.setFilter(filter);
|
|
313
|
+
this.activeProfile = selectedItemValue(this.profilesList) ?? this.activeProfile;
|
|
314
|
+
this.refresh();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private selectProfile(profileName: string): void {
|
|
318
|
+
const profile = this.profiles.find((entry) => entry.profile === profileName);
|
|
319
|
+
if (!profile) return;
|
|
320
|
+
this.activeProfile = profile.profile;
|
|
321
|
+
this.loadProfileIntoInputs(profile);
|
|
322
|
+
this.applyProfileFilter();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private selectOrCreateProfile(value: string): void {
|
|
326
|
+
const profileName = normalized(value);
|
|
327
|
+
if (!profileName) return;
|
|
328
|
+
const existing = this.profiles.find((profile) => profile.profile === profileName);
|
|
329
|
+
if (existing) {
|
|
330
|
+
this.selectProfile(existing.profile);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const next: ProtonMailWorkingProfile = {
|
|
334
|
+
profile: profileName,
|
|
335
|
+
policy: {},
|
|
336
|
+
policyPath: `.pi/protonmail/profiles/${profileName}/policy.json`,
|
|
337
|
+
};
|
|
338
|
+
this.profiles = [...this.profiles, next].sort((left, right) =>
|
|
339
|
+
left.profile.localeCompare(right.profile),
|
|
340
|
+
);
|
|
341
|
+
this.activeProfile = next.profile;
|
|
342
|
+
this.loadProfileIntoInputs(next);
|
|
343
|
+
this.applyProfileFilter();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private currentPolicyFromInputs(): ProtonMailProfilePolicy {
|
|
347
|
+
const default_period = this.periodInput.getValue().trim();
|
|
348
|
+
return {
|
|
349
|
+
default_mailbox: this.defaultMailboxInput.getValue().trim() || undefined,
|
|
350
|
+
mailbox_filter: this.mailboxFilterInput.getValue().trim() || undefined,
|
|
351
|
+
default_period: default_period
|
|
352
|
+
? MONTH_RE.test(default_period)
|
|
353
|
+
? default_period
|
|
354
|
+
: undefined
|
|
355
|
+
: undefined,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private saveCurrentProfile(): void {
|
|
360
|
+
const profile = this.currentProfile();
|
|
361
|
+
if (!profile) return;
|
|
362
|
+
this.done({ kind: "save", profile: profile.profile, policy: this.currentPolicyFromInputs() });
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private deleteCurrentProfile(): void {
|
|
366
|
+
const profile = this.currentProfile();
|
|
367
|
+
if (!profile) return;
|
|
368
|
+
if (profile.profile === "default") {
|
|
369
|
+
this.errorMessage = "The default profile cannot be deleted.";
|
|
370
|
+
this.refresh();
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
this.done({ kind: "delete", profile: profile.profile });
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private refresh(): void {
|
|
377
|
+
this.tui.requestRender();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private updateFocusState(): void {
|
|
381
|
+
const active = this._focused;
|
|
382
|
+
this.profileFilterInput.focused = active && this.focusMode === "profile-filter";
|
|
383
|
+
this.defaultMailboxInput.focused = active && this.focusMode === "default-mailbox";
|
|
384
|
+
this.mailboxFilterInput.focused = active && this.focusMode === "mailbox-filter";
|
|
385
|
+
this.periodInput.focused = active && this.focusMode === "period";
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
invalidate(): void {
|
|
389
|
+
this.profileFilterInput.invalidate();
|
|
390
|
+
this.defaultMailboxInput.invalidate();
|
|
391
|
+
this.mailboxFilterInput.invalidate();
|
|
392
|
+
this.periodInput.invalidate();
|
|
393
|
+
this.profilesList.invalidate();
|
|
394
|
+
this.titleText.invalidate();
|
|
395
|
+
this.helpText.invalidate();
|
|
396
|
+
this.footerText.invalidate();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
handleInput(data: string): void {
|
|
400
|
+
if (matchesKey(data, "ctrl+c")) {
|
|
401
|
+
this.done(null);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (matchesKey(data, "ctrl+s")) {
|
|
405
|
+
this.saveCurrentProfile();
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (matchesKey(data, "ctrl+d")) {
|
|
409
|
+
this.deleteCurrentProfile();
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (matchesKey(data, "escape")) {
|
|
413
|
+
if (this.focusMode === "profile-filter") {
|
|
414
|
+
if (this.profileFilterInput.getValue()) {
|
|
415
|
+
this.profileFilterInput.setValue("");
|
|
416
|
+
this.applyProfileFilter();
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
this.done(null);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (this.focusMode === "period") {
|
|
423
|
+
if (
|
|
424
|
+
this.periodInput.getValue() !== (this.currentPolicy().default_period ?? currentMonth())
|
|
425
|
+
) {
|
|
426
|
+
this.periodInput.setValue(this.currentPolicy().default_period ?? currentMonth());
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
this.focusMode = "mailbox-filter";
|
|
430
|
+
this.updateFocusState();
|
|
431
|
+
this.refresh();
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (this.focusMode === "mailbox-filter") {
|
|
435
|
+
if (this.mailboxFilterInput.getValue() !== (this.currentPolicy().mailbox_filter ?? "")) {
|
|
436
|
+
this.mailboxFilterInput.setValue(this.currentPolicy().mailbox_filter ?? "");
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
this.focusMode = "default-mailbox";
|
|
440
|
+
this.updateFocusState();
|
|
441
|
+
this.refresh();
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if (this.focusMode === "default-mailbox") {
|
|
445
|
+
if (this.defaultMailboxInput.getValue() !== (this.currentPolicy().default_mailbox ?? "")) {
|
|
446
|
+
this.defaultMailboxInput.setValue(this.currentPolicy().default_mailbox ?? "");
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
this.focusMode = "profiles";
|
|
450
|
+
this.updateFocusState();
|
|
451
|
+
this.refresh();
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
this.focusMode = "profile-filter";
|
|
455
|
+
this.updateFocusState();
|
|
456
|
+
this.refresh();
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (matchesKey(data, "tab")) {
|
|
460
|
+
if (this.focusMode === "profile-filter") this.focusMode = "profiles";
|
|
461
|
+
else if (this.focusMode === "profiles") this.focusMode = "default-mailbox";
|
|
462
|
+
else if (this.focusMode === "default-mailbox") this.focusMode = "mailbox-filter";
|
|
463
|
+
else if (this.focusMode === "mailbox-filter") this.focusMode = "period";
|
|
464
|
+
else this.focusMode = "profile-filter";
|
|
465
|
+
this.updateFocusState();
|
|
466
|
+
this.refresh();
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (this.focusMode === "profile-filter") {
|
|
470
|
+
this.profileFilterInput.handleInput(data);
|
|
471
|
+
this.applyProfileFilter();
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
if (this.focusMode === "default-mailbox") {
|
|
475
|
+
this.defaultMailboxInput.handleInput(data);
|
|
476
|
+
this.refresh();
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (this.focusMode === "mailbox-filter") {
|
|
480
|
+
this.mailboxFilterInput.handleInput(data);
|
|
481
|
+
this.refresh();
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (this.focusMode === "period") {
|
|
485
|
+
this.periodInput.handleInput(data);
|
|
486
|
+
this.refresh();
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
this.profilesList.handleInput(data);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
render(width: number): string[] {
|
|
493
|
+
const innerWidth = Math.max(30, width - 2);
|
|
494
|
+
const border = (value: string) => this.theme.fg("border", value);
|
|
495
|
+
const padLine = (content: string) => {
|
|
496
|
+
const clipped = truncateToWidth(content, innerWidth, "", false);
|
|
497
|
+
const pad = Math.max(0, innerWidth - visibleWidth(clipped));
|
|
498
|
+
return `${border("│")}${clipped}${" ".repeat(pad)}${border("│")}`;
|
|
499
|
+
};
|
|
500
|
+
const section = (title: string) => [
|
|
501
|
+
border(`├${"─".repeat(innerWidth)}┤`),
|
|
502
|
+
padLine(this.theme.fg("accent", ` ${title}`)),
|
|
503
|
+
];
|
|
504
|
+
const lines: string[] = [border(`╭${"─".repeat(innerWidth)}╮`)];
|
|
505
|
+
lines.push(...section("Proton Mail"));
|
|
506
|
+
for (const line of this.titleText.render(innerWidth)) lines.push(padLine(line));
|
|
507
|
+
lines.push(padLine(this.theme.fg("dim", ` Active profile: ${this.activeProfile}`)));
|
|
508
|
+
lines.push(...section("Profiles"));
|
|
509
|
+
for (const line of this.profileFilterInput.render(innerWidth)) lines.push(padLine(line));
|
|
510
|
+
for (const line of this.profilesList.render(innerWidth)) lines.push(padLine(line));
|
|
511
|
+
lines.push(...section("Default mailbox"));
|
|
512
|
+
for (const line of this.defaultMailboxInput.render(innerWidth)) lines.push(padLine(line));
|
|
513
|
+
lines.push(...section("Mailbox filter"));
|
|
514
|
+
for (const line of this.mailboxFilterInput.render(innerWidth)) lines.push(padLine(line));
|
|
515
|
+
lines.push(...section("Default period"));
|
|
516
|
+
for (const line of this.periodInput.render(innerWidth)) lines.push(padLine(line));
|
|
517
|
+
lines.push(...section("LLM setup"));
|
|
518
|
+
for (const line of this.helpText.render(innerWidth)) lines.push(padLine(line));
|
|
519
|
+
if (this.errorMessage) {
|
|
520
|
+
lines.push(padLine(this.theme.fg("error", ` ${this.errorMessage}`)));
|
|
521
|
+
}
|
|
522
|
+
for (const line of this.footerText.render(innerWidth)) lines.push(padLine(line));
|
|
523
|
+
lines.push(border(`╰${"─".repeat(innerWidth)}╯`));
|
|
524
|
+
return lines;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
export async function openProtonMailHub(
|
|
529
|
+
ctx: Pick<ExtensionCommandContext, "cwd" | "hasUI" | "ui">,
|
|
530
|
+
profiles: ProtonMailWorkingProfile[],
|
|
531
|
+
rawArgs: string,
|
|
532
|
+
): Promise<ProtonMailHubResult | null> {
|
|
533
|
+
if (!ctx.hasUI) {
|
|
534
|
+
ctx.ui.notify("Proton Mail setup hub requires interactive mode.", "warning");
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
const args = parseHubArgs(rawArgs);
|
|
538
|
+
return new Promise<ProtonMailHubResult | null>((resolve) => {
|
|
539
|
+
void ctx.ui
|
|
540
|
+
.custom(
|
|
541
|
+
(tui, theme, _keybindings, done) =>
|
|
542
|
+
new ProtonMailHubComponent(
|
|
543
|
+
tui,
|
|
544
|
+
theme,
|
|
545
|
+
(value) => {
|
|
546
|
+
done(value);
|
|
547
|
+
resolve(value);
|
|
548
|
+
},
|
|
549
|
+
profiles,
|
|
550
|
+
args.profile,
|
|
551
|
+
),
|
|
552
|
+
{ overlay: true, overlayOptions: { width: "92%", maxHeight: "92%" } },
|
|
553
|
+
)
|
|
554
|
+
.then(
|
|
555
|
+
() => resolve(null),
|
|
556
|
+
() => resolve(null),
|
|
557
|
+
);
|
|
558
|
+
});
|
|
559
|
+
}
|
package/src/index.ts
ADDED