create-glosc 0.2.2 → 0.3.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 CHANGED
@@ -69,6 +69,19 @@ npm run build
69
69
  npm start
70
70
  ```
71
71
 
72
+ ### 发布到 Glosc Store(打包 ZIP)
73
+
74
+ CLI 创建的项目会默认:
75
+
76
+ - 生成 `.gitignore`
77
+ - 尝试自动执行 `git init`(如果本机已安装 Git)
78
+
79
+ 打包时会**只包含未被 `.gitignore` 忽略**的文件,输出到 `dist/*.zip`:
80
+
81
+ ```sh
82
+ npm run package
83
+ ```
84
+
72
85
  ### 本地开发(维护此 CLI)
73
86
 
74
87
  ```sh
package/dist/scaffold.js CHANGED
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.scaffoldProject = scaffoldProject;
37
37
  const fs = __importStar(require("node:fs/promises"));
38
38
  const path = __importStar(require("node:path"));
39
+ const node_child_process_1 = require("node:child_process");
39
40
  const templates_1 = require("./templates");
40
41
  async function pathExists(p) {
41
42
  try {
@@ -53,6 +54,27 @@ async function writeFileEnsuringDir(filePath, content) {
53
54
  await ensureDir(path.dirname(filePath));
54
55
  await fs.writeFile(filePath, content, "utf8");
55
56
  }
57
+ function tryInitGitRepo(targetRoot) {
58
+ try {
59
+ const version = (0, node_child_process_1.spawnSync)("git", ["--version"], {
60
+ cwd: targetRoot,
61
+ stdio: "ignore",
62
+ shell: false,
63
+ });
64
+ if (version.status !== 0)
65
+ return;
66
+ const init = (0, node_child_process_1.spawnSync)("git", ["init"], {
67
+ cwd: targetRoot,
68
+ stdio: "ignore",
69
+ shell: false,
70
+ });
71
+ if (init.status !== 0)
72
+ return;
73
+ }
74
+ catch {
75
+ // best-effort: do not block scaffolding if git isn't available
76
+ }
77
+ }
56
78
  async function scaffoldProject(options) {
57
79
  const targetRoot = path.resolve(process.cwd(), options.projectName);
58
80
  if (await pathExists(targetRoot)) {
@@ -64,6 +86,7 @@ async function scaffoldProject(options) {
64
86
  const absPath = path.join(targetRoot, file.relativePath);
65
87
  await writeFileEnsuringDir(absPath, file.content);
66
88
  }
89
+ tryInitGitRepo(targetRoot);
67
90
  // eslint-disable-next-line no-console
68
91
  console.log(`\nCreated project at: ${targetRoot}`);
69
92
  }
package/dist/templates.js CHANGED
@@ -1,6 +1,206 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getProjectFiles = getProjectFiles;
4
+ function gitignoreContent(language) {
5
+ const common = [
6
+ "# OS",
7
+ ".DS_Store",
8
+ "Thumbs.db",
9
+ "Desktop.ini",
10
+ "",
11
+ "# Editors",
12
+ ".vscode/",
13
+ ".idea/",
14
+ "*.swp",
15
+ "",
16
+ "# Env",
17
+ ".env",
18
+ ".env.*",
19
+ "",
20
+ "# Logs",
21
+ "*.log",
22
+ "",
23
+ "# Builds / artifacts",
24
+ "dist/",
25
+ "build/",
26
+ "*.zip",
27
+ "",
28
+ ];
29
+ const node = [
30
+ "# Node",
31
+ "node_modules/",
32
+ "npm-debug.log*",
33
+ "yarn-debug.log*",
34
+ "yarn-error.log*",
35
+ "pnpm-debug.log*",
36
+ "",
37
+ ];
38
+ const python = [
39
+ "# Python",
40
+ "__pycache__/",
41
+ "*.py[cod]",
42
+ "*.pyd",
43
+ ".Python",
44
+ ".venv/",
45
+ "venv/",
46
+ "ENV/",
47
+ "env/",
48
+ ".pytest_cache/",
49
+ ".mypy_cache/",
50
+ ".ruff_cache/",
51
+ "*.egg-info/",
52
+ "",
53
+ ];
54
+ return (common.join("\n") +
55
+ (language === "typescript" ? node.join("\n") : python.join("\n")));
56
+ }
57
+ function packagingScriptMjs() {
58
+ // Keep this script dependency-free and avoid nested template literals.
59
+ return [
60
+ 'import { spawnSync } from "node:child_process";',
61
+ 'import * as fsp from "node:fs/promises";',
62
+ 'import * as path from "node:path";',
63
+ "",
64
+ "function run(cmd, args, opts = {}) {",
65
+ " return spawnSync(cmd, args, {",
66
+ ' stdio: ["ignore", "pipe", "pipe"],',
67
+ ' encoding: "utf8",',
68
+ " shell: false,",
69
+ " ...opts,",
70
+ " });",
71
+ "}",
72
+ "",
73
+ "function hasCommand(cmd) {",
74
+ ' if (process.platform === "win32") {',
75
+ ' const r = run("where", [cmd]);',
76
+ " return r.status === 0;",
77
+ " }",
78
+ ' const check = "command -v " + cmd + " >/dev/null 2>&1";',
79
+ ' const r = run("sh", ["-lc", check]);',
80
+ " return r.status === 0;",
81
+ "}",
82
+ "",
83
+ "function escapePsSingleQuotes(value) {",
84
+ ' return String(value || "").replace(/\'/g, "\'\'");',
85
+ "}",
86
+ "",
87
+ "function gitFileList() {",
88
+ ' const r = run("git", [',
89
+ ' "ls-files",',
90
+ ' "-z",',
91
+ ' "--cached",',
92
+ ' "--others",',
93
+ ' "--exclude-standard",',
94
+ " ]);",
95
+ "",
96
+ " if (r.status !== 0) {",
97
+ ' const err = (r.stderr || r.stdout || "").trim();',
98
+ " throw new Error(",
99
+ ' "git is required for packaging (and to respect .gitignore).\\n" + err,',
100
+ " );",
101
+ " }",
102
+ "",
103
+ ' const raw = r.stdout || "";',
104
+ ' return raw.split("\\u0000").filter(Boolean);',
105
+ "}",
106
+ "",
107
+ "async function ensureDir(p) {",
108
+ " await fsp.mkdir(p, { recursive: true });",
109
+ "}",
110
+ "",
111
+ "async function writeTempFileList(filePaths) {",
112
+ ' const tmpDir = path.join(process.cwd(), ".glosc-tmp");',
113
+ " await ensureDir(tmpDir);",
114
+ ' const listPath = path.join(tmpDir, "zip-file-list.txt");',
115
+ ' await fsp.writeFile(listPath, filePaths.join("\\n"), "utf8");',
116
+ " return listPath;",
117
+ "}",
118
+ "",
119
+ "function timestamp() {",
120
+ " const d = new Date();",
121
+ ' const pad = (n) => String(n).padStart(2, "0");',
122
+ " return (",
123
+ " String(d.getFullYear()) +",
124
+ " pad(d.getMonth() + 1) +",
125
+ " pad(d.getDate()) +",
126
+ ' "-" +',
127
+ " pad(d.getHours()) +",
128
+ " pad(d.getMinutes()) +",
129
+ " pad(d.getSeconds())",
130
+ " );",
131
+ "}",
132
+ "",
133
+ "async function main() {",
134
+ " const files = gitFileList();",
135
+ " if (files.length === 0) {",
136
+ " throw new Error(",
137
+ ' "No files found to package. If you just created the project, make sure git is initialized and .gitignore exists.",',
138
+ " );",
139
+ " }",
140
+ "",
141
+ ' const outDir = path.join(process.cwd(), "dist");',
142
+ " await ensureDir(outDir);",
143
+ ' const outZip = path.join(outDir, "glosc-package-" + timestamp() + ".zip");',
144
+ "",
145
+ " const listPath = await writeTempFileList(files);",
146
+ "",
147
+ " try {",
148
+ ' if (hasCommand("powershell")) {',
149
+ " const listEsc = escapePsSingleQuotes(listPath);",
150
+ " const outEsc = escapePsSingleQuotes(outZip);",
151
+ " const psCommand = [",
152
+ ' "$paths = Get-Content -LiteralPath \\\'" + listEsc + "\\\';",',
153
+ ' "Compress-Archive -LiteralPath $paths -DestinationPath \\\'" + outEsc + "\\\' -Force -CompressionLevel Optimal;",',
154
+ ' ].join(" ");',
155
+ " const psArgs = [",
156
+ ' "-NoProfile",',
157
+ ' "-ExecutionPolicy",',
158
+ ' "Bypass",',
159
+ ' "-Command",',
160
+ " psCommand,",
161
+ " ];",
162
+ ' const r = run("powershell", psArgs, { cwd: process.cwd() });',
163
+ ' if (r.status !== 0) throw new Error((r.stderr || r.stdout || "").trim());',
164
+ ' } else if (hasCommand("pwsh")) {',
165
+ " const listEsc = escapePsSingleQuotes(listPath);",
166
+ " const outEsc = escapePsSingleQuotes(outZip);",
167
+ " const psCommand = [",
168
+ ' "$paths = Get-Content -LiteralPath \\\'" + listEsc + "\\\';",',
169
+ ' "Compress-Archive -LiteralPath $paths -DestinationPath \\\'" + outEsc + "\\\' -Force -CompressionLevel Optimal;",',
170
+ ' ].join(" ");',
171
+ ' const psArgs = ["-NoProfile", "-Command", psCommand];',
172
+ ' const r = run("pwsh", psArgs, { cwd: process.cwd() });',
173
+ ' if (r.status !== 0) throw new Error((r.stderr || r.stdout || "").trim());',
174
+ ' } else if (hasCommand("zip")) {',
175
+ ' const r = spawnSync("zip", ["-q", "-r", outZip, "-@"], {',
176
+ " cwd: process.cwd(),",
177
+ ' input: files.join("\\n"),',
178
+ ' stdio: ["pipe", "pipe", "pipe"],',
179
+ ' encoding: "utf8",',
180
+ " });",
181
+ ' if (r.status !== 0) throw new Error((r.stderr || r.stdout || "").trim());',
182
+ " } else {",
183
+ " throw new Error(",
184
+ ' "No zip backend found. Install Git + PowerShell (Windows) or `zip` (macOS/Linux), then rerun.",',
185
+ " );",
186
+ " }",
187
+ " } finally {",
188
+ " try {",
189
+ " await fsp.rm(path.dirname(listPath), { recursive: true, force: true });",
190
+ " } catch {",
191
+ " // ignore",
192
+ " }",
193
+ " }",
194
+ "",
195
+ ' console.log("Created: " + outZip);',
196
+ "}",
197
+ "",
198
+ "main().catch((err) => {",
199
+ " console.error(String(err?.message || err));",
200
+ " process.exit(1);",
201
+ "});",
202
+ ].join("\n");
203
+ }
4
204
  function entryPath(options) {
5
205
  return options.language === "python"
6
206
  ? options.mainFileName
@@ -25,6 +225,20 @@ function projectReadme(options) {
25
225
  : `## Run (TypeScript)\n\n\n\n1) Install deps\n\n\n\n\`\`\`sh\nnpm install\n\`\`\`\n\n\n\n2) Build\n\n\n\n\`\`\`sh\nnpm run build\n\`\`\`\n\n\n\n3) Run the MCP server (stdio)\n\n\n\n\`\`\`sh\nnpm start\n\`\`\`\n\n\n\nThis server speaks MCP over stdio. Connect using an MCP client (e.g. an editor integration).\n`;
26
226
  return `# ${projectName}\n\n${description || ""}\n\n## Author\n\n${author || ""}\n\n## Language\n\n${langLabel}\n\n## Entry\n\n- ${entry}\n\n## MCP Tools\n\n- get_current_time: Returns the current time (UTC, ISO 8601)\n\n${runSection}\n\n## Config\n\n- config.yml\n`;
27
227
  }
228
+ function packagingPackageJson(options) {
229
+ const { projectName, description, author } = options;
230
+ const pkg = {
231
+ name: projectName,
232
+ version: "0.1.0",
233
+ description: description || "",
234
+ author: author || "",
235
+ private: true,
236
+ scripts: {
237
+ package: "node scripts/package.mjs",
238
+ },
239
+ };
240
+ return JSON.stringify(pkg, null, 2) + "\n";
241
+ }
28
242
  function configYml(options) {
29
243
  const { projectName, description, author, language, mainFileName } = options;
30
244
  return [
@@ -66,6 +280,7 @@ function nodePackageJson(options) {
66
280
  scripts: {
67
281
  build: "tsc -p .",
68
282
  start: `node ${distEntry}`,
283
+ package: "node scripts/package.mjs",
69
284
  },
70
285
  dependencies: {
71
286
  "@modelcontextprotocol/sdk": "^1.24.3",
@@ -100,6 +315,14 @@ function tsMain({ projectName }) {
100
315
  }
101
316
  function getProjectFiles(options) {
102
317
  const files = [];
318
+ files.push({
319
+ relativePath: ".gitignore",
320
+ content: gitignoreContent(options.language),
321
+ });
322
+ files.push({
323
+ relativePath: "scripts/package.mjs",
324
+ content: packagingScriptMjs(),
325
+ });
103
326
  files.push({ relativePath: "config.yml", content: configYml(options) });
104
327
  if (options.readme) {
105
328
  files.push({
@@ -126,6 +349,10 @@ function getProjectFiles(options) {
126
349
  relativePath: "pyproject.toml",
127
350
  content: pythonPyproject(options),
128
351
  });
352
+ files.push({
353
+ relativePath: "package.json",
354
+ content: packagingPackageJson(options),
355
+ });
129
356
  }
130
357
  if (options.language === "typescript") {
131
358
  files.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-glosc",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Scaffold Glosc projects (Python/TypeScript) via npm create",
5
5
  "author": "glosc-ai",
6
6
  "license": "MIT",
package/src/scaffold.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
+ import { spawnSync } from "node:child_process";
3
4
  import { getProjectFiles, type ProjectOptions } from "./templates";
4
5
 
5
6
  async function pathExists(p: string): Promise<boolean> {
@@ -23,6 +24,28 @@ async function writeFileEnsuringDir(
23
24
  await fs.writeFile(filePath, content, "utf8");
24
25
  }
25
26
 
27
+ function tryInitGitRepo(targetRoot: string): void {
28
+ try {
29
+ const version = spawnSync("git", ["--version"], {
30
+ cwd: targetRoot,
31
+ stdio: "ignore",
32
+ shell: false,
33
+ });
34
+
35
+ if (version.status !== 0) return;
36
+
37
+ const init = spawnSync("git", ["init"], {
38
+ cwd: targetRoot,
39
+ stdio: "ignore",
40
+ shell: false,
41
+ });
42
+
43
+ if (init.status !== 0) return;
44
+ } catch {
45
+ // best-effort: do not block scaffolding if git isn't available
46
+ }
47
+ }
48
+
26
49
  export async function scaffoldProject(options: ProjectOptions): Promise<void> {
27
50
  const targetRoot = path.resolve(process.cwd(), options.projectName);
28
51
 
@@ -39,6 +62,8 @@ export async function scaffoldProject(options: ProjectOptions): Promise<void> {
39
62
  await writeFileEnsuringDir(absPath, file.content);
40
63
  }
41
64
 
65
+ tryInitGitRepo(targetRoot);
66
+
42
67
  // eslint-disable-next-line no-console
43
68
  console.log(`\nCreated project at: ${targetRoot}`);
44
69
  }
package/src/templates.ts CHANGED
@@ -15,6 +15,213 @@ export type ProjectFile = {
15
15
  content: string;
16
16
  };
17
17
 
18
+ function gitignoreContent(language: Language): string {
19
+ const common = [
20
+ "# OS",
21
+ ".DS_Store",
22
+ "Thumbs.db",
23
+ "Desktop.ini",
24
+ "",
25
+ "# Editors",
26
+ ".vscode/",
27
+ ".idea/",
28
+ "*.swp",
29
+ "",
30
+ "# Env",
31
+ ".env",
32
+ ".env.*",
33
+ "",
34
+ "# Logs",
35
+ "*.log",
36
+ "",
37
+ "# Builds / artifacts",
38
+ "dist/",
39
+ "build/",
40
+ "*.zip",
41
+ "",
42
+ ];
43
+
44
+ const node = [
45
+ "# Node",
46
+ "node_modules/",
47
+ "npm-debug.log*",
48
+ "yarn-debug.log*",
49
+ "yarn-error.log*",
50
+ "pnpm-debug.log*",
51
+ "",
52
+ ];
53
+
54
+ const python = [
55
+ "# Python",
56
+ "__pycache__/",
57
+ "*.py[cod]",
58
+ "*.pyd",
59
+ ".Python",
60
+ ".venv/",
61
+ "venv/",
62
+ "ENV/",
63
+ "env/",
64
+ ".pytest_cache/",
65
+ ".mypy_cache/",
66
+ ".ruff_cache/",
67
+ "*.egg-info/",
68
+ "",
69
+ ];
70
+
71
+ return (
72
+ common.join("\n") +
73
+ (language === "typescript" ? node.join("\n") : python.join("\n"))
74
+ );
75
+ }
76
+
77
+ function packagingScriptMjs(): string {
78
+ // Keep this script dependency-free and avoid nested template literals.
79
+ return [
80
+ 'import { spawnSync } from "node:child_process";',
81
+ 'import * as fsp from "node:fs/promises";',
82
+ 'import * as path from "node:path";',
83
+ "",
84
+ "function run(cmd, args, opts = {}) {",
85
+ " return spawnSync(cmd, args, {",
86
+ ' stdio: ["ignore", "pipe", "pipe"],',
87
+ ' encoding: "utf8",',
88
+ " shell: false,",
89
+ " ...opts,",
90
+ " });",
91
+ "}",
92
+ "",
93
+ "function hasCommand(cmd) {",
94
+ ' if (process.platform === "win32") {',
95
+ ' const r = run("where", [cmd]);',
96
+ " return r.status === 0;",
97
+ " }",
98
+ ' const check = "command -v " + cmd + " >/dev/null 2>&1";',
99
+ ' const r = run("sh", ["-lc", check]);',
100
+ " return r.status === 0;",
101
+ "}",
102
+ "",
103
+ "function escapePsSingleQuotes(value) {",
104
+ ' return String(value || "").replace(/\'/g, "\'\'");',
105
+ "}",
106
+ "",
107
+ "function gitFileList() {",
108
+ ' const r = run("git", [',
109
+ ' "ls-files",',
110
+ ' "-z",',
111
+ ' "--cached",',
112
+ ' "--others",',
113
+ ' "--exclude-standard",',
114
+ " ]);",
115
+ "",
116
+ " if (r.status !== 0) {",
117
+ ' const err = (r.stderr || r.stdout || "").trim();',
118
+ " throw new Error(",
119
+ ' "git is required for packaging (and to respect .gitignore).\\n" + err,',
120
+ " );",
121
+ " }",
122
+ "",
123
+ ' const raw = r.stdout || "";',
124
+ ' return raw.split("\\u0000").filter(Boolean);',
125
+ "}",
126
+ "",
127
+ "async function ensureDir(p) {",
128
+ " await fsp.mkdir(p, { recursive: true });",
129
+ "}",
130
+ "",
131
+ "async function writeTempFileList(filePaths) {",
132
+ ' const tmpDir = path.join(process.cwd(), ".glosc-tmp");',
133
+ " await ensureDir(tmpDir);",
134
+ ' const listPath = path.join(tmpDir, "zip-file-list.txt");',
135
+ ' await fsp.writeFile(listPath, filePaths.join("\\n"), "utf8");',
136
+ " return listPath;",
137
+ "}",
138
+ "",
139
+ "function timestamp() {",
140
+ " const d = new Date();",
141
+ ' const pad = (n) => String(n).padStart(2, "0");',
142
+ " return (",
143
+ " String(d.getFullYear()) +",
144
+ " pad(d.getMonth() + 1) +",
145
+ " pad(d.getDate()) +",
146
+ ' "-" +',
147
+ " pad(d.getHours()) +",
148
+ " pad(d.getMinutes()) +",
149
+ " pad(d.getSeconds())",
150
+ " );",
151
+ "}",
152
+ "",
153
+ "async function main() {",
154
+ " const files = gitFileList();",
155
+ " if (files.length === 0) {",
156
+ " throw new Error(",
157
+ ' "No files found to package. If you just created the project, make sure git is initialized and .gitignore exists.",',
158
+ " );",
159
+ " }",
160
+ "",
161
+ ' const outDir = path.join(process.cwd(), "dist");',
162
+ " await ensureDir(outDir);",
163
+ ' const outZip = path.join(outDir, "glosc-package-" + timestamp() + ".zip");',
164
+ "",
165
+ " const listPath = await writeTempFileList(files);",
166
+ "",
167
+ " try {",
168
+ ' if (hasCommand("powershell")) {',
169
+ " const listEsc = escapePsSingleQuotes(listPath);",
170
+ " const outEsc = escapePsSingleQuotes(outZip);",
171
+ " const psCommand = [",
172
+ ' "$paths = Get-Content -LiteralPath \\\'" + listEsc + "\\\';",',
173
+ ' "Compress-Archive -LiteralPath $paths -DestinationPath \\\'" + outEsc + "\\\' -Force -CompressionLevel Optimal;",',
174
+ ' ].join(" ");',
175
+ " const psArgs = [",
176
+ ' "-NoProfile",',
177
+ ' "-ExecutionPolicy",',
178
+ ' "Bypass",',
179
+ ' "-Command",',
180
+ " psCommand,",
181
+ " ];",
182
+ ' const r = run("powershell", psArgs, { cwd: process.cwd() });',
183
+ ' if (r.status !== 0) throw new Error((r.stderr || r.stdout || "").trim());',
184
+ ' } else if (hasCommand("pwsh")) {',
185
+ " const listEsc = escapePsSingleQuotes(listPath);",
186
+ " const outEsc = escapePsSingleQuotes(outZip);",
187
+ " const psCommand = [",
188
+ ' "$paths = Get-Content -LiteralPath \\\'" + listEsc + "\\\';",',
189
+ ' "Compress-Archive -LiteralPath $paths -DestinationPath \\\'" + outEsc + "\\\' -Force -CompressionLevel Optimal;",',
190
+ ' ].join(" ");',
191
+ ' const psArgs = ["-NoProfile", "-Command", psCommand];',
192
+ ' const r = run("pwsh", psArgs, { cwd: process.cwd() });',
193
+ ' if (r.status !== 0) throw new Error((r.stderr || r.stdout || "").trim());',
194
+ ' } else if (hasCommand("zip")) {',
195
+ ' const r = spawnSync("zip", ["-q", "-r", outZip, "-@"], {',
196
+ " cwd: process.cwd(),",
197
+ ' input: files.join("\\n"),',
198
+ ' stdio: ["pipe", "pipe", "pipe"],',
199
+ ' encoding: "utf8",',
200
+ " });",
201
+ ' if (r.status !== 0) throw new Error((r.stderr || r.stdout || "").trim());',
202
+ " } else {",
203
+ " throw new Error(",
204
+ ' "No zip backend found. Install Git + PowerShell (Windows) or `zip` (macOS/Linux), then rerun.",',
205
+ " );",
206
+ " }",
207
+ " } finally {",
208
+ " try {",
209
+ " await fsp.rm(path.dirname(listPath), { recursive: true, force: true });",
210
+ " } catch {",
211
+ " // ignore",
212
+ " }",
213
+ " }",
214
+ "",
215
+ ' console.log("Created: " + outZip);',
216
+ "}",
217
+ "",
218
+ "main().catch((err) => {",
219
+ " console.error(String(err?.message || err));",
220
+ " process.exit(1);",
221
+ "});",
222
+ ].join("\n");
223
+ }
224
+
18
225
  function entryPath(options: ProjectOptions): string {
19
226
  return options.language === "python"
20
227
  ? options.mainFileName
@@ -49,6 +256,22 @@ function projectReadme(options: ProjectOptions): string {
49
256
  }\n\n## Language\n\n${langLabel}\n\n## Entry\n\n- ${entry}\n\n## MCP Tools\n\n- get_current_time: Returns the current time (UTC, ISO 8601)\n\n${runSection}\n\n## Config\n\n- config.yml\n`;
50
257
  }
51
258
 
259
+ function packagingPackageJson(options: ProjectOptions): string {
260
+ const { projectName, description, author } = options;
261
+ const pkg: Record<string, unknown> = {
262
+ name: projectName,
263
+ version: "0.1.0",
264
+ description: description || "",
265
+ author: author || "",
266
+ private: true,
267
+ scripts: {
268
+ package: "node scripts/package.mjs",
269
+ },
270
+ };
271
+
272
+ return JSON.stringify(pkg, null, 2) + "\n";
273
+ }
274
+
52
275
  function configYml(options: ProjectOptions): string {
53
276
  const { projectName, description, author, language, mainFileName } =
54
277
  options;
@@ -105,6 +328,7 @@ function nodePackageJson(options: ProjectOptions): string {
105
328
  scripts: {
106
329
  build: "tsc -p .",
107
330
  start: `node ${distEntry}`,
331
+ package: "node scripts/package.mjs",
108
332
  },
109
333
  dependencies: {
110
334
  "@modelcontextprotocol/sdk": "^1.24.3",
@@ -151,6 +375,14 @@ function tsMain({ projectName }: Pick<ProjectOptions, "projectName">): string {
151
375
  export function getProjectFiles(options: ProjectOptions): ProjectFile[] {
152
376
  const files: ProjectFile[] = [];
153
377
 
378
+ files.push({
379
+ relativePath: ".gitignore",
380
+ content: gitignoreContent(options.language),
381
+ });
382
+ files.push({
383
+ relativePath: "scripts/package.mjs",
384
+ content: packagingScriptMjs(),
385
+ });
154
386
  files.push({ relativePath: "config.yml", content: configYml(options) });
155
387
 
156
388
  if (options.readme) {
@@ -180,6 +412,10 @@ export function getProjectFiles(options: ProjectOptions): ProjectFile[] {
180
412
  relativePath: "pyproject.toml",
181
413
  content: pythonPyproject(options),
182
414
  });
415
+ files.push({
416
+ relativePath: "package.json",
417
+ content: packagingPackageJson(options),
418
+ });
183
419
  }
184
420
 
185
421
  if (options.language === "typescript") {