-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathipc.ts
More file actions
3615 lines (3183 loc) · 124 KB
/
Copy pathipc.ts
File metadata and controls
3615 lines (3183 loc) · 124 KB
1
2
3
4
5
6
7
8
9
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
55
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
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
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
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
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
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
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
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import { invoke } from "@tauri-apps/api/core";
import type { BackupImportResult, ClipEntry, Note, Snippet } from "./types";
// ── Clipboard history ────────────────────────────────────────────────────────
export function getHistory(limit = 500, offset = 0): Promise<ClipEntry[]> {
return invoke("get_history", { limit, offset });
}
/** Fetch one entry with its **full** payload — including the image blob the
* slim history list omits. Used by the preview when an image clip is
* selected. Returns null if the row no longer exists. */
export function getClip(id: number): Promise<ClipEntry | null> {
return invoke("get_clip", { id });
}
export function searchHistory(query: string, limit = 500): Promise<ClipEntry[]> {
return invoke("search_history", { query, limit });
}
/** Paste a clipboard entry. Honours the `paste.plain_text_only` setting:
* HTML / RTF entries are downgraded to their plain-text preview when
* the toggle is on. Image / Files entries paste as-is. */
export function pasteEntry(id: number): Promise<void> {
return invoke("paste_entry", { id });
}
/** Paste a clipboard entry preserving its original content type. Bypasses
* the plain-text setting — used by Shift+Enter as a one-shot override. */
export function pasteEntryFormatted(id: number): Promise<void> {
return invoke("paste_entry_formatted", { id });
}
export function getPastePlainTextOnly(): Promise<boolean> {
return invoke("get_paste_plain_text_only");
}
export function setPastePlainTextOnly(value: boolean): Promise<void> {
return invoke("set_paste_plain_text_only", { value });
}
/** Read the persisted `ocr.save_source_image` flag. When `false`
* (the default since v0.26.3), the OCR pipeline persists only the
* recognised text to history; when `true`, the source PNG is also
* upserted so the user can re-OCR it later. */
export function getOcrSaveSourceImage(): Promise<boolean> {
return invoke("get_ocr_save_source_image");
}
export function setOcrSaveSourceImage(value: boolean): Promise<void> {
return invoke("set_ocr_save_source_image", { value });
}
// ── Screenshot preview window (CleanShot-X-style) ──────────────────────────
/** Path of the currently-pending captured PNG, or null if none. The
* preview React component calls this on mount to know which file to
* display in its thumbnail. */
export function getPendingScreenshotPath(): Promise<string | null> {
return invoke("get_pending_screenshot_path");
}
/** Richer variant — includes the frontmost-app name captured at
* shot time + the current pin state. Used by the preview HUD to
* show the source-app chip and reflect the pinned visual state. */
export interface PendingScreenshotInfo {
path: string;
app_name: string | null;
pinned: boolean;
}
export function getPendingScreenshotInfo(): Promise<PendingScreenshotInfo | null> {
return invoke("get_pending_screenshot_info");
}
/** Read the pending screenshot's PNG bytes as a `data:image/png;base64,…`
* URL. The annotation editor loads this instead of `convertFileSrc` so
* the image is same-origin: on Windows the asset protocol both fails to
* render inside the editor webview and taints the canvas (breaking the
* Save path's `toDataURL()`). A data URL renders everywhere and never
* taints. Returns null when nothing is pending or the file is unreadable. */
export function getPendingScreenshotDataUrl(): Promise<string | null> {
return invoke("get_pending_screenshot_data_url");
}
/** Set the pin state. While pinned, a subsequent screenshot does NOT
* replace the on-screen preview (the new PNG still goes to clipboard
* + history). Returns the resulting state. */
export function setScreenshotPinned(pinned: boolean): Promise<boolean> {
return invoke("set_screenshot_pinned", { pinned });
}
/** Save: promote the temp PNG to ~/Downloads (with the captured app
* name baked into the filename), push to clipboard, push to history,
* close the preview window. */
export function screenshotPreviewSave(): Promise<void> {
return invoke("screenshot_preview_save");
}
/** Copy: re-write the PNG to the clipboard. Preview stays open
* (unlike Save). Useful when the user has copied something else in
* the meantime and wants the screenshot back on the clipboard. */
export function screenshotPreviewCopy(): Promise<void> {
return invoke("screenshot_preview_copy");
}
/** Discard: delete the temp PNG, close the preview window. No
* side effects on clipboard / Downloads / history. */
export function screenshotPreviewDiscard(): Promise<void> {
return invoke("screenshot_preview_discard");
}
/** Edit: open the annotation editor window (arrows / text / rect /
* highlight / blur). The preview hides itself; the editor's Save
* bakes the annotated PNG to ~/Downloads + clipboard + history and
* re-shows the preview with the edited image. */
export function screenshotPreviewEdit(): Promise<void> {
return invoke("screenshot_preview_edit");
}
// ── Screenshot editor ──────────────────────────────────────────────────────
/** Save the annotated PNG (base64 from canvas.toDataURL). Backend
* writes to ~/Downloads with `<App>-<ts>-edited.png`, pushes to
* clipboard + history, closes the editor, re-shows the preview. */
export function editorSave(pngB64: string): Promise<string> {
return invoke("editor_save", { pngB64 });
}
/** Copy the *edited* canvas (base64 PNG) straight to the clipboard —
* no file, no window close. Bound to Cmd/Ctrl+C in the editor.
* Returns the PNG byte size. */
export function editorCopy(pngB64: string): Promise<number> {
return invoke("editor_copy", { pngB64 });
}
/** Persist the editor window size (logical px) so the next open restores
* it. Called from the editor's debounced resize listener. (v0.66.0) */
export function setEditorSize(width: number, height: number): Promise<void> {
return invoke("set_editor_size", { width, height });
}
/** Cancel: close the editor, re-show the preview with the original
* (unedited) capture. */
export function editorCancel(): Promise<void> {
return invoke("editor_cancel");
}
/** Cursor-follow: if the cursor has crossed to a different monitor,
* re-position the preview window to the new monitor's bottom-left.
* Called from the preview React component every 200 ms while the
* window is open. */
export function repositionPreviewToCursor(): Promise<void> {
return invoke("reposition_preview_to_cursor");
}
// ── Input lock (macOS-lock-style chord-to-unlock) ──────────────────────────
/** Read the persisted unlock chord. Defaults to `["i", "r"]` on a
* fresh install or a malformed stored value. */
export function getInputLockChord(): Promise<string[]> {
return invoke("get_input_lock_chord");
}
/** Persist a new unlock chord. Backend rejects empty / all-unparseable
* chords so the user can't lock themselves out via Settings. */
export function setInputLockChord(keys: string[]): Promise<void> {
return invoke("set_input_lock_chord", { keys });
}
/** Activate the input lock — block all keyboard / mouse input until
* the configured chord is pressed. On macOS needs Accessibility (same
* grant the text-expander already uses). On Linux Wayland this
* returns an error (rdev's grab is X11-only). */
export function startInputLock(): Promise<void> {
return invoke("start_input_lock");
}
// ── Wakelock (mouse-jiggle keep-awake) ─────────────────────────────────────
/** Toggle the wakelock. While active, the cursor jumps 1 px right
* and immediately back every 60 s — defeats idle-sleep timers and
* "away" detection (Teams, Slack, screen savers). Resolves with the
* resulting state. */
/** Toggle keep-awake. `source` ("wakelock" | "caffeine") only brands the
* on-screen status toast; both behave identically. `mode` (v0.116.0):
* "full" (default — screen forced on, the historical behaviour) or
* "dark" (screen may sleep, system stays awake — SSH/remote reachable). */
export function wakelockSet(
enable: boolean,
source?: string,
mode?: "full" | "dark",
keepPopup?: boolean,
): Promise<boolean> {
return invoke("wakelock_set", {
enable,
source: source ?? "wakelock",
mode: mode ?? "full",
keepPopup: keepPopup ?? false,
});
}
/** Wakelock status: on + which mode ("full" | "dark"). The
* `wakelock-changed` event carries the same shape. */
export interface WakelockStatus {
on: boolean;
mode: string;
}
/** Open the user's terminal (iTerm2 if installed, else Terminal.app) at the
* frontmost Finder window's folder. Returns the directory. macOS-only.
* Backend: `commands::finder_open_terminal`. */
export function finderOpenTerminal(): Promise<string> {
return invoke("finder_open_terminal");
}
/** Markdown → PDF (same action as Ctrl+Shift+M). With `path`, converts that
* file; bare, converts the file-manager selection (macOS). Fire-and-forget:
* resolves once the conversion has been kicked off; the result surfaces via
* a system notification. Backend: `commands::md_to_pdf_run`. */
export function mdToPdfRun(path?: string): Promise<void> {
return invoke("md_to_pdf_run", { path: path ?? null });
}
/** Show an on-screen status toast (hide popup + animated flourish). Used for
* timer / alarm confirmations. Backend: `commands::show_status_toast`. */
export function showStatusToast(
kind: string,
on: boolean,
title: string,
subtitle: string,
): Promise<void> {
return invoke("show_status_toast", { kind, on, title, subtitle });
}
export function wakelockGet(): Promise<WakelockStatus> {
return invoke("wakelock_get");
}
// ── Bruno (Brutto-Netto-Rechner — German income-tax + SV) ─────────────
/** Per-user defaults applied to a bare `bruno <€>` invocation.
* Persistent via the SQLite settings table. Settings panel has a
* collapsible Bruno section that edits these. */
export interface BrunoDefaults {
tax_class: number; // 1..6
state: string; // German state ISO short
children: number;
is_church_member: boolean;
/** Krankenkasse-Zusatzbeitrag in **percent** (e.g. 2.45 for TK 2025). */
health_add: number;
// ── Selbständigen-Defaults (nur fürs `f`-Suffix, v0.86.0) ──
/** "gkv" (freiwillig, berechnet) | "pkv" (fester Beitrag). */
kv_type: string;
/** PKV-Monatsbeitrag in Euro (inkl. privater Pflegepflicht). */
pkv_monthly: number;
/** GKV mit Krankengeldanspruch (14,6 %) statt ermäßigt (14,0 %). */
kv_sick_pay: boolean;
/** "freiberufler" (keine GewSt) | "gewerbe". */
business_type: string;
/** Gewerbesteuer-Hebesatz in Prozent (z. B. 400). */
hebesatz: number;
/** Zusammenveranlagung → Splittingtarif. */
self_married: boolean;
}
export function brunoGetDefaults(): Promise<BrunoDefaults> {
return invoke("bruno_get_defaults");
}
export function brunoSetDefaults(defaults: BrunoDefaults): Promise<void> {
return invoke("bruno_set_defaults", { defaults });
}
// ── Faker (fake-data generator, v0.84.270) ────────────────────────────
import type {
CatalogEntry,
FakerDefaults,
FakerGenResult,
FakerSpec,
} from "./faker";
export interface FakerLocaleOption {
code: string;
label: string;
}
/** The generator catalogue with a live sample per row. Backend: faker_catalog. */
export function fakerCatalog(): Promise<CatalogEntry[]> {
return invoke("faker_catalog");
}
// ── Figlet (ASCII-art banners, v0.85.0) ───────────────────────────────
import type {
FigletBanner,
FigletDefaults,
FigletFontMeta,
FigletOpts,
FigletSample,
} from "./figlet";
/** All font metadata (name/category/popular/pinned). Backend: figlet_fonts. */
export function figletFonts(): Promise<FigletFontMeta[]> {
return invoke("figlet_fonts");
}
/** Render the big preview / copy payload for one font. Backend: figlet_render. */
export function figletRender(text: string, font: string, opts: FigletOpts): Promise<FigletBanner> {
return invoke("figlet_render", { text, font, opts });
}
/** Compact samples of `text` for a window of fonts. Backend: figlet_gallery. */
export function figletGallery(
text: string,
fonts: string[],
maxLines: number,
maxCols: number,
): Promise<FigletSample[]> {
return invoke("figlet_gallery", { text, fonts, maxLines, maxCols });
}
export function figletGetDefaults(): Promise<FigletDefaults> {
return invoke("figlet_get_defaults");
}
export function figletSetDefaults(defaults: FigletDefaults): Promise<void> {
return invoke("figlet_set_defaults", { defaults });
}
/** Generate all requested values in one call. Backend: faker_generate. */
export function fakerGenerate(spec: FakerSpec): Promise<FakerGenResult> {
return invoke("faker_generate", {
req: {
generator: spec.generator,
n: spec.n,
locale: spec.locale ?? null,
seed: spec.seed ?? null,
args: spec.args ?? null,
template: spec.template ?? null,
},
});
}
/** The selectable locales (code + label). Backend: faker_locales. */
export function fakerLocales(): Promise<FakerLocaleOption[]> {
return invoke("faker_locales");
}
export function fakerGetDefaults(): Promise<FakerDefaults> {
return invoke("faker_get_defaults");
}
export function fakerSetDefaults(defaults: FakerDefaults): Promise<void> {
return invoke("faker_set_defaults", { defaults });
}
/** Paste generated text into the focused app; optionally store in history.
* Backend: paste_generated. */
export function pasteGenerated(text: string, saveHistory: boolean): Promise<void> {
return invoke("paste_generated", { text, saveHistory });
}
// ── Security command builders (sec, v0.84.271) ────────────────────────
import type { SecCatalog, SecDefaults } from "./sec";
/** The pentest-tool catalogue (presets + flag help). Backend: sec_catalog. */
export function secCatalog(): Promise<SecCatalog> {
return invoke("sec_catalog");
}
export function secGetDefaults(): Promise<SecDefaults> {
return invoke("sec_get_defaults");
}
export function secSetDefaults(defaults: SecDefaults): Promise<void> {
return invoke("sec_set_defaults", { defaults });
}
/** Open the user's terminal with `command` inserted (macOS; no tool subprocess).
* Backend: sec_open_in_terminal. `autoEnter=false` leaves it un-submitted. */
export function secOpenInTerminal(command: string, autoEnter: boolean): Promise<void> {
return invoke("sec_open_in_terminal", { command, autoEnter });
}
/** Whether a path exists (wordlist existence check). Backend: sec_path_exists. */
export function secPathExists(path: string): Promise<boolean> {
return invoke("sec_path_exists", { path });
}
// ── App launcher (Spotlight-like, macOS only in v0.37) ────────────────
export interface AppEntry {
name: string;
path: string;
name_lower: string;
}
/** Return the cached app index (scanned once at startup). One-shot per
* popup mount; no polling. Empty on non-macOS. */
export function listApps(): Promise<AppEntry[]> {
return invoke("list_apps");
}
/** Re-scan installed apps. Used by Settings → Apps → Refresh. Returns
* the new count. Also clears the icon cache. */
export function refreshApps(): Promise<number> {
return invoke("refresh_apps");
}
/** Launch the app at `path` via macOS Launch Services. Activates the
* existing instance if the app is already running. */
export function launchApp(path: string): Promise<void> {
return invoke("launch_app", { path });
}
/** Lazy icon fetch. Returns base64 PNG (128×128). First call per app
* shells out to `sips` (~50 ms); subsequent calls hit the in-memory
* cache (instant). */
export function getAppIcon(path: string): Promise<string> {
return invoke("get_app_icon", { path });
}
// ── Timer (search-bar `timer N s|min|h`) ─────────────────────────────
export interface TimerView {
id: number;
label: string;
remaining_secs: number;
}
/** Start a new timer; backend spawns a worker thread that sleeps for
* `seconds` then fires macOS native notification + sound + emits a
* `timer-fired` event. Returns the new timer's id. */
export function startTimer(seconds: number, label: string): Promise<number> {
return invoke("start_timer", { seconds, label });
}
/** Cancel an in-flight timer by id. Returns `true` if the id was
* active (was cancelled), `false` if it was unknown (already fired). */
export function cancelTimer(id: number): Promise<boolean> {
return invoke("cancel_timer", { id });
}
/** Snapshot of currently-active timers. Used by the footer indicator
* to show count + (future) inline cancel buttons. */
export function listTimers(): Promise<TimerView[]> {
return invoke("list_timers");
}
// ── Finder selection (macOS) ──────────────────────────────────────────
/** One item in the current Finder selection. `is_image` is a cheap
* extension test — good enough to decide whether to surface the
* Resize action. `size_bytes` is `null` when stat fails. */
export interface FinderItem {
path: string;
name: string;
size_bytes: number | null;
is_image: boolean;
}
/** Read the current Finder selection. Returns an empty list if
* nothing is selected. On macOS without Automation→Finder TCC
* permission this rejects with `"finder.automation_denied"`, which
* the frontend surfaces as a tailored "open System Settings" banner. */
export function getFinderSelection(): Promise<FinderItem[]> {
return invoke("get_finder_selection");
}
/** Resize an image file with Lanczos3, writing the output next to
* the source as `<stem>-<W>x<H>.<ext>`. Returns the absolute path
* of the written file. */
export function resizeFile(path: string, width: number, height: number): Promise<string> {
return invoke("resize_file", { path, width, height });
}
/** Dimensions + format of one selected file, for the `rz` preview. Every field
* is optional: an unreadable file is REPORTED, never silently dropped. */
export interface ImageInfo {
path: string;
width: number | null;
height: number | null;
format: string | null;
}
/** Dimensions of the clipboard image, or null when there is none. */
export function clipboardImageSize(): Promise<[number, number] | null> {
return invoke("clipboard_image_size");
}
/** Header-only probe (no decode) of the given paths -- see the `rz` preview. */
export function imageSizes(paths: string[]): Promise<ImageInfo[]> {
return invoke("image_sizes", { paths });
}
/** Optimise a single PNG file losslessly with oxipng. Writes the
* result next to the source as `<stem>-optim.png`. Returns the output
* path + before/after byte counts. Non-PNG sources reject with a
* clear error (oxipng is PNG-only). */
export function optimizeFile(
path: string,
): Promise<{ path: string; before_bytes: number; after_bytes: number }> {
return invoke("optimize_file", { path });
}
/** Create a file named `name` in the frontmost Finder/Explorer window's folder
* (or the Desktop if no window is open), optionally with `content` written into
* it (`touch <name> > <text>`). Returns the absolute path created. Needs the
* Automation→Finder TCC grant on macOS. Backend: `commands::finder_touch`. */
export function finderTouch(name: string, content = ""): Promise<string> {
return invoke("finder_touch", { name, content });
}
/** Create a folder named `name` in the frontmost Finder window's folder.
* Returns the absolute path created. Backend: `commands::finder_mkdir`. */
export function finderMkdir(name: string): Promise<string> {
return invoke("finder_mkdir", { name });
}
/** Read the persisted theme preference — `"light"`, `"dark"`, or
* `"system"`. Defaults to `"system"` on a fresh install. Backend:
* `commands::get_theme_preference`. */
export function getThemePreference(): Promise<string> {
return invoke("get_theme_preference");
}
/** Persist the theme preference. The backend rejects anything that
* isn't one of the three valid values. Backend:
* `commands::set_theme_preference`. */
export function setThemePreference(theme: string): Promise<void> {
return invoke("set_theme_preference", { theme });
}
/** Master toggle for UI feedback sounds (expand click, OCR, screenshot,
* record start/stop, copy). Defaults to `true`. Backend:
* `commands::get_sound_enabled`. */
export function getSoundEnabled(): Promise<boolean> {
return invoke("get_sound_enabled");
}
/** Persist + apply the feedback-sound toggle (takes effect immediately,
* no relaunch). Backend: `commands::set_sound_enabled`. */
/** Screenshot shutter style: "snap" (default) | "dslr" | "switch" | "off". */
export type ScreenshotSoundStyle = "snap" | "dslr" | "switch" | "off";
export function getScreenshotSound(): Promise<ScreenshotSoundStyle> {
return invoke("get_screenshot_sound");
}
/** Persist + apply the style; the backend plays the new sound once as preview. */
export function setScreenshotSound(style: ScreenshotSoundStyle): Promise<ScreenshotSoundStyle> {
return invoke("set_screenshot_sound", { style });
}
export function setSoundEnabled(enabled: boolean): Promise<void> {
return invoke("set_sound_enabled", { enabled });
}
/** Popup overlay size — one of `"small"`, `"medium"`, `"large"`. Defaults
* to `"medium"` (the 700×500 the window ships with). Backend:
* `commands::get_window_size_preference`. */
export function getWindowSizePreference(): Promise<string> {
return invoke("get_window_size_preference");
}
/** Persist the popup size and resize the live window. The backend rejects
* anything that isn't one of the three presets. Backend:
* `commands::set_window_size_preference`. */
export function setWindowSizePreference(size: string): Promise<void> {
return invoke("set_window_size_preference", { size });
}
// ── Status toast (v0.51.0+) ────────────────────────────────────────────
/** Payload rendered by the transient on-screen status-toast window. */
export interface StatusToast {
kind: string;
on: boolean;
title: string;
subtitle: string;
}
/** Pull the latest status-toast payload (read by the toast window on
* mount + on each `status-toast-changed` event). */
export function getStatusToast(): Promise<StatusToast | null> {
return invoke("get_status_toast");
}
/** Hide the toast window — called by its own auto-dismiss timer. */
export function hideStatusToast(): Promise<void> {
return invoke("hide_status_toast");
}
export function deleteEntry(id: number): Promise<void> {
return invoke("delete_entry", { id });
}
/** Pin / unpin a clipboard entry (floats to top, exempt from pruning). */
export function setClipPinned(id: number, pinned: boolean): Promise<void> {
return invoke("set_clip_pinned", { id, pinned });
}
/** Attach / update / clear a note on a clipboard entry ("" clears it). Noted
* entries are highlighted in the list and exempt from pruning. */
export function setClipNote(id: number, note: string): Promise<void> {
return invoke("set_clip_note", { id, note });
}
export interface ClipboardPrivacy {
/** Comma/newline-separated app-name substrings never captured from. */
exclude_apps: string;
/** Seconds after a copy to auto-wipe the clipboard (0 = off). */
auto_clear_seconds: number;
}
export function getClipboardPrivacy(): Promise<ClipboardPrivacy> {
return invoke("get_clipboard_privacy");
}
export function setClipboardPrivacy(p: ClipboardPrivacy): Promise<void> {
return invoke("set_clipboard_privacy", {
excludeApps: p.exclude_apps,
autoClearSeconds: p.auto_clear_seconds,
});
}
export function clearHistory(): Promise<void> {
return invoke("clear_history");
}
export function toggleCapture(paused: boolean): Promise<void> {
return invoke("toggle_capture", { paused });
}
export function getCaptureState(): Promise<boolean> {
return invoke("get_capture_state");
}
export function hidePopup(): Promise<void> {
return invoke("hide_popup");
}
/** Write `text` to the OS clipboard and paste it into the previously
* active app. Used by the inline calculator. */
export function pasteText(text: string): Promise<void> {
return invoke("paste_text", { text });
}
/** Tell the backend to (not) auto-hide the popup on blur. Use while a
* native modal (file dialog) is open, then reset to `false`. */
export function setSuppressHide(suppress: boolean): Promise<void> {
return invoke("set_suppress_hide", { suppress });
}
/** Persisted: does clicking outside the popup close it? Default true. */
export function getPopupCloseOnBlur(): Promise<boolean> {
return invoke("get_popup_close_on_blur");
}
/** Set + persist the click-outside behaviour (live effect, no restart). */
export function setPopupCloseOnBlur(close: boolean): Promise<void> {
return invoke("set_popup_close_on_blur", { close });
}
// ── Snippets ─────────────────────────────────────────────────────────────────
export function listSnippets(): Promise<Snippet[]> {
return invoke("list_snippets");
}
/** Snippet count + on-disk footprint (bytes of the stored, encrypted columns). */
export interface SnippetStorage {
count: number;
bytes: number;
}
export function getSnippetStorage(): Promise<SnippetStorage> {
return invoke("get_snippet_storage");
}
export function findSnippets(query: string): Promise<Snippet[]> {
return invoke("find_snippets", { query });
}
/** Pass id = null to create, id = number to update. Returns the snippet id.
* categoryId = null puts the snippet in no group ("Ungrouped"). */
export function upsertSnippet(
id: number | null,
abbreviation: string,
title: string,
body: string,
categoryId: number | null = null,
): Promise<number> {
return invoke("upsert_snippet", { id, abbreviation, title, body, categoryId });
}
export function deleteSnippet(id: number): Promise<void> {
return invoke("delete_snippet", { id });
}
// ── Snippet categories (groups) ───────────────────────────────────────────────
export interface SnippetCategory {
id: number;
name: string;
sort_order: number;
/** Number of snippets currently in this group. */
count: number;
}
export function listSnippetCategories(): Promise<SnippetCategory[]> {
return invoke("list_snippet_categories");
}
/** Create a group (or return the existing one if the name is taken). Returns its id. */
export function createSnippetCategory(name: string): Promise<number> {
return invoke("create_snippet_category", { name });
}
export function renameSnippetCategory(id: number, name: string): Promise<void> {
return invoke("rename_snippet_category", { id, name });
}
/** Delete a group. Its snippets are *ungrouped*, never deleted. */
export function deleteSnippetCategory(id: number): Promise<void> {
return invoke("delete_snippet_category", { id });
}
/** Persist a new group order (array of category ids, top to bottom). */
export function reorderSnippetCategories(ids: number[]): Promise<void> {
return invoke("reorder_snippet_categories", { ids });
}
/** Assign a snippet to a group (categoryId = null → ungroup). */
export function setSnippetCategory(id: number, categoryId: number | null): Promise<void> {
return invoke("set_snippet_category", { id, categoryId });
}
export function pasteSnippet(id: number): Promise<void> {
return invoke("paste_snippet", { id });
}
export interface ImportResult {
imported: number;
skipped: number;
errors: string[];
}
/** Import snippets from a JSON string. Existing abbreviations get overwritten. */
export function importSnippets(json: string): Promise<ImportResult> {
return invoke("import_snippets", { json });
}
/** Read a JSON file from the given path and import its snippets. Accepts a full
* IR backup (only its snippets + groups are applied), a snippets-only backup,
* or the lean `[{abbreviation,title,body}]` shape. Used by the file picker AND
* the Snippets-tab drag-and-drop. */
export function importSnippetsFromFile(path: string): Promise<ImportResult> {
return invoke("import_snippets_from_file", { path });
}
/** Write all snippets + their groups to `path` as a snippets-only backup
* document — the exchange format for editing them in another app. Returns the
* number of snippets written. */
export function exportSnippetsToFile(path: string): Promise<number> {
return invoke("export_snippets_to_file", { path });
}
/** Re-import the bundled default AI-prompt snippets. Existing snippets
* sharing an `abbreviation` get overwritten; user-added snippets with
* distinct abbreviations are untouched. Surfaced via the Snippets-tab
* "Restore defaults" button. */
export function restoreDefaultPrompts(): Promise<ImportResult> {
return invoke("restore_default_prompts");
}
// ── Notes ────────────────────────────────────────────────────────────────────
export function listNotes(): Promise<Note[]> {
return invoke("list_notes");
}
export function listNoteCategories(): Promise<string[]> {
return invoke("list_note_categories");
}
/** Promote a clipboard entry to a persistent note. Returns the new note id. */
export function saveClipAsNote(
clipId: number,
title: string,
category: string,
): Promise<number> {
return invoke("save_clip_as_note", { clipId, title, category });
}
/** Create a from-scratch text note. Returns the new note id. */
export function createNote(
title: string,
body: string,
category: string,
): Promise<number> {
return invoke("create_note", { title, body, category });
}
/** Update a note's title / body / category. Body edits are ignored for
* image and files notes (the backend short-circuits). */
export function updateNote(
id: number,
title: string,
body: string,
category: string,
): Promise<void> {
return invoke("update_note", { id, title, body, category });
}
export function deleteNote(id: number): Promise<void> {
return invoke("delete_note", { id });
}
export function clearNotes(): Promise<void> {
return invoke("clear_notes");
}
export function pasteNote(id: number): Promise<void> {
return invoke("paste_note", { id });
}
// ── Backup (full app export / import) ────────────────────────────────────────
export interface BackupExportOptions {
includeHistory?: boolean;
includeSnippets?: boolean;
includeNotes?: boolean;
includeTotp?: boolean;
includeSettings?: boolean;
/** Timesheet tracking data — opt-in (bumps the file format to v3). */
includeTimesheet?: boolean;
/** If set, encrypt the backup with this password (AES-256-GCM + Argon2id). */
password?: string;
}
/** Returns a pretty-printed JSON string (or encrypted envelope if password
* is provided). Each section is included only when the corresponding flag
* is true (or undefined — defaults to true for backwards compatibility). */
export function exportBackup(opts: BackupExportOptions = {}): Promise<string> {
return invoke("export_backup", {
includeHistory: opts.includeHistory ?? true,
includeSnippets: opts.includeSnippets ?? true,
includeNotes: opts.includeNotes ?? true,
includeTotp: opts.includeTotp ?? true,
includeSettings: opts.includeSettings ?? true,
includeTimesheet: opts.includeTimesheet ?? false,
password: opts.password ?? null,
});
}
/** Build the backup JSON (with the same selective semantics as
* `exportBackup`) and write it directly to `path`. Returns the number
* of bytes written. */
export function saveBackupToFile(
path: string,
opts: BackupExportOptions = {},
): Promise<number> {
return invoke("save_backup_to_file", {
path,
includeHistory: opts.includeHistory ?? true,
includeSnippets: opts.includeSnippets ?? true,
includeNotes: opts.includeNotes ?? true,
includeTotp: opts.includeTotp ?? true,
includeSettings: opts.includeSettings ?? true,
includeTimesheet: opts.includeTimesheet ?? false,
password: opts.password ?? null,
});
}
/** Check if a backup file is encrypted (requires password to import). */
export function isBackupEncrypted(path: string): Promise<boolean> {
return invoke("is_backup_encrypted", { path });
}
// ── Text expander ────────────────────────────────────────────────────────────
export interface ExpanderConfig {
enabled: boolean;
/** Tauri shortcut string, e.g. "Alt+Backquote", "Ctrl+Shift+E". */
hotkey: string;
/** True if the OS has granted Inspector Rust permission to synthesize keyboard
* events. macOS: Accessibility. Other OSes: always true. */
accessibility_granted: boolean;
}
export function getExpanderConfig(): Promise<ExpanderConfig> {
return invoke("get_expander_config");
}
/** Persist a new expander config and re-register the hotkey. The backend
* validates the hotkey string and errors out *before* writing settings if
* it's malformed, so the previous registration stays intact on failure. */
export function setExpanderConfig(
enabled: boolean,
hotkey: string,
): Promise<ExpanderConfig> {
return invoke("set_expander_config", { enabled, hotkey });
}
/** Programmatically trigger an expand-at-cursor cycle. Used by the
* "Test now" button in settings. */
export function triggerExpandAtCursor(): Promise<void> {
return invoke("trigger_expand_at_cursor");
}
// ── Passive auto-expansion (aText-style, v0.56.0) ──────────────────────────────
export type AutoExpandTrigger = "delimiter" | "immediate";
export interface AutoExpandConfig {
/** Master on/off for the passive keystroke monitor. */
enabled: boolean;
/** When a complete abbreviation expands: after a delimiter (default) or
* the instant it's typed. */
trigger: AutoExpandTrigger;
/** Match abbreviations case-sensitively (default false). */
match_case: boolean;
/** Let an abbreviation fire even when glued to a longer word (default false). */
expand_inside_words: boolean;
/** A single Backspace right after an expansion restores the abbreviation. */
undo_enabled: boolean;
}
export function getAutoExpandConfig(): Promise<AutoExpandConfig> {
return invoke("get_auto_expand_config");
}
// ── Cloud sync with cue (cue.celox.io) ───────────────────────────────────────
export interface SyncConfig {
/** Master on/off for the background sync worker. */
enabled: boolean;
/** cue base URL (default https://cue.celox.io). */
url: string;
/** Per-user sync token, generated in cue → Settings → Snippet-Sync. */
token: string;
}
export interface SyncStatus {
/** Last successful cycle (ms epoch), 0 = never. */
last_ms: number;
/** Empty when the last cycle succeeded. */
last_error: string;
}
export function getSyncConfig(): Promise<SyncConfig> {
return invoke("get_sync_config");
}
export function setSyncConfig(config: SyncConfig): Promise<SyncConfig> {
return invoke("set_sync_config", { config });
}
export function getSyncStatus(): Promise<SyncStatus> {
return invoke("get_sync_status");
}
export function syncNow(): Promise<void> {
return invoke("sync_now");
}
// ── Device sync (shared folder, v0.139.0) ───────────────────────────────────
export interface DeviceSyncConfig {
/** Master on/off. Default OFF — disabled means no thread work at all. */
enabled: boolean;
/** Absolute path of the shared folder (default: iCloud Drive). */
folder: string;
/** 2FA secrets travel only when this is ticked. Default off. */
include_totp: boolean;
}
export interface DeviceSyncStatus {
last_ms: number;
last_error: string;
device_id: string;
/** Other devices' files currently in the folder. */
peers: number;
has_passphrase: boolean;
folder_ok: boolean;
}
export interface DeviceSyncStats {
peers_read: number;
clips: number;
snippets: number;
notes: number;
totp: number;
published: boolean;
skipped: string[];
}
export function getDeviceSyncConfig(): Promise<DeviceSyncConfig> {
return invoke("get_device_sync_config");
}
export function setDeviceSyncConfig(config: DeviceSyncConfig): Promise<void> {
return invoke("set_device_sync_config", { config });