create-pragma-plugin 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ # create-pragma-plugin
2
+
3
+ Scaffold a Pragma plugin. Part of [Pragma](https://github.com/pragma-sh/pragma) — a desktop workspace for
4
+ running persistent, worktree-scoped coding agents.
5
+
6
+ Generates a self-contained Vite project for a pure-TypeScript Pragma plugin:
7
+ a single ESM bundle, React aliased to the host's instance, and a test setup.
8
+
9
+ ```sh
10
+ bun create pragma-plugin my-plugin
11
+ # or: npm create pragma-plugin my-plugin
12
+ ```
13
+
14
+ Capabilities: `ui` (sidebar tab), `commands`, `agents`. Pass
15
+ `--capabilities ui,commands` to skip the prompt.
16
+
17
+ Docs: <https://pragma-app.sh/docs/plugins/getting-started>
18
+
19
+ ## License
20
+
21
+ AGPL-3.0-only. See [LICENSE](https://github.com/pragma-sh/pragma/blob/main/LICENSE).
package/dist/cli.cjs ADDED
@@ -0,0 +1,418 @@
1
+ #!/usr/bin/env node
2
+ var import_node_module = require("node:module");
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toCommonJS = (from) => {
11
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
12
+ if (entry)
13
+ return entry;
14
+ entry = __defProp({}, "__esModule", { value: true });
15
+ if (from && typeof from === "object" || typeof from === "function") {
16
+ for (var key of __getOwnPropNames(from))
17
+ if (!__hasOwnProp.call(entry, key))
18
+ __defProp(entry, key, {
19
+ get: __accessProp.bind(from, key),
20
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
21
+ });
22
+ }
23
+ __moduleCache.set(from, entry);
24
+ return entry;
25
+ };
26
+ var __moduleCache;
27
+ var __returnValue = (v) => v;
28
+ function __exportSetter(name, newValue) {
29
+ this[name] = __returnValue.bind(null, newValue);
30
+ }
31
+ var __export = (target, all) => {
32
+ for (var name in all)
33
+ __defProp(target, name, {
34
+ get: all[name],
35
+ enumerable: true,
36
+ configurable: true,
37
+ set: __exportSetter.bind(all, name)
38
+ });
39
+ };
40
+
41
+ // src/cli.ts
42
+ var import_promises2 = require("node:readline/promises");
43
+ var import_node_process = require("node:process");
44
+
45
+ // src/scaffold.ts
46
+ var import_promises = require("node:fs/promises");
47
+ var import_node_path2 = require("node:path");
48
+
49
+ // src/names.ts
50
+ function normalizePluginName(input) {
51
+ const name = input.trim().replace(/^@/, "").replaceAll(/[^a-zA-Z0-9._~-]+/g, "-").replaceAll(/^-+|-+$/g, "").toLowerCase();
52
+ return name || "pragma-plugin";
53
+ }
54
+
55
+ // src/package-manager.ts
56
+ var import_node_fs = require("node:fs");
57
+ var import_node_path = require("node:path");
58
+ var LOCKFILES = [
59
+ ["bun", ["bun.lock", "bun.lockb"]],
60
+ ["pnpm", ["pnpm-lock.yaml"]],
61
+ ["yarn", ["yarn.lock"]],
62
+ ["npm", ["package-lock.json"]]
63
+ ];
64
+ function detectPackageManager(startDir = process.cwd()) {
65
+ let current = import_node_path.resolve(startDir);
66
+ const root = import_node_path.parse(current).root;
67
+ while (true) {
68
+ for (const [manager, files] of LOCKFILES) {
69
+ if (files.some((file) => import_node_fs.existsSync(import_node_path.join(current, file)))) {
70
+ return manager;
71
+ }
72
+ }
73
+ if (current === root) {
74
+ return "bun";
75
+ }
76
+ current = import_node_path.dirname(current);
77
+ }
78
+ }
79
+ // package.json
80
+ var version = "0.1.0";
81
+
82
+ // src/templates.ts
83
+ function pluginTemplate(input) {
84
+ return [
85
+ { path: "package.json", contents: packageJson(input) },
86
+ { path: "tsconfig.json", contents: tsconfigJson() },
87
+ { path: "vite.config.ts", contents: viteConfig() },
88
+ { path: "README.md", contents: readme(input) },
89
+ { path: "src/index.tsx", contents: source(input) },
90
+ { path: "src/index.test.ts", contents: testSource() }
91
+ ];
92
+ }
93
+ function packageJson(input) {
94
+ return `${JSON.stringify({
95
+ name: input.packageName,
96
+ version: "0.0.0",
97
+ private: true,
98
+ type: "module",
99
+ main: "./dist/index.js",
100
+ scripts: {
101
+ build: "vite build",
102
+ dev: "vite build --watch",
103
+ typecheck: "tsc --noEmit",
104
+ test: "vitest run"
105
+ },
106
+ dependencies: {
107
+ "@pragma-sh/plugin": `^${version}`
108
+ },
109
+ devDependencies: {
110
+ "@types/react": "^19.2.14",
111
+ "@types/react-dom": "^19.2.3",
112
+ "@vitejs/plugin-react": "^6.0.2",
113
+ typescript: "^6.0.3",
114
+ vite: "^8.0.16",
115
+ vitest: "^4.1.8",
116
+ react: "^19.2.6",
117
+ "react-dom": "^19.2.6"
118
+ }
119
+ }, null, 2)}
120
+ `;
121
+ }
122
+ function tsconfigJson() {
123
+ return `${JSON.stringify({
124
+ compilerOptions: {
125
+ target: "ES2022",
126
+ lib: ["ES2022", "DOM", "DOM.Iterable"],
127
+ module: "ESNext",
128
+ moduleResolution: "Bundler",
129
+ jsx: "react-jsx",
130
+ jsxImportSource: "react",
131
+ strict: true,
132
+ noEmit: true,
133
+ skipLibCheck: true
134
+ },
135
+ include: ["src"]
136
+ }, null, 2)}
137
+ `;
138
+ }
139
+ function viteConfig() {
140
+ return `import react from "@vitejs/plugin-react";
141
+ import { defineConfig } from "vite";
142
+
143
+ export default defineConfig({
144
+ plugins: [react()],
145
+ resolve: {
146
+ alias: {
147
+ "react/jsx-runtime": "@pragma-sh/plugin/jsx-runtime",
148
+ "react-dom": "@pragma-sh/plugin/react-dom",
149
+ react: "@pragma-sh/plugin/react",
150
+ },
151
+ },
152
+ build: {
153
+ lib: {
154
+ entry: "src/index.tsx",
155
+ formats: ["es"],
156
+ fileName: "index",
157
+ },
158
+ rollupOptions: {
159
+ output: {
160
+ inlineDynamicImports: true,
161
+ },
162
+ },
163
+ },
164
+ });
165
+ `;
166
+ }
167
+ function source(input) {
168
+ const hasCommand = input.capabilities.includes("commands");
169
+ const commandImport = hasCommand ? ", defineCommand" : "";
170
+ const commandContribution = hasCommand ? `,
171
+ commands: [
172
+ defineCommand({
173
+ id: "${input.packageName}.hello",
174
+ title: "Show ${input.displayName} greeting",
175
+ run: (ctx) => ctx.notify("Hello from ${input.displayName}", { variant: "success" }),
176
+ }),
177
+ ]` : "";
178
+ return `import { definePlugin, defineSidebarTab, useProject${commandImport} } from "@pragma-sh/plugin";
179
+ import { Button, Kbd } from "@pragma-sh/plugin/ui";
180
+
181
+ function OverviewTab() {
182
+ const project = useProject();
183
+ return (
184
+ <div style={{ padding: 12 }}>
185
+ <h2>${input.displayName}</h2>
186
+ <p>Active project: {project?.name ?? "None"}</p>
187
+ <Button variant="secondary" size="sm">
188
+ Press <Kbd>⌘K</Kbd>
189
+ </Button>
190
+ </div>
191
+ );
192
+ }
193
+
194
+ export default definePlugin({
195
+ name: "${input.displayName}",
196
+ description: "A Pragma plugin scaffolded with create-pragma-plugin.",
197
+ ui: {
198
+ sidebarTabs: [
199
+ defineSidebarTab({
200
+ id: "overview",
201
+ title: "${input.displayName}",
202
+ component: OverviewTab,
203
+ }),
204
+ ],
205
+ }${commandContribution},
206
+ });
207
+ `;
208
+ }
209
+ function testSource() {
210
+ return `import { describe, expect, it } from "vitest";
211
+
212
+ import plugin from "./index";
213
+
214
+ describe("plugin", () => {
215
+ it("exports a stamped Pragma plugin", () => {
216
+ expect(plugin.__apiVersion).toBeTypeOf("string");
217
+ expect(plugin.name).toBeTypeOf("string");
218
+ });
219
+ });
220
+ `;
221
+ }
222
+ function readme(input) {
223
+ const install = input.packageManager === "npm" ? "npm install" : `${input.packageManager} install`;
224
+ const run = input.packageManager === "npm" ? "npm run" : `${input.packageManager} run`;
225
+ return `# ${input.displayName}
226
+
227
+ A Pragma plugin scaffolded with \`create-pragma-plugin\`.
228
+
229
+ ## Quick Start
230
+
231
+ \`\`\`bash
232
+ ${install}
233
+ ${run} build
234
+ \`\`\`
235
+
236
+ ## Load In Pragma
237
+
238
+ Add this to your project's \`.pragma/config.json\`:
239
+
240
+ \`\`\`json
241
+ {
242
+ "plugins": [{ "path": "./${input.directoryName}" }]
243
+ }
244
+ \`\`\`
245
+
246
+ Pragma loads local plugin code from this path. Only add plugins you trust.
247
+
248
+ ## Commands
249
+
250
+ \`\`\`bash
251
+ ${run} dev
252
+ ${run} typecheck
253
+ ${run} test
254
+ ${run} build
255
+ \`\`\`
256
+ `;
257
+ }
258
+
259
+ // src/scaffold.ts
260
+ async function scaffoldPlugin(options) {
261
+ const directory = import_node_path2.resolve(options.directory);
262
+ const packageName = normalizePluginName(options.name ?? import_node_path2.basename(directory));
263
+ const packageManager = options.packageManager ?? detectPackageManager(process.cwd());
264
+ const capabilities = options.capabilities?.length ? options.capabilities : ["ui"];
265
+ await assertWritableDirectory(directory, options.force === true);
266
+ const files = pluginTemplate({
267
+ packageName,
268
+ displayName: titleCase(packageName),
269
+ directoryName: import_node_path2.basename(directory),
270
+ packageManager,
271
+ capabilities
272
+ });
273
+ for (const file of files) {
274
+ const path = import_node_path2.join(directory, file.path);
275
+ await import_promises.mkdir(import_node_path2.join(path, ".."), { recursive: true });
276
+ await import_promises.writeFile(path, file.contents);
277
+ }
278
+ return { directory, packageName, packageManager, files: files.map((file) => file.path) };
279
+ }
280
+ async function assertWritableDirectory(directory, force) {
281
+ try {
282
+ const entries = await import_promises.readdir(directory);
283
+ if (!force && entries.length > 0) {
284
+ throw new Error(`destination is not empty: ${directory}`);
285
+ }
286
+ } catch (cause) {
287
+ if (cause instanceof Error && "code" in cause && cause.code === "ENOENT") {
288
+ await import_promises.mkdir(directory, { recursive: true });
289
+ return;
290
+ }
291
+ throw cause;
292
+ }
293
+ }
294
+ function titleCase(packageName) {
295
+ return packageName.split(/[._~-]+/g).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
296
+ }
297
+
298
+ // src/cli.ts
299
+ var CAPABILITIES = new Set(["ui", "commands", "agents"]);
300
+ var PACKAGE_MANAGERS = new Set(["bun", "npm", "pnpm", "yarn"]);
301
+ var HELP_FLAGS = new Set(["--help", "-h"]);
302
+ var VALUE_FLAG_HANDLERS = {
303
+ "--capabilities": (args, value) => {
304
+ args.capabilities = parseCapabilities(value);
305
+ },
306
+ "--name": (args, value) => {
307
+ args.name = value;
308
+ },
309
+ "--pm": (args, value) => {
310
+ args.packageManager = parsePackageManager(value);
311
+ }
312
+ };
313
+ async function main(argv) {
314
+ const options = await parseScaffoldOptions(argv);
315
+ if (!options) {
316
+ return;
317
+ }
318
+ const result = await scaffoldPlugin(options);
319
+ import_node_process.stdout.write(`Created ${result.packageName} in ${result.directory}
320
+ `);
321
+ import_node_process.stdout.write(`Next: ${result.packageManager} install && ${result.packageManager} run build
322
+ `);
323
+ }
324
+ async function parseScaffoldOptions(argv) {
325
+ const args = parseArgs(argv);
326
+ if (!args.directory) {
327
+ printUsage();
328
+ process.exitCode = 1;
329
+ return null;
330
+ }
331
+ return {
332
+ directory: args.directory,
333
+ ...optionalName(args.name),
334
+ packageManager: args.packageManager ?? detectPackageManager(),
335
+ capabilities: args.capabilities ?? await promptCapabilities(),
336
+ force: args.force
337
+ };
338
+ }
339
+ function optionalName(name) {
340
+ return name === undefined ? {} : { name };
341
+ }
342
+ function parseArgs(argv) {
343
+ const args = { directory: null, force: false, capabilities: null };
344
+ for (let index = 0;index < argv.length; ) {
345
+ index = parseArg(argv, args, index);
346
+ }
347
+ return args;
348
+ }
349
+ function parseArg(argv, args, index) {
350
+ const arg = argv[index];
351
+ return arg === undefined ? index + 1 : parsePresentArg(argv, args, index, arg);
352
+ }
353
+ function parsePresentArg(argv, args, index, arg) {
354
+ const valueHandler = VALUE_FLAG_HANDLERS[arg];
355
+ if (valueHandler) {
356
+ valueHandler(args, requireValue(argv, index + 1, arg));
357
+ return index + 2;
358
+ }
359
+ if (arg === "--force") {
360
+ args.force = true;
361
+ return index + 1;
362
+ }
363
+ if (HELP_FLAGS.has(arg)) {
364
+ printUsage();
365
+ process.exit(0);
366
+ }
367
+ return parseDirectoryArg(args, index, arg);
368
+ }
369
+ function parseDirectoryArg(args, index, arg) {
370
+ if (!args.directory) {
371
+ args.directory = arg;
372
+ return index + 1;
373
+ }
374
+ throw new Error(`unexpected argument: ${arg}`);
375
+ }
376
+ function requireValue(argv, index, flag) {
377
+ const value = argv[index];
378
+ if (!value) {
379
+ throw new Error(`${flag} requires a value`);
380
+ }
381
+ return value;
382
+ }
383
+ function parsePackageManager(value) {
384
+ if (PACKAGE_MANAGERS.has(value)) {
385
+ return value;
386
+ }
387
+ throw new Error(`unsupported package manager: ${value}`);
388
+ }
389
+ function parseCapabilities(value) {
390
+ const selected = value.split(",").map((part) => part.trim()).filter(Boolean);
391
+ for (const capability of selected) {
392
+ if (!CAPABILITIES.has(capability)) {
393
+ throw new Error(`unsupported capability: ${capability}`);
394
+ }
395
+ }
396
+ return selected;
397
+ }
398
+ async function promptCapabilities() {
399
+ if (!import_node_process.stdin.isTTY || !import_node_process.stdout.isTTY) {
400
+ return ["ui"];
401
+ }
402
+ const rl = import_promises2.createInterface({ input: import_node_process.stdin, output: import_node_process.stdout });
403
+ try {
404
+ const answer = await rl.question("Capabilities (comma-separated: ui, commands, agents) [ui,commands]: ");
405
+ return answer.trim() ? parseCapabilities(answer) : ["ui", "commands"];
406
+ } finally {
407
+ rl.close();
408
+ }
409
+ }
410
+ function printUsage() {
411
+ import_node_process.stdout.write(`Usage: create-pragma-plugin <directory> [--name <package-name>] [--pm bun|npm|pnpm|yarn] [--capabilities ui,commands,agents] [--force]
412
+ `);
413
+ }
414
+ main(process.argv.slice(2)).catch((cause) => {
415
+ const message = cause instanceof Error ? cause.message : String(cause);
416
+ console.error(message);
417
+ process.exitCode = 1;
418
+ });
package/dist/cli.js ADDED
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ detectPackageManager,
4
+ scaffoldPlugin
5
+ } from "./shared/chunk-8xnme2e1.js";
6
+
7
+ // src/cli.ts
8
+ import { createInterface } from "node:readline/promises";
9
+ import { stdin as input, stdout as output } from "node:process";
10
+ var CAPABILITIES = new Set(["ui", "commands", "agents"]);
11
+ var PACKAGE_MANAGERS = new Set(["bun", "npm", "pnpm", "yarn"]);
12
+ var HELP_FLAGS = new Set(["--help", "-h"]);
13
+ var VALUE_FLAG_HANDLERS = {
14
+ "--capabilities": (args, value) => {
15
+ args.capabilities = parseCapabilities(value);
16
+ },
17
+ "--name": (args, value) => {
18
+ args.name = value;
19
+ },
20
+ "--pm": (args, value) => {
21
+ args.packageManager = parsePackageManager(value);
22
+ }
23
+ };
24
+ async function main(argv) {
25
+ const options = await parseScaffoldOptions(argv);
26
+ if (!options) {
27
+ return;
28
+ }
29
+ const result = await scaffoldPlugin(options);
30
+ output.write(`Created ${result.packageName} in ${result.directory}
31
+ `);
32
+ output.write(`Next: ${result.packageManager} install && ${result.packageManager} run build
33
+ `);
34
+ }
35
+ async function parseScaffoldOptions(argv) {
36
+ const args = parseArgs(argv);
37
+ if (!args.directory) {
38
+ printUsage();
39
+ process.exitCode = 1;
40
+ return null;
41
+ }
42
+ return {
43
+ directory: args.directory,
44
+ ...optionalName(args.name),
45
+ packageManager: args.packageManager ?? detectPackageManager(),
46
+ capabilities: args.capabilities ?? await promptCapabilities(),
47
+ force: args.force
48
+ };
49
+ }
50
+ function optionalName(name) {
51
+ return name === undefined ? {} : { name };
52
+ }
53
+ function parseArgs(argv) {
54
+ const args = { directory: null, force: false, capabilities: null };
55
+ for (let index = 0;index < argv.length; ) {
56
+ index = parseArg(argv, args, index);
57
+ }
58
+ return args;
59
+ }
60
+ function parseArg(argv, args, index) {
61
+ const arg = argv[index];
62
+ return arg === undefined ? index + 1 : parsePresentArg(argv, args, index, arg);
63
+ }
64
+ function parsePresentArg(argv, args, index, arg) {
65
+ const valueHandler = VALUE_FLAG_HANDLERS[arg];
66
+ if (valueHandler) {
67
+ valueHandler(args, requireValue(argv, index + 1, arg));
68
+ return index + 2;
69
+ }
70
+ if (arg === "--force") {
71
+ args.force = true;
72
+ return index + 1;
73
+ }
74
+ if (HELP_FLAGS.has(arg)) {
75
+ printUsage();
76
+ process.exit(0);
77
+ }
78
+ return parseDirectoryArg(args, index, arg);
79
+ }
80
+ function parseDirectoryArg(args, index, arg) {
81
+ if (!args.directory) {
82
+ args.directory = arg;
83
+ return index + 1;
84
+ }
85
+ throw new Error(`unexpected argument: ${arg}`);
86
+ }
87
+ function requireValue(argv, index, flag) {
88
+ const value = argv[index];
89
+ if (!value) {
90
+ throw new Error(`${flag} requires a value`);
91
+ }
92
+ return value;
93
+ }
94
+ function parsePackageManager(value) {
95
+ if (PACKAGE_MANAGERS.has(value)) {
96
+ return value;
97
+ }
98
+ throw new Error(`unsupported package manager: ${value}`);
99
+ }
100
+ function parseCapabilities(value) {
101
+ const selected = value.split(",").map((part) => part.trim()).filter(Boolean);
102
+ for (const capability of selected) {
103
+ if (!CAPABILITIES.has(capability)) {
104
+ throw new Error(`unsupported capability: ${capability}`);
105
+ }
106
+ }
107
+ return selected;
108
+ }
109
+ async function promptCapabilities() {
110
+ if (!input.isTTY || !output.isTTY) {
111
+ return ["ui"];
112
+ }
113
+ const rl = createInterface({ input, output });
114
+ try {
115
+ const answer = await rl.question("Capabilities (comma-separated: ui, commands, agents) [ui,commands]: ");
116
+ return answer.trim() ? parseCapabilities(answer) : ["ui", "commands"];
117
+ } finally {
118
+ rl.close();
119
+ }
120
+ }
121
+ function printUsage() {
122
+ output.write(`Usage: create-pragma-plugin <directory> [--name <package-name>] [--pm bun|npm|pnpm|yarn] [--capabilities ui,commands,agents] [--force]
123
+ `);
124
+ }
125
+ main(process.argv.slice(2)).catch((cause) => {
126
+ const message = cause instanceof Error ? cause.message : String(cause);
127
+ console.error(message);
128
+ process.exitCode = 1;
129
+ });
package/dist/index.cjs ADDED
@@ -0,0 +1,299 @@
1
+ var import_node_module = require("node:module");
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ function __accessProp(key) {
7
+ return this[key];
8
+ }
9
+ var __toCommonJS = (from) => {
10
+ var entry = (__moduleCache ??= new WeakMap).get(from), desc;
11
+ if (entry)
12
+ return entry;
13
+ entry = __defProp({}, "__esModule", { value: true });
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (var key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(entry, key))
17
+ __defProp(entry, key, {
18
+ get: __accessProp.bind(from, key),
19
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
20
+ });
21
+ }
22
+ __moduleCache.set(from, entry);
23
+ return entry;
24
+ };
25
+ var __moduleCache;
26
+ var __returnValue = (v) => v;
27
+ function __exportSetter(name, newValue) {
28
+ this[name] = __returnValue.bind(null, newValue);
29
+ }
30
+ var __export = (target, all) => {
31
+ for (var name in all)
32
+ __defProp(target, name, {
33
+ get: all[name],
34
+ enumerable: true,
35
+ configurable: true,
36
+ set: __exportSetter.bind(all, name)
37
+ });
38
+ };
39
+
40
+ // src/index.ts
41
+ var exports_src = {};
42
+ __export(exports_src, {
43
+ scaffoldPlugin: () => scaffoldPlugin,
44
+ normalizePluginName: () => normalizePluginName,
45
+ detectPackageManager: () => detectPackageManager
46
+ });
47
+ module.exports = __toCommonJS(exports_src);
48
+
49
+ // src/package-manager.ts
50
+ var import_node_fs = require("node:fs");
51
+ var import_node_path = require("node:path");
52
+ var LOCKFILES = [
53
+ ["bun", ["bun.lock", "bun.lockb"]],
54
+ ["pnpm", ["pnpm-lock.yaml"]],
55
+ ["yarn", ["yarn.lock"]],
56
+ ["npm", ["package-lock.json"]]
57
+ ];
58
+ function detectPackageManager(startDir = process.cwd()) {
59
+ let current = import_node_path.resolve(startDir);
60
+ const root = import_node_path.parse(current).root;
61
+ while (true) {
62
+ for (const [manager, files] of LOCKFILES) {
63
+ if (files.some((file) => import_node_fs.existsSync(import_node_path.join(current, file)))) {
64
+ return manager;
65
+ }
66
+ }
67
+ if (current === root) {
68
+ return "bun";
69
+ }
70
+ current = import_node_path.dirname(current);
71
+ }
72
+ }
73
+ // src/scaffold.ts
74
+ var import_promises = require("node:fs/promises");
75
+ var import_node_path2 = require("node:path");
76
+
77
+ // src/names.ts
78
+ function normalizePluginName(input) {
79
+ const name = input.trim().replace(/^@/, "").replaceAll(/[^a-zA-Z0-9._~-]+/g, "-").replaceAll(/^-+|-+$/g, "").toLowerCase();
80
+ return name || "pragma-plugin";
81
+ }
82
+ // package.json
83
+ var version = "0.1.0";
84
+
85
+ // src/templates.ts
86
+ function pluginTemplate(input) {
87
+ return [
88
+ { path: "package.json", contents: packageJson(input) },
89
+ { path: "tsconfig.json", contents: tsconfigJson() },
90
+ { path: "vite.config.ts", contents: viteConfig() },
91
+ { path: "README.md", contents: readme(input) },
92
+ { path: "src/index.tsx", contents: source(input) },
93
+ { path: "src/index.test.ts", contents: testSource() }
94
+ ];
95
+ }
96
+ function packageJson(input) {
97
+ return `${JSON.stringify({
98
+ name: input.packageName,
99
+ version: "0.0.0",
100
+ private: true,
101
+ type: "module",
102
+ main: "./dist/index.js",
103
+ scripts: {
104
+ build: "vite build",
105
+ dev: "vite build --watch",
106
+ typecheck: "tsc --noEmit",
107
+ test: "vitest run"
108
+ },
109
+ dependencies: {
110
+ "@pragma-sh/plugin": `^${version}`
111
+ },
112
+ devDependencies: {
113
+ "@types/react": "^19.2.14",
114
+ "@types/react-dom": "^19.2.3",
115
+ "@vitejs/plugin-react": "^6.0.2",
116
+ typescript: "^6.0.3",
117
+ vite: "^8.0.16",
118
+ vitest: "^4.1.8",
119
+ react: "^19.2.6",
120
+ "react-dom": "^19.2.6"
121
+ }
122
+ }, null, 2)}
123
+ `;
124
+ }
125
+ function tsconfigJson() {
126
+ return `${JSON.stringify({
127
+ compilerOptions: {
128
+ target: "ES2022",
129
+ lib: ["ES2022", "DOM", "DOM.Iterable"],
130
+ module: "ESNext",
131
+ moduleResolution: "Bundler",
132
+ jsx: "react-jsx",
133
+ jsxImportSource: "react",
134
+ strict: true,
135
+ noEmit: true,
136
+ skipLibCheck: true
137
+ },
138
+ include: ["src"]
139
+ }, null, 2)}
140
+ `;
141
+ }
142
+ function viteConfig() {
143
+ return `import react from "@vitejs/plugin-react";
144
+ import { defineConfig } from "vite";
145
+
146
+ export default defineConfig({
147
+ plugins: [react()],
148
+ resolve: {
149
+ alias: {
150
+ "react/jsx-runtime": "@pragma-sh/plugin/jsx-runtime",
151
+ "react-dom": "@pragma-sh/plugin/react-dom",
152
+ react: "@pragma-sh/plugin/react",
153
+ },
154
+ },
155
+ build: {
156
+ lib: {
157
+ entry: "src/index.tsx",
158
+ formats: ["es"],
159
+ fileName: "index",
160
+ },
161
+ rollupOptions: {
162
+ output: {
163
+ inlineDynamicImports: true,
164
+ },
165
+ },
166
+ },
167
+ });
168
+ `;
169
+ }
170
+ function source(input) {
171
+ const hasCommand = input.capabilities.includes("commands");
172
+ const commandImport = hasCommand ? ", defineCommand" : "";
173
+ const commandContribution = hasCommand ? `,
174
+ commands: [
175
+ defineCommand({
176
+ id: "${input.packageName}.hello",
177
+ title: "Show ${input.displayName} greeting",
178
+ run: (ctx) => ctx.notify("Hello from ${input.displayName}", { variant: "success" }),
179
+ }),
180
+ ]` : "";
181
+ return `import { definePlugin, defineSidebarTab, useProject${commandImport} } from "@pragma-sh/plugin";
182
+ import { Button, Kbd } from "@pragma-sh/plugin/ui";
183
+
184
+ function OverviewTab() {
185
+ const project = useProject();
186
+ return (
187
+ <div style={{ padding: 12 }}>
188
+ <h2>${input.displayName}</h2>
189
+ <p>Active project: {project?.name ?? "None"}</p>
190
+ <Button variant="secondary" size="sm">
191
+ Press <Kbd>⌘K</Kbd>
192
+ </Button>
193
+ </div>
194
+ );
195
+ }
196
+
197
+ export default definePlugin({
198
+ name: "${input.displayName}",
199
+ description: "A Pragma plugin scaffolded with create-pragma-plugin.",
200
+ ui: {
201
+ sidebarTabs: [
202
+ defineSidebarTab({
203
+ id: "overview",
204
+ title: "${input.displayName}",
205
+ component: OverviewTab,
206
+ }),
207
+ ],
208
+ }${commandContribution},
209
+ });
210
+ `;
211
+ }
212
+ function testSource() {
213
+ return `import { describe, expect, it } from "vitest";
214
+
215
+ import plugin from "./index";
216
+
217
+ describe("plugin", () => {
218
+ it("exports a stamped Pragma plugin", () => {
219
+ expect(plugin.__apiVersion).toBeTypeOf("string");
220
+ expect(plugin.name).toBeTypeOf("string");
221
+ });
222
+ });
223
+ `;
224
+ }
225
+ function readme(input) {
226
+ const install = input.packageManager === "npm" ? "npm install" : `${input.packageManager} install`;
227
+ const run = input.packageManager === "npm" ? "npm run" : `${input.packageManager} run`;
228
+ return `# ${input.displayName}
229
+
230
+ A Pragma plugin scaffolded with \`create-pragma-plugin\`.
231
+
232
+ ## Quick Start
233
+
234
+ \`\`\`bash
235
+ ${install}
236
+ ${run} build
237
+ \`\`\`
238
+
239
+ ## Load In Pragma
240
+
241
+ Add this to your project's \`.pragma/config.json\`:
242
+
243
+ \`\`\`json
244
+ {
245
+ "plugins": [{ "path": "./${input.directoryName}" }]
246
+ }
247
+ \`\`\`
248
+
249
+ Pragma loads local plugin code from this path. Only add plugins you trust.
250
+
251
+ ## Commands
252
+
253
+ \`\`\`bash
254
+ ${run} dev
255
+ ${run} typecheck
256
+ ${run} test
257
+ ${run} build
258
+ \`\`\`
259
+ `;
260
+ }
261
+
262
+ // src/scaffold.ts
263
+ async function scaffoldPlugin(options) {
264
+ const directory = import_node_path2.resolve(options.directory);
265
+ const packageName = normalizePluginName(options.name ?? import_node_path2.basename(directory));
266
+ const packageManager = options.packageManager ?? detectPackageManager(process.cwd());
267
+ const capabilities = options.capabilities?.length ? options.capabilities : ["ui"];
268
+ await assertWritableDirectory(directory, options.force === true);
269
+ const files = pluginTemplate({
270
+ packageName,
271
+ displayName: titleCase(packageName),
272
+ directoryName: import_node_path2.basename(directory),
273
+ packageManager,
274
+ capabilities
275
+ });
276
+ for (const file of files) {
277
+ const path = import_node_path2.join(directory, file.path);
278
+ await import_promises.mkdir(import_node_path2.join(path, ".."), { recursive: true });
279
+ await import_promises.writeFile(path, file.contents);
280
+ }
281
+ return { directory, packageName, packageManager, files: files.map((file) => file.path) };
282
+ }
283
+ async function assertWritableDirectory(directory, force) {
284
+ try {
285
+ const entries = await import_promises.readdir(directory);
286
+ if (!force && entries.length > 0) {
287
+ throw new Error(`destination is not empty: ${directory}`);
288
+ }
289
+ } catch (cause) {
290
+ if (cause instanceof Error && "code" in cause && cause.code === "ENOENT") {
291
+ await import_promises.mkdir(directory, { recursive: true });
292
+ return;
293
+ }
294
+ throw cause;
295
+ }
296
+ }
297
+ function titleCase(packageName) {
298
+ return packageName.split(/[._~-]+/g).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
299
+ }
@@ -0,0 +1,22 @@
1
+ type PackageManager = "bun" | "npm" | "pnpm" | "yarn";
2
+ /** Detects the nearest package manager by walking up from `startDir` and checking lockfiles. */
3
+ declare function detectPackageManager(startDir?: string): PackageManager;
4
+ type ScaffoldCapability = "ui" | "commands" | "agents";
5
+ interface ScaffoldOptions {
6
+ directory: string;
7
+ name?: string;
8
+ packageManager?: PackageManager;
9
+ capabilities?: readonly ScaffoldCapability[];
10
+ force?: boolean;
11
+ }
12
+ interface ScaffoldResult {
13
+ directory: string;
14
+ packageName: string;
15
+ packageManager: PackageManager;
16
+ files: string[];
17
+ }
18
+ /** Creates a new Pragma plugin project from the built-in TypeScript template. */
19
+ declare function scaffoldPlugin(options: ScaffoldOptions): Promise<ScaffoldResult>;
20
+ /** Converts a directory or package name into a valid lowercase npm package name. */
21
+ declare function normalizePluginName(input: string): string;
22
+ export { scaffoldPlugin, normalizePluginName, detectPackageManager, ScaffoldResult, ScaffoldOptions, ScaffoldCapability, PackageManager };
@@ -0,0 +1,22 @@
1
+ type PackageManager = "bun" | "npm" | "pnpm" | "yarn";
2
+ /** Detects the nearest package manager by walking up from `startDir` and checking lockfiles. */
3
+ declare function detectPackageManager(startDir?: string): PackageManager;
4
+ type ScaffoldCapability = "ui" | "commands" | "agents";
5
+ interface ScaffoldOptions {
6
+ directory: string;
7
+ name?: string;
8
+ packageManager?: PackageManager;
9
+ capabilities?: readonly ScaffoldCapability[];
10
+ force?: boolean;
11
+ }
12
+ interface ScaffoldResult {
13
+ directory: string;
14
+ packageName: string;
15
+ packageManager: PackageManager;
16
+ files: string[];
17
+ }
18
+ /** Creates a new Pragma plugin project from the built-in TypeScript template. */
19
+ declare function scaffoldPlugin(options: ScaffoldOptions): Promise<ScaffoldResult>;
20
+ /** Converts a directory or package name into a valid lowercase npm package name. */
21
+ declare function normalizePluginName(input: string): string;
22
+ export { scaffoldPlugin, normalizePluginName, detectPackageManager, ScaffoldResult, ScaffoldOptions, ScaffoldCapability, PackageManager };
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ import {
2
+ detectPackageManager,
3
+ normalizePluginName,
4
+ scaffoldPlugin
5
+ } from "./shared/chunk-8xnme2e1.js";
6
+ export {
7
+ scaffoldPlugin,
8
+ normalizePluginName,
9
+ detectPackageManager
10
+ };
@@ -0,0 +1,254 @@
1
+ // src/package-manager.ts
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, join, parse, resolve } from "node:path";
4
+ var LOCKFILES = [
5
+ ["bun", ["bun.lock", "bun.lockb"]],
6
+ ["pnpm", ["pnpm-lock.yaml"]],
7
+ ["yarn", ["yarn.lock"]],
8
+ ["npm", ["package-lock.json"]]
9
+ ];
10
+ function detectPackageManager(startDir = process.cwd()) {
11
+ let current = resolve(startDir);
12
+ const root = parse(current).root;
13
+ while (true) {
14
+ for (const [manager, files] of LOCKFILES) {
15
+ if (files.some((file) => existsSync(join(current, file)))) {
16
+ return manager;
17
+ }
18
+ }
19
+ if (current === root) {
20
+ return "bun";
21
+ }
22
+ current = dirname(current);
23
+ }
24
+ }
25
+
26
+ // src/names.ts
27
+ function normalizePluginName(input) {
28
+ const name = input.trim().replace(/^@/, "").replaceAll(/[^a-zA-Z0-9._~-]+/g, "-").replaceAll(/^-+|-+$/g, "").toLowerCase();
29
+ return name || "pragma-plugin";
30
+ }
31
+
32
+ // src/scaffold.ts
33
+ import { mkdir, readdir, writeFile } from "node:fs/promises";
34
+ import { basename, join as join2, resolve as resolve2 } from "node:path";
35
+ // package.json
36
+ var version = "0.1.0";
37
+
38
+ // src/templates.ts
39
+ function pluginTemplate(input) {
40
+ return [
41
+ { path: "package.json", contents: packageJson(input) },
42
+ { path: "tsconfig.json", contents: tsconfigJson() },
43
+ { path: "vite.config.ts", contents: viteConfig() },
44
+ { path: "README.md", contents: readme(input) },
45
+ { path: "src/index.tsx", contents: source(input) },
46
+ { path: "src/index.test.ts", contents: testSource() }
47
+ ];
48
+ }
49
+ function packageJson(input) {
50
+ return `${JSON.stringify({
51
+ name: input.packageName,
52
+ version: "0.0.0",
53
+ private: true,
54
+ type: "module",
55
+ main: "./dist/index.js",
56
+ scripts: {
57
+ build: "vite build",
58
+ dev: "vite build --watch",
59
+ typecheck: "tsc --noEmit",
60
+ test: "vitest run"
61
+ },
62
+ dependencies: {
63
+ "@pragma-sh/plugin": `^${version}`
64
+ },
65
+ devDependencies: {
66
+ "@types/react": "^19.2.14",
67
+ "@types/react-dom": "^19.2.3",
68
+ "@vitejs/plugin-react": "^6.0.2",
69
+ typescript: "^6.0.3",
70
+ vite: "^8.0.16",
71
+ vitest: "^4.1.8",
72
+ react: "^19.2.6",
73
+ "react-dom": "^19.2.6"
74
+ }
75
+ }, null, 2)}
76
+ `;
77
+ }
78
+ function tsconfigJson() {
79
+ return `${JSON.stringify({
80
+ compilerOptions: {
81
+ target: "ES2022",
82
+ lib: ["ES2022", "DOM", "DOM.Iterable"],
83
+ module: "ESNext",
84
+ moduleResolution: "Bundler",
85
+ jsx: "react-jsx",
86
+ jsxImportSource: "react",
87
+ strict: true,
88
+ noEmit: true,
89
+ skipLibCheck: true
90
+ },
91
+ include: ["src"]
92
+ }, null, 2)}
93
+ `;
94
+ }
95
+ function viteConfig() {
96
+ return `import react from "@vitejs/plugin-react";
97
+ import { defineConfig } from "vite";
98
+
99
+ export default defineConfig({
100
+ plugins: [react()],
101
+ resolve: {
102
+ alias: {
103
+ "react/jsx-runtime": "@pragma-sh/plugin/jsx-runtime",
104
+ "react-dom": "@pragma-sh/plugin/react-dom",
105
+ react: "@pragma-sh/plugin/react",
106
+ },
107
+ },
108
+ build: {
109
+ lib: {
110
+ entry: "src/index.tsx",
111
+ formats: ["es"],
112
+ fileName: "index",
113
+ },
114
+ rollupOptions: {
115
+ output: {
116
+ inlineDynamicImports: true,
117
+ },
118
+ },
119
+ },
120
+ });
121
+ `;
122
+ }
123
+ function source(input) {
124
+ const hasCommand = input.capabilities.includes("commands");
125
+ const commandImport = hasCommand ? ", defineCommand" : "";
126
+ const commandContribution = hasCommand ? `,
127
+ commands: [
128
+ defineCommand({
129
+ id: "${input.packageName}.hello",
130
+ title: "Show ${input.displayName} greeting",
131
+ run: (ctx) => ctx.notify("Hello from ${input.displayName}", { variant: "success" }),
132
+ }),
133
+ ]` : "";
134
+ return `import { definePlugin, defineSidebarTab, useProject${commandImport} } from "@pragma-sh/plugin";
135
+ import { Button, Kbd } from "@pragma-sh/plugin/ui";
136
+
137
+ function OverviewTab() {
138
+ const project = useProject();
139
+ return (
140
+ <div style={{ padding: 12 }}>
141
+ <h2>${input.displayName}</h2>
142
+ <p>Active project: {project?.name ?? "None"}</p>
143
+ <Button variant="secondary" size="sm">
144
+ Press <Kbd>⌘K</Kbd>
145
+ </Button>
146
+ </div>
147
+ );
148
+ }
149
+
150
+ export default definePlugin({
151
+ name: "${input.displayName}",
152
+ description: "A Pragma plugin scaffolded with create-pragma-plugin.",
153
+ ui: {
154
+ sidebarTabs: [
155
+ defineSidebarTab({
156
+ id: "overview",
157
+ title: "${input.displayName}",
158
+ component: OverviewTab,
159
+ }),
160
+ ],
161
+ }${commandContribution},
162
+ });
163
+ `;
164
+ }
165
+ function testSource() {
166
+ return `import { describe, expect, it } from "vitest";
167
+
168
+ import plugin from "./index";
169
+
170
+ describe("plugin", () => {
171
+ it("exports a stamped Pragma plugin", () => {
172
+ expect(plugin.__apiVersion).toBeTypeOf("string");
173
+ expect(plugin.name).toBeTypeOf("string");
174
+ });
175
+ });
176
+ `;
177
+ }
178
+ function readme(input) {
179
+ const install = input.packageManager === "npm" ? "npm install" : `${input.packageManager} install`;
180
+ const run = input.packageManager === "npm" ? "npm run" : `${input.packageManager} run`;
181
+ return `# ${input.displayName}
182
+
183
+ A Pragma plugin scaffolded with \`create-pragma-plugin\`.
184
+
185
+ ## Quick Start
186
+
187
+ \`\`\`bash
188
+ ${install}
189
+ ${run} build
190
+ \`\`\`
191
+
192
+ ## Load In Pragma
193
+
194
+ Add this to your project's \`.pragma/config.json\`:
195
+
196
+ \`\`\`json
197
+ {
198
+ "plugins": [{ "path": "./${input.directoryName}" }]
199
+ }
200
+ \`\`\`
201
+
202
+ Pragma loads local plugin code from this path. Only add plugins you trust.
203
+
204
+ ## Commands
205
+
206
+ \`\`\`bash
207
+ ${run} dev
208
+ ${run} typecheck
209
+ ${run} test
210
+ ${run} build
211
+ \`\`\`
212
+ `;
213
+ }
214
+
215
+ // src/scaffold.ts
216
+ async function scaffoldPlugin(options) {
217
+ const directory = resolve2(options.directory);
218
+ const packageName = normalizePluginName(options.name ?? basename(directory));
219
+ const packageManager = options.packageManager ?? detectPackageManager(process.cwd());
220
+ const capabilities = options.capabilities?.length ? options.capabilities : ["ui"];
221
+ await assertWritableDirectory(directory, options.force === true);
222
+ const files = pluginTemplate({
223
+ packageName,
224
+ displayName: titleCase(packageName),
225
+ directoryName: basename(directory),
226
+ packageManager,
227
+ capabilities
228
+ });
229
+ for (const file of files) {
230
+ const path = join2(directory, file.path);
231
+ await mkdir(join2(path, ".."), { recursive: true });
232
+ await writeFile(path, file.contents);
233
+ }
234
+ return { directory, packageName, packageManager, files: files.map((file) => file.path) };
235
+ }
236
+ async function assertWritableDirectory(directory, force) {
237
+ try {
238
+ const entries = await readdir(directory);
239
+ if (!force && entries.length > 0) {
240
+ throw new Error(`destination is not empty: ${directory}`);
241
+ }
242
+ } catch (cause) {
243
+ if (cause instanceof Error && "code" in cause && cause.code === "ENOENT") {
244
+ await mkdir(directory, { recursive: true });
245
+ return;
246
+ }
247
+ throw cause;
248
+ }
249
+ }
250
+ function titleCase(packageName) {
251
+ return packageName.split(/[._~-]+/g).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
252
+ }
253
+
254
+ export { detectPackageManager, normalizePluginName, scaffoldPlugin };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "create-pragma-plugin",
3
+ "version": "0.1.0",
4
+ "description": "Scaffolder CLI for pure TypeScript Pragma plugins.",
5
+ "homepage": "https://github.com/pragma-sh/pragma#readme",
6
+ "bugs": "https://github.com/pragma-sh/pragma/issues",
7
+ "license": "AGPL-3.0-only",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/pragma-sh/pragma.git",
11
+ "directory": "packages/create-pragma-plugin"
12
+ },
13
+ "bin": {
14
+ "create-pragma-plugin": "dist/cli.js"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "type": "module",
20
+ "main": "./dist/index.cjs",
21
+ "module": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js",
27
+ "require": "./dist/index.cjs"
28
+ },
29
+ "./package.json": "./package.json"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "scripts": {
35
+ "build": "bunup src/index.ts src/cli.ts --format esm,cjs --target node --dts",
36
+ "typecheck": "tsc --noEmit",
37
+ "test": "bun --bun vitest run",
38
+ "lint": "oxlint ."
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^24.12.3",
42
+ "bunup": "^0.16.32",
43
+ "typescript": "^6.0.3",
44
+ "vitest": "^4.1.8"
45
+ }
46
+ }