pake-cli 3.15.1 โ 3.15.3
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 +6 -0
- package/dist/cli.js +3 -2
- package/llms.txt +31 -0
- package/package.json +3 -2
- package/src-tauri/Cargo.lock +2 -1
- package/src-tauri/Cargo.toml +8 -1
- package/src-tauri/src/app/setup.rs +4 -1
- package/src-tauri/src/app/window.rs +82 -0
- package/src-tauri/src/lib.rs +4 -1
- package/src-tauri/src/util.rs +7 -2
- package/src-tauri/tauri.conf.json +1 -1
package/README.md
CHANGED
|
@@ -194,6 +194,12 @@ First-time packaging requires environment setup and may be slower, subsequent bu
|
|
|
194
194
|
|
|
195
195
|
Using Pake from a script or AI agent? Pass `--json` for machine-readable results, describe apps declaratively with `--config app.json` ([schema](schema/pake.schema.json)), and package local build output directly with `pake ./dist --name MyTool`. See [llms.txt](llms.txt) for the full agent contract. Claude Code users can install the official skill with `/plugin marketplace add tw93/Pake` and `/plugin install pake@pake`.
|
|
196
196
|
|
|
197
|
+
Copy this to your AI agent to get started:
|
|
198
|
+
|
|
199
|
+
```text
|
|
200
|
+
Use Pake (npm i -g pake-cli) to package webpages as desktop apps. Read https://unpkg.com/pake-cli@latest/llms.txt first; always run pake with --json and parse stdout as a single JSON object. Package <url-or-local-dist> into an app named <AppName>.
|
|
201
|
+
```
|
|
202
|
+
|
|
197
203
|
## Development
|
|
198
204
|
|
|
199
205
|
Requires Rust `>=1.85` and Node `>=22` (recommended LTS; `>=18` also works). For detailed installation guide, see [Tauri documentation](https://v2.tauri.app/start/prerequisites/). If unfamiliar with development environment, use the CLI tool instead.
|
package/dist/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ import * as psl from 'psl';
|
|
|
20
20
|
import { InvalidArgumentError, program as program$1, Option } from 'commander';
|
|
21
21
|
|
|
22
22
|
var name = "pake-cli";
|
|
23
|
-
var version = "3.15.
|
|
23
|
+
var version = "3.15.3";
|
|
24
24
|
var description = "๐คฑ๐ป Turn any webpage into a desktop app with one command. ๐คฑ๐ป ไธ้ฎๆๅ
็ฝ้กต็ๆ่ฝป้ๆก้ขๅบ็จใ";
|
|
25
25
|
var engines = {
|
|
26
26
|
node: ">=18.0.0"
|
|
@@ -47,7 +47,8 @@ var keywords = [
|
|
|
47
47
|
];
|
|
48
48
|
var files = [
|
|
49
49
|
"LICENSE-EXCEPTION",
|
|
50
|
-
"
|
|
50
|
+
"llms.txt",
|
|
51
|
+
"dist/cli.js",
|
|
51
52
|
"src-tauri"
|
|
52
53
|
];
|
|
53
54
|
var scripts = {
|
package/llms.txt
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Pake
|
|
2
|
+
|
|
3
|
+
> Pake turns any webpage or local web build into a lightweight desktop app (~5MB) for macOS, Windows, and Linux, using Tauri (Rust) and the system webview. One CLI command packages a URL, a local HTML file, or a static directory into a native installer.
|
|
4
|
+
|
|
5
|
+
## Install and run
|
|
6
|
+
|
|
7
|
+
- Install: `npm install -g pake-cli` (requires Node 18+ and Rust; the CLI offers to install Rust when missing)
|
|
8
|
+
- Basic: `pake https://example.com --name MyApp`
|
|
9
|
+
- Local build output: `pake ./dist --name MyTool` (directory must contain index.html at its root; hash routing only, history-mode SPA routing unsupported)
|
|
10
|
+
|
|
11
|
+
## Agent contract
|
|
12
|
+
|
|
13
|
+
- Always pass `--json` in automation: stdout carries exactly one JSON object `{ok, name, platform, arch, outputs: [{path, sizeBytes, format}], warnings, error: {code, message, hint} | null}`; all logs go to stderr; interactive prompts are disabled.
|
|
14
|
+
- Exit codes: 0 success, 2 invalid input, 3 build failure, 4 missing environment or dependency setup failure, 1 unexpected.
|
|
15
|
+
- On Linux multi-target builds, ok:true can carry fewer outputs than requested formats; failed targets land in warnings. Check outputs[].format.
|
|
16
|
+
- Error codes: INVALID_INPUT, ENV_MISSING, BUILD_FAILED, UNEXPECTED, plus NETWORK (reserved; current versions report network failures under the phase code, e.g. ENV_MISSING or BUILD_FAILED). `error.hint` states how to fix.
|
|
17
|
+
- Declarative config: `pake --config app.json --json`; fields are camelCase CLI option names plus `url`. Explicit CLI flags win over config fields. Unknown fields fail fast. A relative `url` path resolves against the process working directory, not the config file's directory.
|
|
18
|
+
- Config schema: https://raw.githubusercontent.com/tw93/Pake/main/schema/pake.schema.json
|
|
19
|
+
|
|
20
|
+
## Docs
|
|
21
|
+
|
|
22
|
+
- [CLI usage, all options](https://github.com/tw93/Pake/blob/main/docs/cli-usage.md): every flag with defaults and examples
|
|
23
|
+
- [Advanced usage](https://github.com/tw93/Pake/blob/main/docs/advanced-usage.md): style injection, containers, customization
|
|
24
|
+
- [FAQ](https://github.com/tw93/Pake/blob/main/docs/faq.md): platform-specific troubleshooting
|
|
25
|
+
|
|
26
|
+
## Notes
|
|
27
|
+
|
|
28
|
+
- Output formats: dmg/app (macOS), msi (Windows), deb/AppImage/rpm/zst (Linux); pick with `--targets`.
|
|
29
|
+
- Icons auto-fetch from the site favicon when `--icon` is omitted.
|
|
30
|
+
- `--proxy-url` is baked into the app webview at build time; it does not proxy the icon download itself.
|
|
31
|
+
- Google OAuth and similar providers may reject embedded webviews regardless of flags; `--new-window` and `--safe-domain` help with most SSO flows.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pake-cli",
|
|
3
|
-
"version": "3.15.
|
|
3
|
+
"version": "3.15.3",
|
|
4
4
|
"description": "๐คฑ๐ป Turn any webpage into a desktop app with one command. ๐คฑ๐ป ไธ้ฎๆๅ
็ฝ้กต็ๆ่ฝป้ๆก้ขๅบ็จใ",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18.0.0"
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
],
|
|
28
28
|
"files": [
|
|
29
29
|
"LICENSE-EXCEPTION",
|
|
30
|
-
"
|
|
30
|
+
"llms.txt",
|
|
31
|
+
"dist/cli.js",
|
|
31
32
|
"src-tauri"
|
|
32
33
|
],
|
|
33
34
|
"scripts": {
|
package/src-tauri/Cargo.lock
CHANGED
|
@@ -2564,7 +2564,7 @@ dependencies = [
|
|
|
2564
2564
|
|
|
2565
2565
|
[[package]]
|
|
2566
2566
|
name = "pake"
|
|
2567
|
-
version = "3.15.
|
|
2567
|
+
version = "3.15.3"
|
|
2568
2568
|
dependencies = [
|
|
2569
2569
|
"objc2",
|
|
2570
2570
|
"objc2-app-kit",
|
|
@@ -2582,6 +2582,7 @@ dependencies = [
|
|
|
2582
2582
|
"tauri-plugin-single-instance",
|
|
2583
2583
|
"tauri-plugin-window-state",
|
|
2584
2584
|
"tokio",
|
|
2585
|
+
"windows-sys 0.61.2",
|
|
2585
2586
|
]
|
|
2586
2587
|
|
|
2587
2588
|
[[package]]
|
package/src-tauri/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "pake"
|
|
3
|
-
version = "3.15.
|
|
3
|
+
version = "3.15.3"
|
|
4
4
|
description = "๐คฑ๐ป Turn any webpage into a desktop app with Rust."
|
|
5
5
|
authors = ["Tw93"]
|
|
6
6
|
license = "GPL-3.0-or-later"
|
|
@@ -41,6 +41,13 @@ objc2 = "0.6"
|
|
|
41
41
|
objc2-app-kit = { version = "0.3", features = ["NSApplication", "NSDockTile"] }
|
|
42
42
|
objc2-foundation = { version = "0.3", features = ["NSString"] }
|
|
43
43
|
|
|
44
|
+
[target.'cfg(windows)'.dependencies]
|
|
45
|
+
windows-sys = { version = "0.61.2", features = [
|
|
46
|
+
"Win32_Foundation",
|
|
47
|
+
"Win32_UI_Shell",
|
|
48
|
+
"Win32_UI_WindowsAndMessaging",
|
|
49
|
+
] }
|
|
50
|
+
|
|
44
51
|
[features]
|
|
45
52
|
# this feature is used for development builds from development cli
|
|
46
53
|
cli-build = []
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
use crate::app::window::open_additional_window_safe;
|
|
1
|
+
use crate::app::window::{open_additional_window_safe, reapply_window_icon};
|
|
2
2
|
use std::str::FromStr;
|
|
3
3
|
use std::sync::{Arc, Mutex};
|
|
4
4
|
use std::time::{Duration, Instant};
|
|
@@ -56,6 +56,7 @@ pub fn set_system_tray(
|
|
|
56
56
|
"show_app" => {
|
|
57
57
|
if let Some(window) = app.get_webview_window("pake") {
|
|
58
58
|
let _ = window.show();
|
|
59
|
+
reapply_window_icon(&window);
|
|
59
60
|
#[cfg(target_os = "linux")]
|
|
60
61
|
if _init_fullscreen && !window.is_fullscreen().unwrap_or(false) {
|
|
61
62
|
let _ = window.set_fullscreen(true);
|
|
@@ -83,6 +84,7 @@ pub fn set_system_tray(
|
|
|
83
84
|
let _ = window.hide();
|
|
84
85
|
} else {
|
|
85
86
|
let _ = window.show();
|
|
87
|
+
reapply_window_icon(&window);
|
|
86
88
|
let _ = window.set_focus();
|
|
87
89
|
#[cfg(target_os = "linux")]
|
|
88
90
|
if _init_fullscreen && !window.is_fullscreen().unwrap_or(false) {
|
|
@@ -153,6 +155,7 @@ pub fn set_global_shortcut(
|
|
|
153
155
|
let _ = window.hide();
|
|
154
156
|
} else {
|
|
155
157
|
let _ = window.show();
|
|
158
|
+
reapply_window_icon(&window);
|
|
156
159
|
let _ = window.set_focus();
|
|
157
160
|
#[cfg(target_os = "linux")]
|
|
158
161
|
if _init_fullscreen && !window.is_fullscreen().unwrap_or(false) {
|
|
@@ -3,6 +3,8 @@ use crate::util::{
|
|
|
3
3
|
check_file_or_append, get_data_dir, get_download_message_with_lang, sanitize_download_filename,
|
|
4
4
|
show_toast, MessageType,
|
|
5
5
|
};
|
|
6
|
+
#[cfg(target_os = "windows")]
|
|
7
|
+
use std::{os::windows::ffi::OsStrExt, ptr, sync::OnceLock};
|
|
6
8
|
use std::{
|
|
7
9
|
path::PathBuf,
|
|
8
10
|
str::FromStr,
|
|
@@ -13,6 +15,12 @@ use tauri::{
|
|
|
13
15
|
AppHandle, Config, Manager, Url, WebviewUrl, WebviewWindow, WebviewWindowBuilder,
|
|
14
16
|
};
|
|
15
17
|
|
|
18
|
+
#[cfg(target_os = "windows")]
|
|
19
|
+
use windows_sys::Win32::UI::{
|
|
20
|
+
Shell::ExtractIconExW,
|
|
21
|
+
WindowsAndMessaging::{SendMessageW, ICON_BIG, WM_SETICON},
|
|
22
|
+
};
|
|
23
|
+
|
|
16
24
|
use tauri::Theme;
|
|
17
25
|
|
|
18
26
|
#[cfg(target_os = "macos")]
|
|
@@ -69,6 +77,77 @@ pub fn open_additional_window(app: &AppHandle) -> tauri::Result<WebviewWindow> {
|
|
|
69
77
|
build_window_with_label(app, &state.pake_config, &state.tauri_config, &label)
|
|
70
78
|
}
|
|
71
79
|
|
|
80
|
+
#[cfg(target_os = "windows")]
|
|
81
|
+
fn taskbar_icon_handle() -> Option<isize> {
|
|
82
|
+
static TASKBAR_ICON: OnceLock<Option<isize>> = OnceLock::new();
|
|
83
|
+
|
|
84
|
+
*TASKBAR_ICON.get_or_init(|| {
|
|
85
|
+
let executable = match std::env::current_exe() {
|
|
86
|
+
Ok(path) => path,
|
|
87
|
+
Err(error) => {
|
|
88
|
+
eprintln!(
|
|
89
|
+
"[Pake] Failed to resolve the app executable for its taskbar icon: {error}"
|
|
90
|
+
);
|
|
91
|
+
return None;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
let executable_wide: Vec<u16> = executable
|
|
95
|
+
.as_os_str()
|
|
96
|
+
.encode_wide()
|
|
97
|
+
.chain(std::iter::once(0))
|
|
98
|
+
.collect();
|
|
99
|
+
let mut large_icon = ptr::null_mut();
|
|
100
|
+
let extracted = unsafe {
|
|
101
|
+
ExtractIconExW(
|
|
102
|
+
executable_wide.as_ptr(),
|
|
103
|
+
0,
|
|
104
|
+
&mut large_icon,
|
|
105
|
+
ptr::null_mut(),
|
|
106
|
+
1,
|
|
107
|
+
)
|
|
108
|
+
};
|
|
109
|
+
if extracted == 0 || large_icon.is_null() {
|
|
110
|
+
eprintln!(
|
|
111
|
+
"[Pake] Failed to extract the taskbar icon from {}.",
|
|
112
|
+
executable.display()
|
|
113
|
+
);
|
|
114
|
+
return None;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// WM_SETICON keeps this handle rather than copying it. Cache the single
|
|
118
|
+
// extracted icon for the process lifetime so repeated tray restores do
|
|
119
|
+
// not leak a new HICON or leave the window with a dangling handle.
|
|
120
|
+
Some(large_icon as isize)
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Apps autostarted at Windows logon can register their icons before Explorer's
|
|
125
|
+
// icon cache is ready (#1323). Re-assert both the small/title-bar icon and the
|
|
126
|
+
// large taskbar icon whenever a window becomes visible.
|
|
127
|
+
#[cfg(target_os = "windows")]
|
|
128
|
+
pub fn reapply_window_icon(window: &WebviewWindow) {
|
|
129
|
+
if let Some(icon) = window.app_handle().default_window_icon().cloned() {
|
|
130
|
+
if let Err(error) = window.set_icon(icon) {
|
|
131
|
+
eprintln!("[Pake] Failed to re-apply the window icon: {error}");
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let Some(taskbar_icon) = taskbar_icon_handle() else {
|
|
136
|
+
return;
|
|
137
|
+
};
|
|
138
|
+
match window.hwnd() {
|
|
139
|
+
Ok(hwnd) => unsafe {
|
|
140
|
+
SendMessageW(hwnd.0, WM_SETICON, ICON_BIG as usize, taskbar_icon);
|
|
141
|
+
},
|
|
142
|
+
Err(error) => {
|
|
143
|
+
eprintln!("[Pake] Failed to resolve the window handle for its taskbar icon: {error}");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#[cfg(not(target_os = "windows"))]
|
|
149
|
+
pub fn reapply_window_icon(_window: &WebviewWindow) {}
|
|
150
|
+
|
|
72
151
|
struct WindowBuildOptions<'a> {
|
|
73
152
|
label: &'a str,
|
|
74
153
|
url: WebviewUrl,
|
|
@@ -99,6 +178,7 @@ fn open_requested_window(
|
|
|
99
178
|
|
|
100
179
|
let title = target_url.host_str().unwrap_or(target_url.as_str());
|
|
101
180
|
let _ = window.set_title(title);
|
|
181
|
+
reapply_window_icon(&window);
|
|
102
182
|
let _ = window.set_focus();
|
|
103
183
|
|
|
104
184
|
Ok(window)
|
|
@@ -111,6 +191,7 @@ pub fn open_additional_window_safe(app: &AppHandle) {
|
|
|
111
191
|
std::thread::spawn(move || {
|
|
112
192
|
if let Ok(window) = open_additional_window(&app_handle) {
|
|
113
193
|
let _ = window.show();
|
|
194
|
+
reapply_window_icon(&window);
|
|
114
195
|
let _ = window.set_focus();
|
|
115
196
|
}
|
|
116
197
|
});
|
|
@@ -120,6 +201,7 @@ pub fn open_additional_window_safe(app: &AppHandle) {
|
|
|
120
201
|
{
|
|
121
202
|
if let Ok(window) = open_additional_window(app) {
|
|
122
203
|
let _ = window.show();
|
|
204
|
+
reapply_window_icon(&window);
|
|
123
205
|
let _ = window.set_focus();
|
|
124
206
|
}
|
|
125
207
|
}
|
package/src-tauri/src/lib.rs
CHANGED
|
@@ -25,7 +25,7 @@ use app::{
|
|
|
25
25
|
set_dock_badge_label, set_zoom, update_theme_mode,
|
|
26
26
|
},
|
|
27
27
|
setup::{set_global_shortcut, set_system_tray},
|
|
28
|
-
window::{open_additional_window_safe, set_window, MultiWindowState},
|
|
28
|
+
window::{open_additional_window_safe, reapply_window_icon, set_window, MultiWindowState},
|
|
29
29
|
};
|
|
30
30
|
use util::get_pake_config;
|
|
31
31
|
|
|
@@ -179,6 +179,7 @@ pub fn run_app() {
|
|
|
179
179
|
} else if let Some(window) = app.get_webview_window("pake") {
|
|
180
180
|
let _ = window.unminimize();
|
|
181
181
|
let _ = window.show();
|
|
182
|
+
reapply_window_icon(&window);
|
|
182
183
|
let _ = window.set_focus();
|
|
183
184
|
}
|
|
184
185
|
},
|
|
@@ -231,6 +232,7 @@ pub fn run_app() {
|
|
|
231
232
|
tauri::async_runtime::spawn(async move {
|
|
232
233
|
tokio::time::sleep(tokio::time::Duration::from_millis(WINDOW_SHOW_DELAY)).await;
|
|
233
234
|
let _ = window_clone.show();
|
|
235
|
+
reapply_window_icon(&window_clone);
|
|
234
236
|
|
|
235
237
|
// Fixed: Linux fullscreen issue with virtual keyboard
|
|
236
238
|
#[cfg(target_os = "linux")]
|
|
@@ -300,6 +302,7 @@ pub fn run_app() {
|
|
|
300
302
|
if !has_visible_windows {
|
|
301
303
|
if let Some(window) = _app.get_webview_window("pake") {
|
|
302
304
|
let _ = window.show();
|
|
305
|
+
reapply_window_icon(&window);
|
|
303
306
|
let _ = window.set_focus();
|
|
304
307
|
}
|
|
305
308
|
}
|
package/src-tauri/src/util.rs
CHANGED
|
@@ -183,16 +183,21 @@ mod tests {
|
|
|
183
183
|
use std::env;
|
|
184
184
|
use std::fs;
|
|
185
185
|
use std::path::PathBuf;
|
|
186
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
187
|
+
|
|
188
|
+
static TEMP_PATH_COUNTER: AtomicU64 = AtomicU64::new(0);
|
|
186
189
|
|
|
187
190
|
fn temp_path(name: &str) -> PathBuf {
|
|
188
191
|
let mut dir = env::temp_dir();
|
|
192
|
+
let unique = TEMP_PATH_COUNTER.fetch_add(1, Ordering::Relaxed);
|
|
189
193
|
dir.push(format!(
|
|
190
|
-
"pake-util-test-{}-{}",
|
|
194
|
+
"pake-util-test-{}-{}-{}",
|
|
191
195
|
std::process::id(),
|
|
192
196
|
std::time::SystemTime::now()
|
|
193
197
|
.duration_since(std::time::UNIX_EPOCH)
|
|
194
198
|
.map(|d| d.as_nanos())
|
|
195
|
-
.unwrap_or(0)
|
|
199
|
+
.unwrap_or(0),
|
|
200
|
+
unique
|
|
196
201
|
));
|
|
197
202
|
fs::create_dir_all(&dir).unwrap();
|
|
198
203
|
dir.push(name);
|