create-renre-extension 0.0.22-beta.5

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.
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,351 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/scaffold.ts
4
+ import path from "path";
5
+ import fse from "fs-extra";
6
+
7
+ // src/templates/shared.ts
8
+ function getTsconfig() {
9
+ const config = {
10
+ compilerOptions: {
11
+ target: "ES2022",
12
+ module: "NodeNext",
13
+ moduleResolution: "NodeNext",
14
+ declaration: true,
15
+ outDir: "./dist",
16
+ rootDir: "./src",
17
+ strict: true,
18
+ esModuleInterop: true,
19
+ skipLibCheck: true
20
+ },
21
+ include: ["src"],
22
+ exclude: ["dist", "node_modules"]
23
+ };
24
+ return `${JSON.stringify(config, null, 2)}
25
+ `;
26
+ }
27
+
28
+ // src/templates/standard.ts
29
+ function getStandardPackageJson(name2) {
30
+ const pkg = {
31
+ name: name2,
32
+ version: "0.0.1",
33
+ description: `A RenreKit extension: ${name2}`,
34
+ type: "module",
35
+ main: "./dist/index.js",
36
+ scripts: {
37
+ build: "node build.js",
38
+ dev: "tsc --watch"
39
+ },
40
+ dependencies: {
41
+ "@renre-kit/extension-sdk": ">=0.0.1"
42
+ },
43
+ devDependencies: {
44
+ esbuild: "^0.21.0",
45
+ typescript: "^5.7.0"
46
+ }
47
+ };
48
+ return `${JSON.stringify(pkg, null, 2)}
49
+ `;
50
+ }
51
+ function getStandardManifest(name2) {
52
+ const manifest = {
53
+ name: name2,
54
+ title: name2,
55
+ version: "0.0.1",
56
+ description: "A RenreKit extension",
57
+ type: "standard",
58
+ main: "dist/index.js",
59
+ engines: {
60
+ "renre-kit": ">=0.0.1",
61
+ "extension-sdk": ">=0.0.1"
62
+ },
63
+ commands: {
64
+ hello: {
65
+ handler: "dist/commands/hello.js",
66
+ description: "Say hello"
67
+ }
68
+ }
69
+ };
70
+ return `${JSON.stringify(manifest, null, 2)}
71
+ `;
72
+ }
73
+ function getStandardEntryPoint(name2) {
74
+ return `import type { HookContext } from '@renre-kit/extension-sdk/node';
75
+
76
+ export function onInit(context: HookContext): void {
77
+ context.sdk.deployAgentAssets();
78
+ console.log('${name2} initialized in', context.projectDir);
79
+ }
80
+
81
+ export function onDestroy(context: HookContext): void {
82
+ context.sdk.cleanupAgentAssets();
83
+ console.log('${name2} destroyed in', context.projectDir);
84
+ }
85
+ `;
86
+ }
87
+ function getStandardCommandHandler(name2) {
88
+ return `import { defineCommand } from '@renre-kit/extension-sdk/node';
89
+
90
+ export default defineCommand({
91
+ handler: () => {
92
+ return { output: 'Hello from ${name2}!', exitCode: 0 };
93
+ },
94
+ });
95
+ `;
96
+ }
97
+ function getStandardBuildJs() {
98
+ return `import { readFileSync, rmSync } from 'node:fs';
99
+
100
+ import { buildExtension, archiveDist } from '@renre-kit/extension-sdk/node';
101
+
102
+ rmSync('dist', { recursive: true, force: true });
103
+
104
+ const manifest = JSON.parse(readFileSync('manifest.json', 'utf-8'));
105
+
106
+ await buildExtension({
107
+ entryPoints: [
108
+ { in: 'src/index.ts', out: 'index' },
109
+ { in: 'src/commands/hello.ts', out: 'commands/hello' },
110
+ ],
111
+ outdir: 'dist',
112
+ external: [],
113
+ splitting: true,
114
+ });
115
+
116
+ await archiveDist('dist', manifest.version);
117
+ `;
118
+ }
119
+ function getStandardSkillMd(name2) {
120
+ return `---
121
+ name: hello
122
+ description: This tool should be used when the user wants to say hello or get a greeting from the ${name2} extension
123
+ ---
124
+
125
+ # ${name2}
126
+
127
+ ## Description
128
+
129
+ A RenreKit extension that provides additional functionality.
130
+
131
+ ## Commands
132
+
133
+ ### hello
134
+
135
+ Say hello from the extension.
136
+
137
+ **Usage:**
138
+ \`\`\`
139
+ renre ${name2} hello
140
+ \`\`\`
141
+
142
+ ## Configuration
143
+
144
+ No configuration required.
145
+ `;
146
+ }
147
+
148
+ // src/templates/mcp.ts
149
+ function getMcpPackageJson(name2) {
150
+ const pkg = {
151
+ name: name2,
152
+ version: "0.0.1",
153
+ description: `A RenreKit MCP extension: ${name2}`,
154
+ type: "module",
155
+ main: "./dist/server.js",
156
+ scripts: {
157
+ build: "node build.js",
158
+ dev: "tsc --watch"
159
+ },
160
+ dependencies: {
161
+ "@renre-kit/extension-sdk": ">=0.0.1"
162
+ },
163
+ devDependencies: {
164
+ esbuild: "^0.21.0",
165
+ typescript: "^5.7.0"
166
+ }
167
+ };
168
+ return `${JSON.stringify(pkg, null, 2)}
169
+ `;
170
+ }
171
+ function getMcpManifest(name2) {
172
+ const manifest = {
173
+ name: name2,
174
+ title: name2,
175
+ version: "0.0.1",
176
+ description: "A RenreKit MCP extension",
177
+ type: "mcp",
178
+ main: "dist/index.js",
179
+ engines: {
180
+ "renre-kit": ">=0.0.1",
181
+ "extension-sdk": ">=0.0.1"
182
+ },
183
+ commands: {},
184
+ mcp: {
185
+ transport: "stdio",
186
+ command: "node",
187
+ args: ["dist/server.js"]
188
+ }
189
+ };
190
+ return `${JSON.stringify(manifest, null, 2)}
191
+ `;
192
+ }
193
+ function getMcpServerEntryPoint(name2) {
194
+ return `import { createInterface } from 'node:readline';
195
+
196
+ interface JsonRpcRequest {
197
+ jsonrpc: string;
198
+ id: number | string;
199
+ method: string;
200
+ params?: Record<string, unknown>;
201
+ }
202
+
203
+ interface JsonRpcResponse {
204
+ jsonrpc: string;
205
+ id: number | string;
206
+ result?: unknown;
207
+ error?: { code: number; message: string };
208
+ }
209
+
210
+ function handleRequest(request: JsonRpcRequest): JsonRpcResponse {
211
+ switch (request.method) {
212
+ case 'hello':
213
+ return {
214
+ jsonrpc: '2.0',
215
+ id: request.id,
216
+ result: { output: 'Hello from ${name2}!', exitCode: 0 },
217
+ };
218
+ default:
219
+ return {
220
+ jsonrpc: '2.0',
221
+ id: request.id,
222
+ error: { code: -32601, message: \`Method not found: \${request.method}\` },
223
+ };
224
+ }
225
+ }
226
+
227
+ const rl = createInterface({ input: process.stdin });
228
+
229
+ rl.on('line', (line: string) => {
230
+ try {
231
+ const request = JSON.parse(line) as JsonRpcRequest;
232
+ const response = handleRequest(request);
233
+ process.stdout.write(JSON.stringify(response) + '\\n');
234
+ } catch {
235
+ const errorResponse: JsonRpcResponse = {
236
+ jsonrpc: '2.0',
237
+ id: 0,
238
+ error: { code: -32700, message: 'Parse error' },
239
+ };
240
+ process.stdout.write(JSON.stringify(errorResponse) + '\\n');
241
+ }
242
+ });
243
+ `;
244
+ }
245
+ function getMcpBuildJs() {
246
+ return `import { readFileSync, rmSync } from 'node:fs';
247
+
248
+ import { buildExtension, archiveDist } from '@renre-kit/extension-sdk/node';
249
+
250
+ rmSync('dist', { recursive: true, force: true });
251
+
252
+ const manifest = JSON.parse(readFileSync('manifest.json', 'utf-8'));
253
+
254
+ await buildExtension({
255
+ entryPoints: [
256
+ { in: 'src/server.ts', out: 'server' },
257
+ ],
258
+ outdir: 'dist',
259
+ external: [],
260
+ splitting: true,
261
+ });
262
+
263
+ await archiveDist('dist', manifest.version);
264
+ `;
265
+ }
266
+ function getMcpSkillMd(name2) {
267
+ return `---
268
+ name: hello
269
+ description: This tool should be used when the user wants to say hello or test the ${name2} MCP extension
270
+ ---
271
+
272
+ # ${name2}
273
+
274
+ ## Description
275
+
276
+ A RenreKit MCP extension that communicates via JSON-RPC over stdio.
277
+
278
+ ## Commands
279
+
280
+ ### hello
281
+
282
+ Say hello from the MCP extension.
283
+
284
+ **Usage:**
285
+ \`\`\`
286
+ renre ${name2} hello
287
+ \`\`\`
288
+
289
+ ## Configuration
290
+
291
+ No configuration required.
292
+ `;
293
+ }
294
+
295
+ // src/scaffold.ts
296
+ function getStandardFiles(name2, extDir) {
297
+ return [
298
+ { filePath: path.join(extDir, "package.json"), content: getStandardPackageJson(name2) },
299
+ { filePath: path.join(extDir, "manifest.json"), content: getStandardManifest(name2) },
300
+ { filePath: path.join(extDir, "src", "index.ts"), content: getStandardEntryPoint(name2) },
301
+ {
302
+ filePath: path.join(extDir, "src", "commands", "hello.ts"),
303
+ content: getStandardCommandHandler(name2)
304
+ },
305
+ { filePath: path.join(extDir, "tsconfig.json"), content: getTsconfig() },
306
+ { filePath: path.join(extDir, "build.js"), content: getStandardBuildJs() },
307
+ { filePath: path.join(extDir, "SKILL.md"), content: getStandardSkillMd(name2) }
308
+ ];
309
+ }
310
+ function getMcpFiles(name2, extDir) {
311
+ return [
312
+ { filePath: path.join(extDir, "package.json"), content: getMcpPackageJson(name2) },
313
+ { filePath: path.join(extDir, "manifest.json"), content: getMcpManifest(name2) },
314
+ { filePath: path.join(extDir, "src", "server.ts"), content: getMcpServerEntryPoint(name2) },
315
+ { filePath: path.join(extDir, "tsconfig.json"), content: getTsconfig() },
316
+ { filePath: path.join(extDir, "build.js"), content: getMcpBuildJs() },
317
+ { filePath: path.join(extDir, "SKILL.md"), content: getMcpSkillMd(name2) }
318
+ ];
319
+ }
320
+ async function scaffoldExtension(name2, type2, outputDir2) {
321
+ const extDir = path.join(outputDir2, name2);
322
+ if (await fse.pathExists(extDir)) {
323
+ throw new Error(`Directory "${name2}" already exists`);
324
+ }
325
+ const files = type2 === "mcp" ? getMcpFiles(name2, extDir) : getStandardFiles(name2, extDir);
326
+ for (const file of files) {
327
+ await fse.ensureDir(path.dirname(file.filePath));
328
+ await fse.writeFile(file.filePath, file.content, "utf-8");
329
+ }
330
+ }
331
+
332
+ // src/index.ts
333
+ var args = process.argv.slice(2);
334
+ var name = args[0] ?? "my-extension";
335
+ var typeFlag = args.indexOf("--type");
336
+ var type = typeFlag !== -1 && args[typeFlag + 1] === "mcp" ? "mcp" : "standard";
337
+ var outputDir = process.cwd();
338
+ try {
339
+ await scaffoldExtension(name, type, outputDir);
340
+ console.log(`
341
+ Extension "${name}" created successfully!`);
342
+ console.log(`
343
+ Next steps:`);
344
+ console.log(` cd ${name}`);
345
+ console.log(` pnpm install`);
346
+ console.log(` pnpm run build`);
347
+ } catch (err) {
348
+ console.error("Failed to create extension:", err instanceof Error ? err.message : String(err));
349
+ process.exit(1);
350
+ }
351
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/scaffold.ts","../src/templates/shared.ts","../src/templates/standard.ts","../src/templates/mcp.ts","../src/index.ts"],"sourcesContent":["import path from 'node:path';\n\nimport fse from 'fs-extra';\n\nimport {\n getStandardPackageJson,\n getStandardManifest,\n getStandardEntryPoint,\n getStandardCommandHandler,\n getStandardTsconfig,\n getStandardSkillMd,\n getStandardBuildJs,\n} from './templates/standard.js';\nimport {\n getMcpPackageJson,\n getMcpManifest,\n getMcpServerEntryPoint,\n getMcpTsconfig,\n getMcpSkillMd,\n getMcpBuildJs,\n} from './templates/mcp.js';\n\nexport type ExtensionType = 'standard' | 'mcp';\n\ninterface FileEntry {\n filePath: string;\n content: string;\n}\n\nfunction getStandardFiles(name: string, extDir: string): FileEntry[] {\n return [\n { filePath: path.join(extDir, 'package.json'), content: getStandardPackageJson(name) },\n { filePath: path.join(extDir, 'manifest.json'), content: getStandardManifest(name) },\n { filePath: path.join(extDir, 'src', 'index.ts'), content: getStandardEntryPoint(name) },\n {\n filePath: path.join(extDir, 'src', 'commands', 'hello.ts'),\n content: getStandardCommandHandler(name),\n },\n { filePath: path.join(extDir, 'tsconfig.json'), content: getStandardTsconfig() },\n { filePath: path.join(extDir, 'build.js'), content: getStandardBuildJs() },\n { filePath: path.join(extDir, 'SKILL.md'), content: getStandardSkillMd(name) },\n ];\n}\n\nfunction getMcpFiles(name: string, extDir: string): FileEntry[] {\n return [\n { filePath: path.join(extDir, 'package.json'), content: getMcpPackageJson(name) },\n { filePath: path.join(extDir, 'manifest.json'), content: getMcpManifest(name) },\n { filePath: path.join(extDir, 'src', 'server.ts'), content: getMcpServerEntryPoint(name) },\n { filePath: path.join(extDir, 'tsconfig.json'), content: getMcpTsconfig() },\n { filePath: path.join(extDir, 'build.js'), content: getMcpBuildJs() },\n { filePath: path.join(extDir, 'SKILL.md'), content: getMcpSkillMd(name) },\n ];\n}\n\nexport async function scaffoldExtension(\n name: string,\n type: ExtensionType,\n outputDir: string,\n): Promise<void> {\n const extDir = path.join(outputDir, name);\n\n if (await fse.pathExists(extDir)) {\n throw new Error(`Directory \"${name}\" already exists`);\n }\n\n const files = type === 'mcp' ? getMcpFiles(name, extDir) : getStandardFiles(name, extDir);\n\n for (const file of files) {\n await fse.ensureDir(path.dirname(file.filePath));\n await fse.writeFile(file.filePath, file.content, 'utf-8');\n }\n}\n","/** Generates a tsconfig.json for any extension type. */\nexport function getTsconfig(): string {\n const config = {\n compilerOptions: {\n target: 'ES2022',\n module: 'NodeNext',\n moduleResolution: 'NodeNext',\n declaration: true,\n outDir: './dist',\n rootDir: './src',\n strict: true,\n esModuleInterop: true,\n skipLibCheck: true,\n },\n include: ['src'],\n exclude: ['dist', 'node_modules'],\n };\n return `${JSON.stringify(config, null, 2) }\\n`;\n}\n","export function getStandardPackageJson(name: string): string {\n const pkg = {\n name,\n version: '0.0.1',\n description: `A RenreKit extension: ${name}`,\n type: 'module',\n main: './dist/index.js',\n scripts: {\n build: 'node build.js',\n dev: 'tsc --watch',\n },\n dependencies: {\n '@renre-kit/extension-sdk': '>=0.0.1',\n },\n devDependencies: {\n esbuild: '^0.21.0',\n typescript: '^5.7.0',\n },\n };\n return `${JSON.stringify(pkg, null, 2) }\\n`;\n}\n\nexport function getStandardManifest(name: string): string {\n const manifest = {\n name,\n title: name,\n version: '0.0.1',\n description: 'A RenreKit extension',\n type: 'standard',\n main: 'dist/index.js',\n engines: {\n 'renre-kit': '>=0.0.1',\n 'extension-sdk': '>=0.0.1',\n },\n commands: {\n hello: {\n handler: 'dist/commands/hello.js',\n description: 'Say hello',\n },\n },\n };\n return `${JSON.stringify(manifest, null, 2) }\\n`;\n}\n\nexport function getStandardEntryPoint(name: string): string {\n return `import type { HookContext } from '@renre-kit/extension-sdk/node';\n\nexport function onInit(context: HookContext): void {\n context.sdk.deployAgentAssets();\n console.log('${name} initialized in', context.projectDir);\n}\n\nexport function onDestroy(context: HookContext): void {\n context.sdk.cleanupAgentAssets();\n console.log('${name} destroyed in', context.projectDir);\n}\n`;\n}\n\nexport function getStandardCommandHandler(name: string): string {\n return `import { defineCommand } from '@renre-kit/extension-sdk/node';\n\nexport default defineCommand({\n handler: () => {\n return { output: 'Hello from ${name}!', exitCode: 0 };\n },\n});\n`;\n}\n\nexport { getTsconfig as getStandardTsconfig } from './shared.js';\n\nexport function getStandardBuildJs(): string {\n return `import { readFileSync, rmSync } from 'node:fs';\n\nimport { buildExtension, archiveDist } from '@renre-kit/extension-sdk/node';\n\nrmSync('dist', { recursive: true, force: true });\n\nconst manifest = JSON.parse(readFileSync('manifest.json', 'utf-8'));\n\nawait buildExtension({\n entryPoints: [\n { in: 'src/index.ts', out: 'index' },\n { in: 'src/commands/hello.ts', out: 'commands/hello' },\n ],\n outdir: 'dist',\n external: [],\n splitting: true,\n});\n\nawait archiveDist('dist', manifest.version);\n`;\n}\n\nexport function getStandardSkillMd(name: string): string {\n return `---\nname: hello\ndescription: This tool should be used when the user wants to say hello or get a greeting from the ${name} extension\n---\n\n# ${name}\n\n## Description\n\nA RenreKit extension that provides additional functionality.\n\n## Commands\n\n### hello\n\nSay hello from the extension.\n\n**Usage:**\n\\`\\`\\`\nrenre ${name} hello\n\\`\\`\\`\n\n## Configuration\n\nNo configuration required.\n`;\n}\n","export function getMcpPackageJson(name: string): string {\n const pkg = {\n name,\n version: '0.0.1',\n description: `A RenreKit MCP extension: ${name}`,\n type: 'module',\n main: './dist/server.js',\n scripts: {\n build: 'node build.js',\n dev: 'tsc --watch',\n },\n dependencies: {\n '@renre-kit/extension-sdk': '>=0.0.1',\n },\n devDependencies: {\n esbuild: '^0.21.0',\n typescript: '^5.7.0',\n },\n };\n return `${JSON.stringify(pkg, null, 2) }\\n`;\n}\n\nexport function getMcpManifest(name: string): string {\n const manifest = {\n name,\n title: name,\n version: '0.0.1',\n description: 'A RenreKit MCP extension',\n type: 'mcp',\n main: 'dist/index.js',\n engines: {\n 'renre-kit': '>=0.0.1',\n 'extension-sdk': '>=0.0.1',\n },\n commands: {},\n mcp: {\n transport: 'stdio',\n command: 'node',\n args: ['dist/server.js'],\n },\n };\n return `${JSON.stringify(manifest, null, 2) }\\n`;\n}\n\nexport function getMcpServerEntryPoint(name: string): string {\n return `import { createInterface } from 'node:readline';\n\ninterface JsonRpcRequest {\n jsonrpc: string;\n id: number | string;\n method: string;\n params?: Record<string, unknown>;\n}\n\ninterface JsonRpcResponse {\n jsonrpc: string;\n id: number | string;\n result?: unknown;\n error?: { code: number; message: string };\n}\n\nfunction handleRequest(request: JsonRpcRequest): JsonRpcResponse {\n switch (request.method) {\n case 'hello':\n return {\n jsonrpc: '2.0',\n id: request.id,\n result: { output: 'Hello from ${name}!', exitCode: 0 },\n };\n default:\n return {\n jsonrpc: '2.0',\n id: request.id,\n error: { code: -32601, message: \\`Method not found: \\${request.method}\\` },\n };\n }\n}\n\nconst rl = createInterface({ input: process.stdin });\n\nrl.on('line', (line: string) => {\n try {\n const request = JSON.parse(line) as JsonRpcRequest;\n const response = handleRequest(request);\n process.stdout.write(JSON.stringify(response) + '\\\\n');\n } catch {\n const errorResponse: JsonRpcResponse = {\n jsonrpc: '2.0',\n id: 0,\n error: { code: -32700, message: 'Parse error' },\n };\n process.stdout.write(JSON.stringify(errorResponse) + '\\\\n');\n }\n});\n`;\n}\n\nexport { getTsconfig as getMcpTsconfig } from './shared.js';\n\nexport function getMcpBuildJs(): string {\n return `import { readFileSync, rmSync } from 'node:fs';\n\nimport { buildExtension, archiveDist } from '@renre-kit/extension-sdk/node';\n\nrmSync('dist', { recursive: true, force: true });\n\nconst manifest = JSON.parse(readFileSync('manifest.json', 'utf-8'));\n\nawait buildExtension({\n entryPoints: [\n { in: 'src/server.ts', out: 'server' },\n ],\n outdir: 'dist',\n external: [],\n splitting: true,\n});\n\nawait archiveDist('dist', manifest.version);\n`;\n}\n\nexport function getMcpSkillMd(name: string): string {\n return `---\nname: hello\ndescription: This tool should be used when the user wants to say hello or test the ${name} MCP extension\n---\n\n# ${name}\n\n## Description\n\nA RenreKit MCP extension that communicates via JSON-RPC over stdio.\n\n## Commands\n\n### hello\n\nSay hello from the MCP extension.\n\n**Usage:**\n\\`\\`\\`\nrenre ${name} hello\n\\`\\`\\`\n\n## Configuration\n\nNo configuration required.\n`;\n}\n","import { scaffoldExtension } from './scaffold.js';\n\nconst args = process.argv.slice(2);\nconst name = args[0] ?? 'my-extension';\nconst typeFlag = args.indexOf('--type');\nconst type = typeFlag !== -1 && args[typeFlag + 1] === 'mcp' ? 'mcp' : 'standard';\nconst outputDir = process.cwd();\n\ntry {\n await scaffoldExtension(name, type, outputDir);\n console.log(`\\nExtension \"${name}\" created successfully!`);\n console.log(`\\nNext steps:`);\n console.log(` cd ${name}`);\n console.log(` pnpm install`);\n console.log(` pnpm run build`);\n} catch (err: unknown) {\n console.error('Failed to create extension:', err instanceof Error ? err.message : String(err));\n process.exit(1);\n}\n"],"mappings":"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","names":["name","name","name","type","outputDir"]}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "create-renre-extension",
3
+ "version": "0.0.22-beta.5",
4
+ "description": "Scaffolding tool for creating RenreKit extensions",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-renre-extension": "./dist/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "scripts": {
12
+ "build": "tsup",
13
+ "dev": "tsup --watch",
14
+ "test": "vitest run",
15
+ "test:coverage": "vitest run --coverage",
16
+ "lint": "eslint --cache src/",
17
+ "lint:duplication": "jscpd src/ --config ../../.jscpd.json",
18
+ "typecheck": "tsc --noEmit"
19
+ },
20
+ "dependencies": {
21
+ "fs-extra": "^11.2.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/fs-extra": "^11.0.0",
25
+ "@types/node": "^20",
26
+ "@vitest/coverage-istanbul": "^2.1.0",
27
+ "tsup": "^8.3.0",
28
+ "typescript": "^5.7.0",
29
+ "vitest": "^2.1.0"
30
+ }
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { scaffoldExtension } from './scaffold.js';
2
+
3
+ const args = process.argv.slice(2);
4
+ const name = args[0] ?? 'my-extension';
5
+ const typeFlag = args.indexOf('--type');
6
+ const type = typeFlag !== -1 && args[typeFlag + 1] === 'mcp' ? 'mcp' : 'standard';
7
+ const outputDir = process.cwd();
8
+
9
+ try {
10
+ await scaffoldExtension(name, type, outputDir);
11
+ console.log(`\nExtension "${name}" created successfully!`);
12
+ console.log(`\nNext steps:`);
13
+ console.log(` cd ${name}`);
14
+ console.log(` pnpm install`);
15
+ console.log(` pnpm run build`);
16
+ } catch (err: unknown) {
17
+ console.error('Failed to create extension:', err instanceof Error ? err.message : String(err));
18
+ process.exit(1);
19
+ }
@@ -0,0 +1,159 @@
1
+ import path from 'node:path';
2
+ import os from 'node:os';
3
+
4
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
5
+ import fse from 'fs-extra';
6
+
7
+ import { scaffoldExtension } from './scaffold.js';
8
+
9
+ describe('scaffoldExtension', () => {
10
+ let tmpDir: string;
11
+
12
+ beforeEach(async () => {
13
+ tmpDir = await fse.mkdtemp(path.join(os.tmpdir(), 'renre-test-'));
14
+ });
15
+
16
+ afterEach(async () => {
17
+ await fse.remove(tmpDir);
18
+ });
19
+
20
+ describe('standard extension', () => {
21
+ it('should create all required files', async () => {
22
+ await scaffoldExtension('test-ext', 'standard', tmpDir);
23
+
24
+ const extDir = path.join(tmpDir, 'test-ext');
25
+ expect(await fse.pathExists(path.join(extDir, 'package.json'))).toBe(true);
26
+ expect(await fse.pathExists(path.join(extDir, 'manifest.json'))).toBe(true);
27
+ expect(await fse.pathExists(path.join(extDir, 'src', 'index.ts'))).toBe(true);
28
+ expect(await fse.pathExists(path.join(extDir, 'src', 'commands', 'hello.ts'))).toBe(true);
29
+ expect(await fse.pathExists(path.join(extDir, 'tsconfig.json'))).toBe(true);
30
+ expect(await fse.pathExists(path.join(extDir, 'build.js'))).toBe(true);
31
+ expect(await fse.pathExists(path.join(extDir, 'SKILL.md'))).toBe(true);
32
+ });
33
+
34
+ it('should generate correct package.json', async () => {
35
+ await scaffoldExtension('my-plugin', 'standard', tmpDir);
36
+
37
+ const pkgPath = path.join(tmpDir, 'my-plugin', 'package.json');
38
+ const pkg = (await fse.readJson(pkgPath)) as Record<string, unknown>;
39
+ expect(pkg['name']).toBe('my-plugin');
40
+ expect(pkg['version']).toBe('0.0.1');
41
+ expect(pkg['type']).toBe('module');
42
+ expect(pkg['main']).toBe('./dist/index.js');
43
+ });
44
+
45
+ it('should generate correct manifest.json', async () => {
46
+ await scaffoldExtension('my-plugin', 'standard', tmpDir);
47
+
48
+ const manifestPath = path.join(tmpDir, 'my-plugin', 'manifest.json');
49
+ const manifest = (await fse.readJson(manifestPath)) as Record<string, unknown>;
50
+ expect(manifest['name']).toBe('my-plugin');
51
+ expect(manifest['type']).toBe('standard');
52
+ expect(manifest['main']).toBe('dist/index.js');
53
+ const commands = manifest['commands'] as Record<string, Record<string, string>>;
54
+ expect(commands['hello']!['handler']).toBe('dist/commands/hello.js');
55
+ });
56
+
57
+ it('should include engines field in manifest', async () => {
58
+ await scaffoldExtension('my-plugin', 'standard', tmpDir);
59
+
60
+ const manifestPath = path.join(tmpDir, 'my-plugin', 'manifest.json');
61
+ const manifest = (await fse.readJson(manifestPath)) as Record<string, unknown>;
62
+ const engines = manifest['engines'] as Record<string, string>;
63
+ expect(engines).toBeDefined();
64
+ expect(engines['renre-kit']).toBe('>=0.0.1');
65
+ expect(engines['extension-sdk']).toBe('>=0.0.1');
66
+ });
67
+
68
+ it('should generate entry point with lifecycle hooks', async () => {
69
+ await scaffoldExtension('my-plugin', 'standard', tmpDir);
70
+
71
+ const entryPath = path.join(tmpDir, 'my-plugin', 'src', 'index.ts');
72
+ const content = await fse.readFile(entryPath, 'utf-8');
73
+ expect(content).toContain('export function onInit');
74
+ expect(content).toContain('export function onDestroy');
75
+ });
76
+
77
+ it('should generate command handler file', async () => {
78
+ await scaffoldExtension('my-plugin', 'standard', tmpDir);
79
+
80
+ const cmdPath = path.join(tmpDir, 'my-plugin', 'src', 'commands', 'hello.ts');
81
+ const content = await fse.readFile(cmdPath, 'utf-8');
82
+ expect(content).toContain('Hello from my-plugin!');
83
+ expect(content).toContain('export default defineCommand');
84
+ });
85
+
86
+ it('should generate SKILL.md with extension name', async () => {
87
+ await scaffoldExtension('my-plugin', 'standard', tmpDir);
88
+
89
+ const skillPath = path.join(tmpDir, 'my-plugin', 'SKILL.md');
90
+ const content = await fse.readFile(skillPath, 'utf-8');
91
+ expect(content).toContain('# my-plugin');
92
+ });
93
+ });
94
+
95
+ describe('mcp extension', () => {
96
+ it('should create all required files', async () => {
97
+ await scaffoldExtension('mcp-ext', 'mcp', tmpDir);
98
+
99
+ const extDir = path.join(tmpDir, 'mcp-ext');
100
+ expect(await fse.pathExists(path.join(extDir, 'package.json'))).toBe(true);
101
+ expect(await fse.pathExists(path.join(extDir, 'manifest.json'))).toBe(true);
102
+ expect(await fse.pathExists(path.join(extDir, 'src', 'server.ts'))).toBe(true);
103
+ expect(await fse.pathExists(path.join(extDir, 'tsconfig.json'))).toBe(true);
104
+ expect(await fse.pathExists(path.join(extDir, 'build.js'))).toBe(true);
105
+ expect(await fse.pathExists(path.join(extDir, 'SKILL.md'))).toBe(true);
106
+ });
107
+
108
+ it('should generate correct manifest with mcp type', async () => {
109
+ await scaffoldExtension('mcp-ext', 'mcp', tmpDir);
110
+
111
+ const manifestPath = path.join(tmpDir, 'mcp-ext', 'manifest.json');
112
+ const manifest = (await fse.readJson(manifestPath)) as Record<string, unknown>;
113
+ expect(manifest['name']).toBe('mcp-ext');
114
+ expect(manifest['type']).toBe('mcp');
115
+ expect(manifest['main']).toBe('dist/index.js');
116
+ expect(manifest['mcp']).toBeDefined();
117
+ });
118
+
119
+ it('should include engines field in manifest', async () => {
120
+ await scaffoldExtension('mcp-ext', 'mcp', tmpDir);
121
+
122
+ const manifestPath = path.join(tmpDir, 'mcp-ext', 'manifest.json');
123
+ const manifest = (await fse.readJson(manifestPath)) as Record<string, string>;
124
+ const engines = manifest['engines'] as unknown as Record<string, string>;
125
+ expect(engines).toBeDefined();
126
+ expect(engines['renre-kit']).toBe('>=0.0.1');
127
+ expect(engines['extension-sdk']).toBe('>=0.0.1');
128
+ });
129
+
130
+ it('should generate server entry point with JSON-RPC handler', async () => {
131
+ await scaffoldExtension('mcp-ext', 'mcp', tmpDir);
132
+
133
+ const serverPath = path.join(tmpDir, 'mcp-ext', 'src', 'server.ts');
134
+ const content = await fse.readFile(serverPath, 'utf-8');
135
+ expect(content).toContain('Hello from mcp-ext!');
136
+ expect(content).toContain('JsonRpcRequest');
137
+ expect(content).toContain('handleRequest');
138
+ expect(content).toContain('createInterface');
139
+ });
140
+
141
+ it('should generate package.json with server as main', async () => {
142
+ await scaffoldExtension('mcp-ext', 'mcp', tmpDir);
143
+
144
+ const pkgPath = path.join(tmpDir, 'mcp-ext', 'package.json');
145
+ const pkg = (await fse.readJson(pkgPath)) as Record<string, unknown>;
146
+ expect(pkg['main']).toBe('./dist/server.js');
147
+ });
148
+ });
149
+
150
+ describe('error handling', () => {
151
+ it('should throw if directory already exists', async () => {
152
+ await fse.ensureDir(path.join(tmpDir, 'existing-ext'));
153
+
154
+ await expect(scaffoldExtension('existing-ext', 'standard', tmpDir)).rejects.toThrow(
155
+ 'Directory "existing-ext" already exists',
156
+ );
157
+ });
158
+ });
159
+ });
@@ -0,0 +1,73 @@
1
+ import path from 'node:path';
2
+
3
+ import fse from 'fs-extra';
4
+
5
+ import {
6
+ getStandardPackageJson,
7
+ getStandardManifest,
8
+ getStandardEntryPoint,
9
+ getStandardCommandHandler,
10
+ getStandardTsconfig,
11
+ getStandardSkillMd,
12
+ getStandardBuildJs,
13
+ } from './templates/standard.js';
14
+ import {
15
+ getMcpPackageJson,
16
+ getMcpManifest,
17
+ getMcpServerEntryPoint,
18
+ getMcpTsconfig,
19
+ getMcpSkillMd,
20
+ getMcpBuildJs,
21
+ } from './templates/mcp.js';
22
+
23
+ export type ExtensionType = 'standard' | 'mcp';
24
+
25
+ interface FileEntry {
26
+ filePath: string;
27
+ content: string;
28
+ }
29
+
30
+ function getStandardFiles(name: string, extDir: string): FileEntry[] {
31
+ return [
32
+ { filePath: path.join(extDir, 'package.json'), content: getStandardPackageJson(name) },
33
+ { filePath: path.join(extDir, 'manifest.json'), content: getStandardManifest(name) },
34
+ { filePath: path.join(extDir, 'src', 'index.ts'), content: getStandardEntryPoint(name) },
35
+ {
36
+ filePath: path.join(extDir, 'src', 'commands', 'hello.ts'),
37
+ content: getStandardCommandHandler(name),
38
+ },
39
+ { filePath: path.join(extDir, 'tsconfig.json'), content: getStandardTsconfig() },
40
+ { filePath: path.join(extDir, 'build.js'), content: getStandardBuildJs() },
41
+ { filePath: path.join(extDir, 'SKILL.md'), content: getStandardSkillMd(name) },
42
+ ];
43
+ }
44
+
45
+ function getMcpFiles(name: string, extDir: string): FileEntry[] {
46
+ return [
47
+ { filePath: path.join(extDir, 'package.json'), content: getMcpPackageJson(name) },
48
+ { filePath: path.join(extDir, 'manifest.json'), content: getMcpManifest(name) },
49
+ { filePath: path.join(extDir, 'src', 'server.ts'), content: getMcpServerEntryPoint(name) },
50
+ { filePath: path.join(extDir, 'tsconfig.json'), content: getMcpTsconfig() },
51
+ { filePath: path.join(extDir, 'build.js'), content: getMcpBuildJs() },
52
+ { filePath: path.join(extDir, 'SKILL.md'), content: getMcpSkillMd(name) },
53
+ ];
54
+ }
55
+
56
+ export async function scaffoldExtension(
57
+ name: string,
58
+ type: ExtensionType,
59
+ outputDir: string,
60
+ ): Promise<void> {
61
+ const extDir = path.join(outputDir, name);
62
+
63
+ if (await fse.pathExists(extDir)) {
64
+ throw new Error(`Directory "${name}" already exists`);
65
+ }
66
+
67
+ const files = type === 'mcp' ? getMcpFiles(name, extDir) : getStandardFiles(name, extDir);
68
+
69
+ for (const file of files) {
70
+ await fse.ensureDir(path.dirname(file.filePath));
71
+ await fse.writeFile(file.filePath, file.content, 'utf-8');
72
+ }
73
+ }
@@ -0,0 +1,112 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ import {
4
+ getMcpPackageJson,
5
+ getMcpManifest,
6
+ getMcpServerEntryPoint,
7
+ getMcpTsconfig,
8
+ getMcpBuildJs,
9
+ getMcpSkillMd,
10
+ } from './mcp.js';
11
+
12
+ describe('mcp templates', () => {
13
+ describe('getMcpPackageJson', () => {
14
+ it('returns valid JSON with correct name and mcp description', () => {
15
+ const result = JSON.parse(getMcpPackageJson('mcp-ext')) as Record<string, unknown>;
16
+ expect(result['name']).toBe('mcp-ext');
17
+ expect(result['version']).toBe('0.0.1');
18
+ expect(result['type']).toBe('module');
19
+ expect(result['main']).toBe('./dist/server.js');
20
+ });
21
+
22
+ it('includes esbuild and extension-sdk dependencies', () => {
23
+ const result = JSON.parse(getMcpPackageJson('mcp-ext')) as Record<string, unknown>;
24
+ const deps = result['dependencies'] as Record<string, string>;
25
+ const devDeps = result['devDependencies'] as Record<string, string>;
26
+ expect(deps['@renre-kit/extension-sdk']).toBe('>=0.0.1');
27
+ expect(devDeps['esbuild']).toBe('^0.21.0');
28
+ });
29
+
30
+ it('uses node build.js as build script', () => {
31
+ const result = JSON.parse(getMcpPackageJson('mcp-ext')) as Record<string, unknown>;
32
+ const scripts = result['scripts'] as Record<string, string>;
33
+ expect(scripts['build']).toBe('node build.js');
34
+ });
35
+ });
36
+
37
+ describe('getMcpManifest', () => {
38
+ it('returns valid JSON with mcp type and transport config', () => {
39
+ const result = JSON.parse(getMcpManifest('mcp-ext')) as Record<string, unknown>;
40
+ expect(result['name']).toBe('mcp-ext');
41
+ expect(result['type']).toBe('mcp');
42
+ expect(result['engines']).toBeDefined();
43
+ const mcp = result['mcp'] as Record<string, unknown>;
44
+ expect(mcp['transport']).toBe('stdio');
45
+ expect(mcp['command']).toBe('node');
46
+ });
47
+ });
48
+
49
+ describe('getMcpServerEntryPoint', () => {
50
+ it('includes JSON-RPC handler with extension name', () => {
51
+ const result = getMcpServerEntryPoint('mcp-ext');
52
+ expect(result).toContain('JsonRpcRequest');
53
+ expect(result).toContain('handleRequest');
54
+ expect(result).toContain('Hello from mcp-ext!');
55
+ expect(result).toContain('createInterface');
56
+ });
57
+ });
58
+
59
+ describe('getMcpTsconfig', () => {
60
+ it('returns valid JSON', () => {
61
+ const result = JSON.parse(getMcpTsconfig()) as Record<string, unknown>;
62
+ expect(result['compilerOptions']).toBeDefined();
63
+ });
64
+ });
65
+
66
+ describe('getMcpBuildJs', () => {
67
+ it('imports buildExtension and archiveDist from SDK', () => {
68
+ const result = getMcpBuildJs();
69
+ expect(result).toContain(
70
+ "import { buildExtension, archiveDist } from '@renre-kit/extension-sdk/node'",
71
+ );
72
+ });
73
+
74
+ it('cleans dist before building', () => {
75
+ const result = getMcpBuildJs();
76
+ expect(result).toContain("rmSync('dist', { recursive: true, force: true })");
77
+ });
78
+
79
+ it('reads manifest for version', () => {
80
+ const result = getMcpBuildJs();
81
+ expect(result).toContain("readFileSync('manifest.json', 'utf-8')");
82
+ });
83
+
84
+ it('includes server entry point', () => {
85
+ const result = getMcpBuildJs();
86
+ expect(result).toContain("{ in: 'src/server.ts', out: 'server' }");
87
+ });
88
+
89
+ it('enables code splitting', () => {
90
+ const result = getMcpBuildJs();
91
+ expect(result).toContain('splitting: true');
92
+ });
93
+
94
+ it('archives dist with version', () => {
95
+ const result = getMcpBuildJs();
96
+ expect(result).toContain('await archiveDist');
97
+ });
98
+
99
+ it('outputs to dist directory', () => {
100
+ const result = getMcpBuildJs();
101
+ expect(result).toContain("outdir: 'dist'");
102
+ });
103
+ });
104
+
105
+ describe('getMcpSkillMd', () => {
106
+ it('includes extension name and MCP description', () => {
107
+ const result = getMcpSkillMd('mcp-ext');
108
+ expect(result).toContain('# mcp-ext');
109
+ expect(result).toContain('MCP');
110
+ });
111
+ });
112
+ });
@@ -0,0 +1,149 @@
1
+ export function getMcpPackageJson(name: string): string {
2
+ const pkg = {
3
+ name,
4
+ version: '0.0.1',
5
+ description: `A RenreKit MCP extension: ${name}`,
6
+ type: 'module',
7
+ main: './dist/server.js',
8
+ scripts: {
9
+ build: 'node build.js',
10
+ dev: 'tsc --watch',
11
+ },
12
+ dependencies: {
13
+ '@renre-kit/extension-sdk': '>=0.0.1',
14
+ },
15
+ devDependencies: {
16
+ esbuild: '^0.21.0',
17
+ typescript: '^5.7.0',
18
+ },
19
+ };
20
+ return `${JSON.stringify(pkg, null, 2) }\n`;
21
+ }
22
+
23
+ export function getMcpManifest(name: string): string {
24
+ const manifest = {
25
+ name,
26
+ title: name,
27
+ version: '0.0.1',
28
+ description: 'A RenreKit MCP extension',
29
+ type: 'mcp',
30
+ main: 'dist/index.js',
31
+ engines: {
32
+ 'renre-kit': '>=0.0.1',
33
+ 'extension-sdk': '>=0.0.1',
34
+ },
35
+ commands: {},
36
+ mcp: {
37
+ transport: 'stdio',
38
+ command: 'node',
39
+ args: ['dist/server.js'],
40
+ },
41
+ };
42
+ return `${JSON.stringify(manifest, null, 2) }\n`;
43
+ }
44
+
45
+ export function getMcpServerEntryPoint(name: string): string {
46
+ return `import { createInterface } from 'node:readline';
47
+
48
+ interface JsonRpcRequest {
49
+ jsonrpc: string;
50
+ id: number | string;
51
+ method: string;
52
+ params?: Record<string, unknown>;
53
+ }
54
+
55
+ interface JsonRpcResponse {
56
+ jsonrpc: string;
57
+ id: number | string;
58
+ result?: unknown;
59
+ error?: { code: number; message: string };
60
+ }
61
+
62
+ function handleRequest(request: JsonRpcRequest): JsonRpcResponse {
63
+ switch (request.method) {
64
+ case 'hello':
65
+ return {
66
+ jsonrpc: '2.0',
67
+ id: request.id,
68
+ result: { output: 'Hello from ${name}!', exitCode: 0 },
69
+ };
70
+ default:
71
+ return {
72
+ jsonrpc: '2.0',
73
+ id: request.id,
74
+ error: { code: -32601, message: \`Method not found: \${request.method}\` },
75
+ };
76
+ }
77
+ }
78
+
79
+ const rl = createInterface({ input: process.stdin });
80
+
81
+ rl.on('line', (line: string) => {
82
+ try {
83
+ const request = JSON.parse(line) as JsonRpcRequest;
84
+ const response = handleRequest(request);
85
+ process.stdout.write(JSON.stringify(response) + '\\n');
86
+ } catch {
87
+ const errorResponse: JsonRpcResponse = {
88
+ jsonrpc: '2.0',
89
+ id: 0,
90
+ error: { code: -32700, message: 'Parse error' },
91
+ };
92
+ process.stdout.write(JSON.stringify(errorResponse) + '\\n');
93
+ }
94
+ });
95
+ `;
96
+ }
97
+
98
+ export { getTsconfig as getMcpTsconfig } from './shared.js';
99
+
100
+ export function getMcpBuildJs(): string {
101
+ return `import { readFileSync, rmSync } from 'node:fs';
102
+
103
+ import { buildExtension, archiveDist } from '@renre-kit/extension-sdk/node';
104
+
105
+ rmSync('dist', { recursive: true, force: true });
106
+
107
+ const manifest = JSON.parse(readFileSync('manifest.json', 'utf-8'));
108
+
109
+ await buildExtension({
110
+ entryPoints: [
111
+ { in: 'src/server.ts', out: 'server' },
112
+ ],
113
+ outdir: 'dist',
114
+ external: [],
115
+ splitting: true,
116
+ });
117
+
118
+ await archiveDist('dist', manifest.version);
119
+ `;
120
+ }
121
+
122
+ export function getMcpSkillMd(name: string): string {
123
+ return `---
124
+ name: hello
125
+ description: This tool should be used when the user wants to say hello or test the ${name} MCP extension
126
+ ---
127
+
128
+ # ${name}
129
+
130
+ ## Description
131
+
132
+ A RenreKit MCP extension that communicates via JSON-RPC over stdio.
133
+
134
+ ## Commands
135
+
136
+ ### hello
137
+
138
+ Say hello from the MCP extension.
139
+
140
+ **Usage:**
141
+ \`\`\`
142
+ renre ${name} hello
143
+ \`\`\`
144
+
145
+ ## Configuration
146
+
147
+ No configuration required.
148
+ `;
149
+ }
@@ -0,0 +1,19 @@
1
+ /** Generates a tsconfig.json for any extension type. */
2
+ export function getTsconfig(): string {
3
+ const config = {
4
+ compilerOptions: {
5
+ target: 'ES2022',
6
+ module: 'NodeNext',
7
+ moduleResolution: 'NodeNext',
8
+ declaration: true,
9
+ outDir: './dist',
10
+ rootDir: './src',
11
+ strict: true,
12
+ esModuleInterop: true,
13
+ skipLibCheck: true,
14
+ },
15
+ include: ['src'],
16
+ exclude: ['dist', 'node_modules'],
17
+ };
18
+ return `${JSON.stringify(config, null, 2) }\n`;
19
+ }
@@ -0,0 +1,117 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ import {
4
+ getStandardPackageJson,
5
+ getStandardManifest,
6
+ getStandardEntryPoint,
7
+ getStandardCommandHandler,
8
+ getStandardTsconfig,
9
+ getStandardBuildJs,
10
+ getStandardSkillMd,
11
+ } from './standard.js';
12
+
13
+ describe('standard templates', () => {
14
+ describe('getStandardPackageJson', () => {
15
+ it('returns valid JSON with correct name', () => {
16
+ const result = JSON.parse(getStandardPackageJson('my-ext')) as Record<string, unknown>;
17
+ expect(result['name']).toBe('my-ext');
18
+ expect(result['version']).toBe('0.0.1');
19
+ expect(result['type']).toBe('module');
20
+ expect(result['main']).toBe('./dist/index.js');
21
+ });
22
+
23
+ it('includes esbuild and extension-sdk dependencies', () => {
24
+ const result = JSON.parse(getStandardPackageJson('my-ext')) as Record<string, unknown>;
25
+ const deps = result['dependencies'] as Record<string, string>;
26
+ const devDeps = result['devDependencies'] as Record<string, string>;
27
+ expect(deps['@renre-kit/extension-sdk']).toBe('>=0.0.1');
28
+ expect(devDeps['esbuild']).toBe('^0.21.0');
29
+ });
30
+
31
+ it('uses node build.js as build script', () => {
32
+ const result = JSON.parse(getStandardPackageJson('my-ext')) as Record<string, unknown>;
33
+ const scripts = result['scripts'] as Record<string, string>;
34
+ expect(scripts['build']).toBe('node build.js');
35
+ });
36
+ });
37
+
38
+ describe('getStandardManifest', () => {
39
+ it('returns valid JSON with correct structure', () => {
40
+ const result = JSON.parse(getStandardManifest('my-ext')) as Record<string, unknown>;
41
+ expect(result['name']).toBe('my-ext');
42
+ expect(result['type']).toBe('standard');
43
+ expect(result['engines']).toBeDefined();
44
+ });
45
+ });
46
+
47
+ describe('getStandardEntryPoint', () => {
48
+ it('includes lifecycle hooks with extension name', () => {
49
+ const result = getStandardEntryPoint('my-ext');
50
+ expect(result).toContain('export function onInit');
51
+ expect(result).toContain('export function onDestroy');
52
+ expect(result).toContain('my-ext');
53
+ });
54
+ });
55
+
56
+ describe('getStandardCommandHandler', () => {
57
+ it('generates a defineCommand export with name', () => {
58
+ const result = getStandardCommandHandler('my-ext');
59
+ expect(result).toContain('export default defineCommand');
60
+ expect(result).toContain('Hello from my-ext!');
61
+ });
62
+ });
63
+
64
+ describe('getStandardTsconfig', () => {
65
+ it('returns valid JSON', () => {
66
+ const result = JSON.parse(getStandardTsconfig()) as Record<string, unknown>;
67
+ expect(result['compilerOptions']).toBeDefined();
68
+ });
69
+ });
70
+
71
+ describe('getStandardBuildJs', () => {
72
+ it('imports buildExtension and archiveDist from SDK', () => {
73
+ const result = getStandardBuildJs();
74
+ expect(result).toContain(
75
+ "import { buildExtension, archiveDist } from '@renre-kit/extension-sdk/node'",
76
+ );
77
+ });
78
+
79
+ it('cleans dist before building', () => {
80
+ const result = getStandardBuildJs();
81
+ expect(result).toContain("rmSync('dist', { recursive: true, force: true })");
82
+ });
83
+
84
+ it('reads manifest for version', () => {
85
+ const result = getStandardBuildJs();
86
+ expect(result).toContain("readFileSync('manifest.json', 'utf-8')");
87
+ });
88
+
89
+ it('includes index and commands/hello entry points', () => {
90
+ const result = getStandardBuildJs();
91
+ expect(result).toContain("{ in: 'src/index.ts', out: 'index' }");
92
+ expect(result).toContain("{ in: 'src/commands/hello.ts', out: 'commands/hello' }");
93
+ });
94
+
95
+ it('enables code splitting', () => {
96
+ const result = getStandardBuildJs();
97
+ expect(result).toContain('splitting: true');
98
+ });
99
+
100
+ it('archives dist with version', () => {
101
+ const result = getStandardBuildJs();
102
+ expect(result).toContain('await archiveDist');
103
+ });
104
+
105
+ it('outputs to dist directory', () => {
106
+ const result = getStandardBuildJs();
107
+ expect(result).toContain("outdir: 'dist'");
108
+ });
109
+ });
110
+
111
+ describe('getStandardSkillMd', () => {
112
+ it('includes extension name', () => {
113
+ const result = getStandardSkillMd('my-ext');
114
+ expect(result).toContain('# my-ext');
115
+ });
116
+ });
117
+ });
@@ -0,0 +1,123 @@
1
+ export function getStandardPackageJson(name: string): string {
2
+ const pkg = {
3
+ name,
4
+ version: '0.0.1',
5
+ description: `A RenreKit extension: ${name}`,
6
+ type: 'module',
7
+ main: './dist/index.js',
8
+ scripts: {
9
+ build: 'node build.js',
10
+ dev: 'tsc --watch',
11
+ },
12
+ dependencies: {
13
+ '@renre-kit/extension-sdk': '>=0.0.1',
14
+ },
15
+ devDependencies: {
16
+ esbuild: '^0.21.0',
17
+ typescript: '^5.7.0',
18
+ },
19
+ };
20
+ return `${JSON.stringify(pkg, null, 2) }\n`;
21
+ }
22
+
23
+ export function getStandardManifest(name: string): string {
24
+ const manifest = {
25
+ name,
26
+ title: name,
27
+ version: '0.0.1',
28
+ description: 'A RenreKit extension',
29
+ type: 'standard',
30
+ main: 'dist/index.js',
31
+ engines: {
32
+ 'renre-kit': '>=0.0.1',
33
+ 'extension-sdk': '>=0.0.1',
34
+ },
35
+ commands: {
36
+ hello: {
37
+ handler: 'dist/commands/hello.js',
38
+ description: 'Say hello',
39
+ },
40
+ },
41
+ };
42
+ return `${JSON.stringify(manifest, null, 2) }\n`;
43
+ }
44
+
45
+ export function getStandardEntryPoint(name: string): string {
46
+ return `import type { HookContext } from '@renre-kit/extension-sdk/node';
47
+
48
+ export function onInit(context: HookContext): void {
49
+ context.sdk.deployAgentAssets();
50
+ console.log('${name} initialized in', context.projectDir);
51
+ }
52
+
53
+ export function onDestroy(context: HookContext): void {
54
+ context.sdk.cleanupAgentAssets();
55
+ console.log('${name} destroyed in', context.projectDir);
56
+ }
57
+ `;
58
+ }
59
+
60
+ export function getStandardCommandHandler(name: string): string {
61
+ return `import { defineCommand } from '@renre-kit/extension-sdk/node';
62
+
63
+ export default defineCommand({
64
+ handler: () => {
65
+ return { output: 'Hello from ${name}!', exitCode: 0 };
66
+ },
67
+ });
68
+ `;
69
+ }
70
+
71
+ export { getTsconfig as getStandardTsconfig } from './shared.js';
72
+
73
+ export function getStandardBuildJs(): string {
74
+ return `import { readFileSync, rmSync } from 'node:fs';
75
+
76
+ import { buildExtension, archiveDist } from '@renre-kit/extension-sdk/node';
77
+
78
+ rmSync('dist', { recursive: true, force: true });
79
+
80
+ const manifest = JSON.parse(readFileSync('manifest.json', 'utf-8'));
81
+
82
+ await buildExtension({
83
+ entryPoints: [
84
+ { in: 'src/index.ts', out: 'index' },
85
+ { in: 'src/commands/hello.ts', out: 'commands/hello' },
86
+ ],
87
+ outdir: 'dist',
88
+ external: [],
89
+ splitting: true,
90
+ });
91
+
92
+ await archiveDist('dist', manifest.version);
93
+ `;
94
+ }
95
+
96
+ export function getStandardSkillMd(name: string): string {
97
+ return `---
98
+ name: hello
99
+ description: This tool should be used when the user wants to say hello or get a greeting from the ${name} extension
100
+ ---
101
+
102
+ # ${name}
103
+
104
+ ## Description
105
+
106
+ A RenreKit extension that provides additional functionality.
107
+
108
+ ## Commands
109
+
110
+ ### hello
111
+
112
+ Say hello from the extension.
113
+
114
+ **Usage:**
115
+ \`\`\`
116
+ renre ${name} hello
117
+ \`\`\`
118
+
119
+ ## Configuration
120
+
121
+ No configuration required.
122
+ `;
123
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "module": "NodeNext",
7
+ "moduleResolution": "NodeNext"
8
+ },
9
+ "include": ["src"],
10
+ "exclude": ["src/**/*.test.ts", "dist", "node_modules"]
11
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "module": "NodeNext",
7
+ "moduleResolution": "NodeNext",
8
+ "noEmit": true
9
+ },
10
+ "include": ["src"],
11
+ "exclude": ["dist", "node_modules"]
12
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['esm'],
6
+ dts: true,
7
+ clean: true,
8
+ sourcemap: true,
9
+ target: 'es2022',
10
+ outDir: 'dist',
11
+ banner: {
12
+ js: '#!/usr/bin/env node',
13
+ },
14
+ });
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ import { createCoverageConfig } from '../../vitest.shared.js';
3
+
4
+ export default defineConfig({
5
+ test: {
6
+ globals: true,
7
+ passWithNoTests: true,
8
+ include: ['src/**/*.test.ts'],
9
+ ...createCoverageConfig(),
10
+ },
11
+ });