@ericsanchezok/synergy-plugin-kit 2.2.1

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.
Files changed (53) hide show
  1. package/dist/cli.d.ts +2 -0
  2. package/dist/cli.js +25 -0
  3. package/dist/cmd.d.ts +6 -0
  4. package/dist/cmd.js +3 -0
  5. package/dist/commands/build.d.ts +6 -0
  6. package/dist/commands/build.js +194 -0
  7. package/dist/commands/create.d.ts +9 -0
  8. package/dist/commands/create.js +416 -0
  9. package/dist/commands/dev.d.ts +9 -0
  10. package/dist/commands/dev.js +192 -0
  11. package/dist/commands/entry.d.ts +19 -0
  12. package/dist/commands/entry.js +71 -0
  13. package/dist/commands/index.d.ts +9 -0
  14. package/dist/commands/index.js +9 -0
  15. package/dist/commands/pack.d.ts +8 -0
  16. package/dist/commands/pack.js +64 -0
  17. package/dist/commands/publish-market.d.ts +23 -0
  18. package/dist/commands/publish-market.js +224 -0
  19. package/dist/commands/sign.d.ts +10 -0
  20. package/dist/commands/sign.js +120 -0
  21. package/dist/commands/test.d.ts +5 -0
  22. package/dist/commands/test.js +40 -0
  23. package/dist/commands/validate.d.ts +10 -0
  24. package/dist/commands/validate.js +348 -0
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.js +3 -0
  27. package/dist/lib/capability.d.ts +2 -0
  28. package/dist/lib/capability.js +42 -0
  29. package/dist/lib/crypto.d.ts +5 -0
  30. package/dist/lib/crypto.js +27 -0
  31. package/dist/lib/hash.d.ts +3 -0
  32. package/dist/lib/hash.js +14 -0
  33. package/dist/lib/ids.d.ts +3 -0
  34. package/dist/lib/ids.js +7 -0
  35. package/dist/lib/market-entry.d.ts +57 -0
  36. package/dist/lib/market-entry.js +181 -0
  37. package/dist/lib/paths.d.ts +3 -0
  38. package/dist/lib/paths.js +5 -0
  39. package/dist/lib/risk.d.ts +2 -0
  40. package/dist/lib/risk.js +28 -0
  41. package/dist/lib/runtime-discovery.d.ts +12 -0
  42. package/dist/lib/runtime-discovery.js +13 -0
  43. package/dist/lib/runtime-mode.d.ts +9 -0
  44. package/dist/lib/runtime-mode.js +15 -0
  45. package/dist/lib/runtime-policy.d.ts +12 -0
  46. package/dist/lib/runtime-policy.js +62 -0
  47. package/dist/lib/signature.d.ts +15 -0
  48. package/dist/lib/signature.js +15 -0
  49. package/dist/lib/spec.d.ts +7 -0
  50. package/dist/lib/spec.js +21 -0
  51. package/dist/ui.d.ts +15 -0
  52. package/dist/ui.js +26 -0
  53. package/package.json +43 -0
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env bun
2
+ import yargs from "yargs";
3
+ import { hideBin } from "yargs/helpers";
4
+ import { PluginBuildCommand, PluginCreateCommand, PluginDevCommand, PluginEntryCommand, PluginPackCommand, PluginPublishMarketCommand, PluginSignCommand, PluginTestCommand, PluginValidateCommand, } from "./commands";
5
+ function assertBunRuntime() {
6
+ if (!process.versions.bun) {
7
+ throw new Error("synergy-plugin requires Bun. Install Bun from https://bun.sh and retry.");
8
+ }
9
+ }
10
+ assertBunRuntime();
11
+ await yargs(hideBin(process.argv))
12
+ .scriptName("synergy-plugin")
13
+ .command(PluginCreateCommand)
14
+ .command(PluginValidateCommand)
15
+ .command(PluginDevCommand)
16
+ .command(PluginBuildCommand)
17
+ .command(PluginPackCommand)
18
+ .command(PluginSignCommand)
19
+ .command(PluginTestCommand)
20
+ .command(PluginPublishMarketCommand)
21
+ .command(PluginEntryCommand)
22
+ .demandCommand(1)
23
+ .strict()
24
+ .help()
25
+ .parse();
package/dist/cmd.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { CommandModule } from "yargs";
2
+ type WithDoubleDash<T> = T & {
3
+ "--"?: string[];
4
+ };
5
+ export declare function cmd<T, U>(input: CommandModule<T, WithDoubleDash<U>>): CommandModule<T, WithDoubleDash<U>>;
6
+ export {};
package/dist/cmd.js ADDED
@@ -0,0 +1,3 @@
1
+ export function cmd(input) {
2
+ return input;
3
+ }
@@ -0,0 +1,6 @@
1
+ export declare function buildPluginProject(pluginDir: string): Promise<boolean>;
2
+ export declare const PluginBuildCommand: import("yargs").CommandModule<{}, {
3
+ path: string | undefined;
4
+ } & {
5
+ "--"?: string[];
6
+ }>;
@@ -0,0 +1,194 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import { EOL } from "os";
4
+ import { PluginManifest } from "@ericsanchezok/synergy-plugin";
5
+ import { cmd } from "../cmd";
6
+ import { UI } from "../ui";
7
+ import { sha256File, sha256JSON } from "../lib/crypto";
8
+ function ensureDir(dirPath) {
9
+ fs.mkdirSync(dirPath, { recursive: true });
10
+ }
11
+ function copyDir(src, dest) {
12
+ ensureDir(dest);
13
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
14
+ const srcPath = path.join(src, entry.name);
15
+ const destPath = path.join(dest, entry.name);
16
+ if (entry.isDirectory())
17
+ copyDir(srcPath, destPath);
18
+ else
19
+ fs.copyFileSync(srcPath, destPath);
20
+ }
21
+ }
22
+ function copyFilePreserve(pluginDir, distDir, relativePath) {
23
+ const normalized = relativePath.replace(/^\.\//, "");
24
+ const src = path.resolve(pluginDir, normalized);
25
+ if (!fs.existsSync(src) || !fs.statSync(src).isFile())
26
+ return;
27
+ const dest = path.join(distDir, normalized);
28
+ ensureDir(path.dirname(dest));
29
+ fs.copyFileSync(src, dest);
30
+ }
31
+ function findUiSource(pluginDir) {
32
+ const candidates = ["src/ui.tsx", "src/ui/index.tsx", "src/ui.ts", "src/ui/index.ts"];
33
+ return candidates.map((candidate) => path.join(pluginDir, candidate)).find((candidate) => fs.existsSync(candidate));
34
+ }
35
+ function packagedManifest(manifest) {
36
+ const next = structuredClone(manifest);
37
+ next.main = "./runtime/index.js";
38
+ if (next.contributes?.ui?.entry) {
39
+ next.contributes.ui.entry = next.contributes.ui.entry.replace(/^\.\//, "").replace(/^dist\//, "./");
40
+ if (!next.contributes.ui.entry.startsWith("."))
41
+ next.contributes.ui.entry = `./${next.contributes.ui.entry}`;
42
+ }
43
+ return next;
44
+ }
45
+ function permissionSummary(manifest) {
46
+ const perms = manifest.permissions ?? {};
47
+ const result = {};
48
+ if (perms.tools)
49
+ result.tools = perms.tools;
50
+ if (perms.data)
51
+ result.data = perms.data;
52
+ if (perms.network)
53
+ result.network = perms.network;
54
+ if (perms.ui)
55
+ result.ui = perms.ui;
56
+ if (perms.hooks)
57
+ result.hooks = perms.hooks;
58
+ const tools = manifest.contributes?.tools ?? [];
59
+ if (tools.length > 0) {
60
+ const toolPerms = {};
61
+ for (const tool of tools) {
62
+ if (tool.capabilities)
63
+ toolPerms[tool.name] = tool.capabilities;
64
+ }
65
+ if (Object.keys(toolPerms).length > 0)
66
+ result.contributedTools = toolPerms;
67
+ }
68
+ return result;
69
+ }
70
+ export async function buildPluginProject(pluginDir) {
71
+ const manifestPath = path.join(pluginDir, "plugin.json");
72
+ if (!fs.existsSync(manifestPath)) {
73
+ UI.error(`No plugin.json found at ${manifestPath}`);
74
+ return false;
75
+ }
76
+ let manifest;
77
+ const raw = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
78
+ const parsed = PluginManifest.safeParse(raw);
79
+ if (!parsed.success) {
80
+ UI.error("Invalid plugin manifest:");
81
+ for (const issue of parsed.error.issues) {
82
+ UI.println(` ${UI.Style.TEXT_DIM}${issue.path.join(".")}:${UI.Style.TEXT_NORMAL} ${issue.message}`);
83
+ }
84
+ return false;
85
+ }
86
+ manifest = parsed.data;
87
+ const spinner = (message) => {
88
+ process.stderr.write(`${UI.Style.TEXT_DIM} ${message}...${UI.Style.TEXT_NORMAL}${EOL}`);
89
+ };
90
+ UI.println(`${UI.Style.TEXT_NORMAL_BOLD}Building${UI.Style.TEXT_NORMAL} ${manifest.name} v${manifest.version}`);
91
+ UI.println(` ${UI.Style.TEXT_DIM}Source:${UI.Style.TEXT_NORMAL} ${pluginDir}`);
92
+ const distDir = path.join(pluginDir, "dist");
93
+ fs.rmSync(distDir, { recursive: true, force: true });
94
+ ensureDir(distDir);
95
+ const entryFile = manifest.main ?? "./src/index.ts";
96
+ const entryPath = path.resolve(pluginDir, entryFile);
97
+ const runtimeOutdir = path.join(distDir, "runtime");
98
+ spinner("Building backend");
99
+ const backendResult = await Bun.build({
100
+ entrypoints: [entryPath],
101
+ outdir: runtimeOutdir,
102
+ target: "bun",
103
+ naming: "index.js",
104
+ external: ["@ericsanchezok/synergy-plugin", "@ericsanchezok/synergy-sdk", "@ericsanchezok/synergy-util"],
105
+ });
106
+ if (!backendResult.success) {
107
+ for (const log of backendResult.logs) {
108
+ UI.println(` ${UI.Style.TEXT_WARNING}${log.message}${UI.Style.TEXT_NORMAL}`);
109
+ }
110
+ UI.error("Backend build failed");
111
+ return false;
112
+ }
113
+ const uiEntry = manifest.contributes?.ui?.entry;
114
+ if (uiEntry) {
115
+ const uiSourcePath = findUiSource(pluginDir);
116
+ const uiOutputPath = path.resolve(pluginDir, uiEntry);
117
+ if (uiSourcePath) {
118
+ const uiOutdir = path.dirname(uiOutputPath);
119
+ spinner("Building frontend");
120
+ const frontendResult = await Bun.build({
121
+ entrypoints: [uiSourcePath],
122
+ outdir: uiOutdir,
123
+ target: "browser",
124
+ naming: path.basename(uiOutputPath),
125
+ });
126
+ if (!frontendResult.success) {
127
+ for (const log of frontendResult.logs) {
128
+ UI.println(` ${UI.Style.TEXT_WARNING}${log.message}${UI.Style.TEXT_NORMAL}`);
129
+ }
130
+ UI.error("Frontend build failed");
131
+ return false;
132
+ }
133
+ }
134
+ else if (!fs.existsSync(uiOutputPath)) {
135
+ UI.error(`UI source not found. Expected one of src/ui.tsx or src/ui/index.tsx for ${uiEntry}`);
136
+ return false;
137
+ }
138
+ }
139
+ spinner("Normalizing manifest");
140
+ const distManifest = packagedManifest(manifest);
141
+ const distManifestPath = path.join(distDir, "plugin.json");
142
+ fs.writeFileSync(distManifestPath, JSON.stringify(distManifest, null, 2));
143
+ const normalizedPath = path.join(distDir, "plugin.normalized.json");
144
+ fs.writeFileSync(normalizedPath, JSON.stringify(distManifest, null, 2));
145
+ const packageJsonPath = path.join(pluginDir, "package.json");
146
+ if (fs.existsSync(packageJsonPath)) {
147
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
148
+ pkg.main = "./runtime/index.js";
149
+ pkg.exports = { ".": "./runtime/index.js" };
150
+ fs.writeFileSync(path.join(distDir, "package.json"), JSON.stringify(pkg, null, 2));
151
+ }
152
+ spinner("Generating permission summary");
153
+ const summary = permissionSummary(manifest);
154
+ fs.writeFileSync(path.join(distDir, "permissions.summary.json"), JSON.stringify(summary, null, 2));
155
+ const publicAssetsPath = path.join(pluginDir, "public", "assets");
156
+ if (fs.existsSync(publicAssetsPath)) {
157
+ spinner("Copying assets");
158
+ copyDir(publicAssetsPath, path.join(distDir, "assets"));
159
+ }
160
+ for (const theme of manifest.contributes?.ui?.themes ?? [])
161
+ copyFilePreserve(pluginDir, distDir, theme.path);
162
+ for (const icon of manifest.contributes?.ui?.icons ?? [])
163
+ copyFilePreserve(pluginDir, distDir, icon.path);
164
+ spinner("Computing integrity hashes");
165
+ const integrity = {
166
+ manifest: sha256File(distManifestPath),
167
+ permissions: sha256JSON(summary),
168
+ };
169
+ const runtimeIndex = path.join(runtimeOutdir, "index.js");
170
+ if (fs.existsSync(runtimeIndex))
171
+ integrity.runtime = sha256File(runtimeIndex);
172
+ if (uiEntry) {
173
+ const uiIndex = path.resolve(pluginDir, uiEntry);
174
+ if (fs.existsSync(uiIndex))
175
+ integrity.ui = sha256File(uiIndex);
176
+ }
177
+ fs.writeFileSync(path.join(distDir, "integrity.json"), JSON.stringify(integrity, null, 2));
178
+ UI.println(`${UI.Style.TEXT_SUCCESS}✔${UI.Style.TEXT_NORMAL} Built ${manifest.name} v${manifest.version} -> ${distDir}`);
179
+ UI.println(` ${UI.Style.TEXT_DIM}Output:${UI.Style.TEXT_NORMAL} ${distDir}`);
180
+ return true;
181
+ }
182
+ export const PluginBuildCommand = cmd({
183
+ command: "build [path]",
184
+ describe: "build a plugin for distribution",
185
+ builder: (yargs) => yargs.positional("path", {
186
+ type: "string",
187
+ describe: "path to plugin directory (defaults to cwd)",
188
+ }),
189
+ async handler(args) {
190
+ const ok = await buildPluginProject(path.resolve(args.path ?? process.cwd()));
191
+ if (!ok)
192
+ process.exitCode = 1;
193
+ },
194
+ });
@@ -0,0 +1,9 @@
1
+ type TemplateName = "tool-ui" | "workspace-panel" | "api-connector" | "theme-icon";
2
+ export declare const PluginCreateCommand: import("yargs").CommandModule<{}, {
3
+ name: string;
4
+ } & {
5
+ template: TemplateName;
6
+ } & {
7
+ "--"?: string[];
8
+ }>;
9
+ export {};
@@ -0,0 +1,416 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import { EOL } from "os";
4
+ import { cmd } from "../cmd";
5
+ import { UI } from "../ui";
6
+ const TEMPLATES = ["tool-ui", "workspace-panel", "api-connector", "theme-icon"];
7
+ function pluginJson(name, extra) {
8
+ const base = {
9
+ name,
10
+ version: "0.1.0",
11
+ description: `${name} plugin`,
12
+ permissions: {},
13
+ contributes: {},
14
+ };
15
+ return JSON.stringify({ ...base, ...extra(name) }, null, 2) + EOL;
16
+ }
17
+ function packageJson(name, templateName) {
18
+ const needsSolid = templateName !== "theme-icon";
19
+ const pkg = {
20
+ name,
21
+ version: "0.1.0",
22
+ type: "module",
23
+ scripts: {
24
+ dev: "synergy-plugin dev",
25
+ validate: "synergy-plugin validate --runtime-discovery",
26
+ build: "synergy-plugin build",
27
+ pack: "synergy-plugin pack",
28
+ sign: "synergy-plugin sign",
29
+ "publish:market": "synergy-plugin publish-market",
30
+ test: "synergy-plugin test",
31
+ },
32
+ dependencies: {
33
+ "@ericsanchezok/synergy-plugin": "latest",
34
+ zod: "^4.0.0",
35
+ ...(needsSolid ? { "solid-js": "^1.9.0" } : {}),
36
+ },
37
+ devDependencies: {
38
+ "@ericsanchezok/synergy-plugin-kit": "latest",
39
+ typescript: "^5.8.0",
40
+ },
41
+ };
42
+ return JSON.stringify(pkg, null, 2) + EOL;
43
+ }
44
+ function tsconfigJson() {
45
+ return (JSON.stringify({
46
+ compilerOptions: {
47
+ target: "ESNext",
48
+ module: "ESNext",
49
+ moduleResolution: "bundler",
50
+ strict: true,
51
+ esModuleInterop: true,
52
+ skipLibCheck: true,
53
+ outDir: "dist",
54
+ rootDir: "src",
55
+ jsx: "preserve",
56
+ jsxImportSource: "solid-js",
57
+ },
58
+ include: ["src"],
59
+ }, null, 2) + EOL);
60
+ }
61
+ function readmeMd(name) {
62
+ return `# ${name}
63
+
64
+ Synergy plugin generated with \`synergy-plugin create\`.
65
+
66
+ ## Commands
67
+
68
+ \`\`\`bash
69
+ bun install
70
+ bun run validate
71
+ bun run build
72
+ bun run pack
73
+ bun run sign ${name}-0.1.0.synergy-plugin.tgz
74
+ bun run publish:market
75
+ \`\`\`
76
+ `;
77
+ }
78
+ function indexToolUI(name) {
79
+ return `import type { PluginDescriptor, PluginInput, PluginHooks } from "@ericsanchezok/synergy-plugin"
80
+ import { greet } from "./tools"
81
+
82
+ export const plugin: PluginDescriptor = {
83
+ id: "${name}",
84
+ name: "${name}",
85
+ async init(_input: PluginInput): Promise<PluginHooks> {
86
+ return {
87
+ tool: {
88
+ greet,
89
+ },
90
+ }
91
+ },
92
+ }
93
+
94
+ export default plugin
95
+ `;
96
+ }
97
+ function indexWorkspacePanel(name) {
98
+ return `import type { PluginDescriptor, PluginInput, PluginHooks } from "@ericsanchezok/synergy-plugin"
99
+
100
+ export const plugin: PluginDescriptor = {
101
+ id: "${name}",
102
+ name: "${name}",
103
+ async init(_input: PluginInput): Promise<PluginHooks> {
104
+ return {}
105
+ },
106
+ }
107
+
108
+ export default plugin
109
+ `;
110
+ }
111
+ function indexApiConnector(name) {
112
+ return `import type { PluginDescriptor, PluginInput, PluginHooks } from "@ericsanchezok/synergy-plugin"
113
+ import { getJSON, postJSON } from "./tools"
114
+
115
+ export const plugin: PluginDescriptor = {
116
+ id: "${name}",
117
+ name: "${name}",
118
+ async init(_input: PluginInput): Promise<PluginHooks> {
119
+ return {
120
+ tool: {
121
+ getJSON,
122
+ postJSON,
123
+ },
124
+ }
125
+ },
126
+ }
127
+
128
+ export default plugin
129
+ `;
130
+ }
131
+ function indexThemeIcon(name) {
132
+ return `import type { PluginDescriptor, PluginInput, PluginHooks } from "@ericsanchezok/synergy-plugin"
133
+
134
+ export const plugin: PluginDescriptor = {
135
+ id: "${name}",
136
+ name: "${name}",
137
+ async init(_input: PluginInput): Promise<PluginHooks> {
138
+ return {}
139
+ },
140
+ }
141
+
142
+ export default plugin
143
+ `;
144
+ }
145
+ function toolsToolUI(_name) {
146
+ return `import { tool } from "@ericsanchezok/synergy-plugin/tool"
147
+
148
+ export const greet = tool({
149
+ description: "Greet a user by name",
150
+ args: {
151
+ name: tool.schema.string().describe("The name to greet"),
152
+ },
153
+ async execute(args) {
154
+ return { output: \`Hello, \${args.name}!\` }
155
+ },
156
+ })
157
+ `;
158
+ }
159
+ function toolsApiConnector(_name) {
160
+ return `import { tool } from "@ericsanchezok/synergy-plugin/tool"
161
+
162
+ export const getJSON = tool({
163
+ description: "Fetch and parse JSON from an API endpoint",
164
+ args: {
165
+ url: tool.schema.string().describe("The API endpoint URL"),
166
+ },
167
+ async execute(args) {
168
+ const res = await fetch(args.url)
169
+ const json = await res.json()
170
+ return { output: JSON.stringify(json, null, 2) }
171
+ },
172
+ })
173
+
174
+ export const postJSON = tool({
175
+ description: "POST JSON to an API endpoint",
176
+ args: {
177
+ url: tool.schema.string().describe("The API endpoint URL"),
178
+ body: tool.schema.string().describe("The JSON request body"),
179
+ },
180
+ async execute(args) {
181
+ const res = await fetch(args.url, {
182
+ method: "POST",
183
+ headers: { "Content-Type": "application/json" },
184
+ body: args.body,
185
+ })
186
+ const text = await res.text()
187
+ return { output: text }
188
+ },
189
+ })
190
+ `;
191
+ }
192
+ function uiToolUI(_name) {
193
+ return `import type { Component } from "solid-js"
194
+
195
+ interface Props {
196
+ tool: string
197
+ output: string
198
+ metadata?: Record<string, unknown>
199
+ }
200
+
201
+ const ToolRenderer: Component<Props> = (props) => {
202
+ return <div>{props.output}</div>
203
+ }
204
+
205
+ export default ToolRenderer
206
+ `;
207
+ }
208
+ function uiWorkspacePanel(_name) {
209
+ return `import type { Component } from "solid-js"
210
+
211
+ const WorkspacePanel: Component = () => {
212
+ return <div>Workspace panel content</div>
213
+ }
214
+
215
+ export default WorkspacePanel
216
+ `;
217
+ }
218
+ function manifestToolUI(name) {
219
+ return {
220
+ permissions: {
221
+ tools: {
222
+ invoke: true,
223
+ shell: false,
224
+ filesystem: "none",
225
+ network: false,
226
+ mcp: "none",
227
+ },
228
+ },
229
+ contributes: {
230
+ tools: [
231
+ {
232
+ name: "greet",
233
+ title: "Greet",
234
+ description: "Greet a user by name",
235
+ capabilities: {
236
+ filesystem: "none",
237
+ network: false,
238
+ shell: false,
239
+ },
240
+ },
241
+ ],
242
+ ui: {
243
+ entry: "./dist/ui/index.js",
244
+ toolRenderers: [{ tool: "greet" }],
245
+ },
246
+ },
247
+ };
248
+ }
249
+ function manifestWorkspacePanel(name) {
250
+ return {
251
+ contributes: {
252
+ ui: {
253
+ entry: "./dist/ui/index.js",
254
+ workspacePanels: [
255
+ {
256
+ id: `${name}-panel`,
257
+ label: name,
258
+ icon: "layout-panel-left",
259
+ },
260
+ ],
261
+ },
262
+ },
263
+ };
264
+ }
265
+ function manifestApiConnector(_name) {
266
+ return {
267
+ permissions: {
268
+ tools: {
269
+ invoke: true,
270
+ network: true,
271
+ shell: false,
272
+ filesystem: "none",
273
+ mcp: "none",
274
+ },
275
+ network: {
276
+ connectDomains: ["*"],
277
+ },
278
+ },
279
+ contributes: {
280
+ tools: [
281
+ {
282
+ name: "getJSON",
283
+ title: "Get JSON",
284
+ description: "Fetch and parse JSON from an API endpoint",
285
+ capabilities: {
286
+ network: true,
287
+ filesystem: "none",
288
+ shell: false,
289
+ },
290
+ },
291
+ {
292
+ name: "postJSON",
293
+ title: "Post JSON",
294
+ description: "POST JSON to an API endpoint",
295
+ capabilities: {
296
+ network: true,
297
+ filesystem: "none",
298
+ shell: false,
299
+ },
300
+ },
301
+ ],
302
+ ui: {
303
+ entry: "./dist/ui/index.js",
304
+ toolRenderers: [{ tool: "getJSON" }, { tool: "postJSON" }],
305
+ },
306
+ },
307
+ };
308
+ }
309
+ function manifestThemeIcon(name) {
310
+ return {
311
+ contributes: {
312
+ ui: {
313
+ themes: [{ id: `${name}-theme`, label: name, path: "./themes/default.css" }],
314
+ icons: [{ name: `${name}-logo`, path: "./icons/logo.svg" }],
315
+ },
316
+ },
317
+ };
318
+ }
319
+ const TEMPLATE_DEFS = {
320
+ "tool-ui": {
321
+ label: "Tool UI - tool definitions with Solid tool renderer",
322
+ manifest: manifestToolUI,
323
+ files: [
324
+ { relativePath: "src/index.ts", content: indexToolUI },
325
+ { relativePath: "src/tools.ts", content: toolsToolUI },
326
+ { relativePath: "src/ui.tsx", content: uiToolUI },
327
+ ],
328
+ },
329
+ "workspace-panel": {
330
+ label: "Workspace Panel - Solid workspace panel with no tools",
331
+ manifest: manifestWorkspacePanel,
332
+ files: [
333
+ { relativePath: "src/index.ts", content: indexWorkspacePanel },
334
+ { relativePath: "src/ui.tsx", content: uiWorkspacePanel },
335
+ ],
336
+ },
337
+ "api-connector": {
338
+ label: "API Connector - network-enabled tools for API integration",
339
+ manifest: manifestApiConnector,
340
+ files: [
341
+ { relativePath: "src/index.ts", content: indexApiConnector },
342
+ { relativePath: "src/tools.ts", content: toolsApiConnector },
343
+ { relativePath: "src/ui.tsx", content: uiToolUI },
344
+ ],
345
+ },
346
+ "theme-icon": {
347
+ label: "Theme & Icon - themes and icon contributions",
348
+ manifest: manifestThemeIcon,
349
+ files: [
350
+ { relativePath: "src/index.ts", content: indexThemeIcon },
351
+ { relativePath: "themes/default.css", content: () => ":root { --plugin-accent: #2563eb; }\n" },
352
+ {
353
+ relativePath: "icons/logo.svg",
354
+ content: () => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="currentColor"/></svg>\n',
355
+ },
356
+ ],
357
+ },
358
+ };
359
+ function scaffold(name, templateName, template, targetDir) {
360
+ const created = [];
361
+ fs.mkdirSync(targetDir, { recursive: true });
362
+ const files = [
363
+ { relativePath: "plugin.json", content: (pluginName) => pluginJson(pluginName, template.manifest) },
364
+ { relativePath: "package.json", content: (pluginName) => packageJson(pluginName, templateName) },
365
+ { relativePath: "tsconfig.json", content: tsconfigJson },
366
+ { relativePath: "README.md", content: readmeMd },
367
+ ...template.files,
368
+ ];
369
+ for (const file of files) {
370
+ const filePath = path.join(targetDir, file.relativePath);
371
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
372
+ fs.writeFileSync(filePath, file.content(name));
373
+ created.push(filePath);
374
+ }
375
+ return created;
376
+ }
377
+ export const PluginCreateCommand = cmd({
378
+ command: "create <name>",
379
+ describe: "scaffold a new Synergy plugin project",
380
+ builder: (yargs) => yargs
381
+ .positional("name", {
382
+ type: "string",
383
+ describe: "plugin name (used as directory name and plugin id)",
384
+ demandOption: true,
385
+ })
386
+ .option("template", {
387
+ type: "string",
388
+ describe: "template to scaffold",
389
+ choices: [...TEMPLATES],
390
+ default: "tool-ui",
391
+ }),
392
+ async handler(args) {
393
+ const name = args.name;
394
+ const templateName = args.template ?? "tool-ui";
395
+ const template = TEMPLATE_DEFS[templateName];
396
+ const targetDir = path.resolve(process.cwd(), name);
397
+ if (!/^[a-z0-9][-a-z0-9]*$/.test(name)) {
398
+ UI.error(`Invalid plugin name "${name}". Use lowercase letters, digits, and hyphens.`);
399
+ process.exitCode = 1;
400
+ return;
401
+ }
402
+ if (fs.existsSync(targetDir)) {
403
+ UI.error(`Directory "${targetDir}" already exists. Remove it first or use a different name.`);
404
+ process.exitCode = 1;
405
+ return;
406
+ }
407
+ const created = scaffold(name, templateName, template, targetDir);
408
+ UI.println(`${UI.Style.TEXT_SUCCESS}✔${UI.Style.TEXT_NORMAL} Created plugin "${name}" (${template.label})`);
409
+ UI.println();
410
+ for (const filePath of created) {
411
+ UI.println(` ${UI.Style.TEXT_DIM}${path.relative(process.cwd(), filePath)}${UI.Style.TEXT_NORMAL}`);
412
+ }
413
+ UI.println();
414
+ UI.println(`${UI.Style.TEXT_DIM}Next: cd ${name} && bun install && bun run validate${UI.Style.TEXT_NORMAL}`);
415
+ },
416
+ });
@@ -0,0 +1,9 @@
1
+ export declare const PluginDevCommand: import("yargs").CommandModule<{}, {
2
+ path: string | undefined;
3
+ } & {
4
+ "sandbox-preview": boolean;
5
+ } & {
6
+ port: number;
7
+ } & {
8
+ "--"?: string[];
9
+ }>;