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 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.1";
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
- "dist",
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.1",
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
- "dist",
30
+ "llms.txt",
31
+ "dist/cli.js",
31
32
  "src-tauri"
32
33
  ],
33
34
  "scripts": {
@@ -2564,7 +2564,7 @@ dependencies = [
2564
2564
 
2565
2565
  [[package]]
2566
2566
  name = "pake"
2567
- version = "3.15.1"
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]]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "pake"
3
- version = "3.15.1"
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
  }
@@ -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
  }
@@ -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);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "productName": "Weekly",
3
3
  "identifier": "com.pake.weekly",
4
- "version": "3.15.1",
4
+ "version": "3.15.3",
5
5
  "app": {
6
6
  "withGlobalTauri": true,
7
7
  "trayIcon": {