deck-plugin-sdk 1.14.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,6 +13,7 @@
13
13
  // Run from a plugin's own directory.
14
14
  import { watch } from "node:fs";
15
15
  import { cpSync, existsSync, mkdirSync, readFileSync } from "node:fs";
16
+ import { homedir } from "node:os";
16
17
  import { join } from "node:path";
17
18
  import { buildPlugin } from "../build-plugin.mjs";
18
19
  import { publish } from "../publish.mjs";
@@ -70,15 +71,27 @@ function check() {
70
71
  * writes to your real Deck ("main"), for when the plugin is done.
71
72
  */
72
73
  function pluginsDir(defaultTarget) {
73
- const appData = process.env.APPDATA;
74
- if (!appData) die("APPDATA is not set — this command is for Windows, where Deck runs");
75
74
  const target = targetFlag ?? defaultTarget;
76
75
  if (target !== "test" && target !== "main") die(`--target is "test" or "main", not "${target}"`);
77
- return { target, dir: join(appData, target === "test" ? "Deck-Dev" : "Deck", "plugins") };
76
+ return { target, dir: join(configRoot(), target === "test" ? "Deck-Dev" : "Deck", "plugins") };
77
+ }
78
+
79
+ /** Where Deck keeps its folder, as its Rust side finds it: %APPDATA% on Windows, $XDG_CONFIG_HOME
80
+ * or ~/.config on Linux, ~/Library/Application Support on macOS. */
81
+ function configRoot() {
82
+ if (process.platform === "win32") return process.env.APPDATA || die("APPDATA is not set");
83
+ if (process.platform === "darwin") return join(homedir(), "Library", "Application Support");
84
+ return process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
78
85
  }
79
86
 
80
87
  /** Copy the built plugin into a Deck's plugins folder. Deck watches it and reloads the plugin. */
81
88
  function copyInto(manifest, defaultTarget) {
89
+ // A plugin that lists the systems it runs on is not put into a Deck on any other, which would not
90
+ // load it anyway.
91
+ const here = { win32: "windows", darwin: "mac" }[process.platform] ?? "linux";
92
+ if (Array.isArray(manifest.platforms) && manifest.platforms.length && !manifest.platforms.includes(here)) {
93
+ die(`${manifest.id} runs on ${manifest.platforms.join(", ")} only (its plugin.json "platforms"), and this is ${here}`);
94
+ }
82
95
  const { target, dir: plugins } = pluginsDir(defaultTarget);
83
96
  const dest = join(plugins, manifest.id);
84
97
  mkdirSync(dest, { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deck-plugin-sdk",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "description": "Build a plugin for Deck, outside the Deck repository.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/permissions.json CHANGED
@@ -231,6 +231,7 @@
231
231
  "fsHomeDir",
232
232
  "getAutostart",
233
233
  "isFullscreen",
234
+ "isWindows",
234
235
  "languagesDir",
235
236
  "launchUrls",
236
237
  "mediaSetNowPlaying",
@@ -238,6 +239,7 @@
238
239
  "memUsage",
239
240
  "notifyOs",
240
241
  "openExternal",
242
+ "pathKey",
241
243
  "pickAnyFile",
242
244
  "pickExe",
243
245
  "pickFile",
@@ -245,8 +247,10 @@
245
247
  "pickImage",
246
248
  "pickSavePath",
247
249
  "pickScript",
250
+ "platform",
248
251
  "pluginsDir",
249
252
  "revealInFolder",
253
+ "samePath",
250
254
  "startupMarks",
251
255
  "toggleFullscreen"
252
256
  ]
package/publish.mjs CHANGED
@@ -18,7 +18,7 @@ import { execFileSync } from "node:child_process";
18
18
  import { existsSync, readFileSync, statSync } from "node:fs";
19
19
  import { dirname, join } from "node:path";
20
20
  import { fileURLToPath } from "node:url";
21
- import { checkAssets, checkIcon, checkId, checkLockfile, checkManifest, checkPermissions, fileFacts, ICON_FILE, points } from "./validate.mjs";
21
+ import { checkAssets, checkIcon, checkId, checkLockfile, checkManifest, checkPermissions, checkPortable, fileFacts, ICON_FILE, points } from "./validate.mjs";
22
22
 
23
23
  const here = dirname(fileURLToPath(import.meta.url));
24
24
  const REGISTRY = "deck-desktop/deck-registry";
@@ -83,6 +83,7 @@ export async function publish(dir, manifest, { dryRun, buildPlugin }) {
83
83
  const perms = checkPermissions(manifest.permissions, readFileSync(join(dir, "plugin.js"), "utf8"), table, manifest.id);
84
84
  errors.push(...perms.errors);
85
85
  notes.push(...perms.notes);
86
+ notes.push(...checkPortable(manifest, readFileSync(join(dir, "plugin.js"), "utf8")));
86
87
  if (errors.length) {
87
88
  for (const e of errors) console.error(` ✗ ${e}`);
88
89
  process.exit(1);
package/shim/bridge.js CHANGED
@@ -161,6 +161,7 @@ export const installAgentMcp = (...a) => b().installAgentMcp(...a);
161
161
  export const installAgentSkills = (...a) => b().installAgentSkills(...a);
162
162
  export const installFromRegistry = (...a) => b().installFromRegistry(...a);
163
163
  export const isFullscreen = (...a) => b().isFullscreen(...a);
164
+ export const isWindows = (...a) => b().isWindows(...a);
164
165
  export const languagesDir = (...a) => b().languagesDir(...a);
165
166
  export const launchFiles = (...a) => b().launchFiles(...a);
166
167
  export const launchUrls = (...a) => b().launchUrls(...a);
@@ -176,6 +177,7 @@ export const notifyOs = (...a) => b().notifyOs(...a);
176
177
  export const openExternal = (...a) => b().openExternal(...a);
177
178
  export const openFile = (...a) => b().openFile(...a);
178
179
  export const openTestDeck = (...a) => b().openTestDeck(...a);
180
+ export const pathKey = (...a) => b().pathKey(...a);
179
181
  export const pickAnyFile = (...a) => b().pickAnyFile(...a);
180
182
  export const pickExe = (...a) => b().pickExe(...a);
181
183
  export const pickFile = (...a) => b().pickFile(...a);
@@ -183,6 +185,7 @@ export const pickFolder = (...a) => b().pickFolder(...a);
183
185
  export const pickImage = (...a) => b().pickImage(...a);
184
186
  export const pickSavePath = (...a) => b().pickSavePath(...a);
185
187
  export const pickScript = (...a) => b().pickScript(...a);
188
+ export const platform = (...a) => b().platform(...a);
186
189
  export const pluginsDir = (...a) => b().pluginsDir(...a);
187
190
  export const procKill = (...a) => b().procKill(...a);
188
191
  export const procStream = (...a) => b().procStream(...a);
@@ -198,6 +201,7 @@ export const removeLanguage = (...a) => b().removeLanguage(...a);
198
201
  export const restartApp = (...a) => b().restartApp(...a);
199
202
  export const revealInFolder = (...a) => b().revealInFolder(...a);
200
203
  export const runScriptNow = (...a) => b().runScriptNow(...a);
204
+ export const samePath = (...a) => b().samePath(...a);
201
205
  export const sessionList = (...a) => b().sessionList(...a);
202
206
  export const setAutostart = (...a) => b().setAutostart(...a);
203
207
  export const setDebugTargets = (...a) => b().setDebugTargets(...a);
@@ -5,6 +5,6 @@
5
5
  "icon": "Sparkles",
6
6
  "color": "#a78bfa",
7
7
  "version": "0.1.0",
8
- "minDeck": "1.14.0",
8
+ "minDeck": "1.15.0",
9
9
  "permissions": []
10
10
  }
@@ -0,0 +1,14 @@
1
+ // GENERATED by sdk/gen-types.mjs from Deck's source — do not edit.
2
+ export type Platform = "windows" | "linux" | "mac";
3
+ /** Which system Deck runs on. A function, as everything plugins reach through the bridge is. */
4
+ export declare const platform: () => Platform;
5
+ export declare const isWindows: () => boolean;
6
+ /** Whether two paths name the same file: without regard to case on Windows, whose file system
7
+ * ignores it, and exactly elsewhere, where `Readme.md` and `README.md` are two files. */
8
+ export declare function samePath(a: string, b: string): boolean;
9
+ /** A path as a key that `samePath` would match: lowercased on Windows only. */
10
+ export declare function pathKey(p: string): string;
11
+ /** The system's name, for words shown to the user. */
12
+ export declare const osName: () => string;
13
+ /** The menu item that shows a file in the system's file manager. */
14
+ export declare const revealLabel: () => string;
@@ -672,6 +672,11 @@ export interface TrackMeta {
672
672
  }
673
673
  export declare const readTrackMeta: (paths: string[]) => Promise<TrackMeta[]>;
674
674
  export { convertFileSrc } from "@tauri-apps/api/core";
675
+ export { platform } from "./app/platform";
676
+ export { isWindows } from "./app/platform";
677
+ export { samePath } from "./app/platform";
678
+ export { pathKey } from "./app/platform";
679
+ export type { Platform } from "./app/platform";
675
680
  /** Start a sign-in. Returns the consent URL to open in the browser. */
676
681
  export declare const googleSignIn: () => Promise<string>;
677
682
  /** A valid access token, refreshed if needed. Rejects when not connected. */
@@ -0,0 +1,9 @@
1
+ // GENERATED by sdk/gen-types.mjs from Deck's source — do not edit.
2
+ /** A Chromium browser's bookmarks on this machine, one per profile that has any. */
3
+ export interface BookmarkSource {
4
+ browser: string;
5
+ profile: string;
6
+ path: string;
7
+ }
8
+ /** Every profile of Chrome, Edge and Brave with a Bookmarks file. A browser not installed is no error. */
9
+ export declare function findBookmarkSources(): Promise<BookmarkSource[]>;
@@ -203,14 +203,8 @@ export declare function forgetDownload(key: string): void;
203
203
  export declare function clearDownloads(): void;
204
204
  export declare const browserBookmarks: () => Bookmarks;
205
205
  export declare function setBookmarks(next: Bookmarks): void;
206
- /** A Chromium browser's bookmarks on this machine, one per profile that has any. */
207
- export interface BookmarkSource {
208
- browser: string;
209
- profile: string;
210
- path: string;
211
- }
212
- /** Every profile of Chrome, Edge and Brave with a Bookmarks file. A browser not installed is no error. */
213
- export declare function findBookmarkSources(): Promise<BookmarkSource[]>;
206
+ import type { BookmarkSource } from "./bookmarksources";
207
+ export { findBookmarkSources, type BookmarkSource } from "./bookmarksources";
214
208
  /** Import one source's bookmarks as a "From Chrome" folder in each root. Returns how many pages. */
215
209
  export declare function importBookmarks(src: BookmarkSource): Promise<number>;
216
210
  /** Every bookmark to a bookmarks HTML file, which any browser imports, and Deck too. How many pages. */
@@ -89,10 +89,4 @@ export declare function toMonaco(chord: string, mods: {
89
89
  Shift: number;
90
90
  WinCtrl: number;
91
91
  }, codes: Record<string, number>): number | null;
92
- /**
93
- * Deck's built-in bindings.
94
- *
95
- * Plugin bindings are not here — they arrive at load time through the `keybindings` export and
96
- * are concatenated on, so a disabled plugin's chords leave the table with it.
97
- */
98
92
  export declare const DEFAULT_BINDINGS: readonly KeyBinding[];
@@ -32,7 +32,7 @@ export declare function activeElement(): Element | null;
32
32
  /** Run `fn` for every Deck window, now and as floats open; what it returns runs when that window goes, or on the returned unsubscribe. */
33
33
  export declare function onEachWindow(fn: Hook): () => void;
34
34
  /** A floating window opened (floatwin.ts), its label known: every hook runs for it. */
35
- export declare function addWindow(win: Window): void;
35
+ export declare function addWindow(win: Window, label?: string): void;
36
36
  /** A floating window is going: what each hook installed in it is undone. */
37
37
  export declare function removeWindow(win: Window): void;
38
38
  /**
package/validate.mjs CHANGED
@@ -44,6 +44,10 @@ export function checkManifest(m) {
44
44
  if (typeof m.version !== "string" || !SEMVER.test(m.version)) errors.push("`version` must be a version, such as \"1.0.0\"");
45
45
  if (typeof m.minDeck !== "string" || !SEMVER.test(m.minDeck)) errors.push("`minDeck` must say which Deck it needs, such as \"1.8.0\"");
46
46
  if (m.color !== undefined && !/^#[0-9a-f]{6}$/i.test(m.color)) errors.push("`color` must be #rrggbb");
47
+ // Where it runs: absent means every system. Deck will not load or install it on any other.
48
+ if (m.platforms !== undefined && (!Array.isArray(m.platforms) || !m.platforms.length || !m.platforms.every((p) => ["windows", "linux", "mac"].includes(p)))) {
49
+ errors.push("`platforms` lists the systems it runs on, from \"windows\", \"linux\" and \"mac\"; leave it out to run on all of them");
50
+ }
47
51
  if (typeof m.icon === "string" && m.icon.endsWith(".svg") && m.icon !== ICON_FILE) errors.push(`an icon of its own must be named ${ICON_FILE}`);
48
52
  return errors;
49
53
  }
@@ -152,6 +156,21 @@ export function checkPermissions(declared, bundle, table, id) {
152
156
  return { errors, notes, used: [...needs.keys()] };
153
157
  }
154
158
 
159
+ /**
160
+ * Windows-only habits in a plugin that says it runs on Linux too (no "platforms", or a list naming
161
+ * "linux"): a path joined with a backslash, a program named with ".exe". Notes, not errors, and
162
+ * none for a bundle that asks isWindows(), which keeps a Windows branch beside a Linux one.
163
+ */
164
+ export function checkPortable(manifest, bundle) {
165
+ const runs = Array.isArray(manifest.platforms) ? manifest.platforms : ["windows", "linux"];
166
+ if (!runs.includes("linux") || /\(\)\.isWindows\(|\bisWindows\(/.test(bundle)) return [];
167
+ const notes = [];
168
+ const joins = (bundle.match(/\}\\\\|["'`]\\\\["'`]/g) ?? []).length;
169
+ if (joins) notes.push(`It joins paths with a backslash (${joins} place${joins > 1 ? "s" : ""}), which on Linux is part of a file name: ask isWindows() from the bridge for the separator, or say "platforms": ["windows"] in plugin.json.`);
170
+ if (/\.exe["'`]/.test(bundle)) notes.push(`It names a program with ".exe", which Linux programs do not have: ask isWindows() from the bridge, or say "platforms": ["windows"] in plugin.json.`);
171
+ return notes;
172
+ }
173
+
155
174
  // ---- points ----
156
175
  // A version's points: advice for whoever is choosing a plugin, never a gate (the checks above are
157
176
  // that). Worked out once when it is published, by the registry for a submission and by