create-glosc 0.2.1 → 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/index.js CHANGED
@@ -124,6 +124,11 @@ function normalizeMainFileName(language, mainFileNameRaw) {
124
124
  return base;
125
125
  return `${base}${defaultExt}`;
126
126
  }
127
+ function normalizeProjectName(value) {
128
+ return String(value || "")
129
+ .trim()
130
+ .replace(/\s+/g, "-");
131
+ }
127
132
  function getDefaultAuthor() {
128
133
  const candidates = [
129
134
  process.env.GIT_AUTHOR_NAME,
@@ -157,7 +162,7 @@ async function run() {
157
162
  }
158
163
  const language = normalizeLanguage(args.language) || "typescript";
159
164
  const options = {
160
- projectName: String(args.projectName).trim(),
165
+ projectName: normalizeProjectName(args.projectName),
161
166
  description: String(args.description || "A brief description of your project").trim(),
162
167
  author: String(args.author || getDefaultAuthor()).trim(),
163
168
  language,
@@ -254,7 +259,7 @@ async function run() {
254
259
  return;
255
260
  }
256
261
  const options = {
257
- projectName: String(response.projectName).trim(),
262
+ projectName: normalizeProjectName(response.projectName),
258
263
  description: String(response.description || "").trim(),
259
264
  author: String(response.author || "").trim(),
260
265
  language: response.language,
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.1",
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/index.ts CHANGED
@@ -129,6 +129,12 @@ function normalizeMainFileName(
129
129
  return `${base}${defaultExt}`;
130
130
  }
131
131
 
132
+ function normalizeProjectName(value: unknown): string {
133
+ return String(value || "")
134
+ .trim()
135
+ .replace(/\s+/g, "-");
136
+ }
137
+
132
138
  function getDefaultAuthor(): string {
133
139
  const candidates = [
134
140
  process.env.GIT_AUTHOR_NAME,
@@ -168,7 +174,7 @@ async function run(): Promise<void> {
168
174
  const language = normalizeLanguage(args.language) || "typescript";
169
175
 
170
176
  const options: ProjectOptions = {
171
- projectName: String(args.projectName).trim(),
177
+ projectName: normalizeProjectName(args.projectName),
172
178
  description: String(
173
179
  args.description || "A brief description of your project"
174
180
  ).trim(),
@@ -276,7 +282,7 @@ async function run(): Promise<void> {
276
282
  }
277
283
 
278
284
  const options: ProjectOptions = {
279
- projectName: String(response.projectName).trim(),
285
+ projectName: normalizeProjectName(response.projectName),
280
286
  description: String(response.description || "").trim(),
281
287
  author: String(response.author || "").trim(),
282
288
  language: response.language as Language,
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") {