deck-plugin-sdk 1.4.0 → 1.5.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.
package/packages.mjs ADDED
@@ -0,0 +1,107 @@
1
+ // What a plugin's npm packages need from the build. Shared by Deck's own plugin build
2
+ // (sdk/shim-builder.mjs, which makes the shim) and the SDK's (sdk/build-plugin.mjs), so the two cannot disagree.
3
+ //
4
+ // A plugin is installed as plugin.json and one plugin.js, so everything a package brings has to
5
+ // end up inside that one file: a sidecar image, font or stylesheet would never be installed.
6
+ //
7
+ // "The plugin's own code" is what is under its src/. Anything else is a package, whether it sits
8
+ // in node_modules or is linked in from another folder (`npm install ../my-lib`).
9
+ import { build } from "esbuild";
10
+ import { readFileSync } from "node:fs";
11
+ import { dirname, join, relative, sep } from "node:path";
12
+
13
+ /** Images and fonts the plugin or a package imports, inlined as data: URLs. */
14
+ export const assetLoaders = Object.fromEntries(
15
+ [".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif", ".bmp", ".ico", ".svg", ".woff", ".woff2", ".ttf", ".otf", ".eot"]
16
+ .map((ext) => [ext, "dataurl"]),
17
+ );
18
+
19
+ const under = (path, dir) => !!path && (path + sep).toLowerCase().startsWith((dir + sep).toLowerCase());
20
+
21
+ /**
22
+ * Whether an import of Deck's shared icons or animation library comes from a package rather than
23
+ * the plugin's own code. The plugin's own imports go to Deck's copies, through a shim that names
24
+ * only the exports its `src/` uses; a package's would find its names missing there, so a package
25
+ * gets the real library, bundled and tree-shaken like any other dependency.
26
+ */
27
+ export const fromPackage = (args, dir) =>
28
+ (args.path === "lucide-react" || args.path === "framer-motion") && !!args.importer && !under(args.importer, join(dir, "src"));
29
+
30
+ /** `import "x.css"` with nothing bound: imported for its effect, so it is applied. */
31
+ function importedBare(importer, spec) {
32
+ let text;
33
+ try { text = readFileSync(importer, "utf8"); } catch { return false; }
34
+ const q = spec.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
35
+ return new RegExp(`(^|[;\\n])\\s*import\\s*["']${q}["']`).test(text);
36
+ }
37
+
38
+ /**
39
+ * CSS imported for its effect is applied: CSS a package imports, and CSS any code imports with
40
+ * nothing bound (`import "some-lib/styles.css"`, `import "@fontsource/inter"`). It is bundled
41
+ * with its `@import`s and the images and fonts its `url()`s name, and added to the page as one
42
+ * <style> per file, replaced rather than duplicated when the plugin reloads.
43
+ *
44
+ * `import css from "./x.css"` in the plugin's own code still gives it the text, to inject where and
45
+ * when it wants (the canvas plugin does that with Excalidraw's stylesheet).
46
+ */
47
+ export function packageCss(pluginId, dir) {
48
+ const src = join(dir, "src");
49
+ return {
50
+ name: "deck-package-css",
51
+ setup(b) {
52
+ b.onResolve({ filter: /.*/ }, async (args) => {
53
+ if (args.pluginData?.css || (args.kind !== "import-statement" && args.kind !== "require-call")) return undefined;
54
+ // The plugin's own code: a bare import, which may name a package whose entry is a
55
+ // stylesheet (@fontsource/inter). A package's: any import of a .css file.
56
+ const own = under(args.importer, src);
57
+ if (!(own ? importedBare(args.importer, args.path) : /\.css$/.test(args.path))) return undefined;
58
+ const r = await b.resolve(args.path, { kind: args.kind, resolveDir: args.resolveDir, importer: args.importer, pluginData: { css: true } });
59
+ if (r.errors.length) return { errors: r.errors };
60
+ if (!/\.css$/.test(r.path)) return undefined;
61
+ return { path: r.path, namespace: "deck-package-css" };
62
+ });
63
+ b.onLoad({ filter: /.*/, namespace: "deck-package-css" }, async (args) => {
64
+ const out = await build({ entryPoints: [args.path], outfile: "package.css", bundle: true, write: false, minify: true, loader: assetLoaders, logLevel: "silent" });
65
+ const css = out.outputFiles.find((f) => f.path.endsWith(".css"))?.text ?? "";
66
+ // Named by where it is inside the project, not on this machine: the bundle is shared.
67
+ const where = /[\\/]node_modules[\\/]/.test(args.path) ? args.path.replace(/^.*[\\/]node_modules[\\/]/, "") : relative(dir, args.path);
68
+ const key = `${pluginId}:${where.replace(/\\/g, "/")}`;
69
+ return {
70
+ resolveDir: dirname(args.path),
71
+ loader: "js",
72
+ contents: `const css = ${JSON.stringify(css)};
73
+ if (typeof document !== "undefined") {
74
+ const key = ${JSON.stringify(key)};
75
+ let s = [...document.head.querySelectorAll("style[data-deck-css]")].find((e) => e.dataset.deckCss === key);
76
+ if (!s) { s = document.createElement("style"); s.dataset.deckCss = key; document.head.appendChild(s); }
77
+ s.textContent = css;
78
+ }
79
+ export default css;
80
+ `,
81
+ };
82
+ });
83
+ },
84
+ };
85
+ }
86
+
87
+ /**
88
+ * `import text from "some/file?raw"` gives a file's contents as a string, the convention Vite
89
+ * uses. What a library needs as text rather than as code: PDF.js's worker, which a plugin starts
90
+ * from a Blob URL because only plugin.js is installed (see the Requests plugin's script worker).
91
+ *
92
+ * `?base64` gives any file's bytes as a base64 string instead: data a library asks for only
93
+ * sometimes (PDF.js's font files and image decoders), decoded when it does rather than every time
94
+ * Deck starts, as a `.wasm` import would be.
95
+ */
96
+ export const rawImports = {
97
+ name: "deck-raw",
98
+ setup(b) {
99
+ b.onResolve({ filter: /\?(raw|base64)$/ }, async (args) => {
100
+ const as = args.path.endsWith("?raw") ? "text" : "base64";
101
+ const r = await b.resolve(args.path.replace(/\?(raw|base64)$/, ""), { kind: args.kind, resolveDir: args.resolveDir, importer: args.importer });
102
+ if (r.errors.length) return { errors: r.errors };
103
+ return { path: r.path, namespace: "deck-raw", pluginData: { as } };
104
+ });
105
+ b.onLoad({ filter: /.*/, namespace: "deck-raw" }, (args) => ({ contents: readFileSync(args.path), loader: args.pluginData?.as === "base64" ? "base64" : "text" }));
106
+ },
107
+ };
package/shim/bridge.js CHANGED
@@ -3,6 +3,7 @@
3
3
  // Deck's bridge. Everything resolves at USE time, never at import time: a plugin module is
4
4
  // evaluated during loadPlugins, and a reference copied out then would capture a half-built
5
5
  // object. Functions forward their arguments; plain values are proxied.
6
+ import { pluginId, scoped } from "./scope.js";
6
7
  const b = () => {
7
8
  const x = window.__deck?.bridge;
8
9
  if (!x) throw new Error("window.__deck.bridge is missing — this plugin only runs inside Deck");
@@ -15,20 +16,18 @@ export const askSudoPassword = (...a) => b().askSudoPassword(...a);
15
16
  export const backupExport = (...a) => b().backupExport(...a);
16
17
  export const backupInspect = (...a) => b().backupInspect(...a);
17
18
  export const backupRestore = (...a) => b().backupRestore(...a);
18
- export const claudeMcpEntry = (...a) => b().claudeMcpEntry(...a);
19
- export const claudeMcpStatus = (...a) => b().claudeMcpStatus(...a);
20
19
  export const claudePlanLimits = (...a) => b().claudePlanLimits(...a);
21
20
  export const claudeUsage = (...a) => b().claudeUsage(...a);
22
21
  export const clearWallpaper = (...a) => b().clearWallpaper(...a);
23
- export const closeApp = (...a) => b().closeApp(...a);
24
- export const configRead = (...a) => b().configRead(...a);
25
- export const configWrite = (...a) => b().configWrite(...a);
22
+ export const configDelete = (name, ...a) => b().configDelete(scoped(name), ...a);
23
+ export const configRead = (name, ...a) => b().configRead(scoped(name), ...a);
24
+ export const configWrite = (name, ...a) => b().configWrite(scoped(name), ...a);
26
25
  export const convertFileSrc = (...a) => b().convertFileSrc(...a);
26
+ export const dataDir = () => b().dataDir(pluginId);
27
27
  export const dbConnect = (...a) => b().dbConnect(...a);
28
28
  export const dbDisconnect = (...a) => b().dbDisconnect(...a);
29
29
  export const dbQuery = (...a) => b().dbQuery(...a);
30
30
  export const dbSchema = (...a) => b().dbSchema(...a);
31
- export const defaultDownloadDir = (...a) => b().defaultDownloadDir(...a);
32
31
  export const deleteIconPack = (...a) => b().deleteIconPack(...a);
33
32
  export const dirsDownload = (...a) => b().dirsDownload(...a);
34
33
  export const exec = (...a) => b().exec(...a);
@@ -57,82 +56,35 @@ export const fsTrash = (...a) => b().fsTrash(...a);
57
56
  export const fsWrite = (...a) => b().fsWrite(...a);
58
57
  export const fsWriteBytes = (...a) => b().fsWriteBytes(...a);
59
58
  export const getAutostart = (...a) => b().getAutostart(...a);
60
- export const getVaultRoot = (...a) => b().getVaultRoot(...a);
61
59
  export const ghPrLineComment = (...a) => b().ghPrLineComment(...a);
62
60
  export const ghPrThreads = (...a) => b().ghPrThreads(...a);
63
61
  export const ghRun = (...a) => b().ghRun(...a);
64
62
  export const ghThreadReply = (...a) => b().ghThreadReply(...a);
65
63
  export const ghThreadResolve = (...a) => b().ghThreadResolve(...a);
66
- export const gitAbortOp = (...a) => b().gitAbortOp(...a);
67
64
  export const gitAiCommitMsg = (...a) => b().gitAiCommitMsg(...a);
68
65
  export const gitBlameLine = (...a) => b().gitBlameLine(...a);
69
- export const gitBranchAt = (...a) => b().gitBranchAt(...a);
70
- export const gitBranchDelete = (...a) => b().gitBranchDelete(...a);
71
- export const gitBranchDeleteRemote = (...a) => b().gitBranchDeleteRemote(...a);
72
- export const gitBranchRename = (...a) => b().gitBranchRename(...a);
73
- export const gitBranches = (...a) => b().gitBranches(...a);
74
66
  export const gitCheckout = (...a) => b().gitCheckout(...a);
75
67
  export const gitCheckoutDetach = (...a) => b().gitCheckoutDetach(...a);
76
68
  export const gitCheckoutNew = (...a) => b().gitCheckoutNew(...a);
77
69
  export const gitCheckoutTrack = (...a) => b().gitCheckoutTrack(...a);
78
- export const gitCherryPick = (...a) => b().gitCherryPick(...a);
79
- export const gitCommit = (...a) => b().gitCommit(...a);
80
- export const gitCommitAmend = (...a) => b().gitCommitAmend(...a);
81
70
  export const gitCommitFiles = (...a) => b().gitCommitFiles(...a);
82
- export const gitContinueOp = (...a) => b().gitContinueOp(...a);
83
71
  export const gitDiscard = (...a) => b().gitDiscard(...a);
84
- export const gitFetch = (...a) => b().gitFetch(...a);
85
- export const gitFetchPr = (...a) => b().gitFetchPr(...a);
86
72
  export const gitFileBytes = (...a) => b().gitFileBytes(...a);
87
73
  export const gitFileDiff = (...a) => b().gitFileDiff(...a);
88
- export const gitHead = (...a) => b().gitHead(...a);
89
- export const gitHeadFile = (...a) => b().gitHeadFile(...a);
90
- export const gitIndexFile = (...a) => b().gitIndexFile(...a);
91
74
  export const gitInfo = (...a) => b().gitInfo(...a);
92
75
  export const gitLogGraph = (...a) => b().gitLogGraph(...a);
93
- export const gitMerge = (...a) => b().gitMerge(...a);
94
- export const gitMergeAs = (...a) => b().gitMergeAs(...a);
95
- export const gitMergeBase = (...a) => b().gitMergeBase(...a);
96
- export const gitPendingOp = (...a) => b().gitPendingOp(...a);
97
76
  export const gitPoll = (...a) => b().gitPoll(...a);
98
- export const gitPull = (...a) => b().gitPull(...a);
99
- export const gitPullRebase = (...a) => b().gitPullRebase(...a);
100
- export const gitPush = (...a) => b().gitPush(...a);
101
- export const gitPushBranch = (...a) => b().gitPushBranch(...a);
102
77
  export const gitRangeFiles = (...a) => b().gitRangeFiles(...a);
103
- export const gitRangeSubjects = (...a) => b().gitRangeSubjects(...a);
104
- export const gitRebase = (...a) => b().gitRebase(...a);
105
78
  export const gitRefs = (...a) => b().gitRefs(...a);
106
- export const gitRemoteUrl = (...a) => b().gitRemoteUrl(...a);
107
- export const gitRemotes = (...a) => b().gitRemotes(...a);
108
79
  export const gitRepos = (...a) => b().gitRepos(...a);
109
- export const gitResetTo = (...a) => b().gitResetTo(...a);
110
- export const gitRevert = (...a) => b().gitRevert(...a);
111
- export const gitShowFile = (...a) => b().gitShowFile(...a);
112
- export const gitStage = (...a) => b().gitStage(...a);
113
- export const gitStageContent = (...a) => b().gitStageContent(...a);
114
- export const gitStash = (...a) => b().gitStash(...a);
115
- export const gitStashApply = (...a) => b().gitStashApply(...a);
116
- export const gitStashClear = (...a) => b().gitStashClear(...a);
117
- export const gitStashDrop = (...a) => b().gitStashDrop(...a);
118
- export const gitStashList = (...a) => b().gitStashList(...a);
119
- export const gitStashPop = (...a) => b().gitStashPop(...a);
120
- export const gitStashShow = (...a) => b().gitStashShow(...a);
80
+ export const gitRun = (...a) => b().gitRun(...a);
121
81
  export const gitStatus = (...a) => b().gitStatus(...a);
122
- export const gitTag = (...a) => b().gitTag(...a);
123
- export const gitTagDelete = (...a) => b().gitTagDelete(...a);
124
- export const gitTagDeleteRemote = (...a) => b().gitTagDeleteRemote(...a);
125
- export const gitTagPush = (...a) => b().gitTagPush(...a);
126
- export const gitUndoCommit = (...a) => b().gitUndoCommit(...a);
127
- export const gitUnstage = (...a) => b().gitUnstage(...a);
128
82
  export const glabApi = (...a) => b().glabApi(...a);
129
83
  export const glabRun = (...a) => b().glabRun(...a);
130
84
  export const googleAccount = (...a) => b().googleAccount(...a);
131
85
  export const googleSignIn = (...a) => b().googleSignIn(...a);
132
86
  export const googleSignOut = (...a) => b().googleSignOut(...a);
133
87
  export const googleToken = (...a) => b().googleToken(...a);
134
- export const historyClear = (...a) => b().historyClear(...a);
135
- export const historyDelete = (...a) => b().historyDelete(...a);
136
88
  export const httpCancel = (...a) => b().httpCancel(...a);
137
89
  export const httpSend = (...a) => b().httpSend(...a);
138
90
  export const httpSendAs = (...a) => b().httpSendAs(...a);
@@ -141,29 +93,21 @@ export const importIconPack = (...a) => b().importIconPack(...a);
141
93
  export const installAgentHooks = (...a) => b().installAgentHooks(...a);
142
94
  export const installAgentMcp = (...a) => b().installAgentMcp(...a);
143
95
  export const installAgentSkills = (...a) => b().installAgentSkills(...a);
144
- export const installClaudeHooks = (...a) => b().installClaudeHooks(...a);
145
- export const installClaudeMcp = (...a) => b().installClaudeMcp(...a);
146
96
  export const installFromRegistry = (...a) => b().installFromRegistry(...a);
147
- export const installPlugin = (...a) => b().installPlugin(...a);
148
97
  export const isFullscreen = (...a) => b().isFullscreen(...a);
149
98
  export const languagesDir = (...a) => b().languagesDir(...a);
150
99
  export const launchFiles = (...a) => b().launchFiles(...a);
151
100
  export const listApps = (...a) => b().listApps(...a);
152
101
  export const listInstalled = (...a) => b().listInstalled(...a);
102
+ export const logLatest = (...a) => b().logLatest(...a);
153
103
  export const mediaMeta = (...a) => b().mediaMeta(...a);
154
104
  export const mediaSetNowPlaying = (...a) => b().mediaSetNowPlaying(...a);
155
105
  export const mediaSetPlayback = (...a) => b().mediaSetPlayback(...a);
156
106
  export const memUsage = (...a) => b().memUsage(...a);
157
- export const notesDelete = (...a) => b().notesDelete(...a);
158
- export const notesList = (...a) => b().notesList(...a);
159
- export const notesRead = (...a) => b().notesRead(...a);
160
- export const notesRename = (...a) => b().notesRename(...a);
161
- export const notesScope = (...a) => b().notesScope(...a);
162
- export const notesWrite = (...a) => b().notesWrite(...a);
163
107
  export const notifyOs = (...a) => b().notifyOs(...a);
164
- export const openApp = (...a) => b().openApp(...a);
165
108
  export const openExternal = (...a) => b().openExternal(...a);
166
109
  export const openFile = (...a) => b().openFile(...a);
110
+ export const openTestDeck = (...a) => b().openTestDeck(...a);
167
111
  export const pickAnyFile = (...a) => b().pickAnyFile(...a);
168
112
  export const pickExe = (...a) => b().pickExe(...a);
169
113
  export const pickFile = (...a) => b().pickFile(...a);
@@ -172,24 +116,15 @@ export const pickImage = (...a) => b().pickImage(...a);
172
116
  export const pickSavePath = (...a) => b().pickSavePath(...a);
173
117
  export const pickScript = (...a) => b().pickScript(...a);
174
118
  export const pluginsDir = (...a) => b().pluginsDir(...a);
175
- export const powerAction = (...a) => b().powerAction(...a);
176
119
  export const procKill = (...a) => b().procKill(...a);
177
120
  export const procStream = (...a) => b().procStream(...a);
178
121
  export const procWrite = (...a) => b().procWrite(...a);
179
- export const readDownloads = (...a) => b().readDownloads(...a);
180
122
  export const readIconPackTheme = (...a) => b().readIconPackTheme(...a);
181
- export const readLibrary = (...a) => b().readLibrary(...a);
182
- export const readMusicSuggestions = (...a) => b().readMusicSuggestions(...a);
183
- export const readMusicTaste = (...a) => b().readMusicTaste(...a);
184
123
  export const readTermLayout = (...a) => b().readTermLayout(...a);
185
124
  export const readTrackMeta = (...a) => b().readTrackMeta(...a);
186
125
  export const remoteHome = (...a) => b().remoteHome(...a);
187
126
  export const remoteStat = (...a) => b().remoteStat(...a);
188
127
  export const remoteSudoPassword = (...a) => b().remoteSudoPassword(...a);
189
- export const removeAgentHooks = (...a) => b().removeAgentHooks(...a);
190
- export const removeAgentMcp = (...a) => b().removeAgentMcp(...a);
191
- export const removeClaudeHooks = (...a) => b().removeClaudeHooks(...a);
192
- export const removeClaudeMcp = (...a) => b().removeClaudeMcp(...a);
193
128
  export const removeLanguage = (...a) => b().removeLanguage(...a);
194
129
  export const restartApp = (...a) => b().restartApp(...a);
195
130
  export const revealInFolder = (...a) => b().revealInFolder(...a);
@@ -197,37 +132,20 @@ export const runScriptNow = (...a) => b().runScriptNow(...a);
197
132
  export const sessionList = (...a) => b().sessionList(...a);
198
133
  export const setAutostart = (...a) => b().setAutostart(...a);
199
134
  export const setDebugTargets = (...a) => b().setDebugTargets(...a);
200
- export const setVaultRoot = (...a) => b().setVaultRoot(...a);
201
135
  export const setWallpaper = (...a) => b().setWallpaper(...a);
202
136
  export const sshHosts = (...a) => b().sshHosts(...a);
203
137
  export const sshTunnelClose = (...a) => b().sshTunnelClose(...a);
204
- export const sshTunnelList = (...a) => b().sshTunnelList(...a);
205
138
  export const sshTunnelOpen = (...a) => b().sshTunnelOpen(...a);
206
139
  export const startupMarks = (...a) => b().startupMarks(...a);
207
- export const startupRead = (...a) => b().startupRead(...a);
208
- export const startupWrite = (...a) => b().startupWrite(...a);
209
140
  export const toggleFullscreen = (...a) => b().toggleFullscreen(...a);
210
- export const vaultDataRead = (...a) => b().vaultDataRead(...a);
211
- export const vaultDataWrite = (...a) => b().vaultDataWrite(...a);
212
- export const vaultRelativize = (...a) => b().vaultRelativize(...a);
213
- export const vaultResolve = (...a) => b().vaultResolve(...a);
214
141
  export const vpsRunsPull = (...a) => b().vpsRunsPull(...a);
215
142
  export const watchFiles = (...a) => b().watchFiles(...a);
216
- export const writeDownloads = (...a) => b().writeDownloads(...a);
217
- export const writeLibrary = (...a) => b().writeLibrary(...a);
218
- export const writeMusicTaste = (...a) => b().writeMusicTaste(...a);
219
- export const writeTermLayout = (...a) => b().writeTermLayout(...a);
220
143
 
221
144
  export const DEFAULT_SETTINGS = new Proxy({}, {
222
145
  get: (_t, k) => b().DEFAULT_SETTINGS[k],
223
146
  ownKeys: () => Reflect.ownKeys(b().DEFAULT_SETTINGS),
224
147
  getOwnPropertyDescriptor: (_t, k) => ({ ...Object.getOwnPropertyDescriptor(b().DEFAULT_SETTINGS, k), configurable: true }),
225
148
  });
226
- export const EMPTY_LIBRARY = new Proxy({}, {
227
- get: (_t, k) => b().EMPTY_LIBRARY[k],
228
- ownKeys: () => Reflect.ownKeys(b().EMPTY_LIBRARY),
229
- getOwnPropertyDescriptor: (_t, k) => ({ ...Object.getOwnPropertyDescriptor(b().EMPTY_LIBRARY, k), configurable: true }),
230
- });
231
149
  export const FOOTER_ITEMS = new Proxy({}, {
232
150
  get: (_t, k) => b().FOOTER_ITEMS[k],
233
151
  ownKeys: () => Reflect.ownKeys(b().FOOTER_ITEMS),
package/shim/event.js CHANGED
@@ -2,4 +2,9 @@
2
2
  //
3
3
  // @tauri-apps/api/event. Only listen: a plugin subscribing to Deck's events is expected, one
4
4
  // emitting into them is not.
5
- export const listen = (event, handler) => window.__deck.listen(event, handler);
5
+ //
6
+ // config-changed names the configs that changed, folders and all. A plugin's own come to it as it
7
+ // names them, without its data/<id>/ folder, so `payload.includes("boards")` keeps working.
8
+ import { unscoped } from "./scope.js";
9
+ export const listen = (event, handler) => window.__deck.listen(event, event !== "config-changed" ? handler
10
+ : (e) => handler({ ...e, payload: Array.isArray(e.payload) ? e.payload.map(unscoped) : e.payload }));
package/shim/scope.js ADDED
@@ -0,0 +1,6 @@
1
+ // GENERATED by sdk/gen-shim.mjs — do not edit.
2
+ const id = (() => { try { return new URL(import.meta.url).searchParams.get("deck-id") ?? ""; } catch { return ""; } })();
3
+ export const pluginId = id;
4
+ const own = id ? `data/${id}/` : "";
5
+ export const scoped = (name) => (own && !String(name).includes("/") ? own + name : name);
6
+ export const unscoped = (name) => (own && typeof name === "string" && name.startsWith(own) ? name.slice(own.length) : name);
package/shim/terminals.js CHANGED
@@ -39,6 +39,7 @@ export const moveZone = (...a) => t().moveZone(...a);
39
39
  export const mutateLayout = (...a) => t().mutateLayout(...a);
40
40
  export const nextActiveAfterClose = (...a) => t().nextActiveAfterClose(...a);
41
41
  export const nextZoneSpot = (...a) => t().nextZoneSpot(...a);
42
+ export const openFile = (...a) => t().openFile(...a);
42
43
  export const openTabInWorkspace = (...a) => t().openTabInWorkspace(...a);
43
44
  export const paneCwd = (...a) => t().paneCwd(...a);
44
45
  export const paneHasAgent = (...a) => t().paneHasAgent(...a);
package/shim/ui.js CHANGED
@@ -63,7 +63,6 @@ export const normalizeChord = (...a) => u().normalizeChord(...a);
63
63
  export const onKey = (...a) => u().onKey(...a);
64
64
  export const onKeys = (...a) => u().onKeys(...a);
65
65
  export const parseChord = (...a) => u().parseChord(...a);
66
- export const parseDirty = (...a) => u().parseDirty(...a);
67
66
  export const pushScope = (...a) => u().pushScope(...a);
68
67
  export const repoLabel = (...a) => u().repoLabel(...a);
69
68
  export const resolve = (...a) => u().resolve(...a);
@@ -0,0 +1,77 @@
1
+ # __NAME__: notes for AI agents
2
+
3
+ This is a plugin for Deck, a Windows app (Tauri, React 19). It is
4
+ bundled into one `plugin.js` that Deck imports at run time, inside Deck's own window. Read this
5
+ before changing anything.
6
+
7
+ ## Commands
8
+
9
+ ```sh
10
+ npm run dev # build on every save and install into the test Deck, which reloads it
11
+ npm run build # build once into plugin.js
12
+ npm run install-local # build and install into the user's real Deck
13
+ npx deck-plugin check # validate plugin.json
14
+ ```
15
+
16
+ The test Deck is a second Deck with its own data, started with
17
+ `& "$env:LOCALAPPDATA\Deck\deck.exe" --test` (PowerShell). Develop against it, never the real
18
+ Deck: `npm run dev` only ever writes there. Run `npm run install-local` only when the user asks
19
+ for the plugin to be installed for real. Its log is `%APPDATA%\Deck-Dev\logs\deck-<date>.log`;
20
+ the plugin's `console.warn` and `console.error` lines land there.
21
+
22
+ ## Files
23
+
24
+ - `plugin.json`: `id` (letters, digits, dashes; it is a folder name and a config key), `name`,
25
+ `description`, `icon` (a lucide icon name), `color`, `version`, `minDeck`.
26
+ - `src/plugin.tsx`: the entry. Its exports are what Deck uses (below).
27
+ - `src/mcp.ts`, if present: tools for Deck's MCP server, built to `mcp.js`.
28
+ - `plugin.js` (and `mcp.js`): the build's output, gitignored. Never edit them.
29
+ - **The API is `node_modules/deck-plugin-sdk/types/core/bridge.d.ts`**: every function Deck
30
+ exposes, with its documentation, generated from Deck's own source. Read it rather than guessing
31
+ a name. A plugin imports it as `deck-plugin-sdk/bridge`.
32
+
33
+ ## What the plugin can export
34
+
35
+ `default` (a tab), `PaneView` (a view that can sit in a split pane, given `{ cwd, runInTerminal }`),
36
+ `Status` (a footer readout), `Panel` (a footer drawer tab), `commands` (palette entries),
37
+ `Settings` (a settings panel), `keybindings`, `Provider` (wraps the app, for state that must
38
+ survive the tab closing), `Bar`, and `dispose`. At least one; none is required on its own.
39
+
40
+ ## Rules that are easy to break
41
+
42
+ - **Import Deck's copies.** `react`, `lucide-react` and the `deck-plugin-sdk/*` modules resolve to
43
+ Deck's own at run time. Never add a second React, or a UI library that bundles one: two
44
+ Reacts in one tree break hooks.
45
+ - **No native code.** A plugin can only call what Deck already exposes (`deck-plugin-sdk/bridge`).
46
+ To run a program, use `exec` or `procStream`; to read and write files, the `fs*` calls.
47
+ - **Opening files**: `export const opens = [".ext"]` with a `PaneView` makes Deck open those files
48
+ in the plugin, each as its own tab, given as the `file` prop. Open one yourself with `openFile`
49
+ from `deck-plugin-sdk/terminals`; save with `fsWriteBytes`.
50
+ - **npm packages are fine** (`npm install` and import): the build bundles them, their images,
51
+ fonts and CSS included, into `plugin.js`. `?raw` on an import gives a file's text (a worker).
52
+ A build step of your own (an esbuild plugin) goes in `deck-plugin.config.mjs` as
53
+ `export const esbuildPlugins = [...]`. Not a package that needs Node (`fs`, `child_process`),
54
+ one that bundles its own React, or one that starts a Web Worker from a file.
55
+ - **Storage goes through `configRead` / `configWrite`** with plain names (`"settings"`, or
56
+ `"notes.md"` for a plain file). Deck keeps them in the plugin's own folder. A name never
57
+ written reads as `""`. Do not write a config whose text has not changed: every write wakes
58
+ every config listener in Deck.
59
+ - **`listen("config-changed", ...)` gets a `string[]` of names**: act only when it includes one
60
+ of yours.
61
+ - **Deck reloads the plugin on every rebuild.** Anything module scope starts (a `setInterval`,
62
+ a `listen`, a window listener) must be stopped in an exported `dispose()`, or the old copy keeps
63
+ running beside the new one. Anything a component creates outside the DOM (an `Audio`, a timer,
64
+ a socket) is stopped in its effect's cleanup.
65
+ - **A poll stops when nobody can see it**: skip ticks while `document.hidden`, and do not run it
66
+ while the readout it feeds is switched off.
67
+ - **Styling uses Deck's stylesheet.** Tailwind classes Deck does not already use do not exist.
68
+ Use Deck's semantic tokens (`text-text-primary`, `text-text-secondary`, `text-text-muted`,
69
+ `bg-elev`, `border-subtle`, `var(--accent)`) and Deck's components from `deck-plugin-sdk/ui`, so
70
+ the plugin follows the user's theme.
71
+ - **No error is dropped silently.** A failed save, load or action logs
72
+ `console.warn("[__ID__] what failed", e)`.
73
+
74
+ ## More
75
+
76
+ The full guide (exports, storage, testing, publishing) is in Deck's **Docs** tab, which also has
77
+ the API reference for the Deck that is installed.
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -1,17 +1,20 @@
1
- # __NAME__
2
-
3
- A plugin for [Deck](https://deck.hasan-zarif.com).
4
-
5
- ## Developing
6
-
7
- ```sh
8
- npm run dev # build, and rebuild as you edit
9
- npm run install-local # copy it into Deck, which loads it without a restart
10
- ```
11
-
12
- `src/plugin.tsx` is the entry point; its default export is the tab.
13
-
14
- ## What it does
15
-
16
- Say what this plugin is for. This file is what the registry shows on its page, so it is worth
17
- writing for somebody deciding whether to install it rather than for yourself.
1
+ # __NAME__
2
+
3
+ A plugin for Deck.
4
+
5
+ ## Developing
6
+
7
+ ```sh
8
+ npm run dev # rebuild as you edit; each save shows in your test Deck
9
+ npm run install-local # when it is done: install it into your real Deck
10
+ ```
11
+
12
+ Start a test Deck from Settings › Plugins › Open a test Deck: it keeps its own settings and data,
13
+ so trying the plugin never touches your real Deck.
14
+
15
+ `src/plugin.tsx` is the entry point; its default export is the tab.
16
+
17
+ ## What it does
18
+
19
+ Say what this plugin is for. This file is what the registry shows on its page, so it is worth
20
+ writing for somebody deciding whether to install it rather than for yourself.
@@ -1,4 +1,4 @@
1
- node_modules/
2
- # Copied in by the build from the SDK — not yours to edit, and not worth committing.
3
- shim/
4
- types/
1
+ node_modules/
2
+ # What the build writes. A release carries plugin.json and these; the repository holds the source.
3
+ plugin.js
4
+ mcp.js
@@ -1,9 +1,9 @@
1
- {
2
- "id": "__ID__",
3
- "name": "__NAME__",
4
- "description": "One line on what this is for, shown under its name in Settings.",
5
- "icon": "Sparkles",
6
- "color": "#a78bfa",
7
- "version": "0.1.0",
8
- "minDeck": "0.17.0"
9
- }
1
+ {
2
+ "id": "__ID__",
3
+ "name": "__NAME__",
4
+ "description": "One line on what this is for, shown under its name in Settings.",
5
+ "icon": "Sparkles",
6
+ "color": "#a78bfa",
7
+ "version": "0.1.0",
8
+ "minDeck": "1.5.0"
9
+ }
@@ -0,0 +1,16 @@
1
+ // What importing a file that is not code gives you, so the editor knows. The build inlines each
2
+ // into plugin.js: an image or font as a data: URL, a stylesheet as its text (or, imported with
3
+ // nothing bound — `import "x.css"` — applied to the page), a .wasm file as its bytes.
4
+ declare module "*.png" { const url: string; export default url; }
5
+ declare module "*.jpg" { const url: string; export default url; }
6
+ declare module "*.jpeg" { const url: string; export default url; }
7
+ declare module "*.gif" { const url: string; export default url; }
8
+ declare module "*.webp" { const url: string; export default url; }
9
+ declare module "*.svg" { const url: string; export default url; }
10
+ declare module "*.woff2" { const url: string; export default url; }
11
+ declare module "*.css" { const text: string; export default text; }
12
+ declare module "*.wasm" { const bytes: Uint8Array; export default bytes; }
13
+ // `import text from "some/file?raw"`: any file, as its text.
14
+ declare module "*?raw" { const text: string; export default text; }
15
+ // `import data from "some/file?base64"`: any file's bytes, as a base64 string.
16
+ declare module "*?base64" { const base64: string; export default base64; }