create-glosc 0.2.2 → 0.4.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,11 +1,219 @@
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
7
207
  : `src/${options.mainFileName}`;
8
208
  }
209
+ function mcpRuntime(options) {
210
+ return options.language === "python" ? "python" : "node";
211
+ }
212
+ function mcpEntry(options) {
213
+ if (options.language === "python")
214
+ return options.mainFileName;
215
+ return `dist/${options.mainFileName.replace(/\.ts$/i, ".js")}`;
216
+ }
9
217
  function escapeYamlString(value) {
10
218
  const s = String(value ?? "");
11
219
  const escaped = s.replace(/"/g, '\\"');
@@ -23,16 +231,36 @@ function projectReadme(options) {
23
231
  const runSection = language === "python"
24
232
  ? `## Run (Python)\n\n\n\n1) Install deps\n\n\n\n\`\`\`sh\npython -m pip install -r requirements.txt\n\`\`\`\n\n\n\n2) Run the MCP server (stdio)\n\n\n\n\`\`\`sh\npython ${entry}\n\`\`\`\n\n\n\nThis server speaks MCP over stdio. Connect using an MCP client (e.g. an editor integration).\n`
25
233
  : `## 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
- 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`;
234
+ 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 (see: mcp.runtime / mcp.entry)\n`;
235
+ }
236
+ function packagingPackageJson(options) {
237
+ const { projectName, description, author } = options;
238
+ const pkg = {
239
+ name: projectName,
240
+ version: "0.1.0",
241
+ description: description || "",
242
+ author: author || "",
243
+ private: true,
244
+ scripts: {
245
+ package: "node scripts/package.mjs",
246
+ },
247
+ };
248
+ return JSON.stringify(pkg, null, 2) + "\n";
27
249
  }
28
250
  function configYml(options) {
29
251
  const { projectName, description, author, language, mainFileName } = options;
30
252
  return [
31
253
  `name: ${escapeYamlString(projectName)}`,
32
254
  `description: ${escapeYamlString(description)}`,
33
- `author: ${escapeYamlString(author)}`,
255
+ `icon: ${escapeYamlString("")}`,
34
256
  `language: ${escapeYamlString(language)}`,
35
- `entry: ${escapeYamlString(entryPath(options))}`,
257
+ `author: ${escapeYamlString(author)}`,
258
+ `mcp:`,
259
+ ` runtime: ${escapeYamlString(mcpRuntime(options))}`,
260
+ ` entry: ${escapeYamlString(mcpEntry(options))}`,
261
+ ` cwd: ${escapeYamlString(".")}`,
262
+ ` env: {}`,
263
+ ` args: []`,
36
264
  "",
37
265
  ].join("\n");
38
266
  }
@@ -66,6 +294,7 @@ function nodePackageJson(options) {
66
294
  scripts: {
67
295
  build: "tsc -p .",
68
296
  start: `node ${distEntry}`,
297
+ package: "node scripts/package.mjs",
69
298
  },
70
299
  dependencies: {
71
300
  "@modelcontextprotocol/sdk": "^1.24.3",
@@ -100,6 +329,14 @@ function tsMain({ projectName }) {
100
329
  }
101
330
  function getProjectFiles(options) {
102
331
  const files = [];
332
+ files.push({
333
+ relativePath: ".gitignore",
334
+ content: gitignoreContent(options.language),
335
+ });
336
+ files.push({
337
+ relativePath: "scripts/package.mjs",
338
+ content: packagingScriptMjs(),
339
+ });
103
340
  files.push({ relativePath: "config.yml", content: configYml(options) });
104
341
  if (options.readme) {
105
342
  files.push({
@@ -126,6 +363,10 @@ function getProjectFiles(options) {
126
363
  relativePath: "pyproject.toml",
127
364
  content: pythonPyproject(options),
128
365
  });
366
+ files.push({
367
+ relativePath: "package.json",
368
+ content: packagingPackageJson(options),
369
+ });
129
370
  }
130
371
  if (options.language === "typescript") {
131
372
  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.4.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,12 +15,228 @@ 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
21
228
  : `src/${options.mainFileName}`;
22
229
  }
23
230
 
231
+ function mcpRuntime(options: ProjectOptions): "python" | "node" {
232
+ return options.language === "python" ? "python" : "node";
233
+ }
234
+
235
+ function mcpEntry(options: ProjectOptions): string {
236
+ if (options.language === "python") return options.mainFileName;
237
+ return `dist/${options.mainFileName.replace(/\.ts$/i, ".js")}`;
238
+ }
239
+
24
240
  function escapeYamlString(value: unknown): string {
25
241
  const s = String(value ?? "");
26
242
  const escaped = s.replace(/"/g, '\\"');
@@ -46,7 +262,23 @@ function projectReadme(options: ProjectOptions): string {
46
262
 
47
263
  return `# ${projectName}\n\n${description || ""}\n\n## Author\n\n${
48
264
  author || ""
49
- }\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`;
265
+ }\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 (see: mcp.runtime / mcp.entry)\n`;
266
+ }
267
+
268
+ function packagingPackageJson(options: ProjectOptions): string {
269
+ const { projectName, description, author } = options;
270
+ const pkg: Record<string, unknown> = {
271
+ name: projectName,
272
+ version: "0.1.0",
273
+ description: description || "",
274
+ author: author || "",
275
+ private: true,
276
+ scripts: {
277
+ package: "node scripts/package.mjs",
278
+ },
279
+ };
280
+
281
+ return JSON.stringify(pkg, null, 2) + "\n";
50
282
  }
51
283
 
52
284
  function configYml(options: ProjectOptions): string {
@@ -55,9 +287,15 @@ function configYml(options: ProjectOptions): string {
55
287
  return [
56
288
  `name: ${escapeYamlString(projectName)}`,
57
289
  `description: ${escapeYamlString(description)}`,
58
- `author: ${escapeYamlString(author)}`,
290
+ `icon: ${escapeYamlString("")}`,
59
291
  `language: ${escapeYamlString(language)}`,
60
- `entry: ${escapeYamlString(entryPath(options))}`,
292
+ `author: ${escapeYamlString(author)}`,
293
+ `mcp:`,
294
+ ` runtime: ${escapeYamlString(mcpRuntime(options))}`,
295
+ ` entry: ${escapeYamlString(mcpEntry(options))}`,
296
+ ` cwd: ${escapeYamlString(".")}`,
297
+ ` env: {}`,
298
+ ` args: []`,
61
299
  "",
62
300
  ].join("\n");
63
301
  }
@@ -105,6 +343,7 @@ function nodePackageJson(options: ProjectOptions): string {
105
343
  scripts: {
106
344
  build: "tsc -p .",
107
345
  start: `node ${distEntry}`,
346
+ package: "node scripts/package.mjs",
108
347
  },
109
348
  dependencies: {
110
349
  "@modelcontextprotocol/sdk": "^1.24.3",
@@ -151,6 +390,14 @@ function tsMain({ projectName }: Pick<ProjectOptions, "projectName">): string {
151
390
  export function getProjectFiles(options: ProjectOptions): ProjectFile[] {
152
391
  const files: ProjectFile[] = [];
153
392
 
393
+ files.push({
394
+ relativePath: ".gitignore",
395
+ content: gitignoreContent(options.language),
396
+ });
397
+ files.push({
398
+ relativePath: "scripts/package.mjs",
399
+ content: packagingScriptMjs(),
400
+ });
154
401
  files.push({ relativePath: "config.yml", content: configYml(options) });
155
402
 
156
403
  if (options.readme) {
@@ -180,6 +427,10 @@ export function getProjectFiles(options: ProjectOptions): ProjectFile[] {
180
427
  relativePath: "pyproject.toml",
181
428
  content: pythonPyproject(options),
182
429
  });
430
+ files.push({
431
+ relativePath: "package.json",
432
+ content: packagingPackageJson(options),
433
+ });
183
434
  }
184
435
 
185
436
  if (options.language === "typescript") {