deck-plugin-sdk 1.11.0 → 1.12.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/README.md +97 -97
- package/bin/deck-plugin.mjs +158 -158
- package/build-plugin.mjs +172 -172
- package/package.json +60 -60
- package/packages.mjs +107 -107
- package/permissions.json +252 -248
- package/publish.mjs +116 -116
- package/shim/bridge.js +253 -249
- package/shim/p2p.d.ts +1 -1
- package/shim/settings.d.ts +1 -1
- package/shim/terminals.d.ts +1 -1
- package/shim/ui.d.ts +10 -10
- package/template/AGENTS.md +81 -81
- package/template/CLAUDE.md +1 -1
- package/template/LICENSE +21 -21
- package/template/README.md +20 -20
- package/template/gitignore +4 -4
- package/template/plugin.json +10 -10
- package/template/src/assets.d.ts +16 -16
- package/template/src/plugin.tsx +65 -65
- package/types/components/FileIcon.d.ts +1 -1
- package/types/components/TabStrip.d.ts +5 -2
- package/types/core/{settingsstore.d.ts → app/settingsstore.d.ts} +1 -1
- package/types/core/bridge.d.ts +1190 -1167
- package/types/core/{bookmarks.d.ts → browser/bookmarks.d.ts} +4 -2
- package/types/core/{browser.d.ts → browser/browser.d.ts} +1 -1
- package/types/core/{editorsettings.d.ts → editor/editorsettings.d.ts} +1 -1
- package/types/core/{icontheme.d.ts → editor/icontheme.d.ts} +1 -1
- package/types/core/workspaces/layout.d.ts +168 -0
- package/types/core/workspaces/terminals.d.ts +252 -0
- package/types/core/workspaces/zones.d.ts +215 -0
- package/types/modules/editor/MonacoHosts.d.ts +3 -3
- package/types/modules/editor/{DiffPeek.d.ts → git/DiffPeek.d.ts} +1 -1
- package/types/modules/editor/models.d.ts +1 -1
- package/types/modules/editor/{tree.d.ts → tree/tree.d.ts} +1 -1
- package/validate.mjs +112 -112
- package/types/core/terminals.d.ts +0 -573
- /package/types/core/{p2p.d.ts → app/p2p.d.ts} +0 -0
- /package/types/core/{theme.d.ts → app/theme.d.ts} +0 -0
- /package/types/core/{timeFormat.d.ts → app/timeFormat.d.ts} +0 -0
- /package/types/core/{browserurl.d.ts → browser/browserurl.d.ts} +0 -0
- /package/types/core/{dap.d.ts → editor/dap.d.ts} +0 -0
- /package/types/core/{debugtargets.d.ts → editor/debugtargets.d.ts} +0 -0
- /package/types/core/{decode.d.ts → editor/decode.d.ts} +0 -0
- /package/types/core/{editorsession.d.ts → editor/editorsession.d.ts} +0 -0
- /package/types/core/{inlay.d.ts → editor/inlay.d.ts} +0 -0
- /package/types/core/{langmap.d.ts → editor/langmap.d.ts} +0 -0
- /package/types/core/{lsp.d.ts → editor/lsp.d.ts} +0 -0
- /package/types/core/{monaco.d.ts → editor/monaco.d.ts} +0 -0
- /package/types/core/{patterntokens.d.ts → editor/patterntokens.d.ts} +0 -0
- /package/types/core/{snippets.d.ts → editor/snippets.d.ts} +0 -0
- /package/types/core/{treehide.d.ts → editor/treehide.d.ts} +0 -0
- /package/types/core/{keys.d.ts → keys/keys.d.ts} +0 -0
- /package/types/core/{keystore.d.ts → keys/keystore.d.ts} +0 -0
- /package/types/core/{notifications.d.ts → notifications/notifications.d.ts} +0 -0
- /package/types/core/{notifyapi.d.ts → notifications/notifyapi.d.ts} +0 -0
- /package/types/core/{fileview.d.ts → plugins/fileview.d.ts} +0 -0
- /package/types/core/{portal.d.ts → workspaces/portal.d.ts} +0 -0
- /package/types/core/{remotepath.d.ts → workspaces/remotepath.d.ts} +0 -0
- /package/types/core/{shellpath.d.ts → workspaces/shellpath.d.ts} +0 -0
- /package/types/core/{windows.d.ts → workspaces/windows.d.ts} +0 -0
- /package/types/modules/editor/{diffline.d.ts → git/diffline.d.ts} +0 -0
- /package/types/modules/editor/{hunks.d.ts → git/hunks.d.ts} +0 -0
- /package/types/modules/editor/{MarkdownLive.d.ts → viewers/MarkdownLive.d.ts} +0 -0
- /package/types/modules/editor/{viewers.d.ts → viewers/viewers.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,97 +1,97 @@
|
|
|
1
|
-
# deck-plugin-sdk
|
|
2
|
-
|
|
3
|
-
Build a plugin for Deck, outside Deck's own repository.
|
|
4
|
-
|
|
5
|
-
```sh
|
|
6
|
-
npm create deck-plugin my-thing
|
|
7
|
-
cd my-thing
|
|
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
|
-
Develop in a **test Deck**: a second Deck beside yours, with its own settings and data, started
|
|
13
|
-
from Settings › Plugins › Open a test Deck (or `deck.exe --test`). `npm run dev` installs into it
|
|
14
|
-
after every build and it reloads the plugin, so a save shows there within a few seconds, while your
|
|
15
|
-
real Deck and its data are never touched.
|
|
16
|
-
|
|
17
|
-
The project comes with an `AGENTS.md` (and a `CLAUDE.md` that points to it), so Claude Code,
|
|
18
|
-
Codex or another agent started in the folder knows the commands, the API and the rules.
|
|
19
|
-
|
|
20
|
-
## What a plugin is
|
|
21
|
-
|
|
22
|
-
A folder with a `plugin.json` and a built `plugin.js`, read from
|
|
23
|
-
`%APPDATA%\Deck\plugins\<id>\` when Deck starts. It becomes a tab, or a readout in the footer,
|
|
24
|
-
or a panel, or commands in the palette — `docs/guide/api.md` in the Deck repository lists every
|
|
25
|
-
slot a plugin can fill. One is enough.
|
|
26
|
-
|
|
27
|
-
Your sources import `react`, `lucide-react` and `deck-plugin-sdk/*` as ordinary code does. At run
|
|
28
|
-
time those resolve to **Deck's** copies, handed over on `window.__deck`. For React that is not
|
|
29
|
-
an optimisation but a requirement: two Reacts in one tree break hooks.
|
|
30
|
-
|
|
31
|
-
## Commands
|
|
32
|
-
|
|
33
|
-
| | |
|
|
34
|
-
|---|---|
|
|
35
|
-
| `deck-plugin build` | bundle `src/` into `plugin.js` |
|
|
36
|
-
| `deck-plugin dev` | rebuild whenever a source changes, and install each build into the test Deck |
|
|
37
|
-
| `deck-plugin check` | validate `plugin.json` without building |
|
|
38
|
-
| `deck-plugin install` | build once, then install into your real Deck |
|
|
39
|
-
| `deck-plugin publish` | check everything Deck's registry checks, then tag, release on GitHub and submit it (`--dry-run`: only check); see [Publishing](../docs/guide/publishing.md) |
|
|
40
|
-
|
|
41
|
-
`--target test` or `--target main` picks the other Deck for either command. The test Deck keeps
|
|
42
|
-
its plugins in `%APPDATA%\Deck-Dev\plugins`, your real Deck in `%APPDATA%\Deck\plugins`.
|
|
43
|
-
|
|
44
|
-
## What you can import
|
|
45
|
-
|
|
46
|
-
```ts
|
|
47
|
-
import { configRead, configWrite } from "deck-plugin-sdk/bridge"; // Deck's Tauri commands
|
|
48
|
-
import { listen } from "deck-plugin-sdk/event"; // Deck's event bus
|
|
49
|
-
import { getSettings } from "deck-plugin-sdk/settings"; // Deck's settings store
|
|
50
|
-
import { Field, GhostButton } from "deck-plugin-sdk/ui"; // Deck's form controls
|
|
51
|
-
import { getLayout } from "deck-plugin-sdk/terminals"; // the terminal layout
|
|
52
|
-
import { monaco } from "deck-plugin-sdk/monaco"; // the editor
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
They come from the package, so the project holds only your own code. A plugin made with an
|
|
56
|
-
older SDK imports `../shim/bridge.js` instead; the build still copies `shim/` and `types/` in
|
|
57
|
-
for one of those, and switching its imports over makes both unnecessary.
|
|
58
|
-
|
|
59
|
-
The types are generated from Deck's own source, so what your editor tells you is what Deck
|
|
60
|
-
actually exposes rather than a hand-written approximation that has drifted.
|
|
61
|
-
|
|
62
|
-
Any other npm package is `npm install` and import: the build bundles it into `plugin.js`, with the
|
|
63
|
-
images, fonts and CSS it brings. Writing a plugin, in the Docs, says what cannot work that way.
|
|
64
|
-
|
|
65
|
-
When the build needs more than that (a worker bundled into the plugin, a dependency's locales left
|
|
66
|
-
out), export esbuild plugins from `deck-plugin.config.mjs` beside `plugin.json`:
|
|
67
|
-
|
|
68
|
-
```js
|
|
69
|
-
export const esbuildPlugins = [{ name: "my-step", setup(build) { /* ... */ } }];
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## What a plugin cannot do
|
|
73
|
-
|
|
74
|
-
Add native capability. Every Tauri command is compiled into Deck, so a plugin calls what already
|
|
75
|
-
exists. Anything needing new Rust belongs in Deck itself.
|
|
76
|
-
|
|
77
|
-
## What a plugin CAN do, which is worth saying plainly
|
|
78
|
-
|
|
79
|
-
**Everything Deck can.** A plugin runs inside Deck with the whole bridge: every Tauri command,
|
|
80
|
-
the config directory, the filesystem, the terminal, a Google access token if one is connected.
|
|
81
|
-
There is no sandbox and no permission prompt.
|
|
82
|
-
|
|
83
|
-
That is a deliberate position rather than an oversight — a plugin is code you chose to run, like
|
|
84
|
-
a VS Code extension or a shell script. It is also why the plugin registry is curated rather than
|
|
85
|
-
open: an entry exists there because it was reviewed, and installing a plugin from anywhere else
|
|
86
|
-
is the same decision as running any other program.
|
|
87
|
-
|
|
88
|
-
## Versions
|
|
89
|
-
|
|
90
|
-
The SDK's major and minor track Deck's, so an SDK 0.15 builds plugins for Deck 0.15. Declare the
|
|
91
|
-
oldest Deck you work with as `minDeck` in `plugin.json`; Deck refuses to load a plugin that
|
|
92
|
-
needs a newer one, with a sentence rather than an error from inside your module.
|
|
93
|
-
|
|
94
|
-
## Publishing
|
|
95
|
-
|
|
96
|
-
A plugin is published by being listed in Deck's registry, which points at a release in your own
|
|
97
|
-
repository. Until that is open to submissions, ask.
|
|
1
|
+
# deck-plugin-sdk
|
|
2
|
+
|
|
3
|
+
Build a plugin for Deck, outside Deck's own repository.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm create deck-plugin my-thing
|
|
7
|
+
cd my-thing
|
|
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
|
+
Develop in a **test Deck**: a second Deck beside yours, with its own settings and data, started
|
|
13
|
+
from Settings › Plugins › Open a test Deck (or `deck.exe --test`). `npm run dev` installs into it
|
|
14
|
+
after every build and it reloads the plugin, so a save shows there within a few seconds, while your
|
|
15
|
+
real Deck and its data are never touched.
|
|
16
|
+
|
|
17
|
+
The project comes with an `AGENTS.md` (and a `CLAUDE.md` that points to it), so Claude Code,
|
|
18
|
+
Codex or another agent started in the folder knows the commands, the API and the rules.
|
|
19
|
+
|
|
20
|
+
## What a plugin is
|
|
21
|
+
|
|
22
|
+
A folder with a `plugin.json` and a built `plugin.js`, read from
|
|
23
|
+
`%APPDATA%\Deck\plugins\<id>\` when Deck starts. It becomes a tab, or a readout in the footer,
|
|
24
|
+
or a panel, or commands in the palette — `docs/guide/api.md` in the Deck repository lists every
|
|
25
|
+
slot a plugin can fill. One is enough.
|
|
26
|
+
|
|
27
|
+
Your sources import `react`, `lucide-react` and `deck-plugin-sdk/*` as ordinary code does. At run
|
|
28
|
+
time those resolve to **Deck's** copies, handed over on `window.__deck`. For React that is not
|
|
29
|
+
an optimisation but a requirement: two Reacts in one tree break hooks.
|
|
30
|
+
|
|
31
|
+
## Commands
|
|
32
|
+
|
|
33
|
+
| | |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `deck-plugin build` | bundle `src/` into `plugin.js` |
|
|
36
|
+
| `deck-plugin dev` | rebuild whenever a source changes, and install each build into the test Deck |
|
|
37
|
+
| `deck-plugin check` | validate `plugin.json` without building |
|
|
38
|
+
| `deck-plugin install` | build once, then install into your real Deck |
|
|
39
|
+
| `deck-plugin publish` | check everything Deck's registry checks, then tag, release on GitHub and submit it (`--dry-run`: only check); see [Publishing](../docs/guide/publishing.md) |
|
|
40
|
+
|
|
41
|
+
`--target test` or `--target main` picks the other Deck for either command. The test Deck keeps
|
|
42
|
+
its plugins in `%APPDATA%\Deck-Dev\plugins`, your real Deck in `%APPDATA%\Deck\plugins`.
|
|
43
|
+
|
|
44
|
+
## What you can import
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { configRead, configWrite } from "deck-plugin-sdk/bridge"; // Deck's Tauri commands
|
|
48
|
+
import { listen } from "deck-plugin-sdk/event"; // Deck's event bus
|
|
49
|
+
import { getSettings } from "deck-plugin-sdk/settings"; // Deck's settings store
|
|
50
|
+
import { Field, GhostButton } from "deck-plugin-sdk/ui"; // Deck's form controls
|
|
51
|
+
import { getLayout } from "deck-plugin-sdk/terminals"; // the terminal layout
|
|
52
|
+
import { monaco } from "deck-plugin-sdk/monaco"; // the editor
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
They come from the package, so the project holds only your own code. A plugin made with an
|
|
56
|
+
older SDK imports `../shim/bridge.js` instead; the build still copies `shim/` and `types/` in
|
|
57
|
+
for one of those, and switching its imports over makes both unnecessary.
|
|
58
|
+
|
|
59
|
+
The types are generated from Deck's own source, so what your editor tells you is what Deck
|
|
60
|
+
actually exposes rather than a hand-written approximation that has drifted.
|
|
61
|
+
|
|
62
|
+
Any other npm package is `npm install` and import: the build bundles it into `plugin.js`, with the
|
|
63
|
+
images, fonts and CSS it brings. Writing a plugin, in the Docs, says what cannot work that way.
|
|
64
|
+
|
|
65
|
+
When the build needs more than that (a worker bundled into the plugin, a dependency's locales left
|
|
66
|
+
out), export esbuild plugins from `deck-plugin.config.mjs` beside `plugin.json`:
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
export const esbuildPlugins = [{ name: "my-step", setup(build) { /* ... */ } }];
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## What a plugin cannot do
|
|
73
|
+
|
|
74
|
+
Add native capability. Every Tauri command is compiled into Deck, so a plugin calls what already
|
|
75
|
+
exists. Anything needing new Rust belongs in Deck itself.
|
|
76
|
+
|
|
77
|
+
## What a plugin CAN do, which is worth saying plainly
|
|
78
|
+
|
|
79
|
+
**Everything Deck can.** A plugin runs inside Deck with the whole bridge: every Tauri command,
|
|
80
|
+
the config directory, the filesystem, the terminal, a Google access token if one is connected.
|
|
81
|
+
There is no sandbox and no permission prompt.
|
|
82
|
+
|
|
83
|
+
That is a deliberate position rather than an oversight — a plugin is code you chose to run, like
|
|
84
|
+
a VS Code extension or a shell script. It is also why the plugin registry is curated rather than
|
|
85
|
+
open: an entry exists there because it was reviewed, and installing a plugin from anywhere else
|
|
86
|
+
is the same decision as running any other program.
|
|
87
|
+
|
|
88
|
+
## Versions
|
|
89
|
+
|
|
90
|
+
The SDK's major and minor track Deck's, so an SDK 0.15 builds plugins for Deck 0.15. Declare the
|
|
91
|
+
oldest Deck you work with as `minDeck` in `plugin.json`; Deck refuses to load a plugin that
|
|
92
|
+
needs a newer one, with a sentence rather than an error from inside your module.
|
|
93
|
+
|
|
94
|
+
## Publishing
|
|
95
|
+
|
|
96
|
+
A plugin is published by being listed in Deck's registry, which points at a release in your own
|
|
97
|
+
repository. Until that is open to submissions, ask.
|
package/bin/deck-plugin.mjs
CHANGED
|
@@ -1,159 +1,159 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// The Deck plugin CLI.
|
|
3
|
-
//
|
|
4
|
-
// deck-plugin build bundle src/ into plugin.js
|
|
5
|
-
// deck-plugin dev rebuild whenever a source changes, into the test Deck each time
|
|
6
|
-
// deck-plugin check validate plugin.json without building
|
|
7
|
-
// deck-plugin install build once and copy into your Deck
|
|
8
|
-
// deck-plugin publish check, tag, release on GitHub, and submit to Deck's registry
|
|
9
|
-
// (--dry-run: only check; see ../publish.mjs)
|
|
10
|
-
//
|
|
11
|
-
// --target test|main which Deck dev and install write to (defaults: dev -> test, install -> main)
|
|
12
|
-
//
|
|
13
|
-
// Run from a plugin's own directory.
|
|
14
|
-
import { watch } from "node:fs";
|
|
15
|
-
import { cpSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
16
|
-
import { join } from "node:path";
|
|
17
|
-
import { buildPlugin } from "../build-plugin.mjs";
|
|
18
|
-
import { publish } from "../publish.mjs";
|
|
19
|
-
|
|
20
|
-
const argv = process.argv.slice(2);
|
|
21
|
-
const at = argv.indexOf("--target");
|
|
22
|
-
const targetFlag = at >= 0 ? argv[at + 1] : undefined;
|
|
23
|
-
const cmd = argv.find((a, i) => !a.startsWith("-") && !(at >= 0 && i === at + 1)) ?? "build";
|
|
24
|
-
const dir = process.cwd();
|
|
25
|
-
|
|
26
|
-
const die = (msg) => { console.error(msg); process.exit(1); };
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* What Deck will refuse at install time, refused here instead.
|
|
30
|
-
*
|
|
31
|
-
* Every rule is one Deck's loader or install_from_registry applies — the id charset especially,
|
|
32
|
-
* because the id becomes a config key and a path component. Finding out here costs a second;
|
|
33
|
-
* finding out from a failed install costs a round trip.
|
|
34
|
-
*/
|
|
35
|
-
function check() {
|
|
36
|
-
const p = join(dir, "plugin.json");
|
|
37
|
-
if (!existsSync(p)) die("no plugin.json here — is this a plugin directory?");
|
|
38
|
-
|
|
39
|
-
let manifest;
|
|
40
|
-
try { manifest = JSON.parse(readFileSync(p, "utf8")); }
|
|
41
|
-
catch (e) { die(`plugin.json is not valid JSON: ${e.message}`); }
|
|
42
|
-
|
|
43
|
-
const id = (manifest.id ?? "").trim();
|
|
44
|
-
if (!id) die("plugin.json has no id");
|
|
45
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9-]*$/.test(id)) {
|
|
46
|
-
die(`id "${id}" must be letters, digits and dashes, starting with a letter or digit`);
|
|
47
|
-
}
|
|
48
|
-
if (!existsSync(join(dir, "src", "plugin.tsx"))) {
|
|
49
|
-
die("no src/plugin.tsx — that is the entry point");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Not fatal, but worth saying: the registry's plugin page renders a README, and a published
|
|
53
|
-
// plugin without one gets an empty page.
|
|
54
|
-
const warn = [];
|
|
55
|
-
if (!manifest.version) warn.push("no version — Settings shows one, and the registry orders by it");
|
|
56
|
-
if (!manifest.description) warn.push("no description — the row says only a name");
|
|
57
|
-
if (!existsSync(join(dir, "README.md"))) warn.push("no README.md — publishing needs one");
|
|
58
|
-
|
|
59
|
-
console.log(`${manifest.name ?? id} ${manifest.version ?? ""}`.trim());
|
|
60
|
-
for (const w of warn) console.log(` note: ${w}`);
|
|
61
|
-
return manifest;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* The plugins folder of one of the two Decks on this machine.
|
|
66
|
-
*
|
|
67
|
-
* "test" is the test Deck (`deck.exe --test`, or Settings › Plugins › Open a test Deck), which keeps
|
|
68
|
-
* its own folder, Deck-Dev, apart from your real data. `dev` always writes there, so a plugin you
|
|
69
|
-
* are still writing never lands in your real Deck, even when the test Deck is closed. `install`
|
|
70
|
-
* writes to your real Deck ("main"), for when the plugin is done.
|
|
71
|
-
*/
|
|
72
|
-
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
|
-
const target = targetFlag ?? defaultTarget;
|
|
76
|
-
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") };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** Copy the built plugin into a Deck's plugins folder. Deck watches it and reloads the plugin. */
|
|
81
|
-
function copyInto(manifest, defaultTarget) {
|
|
82
|
-
const { target, dir: plugins } = pluginsDir(defaultTarget);
|
|
83
|
-
const dest = join(plugins, manifest.id);
|
|
84
|
-
mkdirSync(dest, { recursive: true });
|
|
85
|
-
for (const f of ["plugin.json", "plugin.js", "mcp.js"]) {
|
|
86
|
-
if (existsSync(join(dir, f))) cpSync(join(dir, f), join(dest, f));
|
|
87
|
-
}
|
|
88
|
-
return { target, dest };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Whether the test Deck is running: it answers on its own port. */
|
|
92
|
-
async function testDeckRunning() {
|
|
93
|
-
try { return (await fetch("http://127.0.0.1:8791/health", { signal: AbortSignal.timeout(800) })).ok; }
|
|
94
|
-
catch { return false; }
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
switch (cmd) {
|
|
98
|
-
case "check": {
|
|
99
|
-
check();
|
|
100
|
-
break;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
case "build": {
|
|
104
|
-
const manifest = check();
|
|
105
|
-
await buildPlugin(dir);
|
|
106
|
-
console.log(`built ${manifest.id} -> plugin.js`);
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
case "publish": {
|
|
111
|
-
const manifest = check();
|
|
112
|
-
await publish(dir, manifest, { dryRun: argv.includes("--dry-run"), buildPlugin });
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
case "install": {
|
|
117
|
-
const manifest = check();
|
|
118
|
-
await buildPlugin(dir);
|
|
119
|
-
const { target, dest } = copyInto(manifest, "main");
|
|
120
|
-
console.log(`installed ${manifest.id} into your ${target === "test" ? "test " : ""}Deck -> ${dest}`);
|
|
121
|
-
console.log("Deck watches that folder, so it is loaded already — no restart.");
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
case "dev": {
|
|
126
|
-
const manifest = check();
|
|
127
|
-
let building = false;
|
|
128
|
-
let again = false;
|
|
129
|
-
const once = async () => {
|
|
130
|
-
if (building) { again = true; return; }
|
|
131
|
-
building = true;
|
|
132
|
-
try {
|
|
133
|
-
await buildPlugin(dir);
|
|
134
|
-
// Into the test Deck after every build: it reloads the plugin when its folder changes, so
|
|
135
|
-
// a save shows there within a few seconds.
|
|
136
|
-
const { target, dest } = copyInto(manifest, "test");
|
|
137
|
-
console.log(`rebuilt at ${new Date().toLocaleTimeString()} -> ${target} Deck (${dest})`);
|
|
138
|
-
} catch (e) { console.error(e.message); }
|
|
139
|
-
building = false;
|
|
140
|
-
if (again) { again = false; void once(); }
|
|
141
|
-
};
|
|
142
|
-
if ((targetFlag ?? "test") === "test" && !(await testDeckRunning())) {
|
|
143
|
-
console.log("No test Deck is running, so you will not see changes yet. Start one from");
|
|
144
|
-
console.log("Settings › Plugins › Open a test Deck (or run deck.exe --test); it loads this plugin.");
|
|
145
|
-
}
|
|
146
|
-
await once();
|
|
147
|
-
// Debounced: an editor writing a file emits several events, and each would start a build.
|
|
148
|
-
let timer;
|
|
149
|
-
watch(join(dir, "src"), { recursive: true }, () => {
|
|
150
|
-
clearTimeout(timer);
|
|
151
|
-
timer = setTimeout(() => void once(), 120);
|
|
152
|
-
});
|
|
153
|
-
console.log("watching src/ — ctrl+c to stop");
|
|
154
|
-
break;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
default:
|
|
158
|
-
die(`unknown command "${cmd}" — try build, dev, check or install`);
|
|
159
|
-
}
|
|
2
|
+
// The Deck plugin CLI.
|
|
3
|
+
//
|
|
4
|
+
// deck-plugin build bundle src/ into plugin.js
|
|
5
|
+
// deck-plugin dev rebuild whenever a source changes, into the test Deck each time
|
|
6
|
+
// deck-plugin check validate plugin.json without building
|
|
7
|
+
// deck-plugin install build once and copy into your Deck
|
|
8
|
+
// deck-plugin publish check, tag, release on GitHub, and submit to Deck's registry
|
|
9
|
+
// (--dry-run: only check; see ../publish.mjs)
|
|
10
|
+
//
|
|
11
|
+
// --target test|main which Deck dev and install write to (defaults: dev -> test, install -> main)
|
|
12
|
+
//
|
|
13
|
+
// Run from a plugin's own directory.
|
|
14
|
+
import { watch } from "node:fs";
|
|
15
|
+
import { cpSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { buildPlugin } from "../build-plugin.mjs";
|
|
18
|
+
import { publish } from "../publish.mjs";
|
|
19
|
+
|
|
20
|
+
const argv = process.argv.slice(2);
|
|
21
|
+
const at = argv.indexOf("--target");
|
|
22
|
+
const targetFlag = at >= 0 ? argv[at + 1] : undefined;
|
|
23
|
+
const cmd = argv.find((a, i) => !a.startsWith("-") && !(at >= 0 && i === at + 1)) ?? "build";
|
|
24
|
+
const dir = process.cwd();
|
|
25
|
+
|
|
26
|
+
const die = (msg) => { console.error(msg); process.exit(1); };
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* What Deck will refuse at install time, refused here instead.
|
|
30
|
+
*
|
|
31
|
+
* Every rule is one Deck's loader or install_from_registry applies — the id charset especially,
|
|
32
|
+
* because the id becomes a config key and a path component. Finding out here costs a second;
|
|
33
|
+
* finding out from a failed install costs a round trip.
|
|
34
|
+
*/
|
|
35
|
+
function check() {
|
|
36
|
+
const p = join(dir, "plugin.json");
|
|
37
|
+
if (!existsSync(p)) die("no plugin.json here — is this a plugin directory?");
|
|
38
|
+
|
|
39
|
+
let manifest;
|
|
40
|
+
try { manifest = JSON.parse(readFileSync(p, "utf8")); }
|
|
41
|
+
catch (e) { die(`plugin.json is not valid JSON: ${e.message}`); }
|
|
42
|
+
|
|
43
|
+
const id = (manifest.id ?? "").trim();
|
|
44
|
+
if (!id) die("plugin.json has no id");
|
|
45
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9-]*$/.test(id)) {
|
|
46
|
+
die(`id "${id}" must be letters, digits and dashes, starting with a letter or digit`);
|
|
47
|
+
}
|
|
48
|
+
if (!existsSync(join(dir, "src", "plugin.tsx"))) {
|
|
49
|
+
die("no src/plugin.tsx — that is the entry point");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Not fatal, but worth saying: the registry's plugin page renders a README, and a published
|
|
53
|
+
// plugin without one gets an empty page.
|
|
54
|
+
const warn = [];
|
|
55
|
+
if (!manifest.version) warn.push("no version — Settings shows one, and the registry orders by it");
|
|
56
|
+
if (!manifest.description) warn.push("no description — the row says only a name");
|
|
57
|
+
if (!existsSync(join(dir, "README.md"))) warn.push("no README.md — publishing needs one");
|
|
58
|
+
|
|
59
|
+
console.log(`${manifest.name ?? id} ${manifest.version ?? ""}`.trim());
|
|
60
|
+
for (const w of warn) console.log(` note: ${w}`);
|
|
61
|
+
return manifest;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The plugins folder of one of the two Decks on this machine.
|
|
66
|
+
*
|
|
67
|
+
* "test" is the test Deck (`deck.exe --test`, or Settings › Plugins › Open a test Deck), which keeps
|
|
68
|
+
* its own folder, Deck-Dev, apart from your real data. `dev` always writes there, so a plugin you
|
|
69
|
+
* are still writing never lands in your real Deck, even when the test Deck is closed. `install`
|
|
70
|
+
* writes to your real Deck ("main"), for when the plugin is done.
|
|
71
|
+
*/
|
|
72
|
+
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
|
+
const target = targetFlag ?? defaultTarget;
|
|
76
|
+
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") };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Copy the built plugin into a Deck's plugins folder. Deck watches it and reloads the plugin. */
|
|
81
|
+
function copyInto(manifest, defaultTarget) {
|
|
82
|
+
const { target, dir: plugins } = pluginsDir(defaultTarget);
|
|
83
|
+
const dest = join(plugins, manifest.id);
|
|
84
|
+
mkdirSync(dest, { recursive: true });
|
|
85
|
+
for (const f of ["plugin.json", "plugin.js", "mcp.js"]) {
|
|
86
|
+
if (existsSync(join(dir, f))) cpSync(join(dir, f), join(dest, f));
|
|
87
|
+
}
|
|
88
|
+
return { target, dest };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Whether the test Deck is running: it answers on its own port. */
|
|
92
|
+
async function testDeckRunning() {
|
|
93
|
+
try { return (await fetch("http://127.0.0.1:8791/health", { signal: AbortSignal.timeout(800) })).ok; }
|
|
94
|
+
catch { return false; }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
switch (cmd) {
|
|
98
|
+
case "check": {
|
|
99
|
+
check();
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
case "build": {
|
|
104
|
+
const manifest = check();
|
|
105
|
+
await buildPlugin(dir);
|
|
106
|
+
console.log(`built ${manifest.id} -> plugin.js`);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
case "publish": {
|
|
111
|
+
const manifest = check();
|
|
112
|
+
await publish(dir, manifest, { dryRun: argv.includes("--dry-run"), buildPlugin });
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
case "install": {
|
|
117
|
+
const manifest = check();
|
|
118
|
+
await buildPlugin(dir);
|
|
119
|
+
const { target, dest } = copyInto(manifest, "main");
|
|
120
|
+
console.log(`installed ${manifest.id} into your ${target === "test" ? "test " : ""}Deck -> ${dest}`);
|
|
121
|
+
console.log("Deck watches that folder, so it is loaded already — no restart.");
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
case "dev": {
|
|
126
|
+
const manifest = check();
|
|
127
|
+
let building = false;
|
|
128
|
+
let again = false;
|
|
129
|
+
const once = async () => {
|
|
130
|
+
if (building) { again = true; return; }
|
|
131
|
+
building = true;
|
|
132
|
+
try {
|
|
133
|
+
await buildPlugin(dir);
|
|
134
|
+
// Into the test Deck after every build: it reloads the plugin when its folder changes, so
|
|
135
|
+
// a save shows there within a few seconds.
|
|
136
|
+
const { target, dest } = copyInto(manifest, "test");
|
|
137
|
+
console.log(`rebuilt at ${new Date().toLocaleTimeString()} -> ${target} Deck (${dest})`);
|
|
138
|
+
} catch (e) { console.error(e.message); }
|
|
139
|
+
building = false;
|
|
140
|
+
if (again) { again = false; void once(); }
|
|
141
|
+
};
|
|
142
|
+
if ((targetFlag ?? "test") === "test" && !(await testDeckRunning())) {
|
|
143
|
+
console.log("No test Deck is running, so you will not see changes yet. Start one from");
|
|
144
|
+
console.log("Settings › Plugins › Open a test Deck (or run deck.exe --test); it loads this plugin.");
|
|
145
|
+
}
|
|
146
|
+
await once();
|
|
147
|
+
// Debounced: an editor writing a file emits several events, and each would start a build.
|
|
148
|
+
let timer;
|
|
149
|
+
watch(join(dir, "src"), { recursive: true }, () => {
|
|
150
|
+
clearTimeout(timer);
|
|
151
|
+
timer = setTimeout(() => void once(), 120);
|
|
152
|
+
});
|
|
153
|
+
console.log("watching src/ — ctrl+c to stop");
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
default:
|
|
158
|
+
die(`unknown command "${cmd}" — try build, dev, check or install`);
|
|
159
|
+
}
|