@yufengtadian/freedom-cli 1.13.1 → 1.13.2

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.
Files changed (38) hide show
  1. package/README.md +87 -13
  2. package/lib/agents.js +318 -0
  3. package/lib/build.js +168 -39
  4. package/lib/cli.js +160 -4
  5. package/lib/config.js +20 -1
  6. package/lib/desktop.js +133 -0
  7. package/lib/dev.js +149 -0
  8. package/lib/mcp.js +217 -0
  9. package/lib/release.js +90 -0
  10. package/lib/security.js +113 -54
  11. package/lib/shell.js +5 -2
  12. package/lib/tui.js +37 -1
  13. package/lib/verify.js +43 -8
  14. package/package.json +2 -1
  15. package/shell/darwin-arm64/freedom-shell +0 -0
  16. package/shell/linux-x64/freedom-shell +0 -0
  17. package/shell/win-x64/freedom-shell.exe +0 -0
  18. package/skill/freedom/SKILL.md +159 -0
  19. package/templates/desktop/app.html +559 -0
  20. package/templates/desktop/backend/desktop.mjs +317 -0
  21. package/templates/desktop/freedom.config.js +29 -0
  22. package/templates/desktop/icon.ico +0 -0
  23. package/templates/go/pkg/freedom/anti_debug_windows.go +114 -11
  24. package/templates/go/pkg/freedom/assets/freedom.js +3 -0
  25. package/templates/go/pkg/freedom/freedom.go +52 -9
  26. package/templates/go/pkg/freedom/resources.go +68 -14
  27. package/templates/go/pkg/freedom/securetemp.go +61 -0
  28. package/templates/go/pkg/freedom/securetemp_other.go +20 -0
  29. package/templates/go/pkg/freedom/securetemp_windows.go +25 -0
  30. package/templates/go/pkg/freedom/security.go +240 -61
  31. package/templates/go/pkg/freedom/store.go +8 -0
  32. package/templates/go/pkg/freedom/window_other.go +7 -1
  33. package/templates/go/pkg/freedom/window_windows.go +185 -17
  34. package/templates/installer/app.nsi +43 -0
  35. package/templates/project/freedom.config.js +14 -0
  36. package/templates/project/freedom.d.ts +77 -0
  37. package/templates/project-minimal/freedom.config.js +14 -0
  38. package/templates/project-minimal/freedom.d.ts +77 -0
@@ -9,10 +9,13 @@ import "fmt"
9
9
  // 前端仍可通过 window.freedom.window.isFrameless() 感知当前是否无边框。
10
10
  func (a *App) applyTitleBar() {}
11
11
 
12
+ // setWindowIcon 在 macOS / Linux 上为空实现:图标由应用包(.icns / .desktop)决定。
13
+ func (a *App) setWindowIcon() {}
14
+
12
15
  // installWindowEvents / uninstallWindowEvents 在非 Windows 平台为空实现
13
16
  // (窗口事件与关闭拦截依赖 WndProc 子类化,仅 Windows 提供)。
14
17
  func (a *App) installWindowEvents() {}
15
- func (a *App) uninstallWindowEvents() {}
18
+ func (a *App) uninstallWindowEvents() {}
16
19
 
17
20
  // windowControl 处理前端 window.freedom.window.* 请求(macOS / Linux 占位实现)。
18
21
  // 动作集与 window_windows.go 保持一致:查询类返回中性值,动作类静默 no-op,
@@ -23,6 +26,9 @@ func windowControl(hwnd uintptr, action string, mode TitleBarMode, paramsJSON st
23
26
  // 仅 frameless 返回 true(与 window_windows.go 语义一致);
24
27
  // hidden 模式回退原生标题栏,前端不应自绘按钮。
25
28
  return mode == TitleBarFrameless, nil
29
+ case "appIcon":
30
+ // macOS / Linux 暂不提供 exe 内嵌图标提取,前端隐藏标题栏图标。
31
+ return "", nil
26
32
  case "isMaximized", "isMinimized", "isVisible", "isFocused", "isFullscreen":
27
33
  if action == "isVisible" {
28
34
  return true, nil
@@ -3,8 +3,14 @@
3
3
  package freedom
4
4
 
5
5
  import (
6
+ "bytes"
7
+ "encoding/base64"
8
+ "encoding/binary"
6
9
  "encoding/json"
7
10
  "fmt"
11
+ "image"
12
+ "image/color"
13
+ "image/png"
8
14
  "syscall"
9
15
  "unsafe"
10
16
  )
@@ -34,6 +40,12 @@ var (
34
40
  procGetModuleHandle = kernel32.NewProc("GetModuleHandleW")
35
41
  procLoadImage = user32win.NewProc("LoadImageW")
36
42
  procDestroyIcon = user32win.NewProc("DestroyIcon")
43
+ // PE 资源读取(exeIconData 提取内嵌 RT_ICON 用)与标题栏图标设置。
44
+ procFindResource = kernel32.NewProc("FindResourceW")
45
+ procSizeofResource = kernel32.NewProc("SizeofResource")
46
+ procLoadResource = kernel32.NewProc("LoadResource")
47
+ procLockResource = kernel32.NewProc("LockResource")
48
+ procSendMessage = user32win.NewProc("SendMessageW")
37
49
 
38
50
  dwmapi = syscall.NewLazyDLL("dwmapi.dll")
39
51
  procDwmExtendFrameIntoArea = dwmapi.NewProc("DwmExtendFrameIntoClientArea")
@@ -44,8 +56,8 @@ var (
44
56
  procSetWindowSubclass = comctl32.NewProc("SetWindowSubclass")
45
57
  procDefSubclassProc = comctl32.NewProc("DefSubclassProc")
46
58
 
47
- procGetWindowRect = user32win.NewProc("GetWindowRect")
48
- procGetClientRect = user32win.NewProc("GetClientRect")
59
+ procGetWindowRect = user32win.NewProc("GetWindowRect")
60
+ procGetClientRect = user32win.NewProc("GetClientRect")
49
61
  procBringWindowToTop = user32win.NewProc("BringWindowToTop")
50
62
  procIsIconic = user32win.NewProc("IsIconic")
51
63
  procIsWindowVisible = user32win.NewProc("IsWindowVisible")
@@ -57,12 +69,12 @@ var (
57
69
  procEnumDisplayMonitors = user32win.NewProc("EnumDisplayMonitors")
58
70
 
59
71
  // W6 审查修复追加:PNG→HICON 绘制(gdi32)、图标合成、子类化摘除、单实例互斥量。
60
- gdi32 = syscall.NewLazyDLL("gdi32.dll")
61
- procCreateDIBSection = gdi32.NewProc("CreateDIBSection")
62
- procDeleteObject = gdi32.NewProc("DeleteObject")
63
- procCreateIconIndirect = user32win.NewProc("CreateIconIndirect")
72
+ gdi32 = syscall.NewLazyDLL("gdi32.dll")
73
+ procCreateDIBSection = gdi32.NewProc("CreateDIBSection")
74
+ procDeleteObject = gdi32.NewProc("DeleteObject")
75
+ procCreateIconIndirect = user32win.NewProc("CreateIconIndirect")
64
76
  procRemoveWindowSubclass = comctl32.NewProc("RemoveWindowSubclass")
65
- procCreateMutexW = kernel32.NewProc("CreateMutexW")
77
+ procCreateMutexW = kernel32.NewProc("CreateMutexW")
66
78
 
67
79
  shcore = syscall.NewLazyDLL("shcore.dll")
68
80
  procGetDpiForMonitor = shcore.NewProc("GetDpiForMonitor")
@@ -90,20 +102,20 @@ var (
90
102
  procGlobalSize = kernel32.NewProc("GlobalSize")
91
103
  procGlobalFree = kernel32.NewProc("GlobalFree")
92
104
 
93
- procFindWindowW = user32win.NewProc("FindWindowW")
105
+ procFindWindowW = user32win.NewProc("FindWindowW")
94
106
  procSendMessageTimeoutW = user32win.NewProc("SendMessageTimeoutW")
95
107
 
96
108
  procShellExecuteW = shell32.NewProc("ShellExecuteW")
97
- shlwapi = syscall.NewLazyDLL("shlwapi.dll")
98
- procSHDeleteKeyW = shlwapi.NewProc("SHDeleteKeyW")
109
+ shlwapi = syscall.NewLazyDLL("shlwapi.dll")
110
+ procSHDeleteKeyW = shlwapi.NewProc("SHDeleteKeyW")
99
111
 
100
- advapi32 = syscall.NewLazyDLL("advapi32.dll")
101
- procRegOpenKeyExW = advapi32.NewProc("RegOpenKeyExW")
102
- procRegCreateKeyExW = advapi32.NewProc("RegCreateKeyExW")
103
- procRegSetValueExW = advapi32.NewProc("RegSetValueExW")
104
- procRegQueryValueExW = advapi32.NewProc("RegQueryValueExW")
105
- procRegDeleteValueW = advapi32.NewProc("RegDeleteValueW")
106
- procRegCloseKey = advapi32.NewProc("RegCloseKey")
112
+ advapi32 = syscall.NewLazyDLL("advapi32.dll")
113
+ procRegOpenKeyExW = advapi32.NewProc("RegOpenKeyExW")
114
+ procRegCreateKeyExW = advapi32.NewProc("RegCreateKeyExW")
115
+ procRegSetValueExW = advapi32.NewProc("RegSetValueExW")
116
+ procRegQueryValueExW = advapi32.NewProc("RegQueryValueExW")
117
+ procRegDeleteValueW = advapi32.NewProc("RegDeleteValueW")
118
+ procRegCloseKey = advapi32.NewProc("RegCloseKey")
107
119
 
108
120
  procSetProcessDpiAwarenessContext = user32win.NewProc("SetProcessDpiAwarenessContext")
109
121
  )
@@ -140,6 +152,11 @@ const (
140
152
 
141
153
  smCXSmall = 13 // SM_CXSMICON
142
154
  smCYSmall = 14 // SM_CYSMICON
155
+
156
+ smCXIcon = 11 // SM_CXICON:任务栏 / Alt-Tab 大图标尺寸
157
+ smCYIcon = 12 // SM_CYICON
158
+ wmSetIcon = 0x0080 // WM_SETICON:设置窗口大/小图标(ICON_BIG=1 / ICON_SMALL=0)
159
+ rtIcon = 3 // RT_ICON:ICO 内嵌单幅图像资源类型
143
160
  )
144
161
 
145
162
  // gwlStyle = GWL_STYLE(-16)。用变量声明,避免 uintptr 常量转换溢出。
@@ -205,6 +222,9 @@ func windowControl(hwnd uintptr, action string, mode TitleBarMode, paramsJSON st
205
222
  // 仅 frameless 返回 true:hidden 模式保留 DWM 原生按钮,
206
223
  // 前端若据 isFrameless 自绘按钮会与原生按钮重叠。
207
224
  return mode == TitleBarFrameless, nil
225
+ case "appIcon":
226
+ // exe 内嵌图标 → PNG data URL(无边框自绘标题栏用);无图标时返回 ""。
227
+ return exeAppIconDataURL(), nil
208
228
 
209
229
  // ---- W1 对标 Tauri:位置 / 尺寸 ----
210
230
  case "setPosition":
@@ -509,6 +529,154 @@ func loadExeIcon(cx, cy uintptr) uintptr {
509
529
  return 0
510
530
  }
511
531
 
532
+ // exeIconData 读取当前 exe 内嵌的 RT_ICON 资源原始字节。
533
+ // 不依赖 RT_GROUP_ICON 的 ID(不同注入工具写入的资源 ID 可能不同):
534
+ // 先按 rcedit 约定 RT_ICON ID=1 读取,失败回退 ID=0,与 loadExeIcon 的 ID 假设解耦。
535
+ func exeIconData() []byte {
536
+ hInst, _, _ := procGetModuleHandle.Call(0)
537
+ if hInst == 0 {
538
+ return nil
539
+ }
540
+ for _, id := range []uintptr{1, 0} {
541
+ // FindResourceW(hModule, MAKEINTRESOURCE(id), MAKEINTRESOURCE(RT_ICON))
542
+ hRes, _, _ := procFindResource.Call(hInst, id, rtIcon)
543
+ if hRes == 0 {
544
+ continue
545
+ }
546
+ size, _, _ := procSizeofResource.Call(hInst, hRes)
547
+ if size == 0 {
548
+ continue
549
+ }
550
+ hData, _, _ := procLoadResource.Call(hInst, hRes)
551
+ if hData == 0 {
552
+ continue
553
+ }
554
+ ptr, _, _ := procLockResource.Call(hData)
555
+ if ptr == 0 {
556
+ continue
557
+ }
558
+ return unsafe.Slice((*byte)(unsafe.Pointer(ptr)), size)
559
+ }
560
+ return nil
561
+ }
562
+
563
+ // isPNG 判断字节流是否为 PNG 编码(.ico 内嵌 PNG 时 RT_ICON 数据即 PNG)。
564
+ func isPNG(b []byte) bool {
565
+ return len(b) >= 8 && b[0] == 0x89 && b[1] == 'P' && b[2] == 'N' && b[3] == 'G'
566
+ }
567
+
568
+ // exeAppIconDataURL 从当前 exe 的 PE 资源图标(RT_ICON)提取为 PNG data URL,
569
+ // 供无边框模式前端自绘标题栏显示应用图标——不依赖 resources 文件夹里的任何文件。
570
+ // 无法提取时返回空字符串(前端隐藏图标)。
571
+ func exeAppIconDataURL() string {
572
+ data := exeIconData()
573
+ if len(data) == 0 {
574
+ return ""
575
+ }
576
+ if isPNG(data) {
577
+ return "data:image/png;base64," + base64.StdEncoding.EncodeToString(data)
578
+ }
579
+ img, err := dibToPNG(data)
580
+ if err != nil {
581
+ return ""
582
+ }
583
+ return "data:image/png;base64," + base64.StdEncoding.EncodeToString(img)
584
+ }
585
+
586
+ // setWindowIcon 把 exe 内嵌的应用程序图标同步到窗口标题栏与任务栏:
587
+ // - native 模式:标题栏图标(ICON_SMALL)与任务栏 / Alt-Tab 图标(ICON_BIG)均来自 exe 图标,
588
+ // 保证「标题栏图标 = exe 图标」;
589
+ // - frameless 模式:无标题栏,但任务栏 / Alt-Tab 仍显示 exe 图标。
590
+ //
591
+ // 图标经 LoadImageW 从 PE 资源 RT_GROUP_ICON 加载,不依赖 resources 里的图标文件;
592
+ // exe 未注入图标时静默跳过(保留系统默认图标)。
593
+ func (a *App) setWindowIcon() {
594
+ hwnd := a.WindowHandle()
595
+ if hwnd == 0 {
596
+ return
597
+ }
598
+ cxIcon, _, _ := procGetSystemMetrics.Call(smCXIcon)
599
+ cyIcon, _, _ := procGetSystemMetrics.Call(smCYIcon)
600
+ cxSmall, _, _ := procGetSystemMetrics.Call(smCXSmall)
601
+ cySmall, _, _ := procGetSystemMetrics.Call(smCYSmall)
602
+ big := loadExeIcon(cxIcon, cyIcon)
603
+ small := loadExeIcon(cxSmall, cySmall)
604
+ if big != 0 {
605
+ procSendMessage.Call(hwnd, wmSetIcon, 1 /* ICON_BIG */, big)
606
+ procDestroyIcon.Call(big)
607
+ }
608
+ if small != 0 {
609
+ procSendMessage.Call(hwnd, wmSetIcon, 0 /* ICON_SMALL */, small)
610
+ procDestroyIcon.Call(small)
611
+ }
612
+ }
613
+
614
+ // dibToPNG 将 ICO 内嵌的 DIB 图像(BITMAPINFOHEADER 起始的裸 DIB,即 .ico 未内嵌
615
+ // PNG 时的 RT_ICON 数据)纯 Go 解析为 PNG 字节,不依赖 GDI 绘图 API。
616
+ // 支持 32/24 bpp;透明由 AND mask 决定(对应位=1 表示透明)。
617
+ func dibToPNG(data []byte) ([]byte, error) {
618
+ if len(data) < 40 {
619
+ return nil, fmt.Errorf("DIB too short: %d bytes", len(data))
620
+ }
621
+ biSize := binary.LittleEndian.Uint32(data[0:4])
622
+ if biSize < 40 {
623
+ return nil, fmt.Errorf("unsupported BITMAPINFOHEADER size %d", biSize)
624
+ }
625
+ width := int(binary.LittleEndian.Uint32(data[4:8]))
626
+ heightRaw := int32(binary.LittleEndian.Uint32(data[8:12]))
627
+ bitCount := binary.LittleEndian.Uint16(data[14:16])
628
+ if width <= 0 || heightRaw == 0 {
629
+ return nil, fmt.Errorf("bad dimensions %dx%d", width, heightRaw)
630
+ }
631
+ bpp := (int(bitCount) + 7) / 8
632
+ if bpp != 3 && bpp != 4 {
633
+ return nil, fmt.Errorf("unsupported bitcount %d", bitCount)
634
+ }
635
+ height := int(heightRaw)
636
+ if height < 0 {
637
+ height = -height
638
+ }
639
+ // ICO 的 DIB 高度可能为实际像素高度的 2 倍(上方 XOR + 下方 AND mask),
640
+ // 此时需折半,否则图像会被纵向拉伸。
641
+ pixelRowBytes := (width*bpp + 3) &^ 3
642
+ andRowBytes := ((width + 31) / 32) * 4
643
+ pxLen := pixelRowBytes * height
644
+ if pxLen > len(data)-int(biSize) && height%2 == 0 {
645
+ height /= 2
646
+ pxLen = pixelRowBytes * height
647
+ }
648
+ pxStart := int(biSize)
649
+ andStart := pxStart + pxLen
650
+ // AND mask 是否存在以实际数据长度为准:ICO 的 DIB 若数据不足(无 AND mask 段),
651
+ // andStart+andRowBytes*height 会超出 len(data),下方读取将越界 panic 崩掉壳进程。
652
+ hasAndMask := andStart+andRowBytes*height <= len(data)
653
+ img := image.NewRGBA(image.Rect(0, 0, width, height))
654
+ for y := 0; y < height; y++ {
655
+ srcY := height - 1 - y // DIB 自底向上:数据首行是图像末行
656
+ row := pxStart + srcY*pixelRowBytes
657
+ andRow := andStart + srcY*andRowBytes
658
+ for x := 0; x < width; x++ {
659
+ off := row + x*bpp
660
+ if off+bpp > len(data) {
661
+ return nil, fmt.Errorf("DIB data truncated at x=%d,y=%d", x, y)
662
+ }
663
+ b := data[off]
664
+ g := data[off+1]
665
+ r := data[off+2]
666
+ alpha := uint8(255)
667
+ if hasAndMask && (data[andRow+x/8]>>uint(7-x%8))&1 == 1 {
668
+ alpha = 0
669
+ }
670
+ img.SetRGBA(x, y, color.RGBA{R: r, G: g, B: b, A: alpha})
671
+ }
672
+ }
673
+ var buf bytes.Buffer
674
+ if err := png.Encode(&buf, img); err != nil {
675
+ return nil, err
676
+ }
677
+ return buf.Bytes(), nil
678
+ }
679
+
512
680
  // applyTitleBar 依据配置调整窗口标题栏(Windows 实现)。
513
681
  func (a *App) applyTitleBar() {
514
682
  hwnd := a.WindowHandle()
@@ -0,0 +1,43 @@
1
+ ; Freedom NSIS 安装器模板(freedom build --installer 填充 @...@ 占位符后编译)。
2
+ ; 占位符:@NAME@ 应用名 / @VERSION@ 版本 / @SRC@ 产物目录(exe + resources)
3
+ ; @OUT@ 输出目录 / @EXE@ 可执行文件名。
4
+ Unicode true
5
+ !include "MUI2.nsh"
6
+
7
+ !define APPNAME "@NAME@"
8
+ !define VERSION "@VERSION@"
9
+ !define SRC "@SRC@"
10
+ !define OUT "@OUT@"
11
+ !define EXE "@EXE@"
12
+
13
+ Name "${APPNAME}"
14
+ OutFile "${OUT}\${APPNAME}-setup-${VERSION}.exe"
15
+ InstallDir "$PROGRAMFILES64\${APPNAME}"
16
+ InstallDirRegKey HKLM "Software\${APPNAME}" "InstallDir"
17
+ RequestExecutionLevel admin
18
+
19
+ !insertmacro MUI_PAGE_DIRECTORY
20
+ !insertmacro MUI_PAGE_INSTFILES
21
+ !insertmacro MUI_UNPAGE_CONFIRM
22
+ !insertmacro MUI_UNPAGE_INSTFILES
23
+ !insertmacro MUI_LANGUAGE "SimpChinese"
24
+
25
+ Section "Install"
26
+ SetOutPath "$INSTDIR"
27
+ File /r "${SRC}\*.*"
28
+ WriteUninstaller "$INSTDIR\uninstall.exe"
29
+ CreateShortCut "$DESKTOP\${APPNAME}.lnk" "$INSTDIR\${EXE}"
30
+ CreateShortCut "$SMPROGRAMS\${APPNAME}.lnk" "$INSTDIR\${EXE}"
31
+ WriteRegStr HKLM "Software\${APPNAME}" "InstallDir" "$INSTDIR"
32
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayName" "${APPNAME}"
33
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "DisplayVersion" "${VERSION}"
34
+ WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" "UninstallString" "$INSTDIR\uninstall.exe"
35
+ SectionEnd
36
+
37
+ Section "Uninstall"
38
+ RMDir /r "$INSTDIR"
39
+ Delete "$DESKTOP\${APPNAME}.lnk"
40
+ Delete "$SMPROGRAMS\${APPNAME}.lnk"
41
+ DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}"
42
+ DeleteRegKey HKLM "Software\${APPNAME}"
43
+ SectionEnd
@@ -54,4 +54,18 @@ export default {
54
54
  // 示例(Node 后端):
55
55
  // backend: { command: 'node', args: ['backend/main.mjs'] }
56
56
  // backend: undefined,
57
+
58
+ // 单实例:true 时二次启动把命令行参数转发给已运行实例后自动退出,
59
+ // 主实例前端收 freedom.on('app.secondInstance', ({args})=>…)(Windows 生效)。
60
+ // singleInstance: false,
61
+
62
+ // 自动更新(发布环:freedom keygen 生成密钥 → 公钥填这里 → 发版 freedom manifest 签 latest.json):
63
+ // updater: {
64
+ // manifestURL: 'https://your.host/latest.json',
65
+ // publicKey: '<freedom keygen 输出的 base64 公钥>',
66
+ // requireSignature: false, // 开启后产物还须通过平台代码签名复核(仅 Windows)
67
+ // },
68
+
69
+ // dev 流自定义启动命令(默认 `npm run dev`,freedom dev 用它拉起 vite 并热更壳窗口):
70
+ // dev: { command: 'npm run dev' },
57
71
  };
@@ -0,0 +1,77 @@
1
+ // Freedom 桌面壳前端 SDK 类型声明(window.freedom)
2
+ // 运行时由壳注入(assets/freedom.js);本文件仅类型提示,不参与打包产物。
3
+ // 用法:tsconfig "include": ["src", "freedom.d.ts"],直接 window.freedom.* 获得补全。
4
+
5
+ export {};
6
+
7
+ interface FreedomWindowAPI {
8
+ minimize(): Promise<void>;
9
+ maximize(): Promise<void>;
10
+ unmaximize(): Promise<void>;
11
+ toggleMaximize(): Promise<boolean>;
12
+ close(): Promise<void>;
13
+ isMaximized(): Promise<boolean>;
14
+ isFrameless(): Promise<boolean>;
15
+ show(): Promise<void>;
16
+ hide(): Promise<void>;
17
+ focus(): Promise<void>;
18
+ setPosition(x: number, y: number): Promise<void>;
19
+ setSize(w: number, h: number): Promise<void>;
20
+ getPosition(): Promise<{ x: number; y: number }>;
21
+ getSize(): Promise<{ width: number; height: number }>;
22
+ setAlwaysOnTop(on: boolean): Promise<void>;
23
+ setFullscreen(on: boolean): Promise<void>;
24
+ startDrag(): Promise<void>;
25
+ /** 多窗口:spec.url 或 spec.html 指定页面,返回 {id} */
26
+ create(spec: { title?: string; url?: string; html?: string; width?: number; height?: number }): Promise<{ id: string }>;
27
+ list(): Promise<Array<{ id: string; title?: string }>>;
28
+ focusWindow(id: string): Promise<void>;
29
+ closeWindow(id: string): Promise<void>;
30
+ }
31
+
32
+ interface FreedomAPI {
33
+ /** 调用后端方法(任意语言后端,NDJSON/JSON-RPC over stdio),返回 Promise。 */
34
+ call(method: string, ...args: unknown[]): Promise<any>;
35
+ invoke(method: string, ...args: unknown[]): Promise<any>;
36
+ /** 订阅后端推送事件,返回取消订阅函数。 */
37
+ on(event: string, fn: (data: any) => void): () => void;
38
+ off(event: string, fn: (data: any) => void): void;
39
+ once(event: string, fn: (data: any) => void): () => void;
40
+ emit(event: string, data?: unknown): void;
41
+
42
+ window: FreedomWindowAPI;
43
+ /** 系统能力总入口(taskbar/backdrop/dialog 等,平台差异见 README 能力矩阵)。 */
44
+ sys: Record<string, (params?: unknown) => Promise<any>>;
45
+ clipboard: { read(): Promise<string>; write(text: string): Promise<void> };
46
+ shell: { open(url: string): Promise<void> };
47
+ notification: { show(opts: { title: string; body?: string }): Promise<void> };
48
+ shortcut: { register(combo: string, fn: () => void): Promise<() => void> };
49
+ autostart: { enable(): Promise<void>; disable(): Promise<void>; isEnabled(): Promise<boolean> };
50
+ protocol: { register(scheme: string): Promise<void>; unregister(scheme: string): Promise<void> };
51
+ app: { launchArgs(): Promise<string[]> };
52
+ path: { get(kind: string, name?: string): Promise<string> };
53
+ store: {
54
+ get(namespace: string, key: string): Promise<any>;
55
+ set(namespace: string, key: string, value: unknown): Promise<void>;
56
+ delete(namespace: string, key: string): Promise<void>;
57
+ keys(namespace: string): Promise<string[]>;
58
+ };
59
+ os: { info(): Promise<Record<string, any>> };
60
+ process: { exit(): Promise<void>; restart(): Promise<void> };
61
+ /** 自动更新:check 验签后缓存条目;install 仅认 check 结果,进度经 update.* 事件回推。 */
62
+ update: { check(): Promise<void>; install(): Promise<void> };
63
+ taskbar: Record<string, (params?: unknown) => Promise<any>>;
64
+ dialog: {
65
+ message(opts: { title?: string; text: string; type?: string }): Promise<any>;
66
+ open(opts?: Record<string, any>): Promise<string | string[] | null>;
67
+ save(opts?: Record<string, any>): Promise<string | null>;
68
+ };
69
+ tray: Record<string, (params?: unknown) => Promise<any>>;
70
+ menu: Record<string, (params?: unknown) => Promise<any>>;
71
+ }
72
+
73
+ declare global {
74
+ interface Window {
75
+ freedom: FreedomAPI;
76
+ }
77
+ }
@@ -55,4 +55,18 @@ export default {
55
55
  // 示例(Node 后端):
56
56
  // backend: { command: 'node', args: ['backend/main.mjs'] }
57
57
  // backend: undefined,
58
+
59
+ // 单实例:true 时二次启动把命令行参数转发给已运行实例后自动退出,
60
+ // 主实例前端收 freedom.on('app.secondInstance', ({args})=>…)(Windows 生效)。
61
+ // singleInstance: false,
62
+
63
+ // 自动更新(发布环:freedom keygen 生成密钥 → 公钥填这里 → 发版 freedom manifest 签 latest.json):
64
+ // updater: {
65
+ // manifestURL: 'https://your.host/latest.json',
66
+ // publicKey: '<freedom keygen 输出的 base64 公钥>',
67
+ // requireSignature: false, // 开启后产物还须通过平台代码签名复核(仅 Windows)
68
+ // },
69
+
70
+ // dev 流自定义启动命令(默认 `npm run dev`,freedom dev 用它拉起 vite 并热更壳窗口):
71
+ // dev: { command: 'npm run dev' },
58
72
  };
@@ -0,0 +1,77 @@
1
+ // Freedom 桌面壳前端 SDK 类型声明(window.freedom)
2
+ // 运行时由壳注入(assets/freedom.js);本文件仅类型提示,不参与打包产物。
3
+ // 用法:tsconfig "include": ["src", "freedom.d.ts"],直接 window.freedom.* 获得补全。
4
+
5
+ export {};
6
+
7
+ interface FreedomWindowAPI {
8
+ minimize(): Promise<void>;
9
+ maximize(): Promise<void>;
10
+ unmaximize(): Promise<void>;
11
+ toggleMaximize(): Promise<boolean>;
12
+ close(): Promise<void>;
13
+ isMaximized(): Promise<boolean>;
14
+ isFrameless(): Promise<boolean>;
15
+ show(): Promise<void>;
16
+ hide(): Promise<void>;
17
+ focus(): Promise<void>;
18
+ setPosition(x: number, y: number): Promise<void>;
19
+ setSize(w: number, h: number): Promise<void>;
20
+ getPosition(): Promise<{ x: number; y: number }>;
21
+ getSize(): Promise<{ width: number; height: number }>;
22
+ setAlwaysOnTop(on: boolean): Promise<void>;
23
+ setFullscreen(on: boolean): Promise<void>;
24
+ startDrag(): Promise<void>;
25
+ /** 多窗口:spec.url 或 spec.html 指定页面,返回 {id} */
26
+ create(spec: { title?: string; url?: string; html?: string; width?: number; height?: number }): Promise<{ id: string }>;
27
+ list(): Promise<Array<{ id: string; title?: string }>>;
28
+ focusWindow(id: string): Promise<void>;
29
+ closeWindow(id: string): Promise<void>;
30
+ }
31
+
32
+ interface FreedomAPI {
33
+ /** 调用后端方法(任意语言后端,NDJSON/JSON-RPC over stdio),返回 Promise。 */
34
+ call(method: string, ...args: unknown[]): Promise<any>;
35
+ invoke(method: string, ...args: unknown[]): Promise<any>;
36
+ /** 订阅后端推送事件,返回取消订阅函数。 */
37
+ on(event: string, fn: (data: any) => void): () => void;
38
+ off(event: string, fn: (data: any) => void): void;
39
+ once(event: string, fn: (data: any) => void): () => void;
40
+ emit(event: string, data?: unknown): void;
41
+
42
+ window: FreedomWindowAPI;
43
+ /** 系统能力总入口(taskbar/backdrop/dialog 等,平台差异见 README 能力矩阵)。 */
44
+ sys: Record<string, (params?: unknown) => Promise<any>>;
45
+ clipboard: { read(): Promise<string>; write(text: string): Promise<void> };
46
+ shell: { open(url: string): Promise<void> };
47
+ notification: { show(opts: { title: string; body?: string }): Promise<void> };
48
+ shortcut: { register(combo: string, fn: () => void): Promise<() => void> };
49
+ autostart: { enable(): Promise<void>; disable(): Promise<void>; isEnabled(): Promise<boolean> };
50
+ protocol: { register(scheme: string): Promise<void>; unregister(scheme: string): Promise<void> };
51
+ app: { launchArgs(): Promise<string[]> };
52
+ path: { get(kind: string, name?: string): Promise<string> };
53
+ store: {
54
+ get(namespace: string, key: string): Promise<any>;
55
+ set(namespace: string, key: string, value: unknown): Promise<void>;
56
+ delete(namespace: string, key: string): Promise<void>;
57
+ keys(namespace: string): Promise<string[]>;
58
+ };
59
+ os: { info(): Promise<Record<string, any>> };
60
+ process: { exit(): Promise<void>; restart(): Promise<void> };
61
+ /** 自动更新:check 验签后缓存条目;install 仅认 check 结果,进度经 update.* 事件回推。 */
62
+ update: { check(): Promise<void>; install(): Promise<void> };
63
+ taskbar: Record<string, (params?: unknown) => Promise<any>>;
64
+ dialog: {
65
+ message(opts: { title?: string; text: string; type?: string }): Promise<any>;
66
+ open(opts?: Record<string, any>): Promise<string | string[] | null>;
67
+ save(opts?: Record<string, any>): Promise<string | null>;
68
+ };
69
+ tray: Record<string, (params?: unknown) => Promise<any>>;
70
+ menu: Record<string, (params?: unknown) => Promise<any>>;
71
+ }
72
+
73
+ declare global {
74
+ interface Window {
75
+ freedom: FreedomAPI;
76
+ }
77
+ }