@demogo-cn/cli 0.2.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.
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # DemoGo CLI
2
+
3
+ DemoGo CLI lets AI coding tools publish a local project to DemoGo and return a shareable trial link.
4
+
5
+ ## Local Install
6
+
7
+ Current MVP delivery uses a local install package.
8
+
9
+ 1. Unzip `demogo-cli-v0.2.5.zip`.
10
+ 2. Open a terminal in the extracted folder.
11
+ 3. Run:
12
+
13
+ ```bash
14
+ npm install -g .
15
+ ```
16
+
17
+ 4. Verify:
18
+
19
+ ```bash
20
+ demogo --version
21
+ ```
22
+
23
+ ## Configure
24
+
25
+ ```bash
26
+ demogo config set --api https://demogo.cn --token <DemoGo AI publish token>
27
+ demogo doctor
28
+ ```
29
+
30
+ The token is reusable. Do not reset it for every deployment.
31
+ Reset it only when it is lost, invalid, or exposed in chat/logs.
32
+
33
+ ## Deploy
34
+
35
+ ```bash
36
+ demogo deploy
37
+ ```
38
+
39
+ For a specific folder or project name:
40
+
41
+ ```bash
42
+ demogo deploy --dir <project-folder> --name <project-name>
43
+ ```
44
+
45
+ ## Current Boundary
46
+
47
+ DemoGo supports static pages, single HTML pages, built frontend output, and frontend source projects that can build static output. It does not host long-running backend services, databases, payment systems, login systems, WebSocket services, or SSR runtimes.
48
+
49
+ ## npm / npx Status
50
+
51
+ After npm publication, AI coding tools can run:
52
+
53
+ ```bash
54
+ npx @demogo-cn/cli deploy
55
+ ```
56
+
57
+ Or install it globally:
58
+
59
+ ```bash
60
+ npm install -g @demogo-cn/cli
61
+ demogo deploy
62
+ ```
63
+
64
+ The package name is `@demogo-cn/cli`; the installed command remains `demogo`.
65
+ If an AI coding tool cannot run `demogo` or `npx @demogo-cn/cli`, it should explain why and then use DemoGo MCP or the Agent API fallback.
66
+
67
+
package/bin/demogo.js ADDED
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env node
2
+ import { mkdir, mkdtemp, readFile, rm, unlink, writeFile, stat } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import process from "node:process";
6
+ import {
7
+ MAX_BYTES,
8
+ VERSION,
9
+ assertDirectory,
10
+ collectFiles,
11
+ createProjectArchive,
12
+ deployArchive,
13
+ formatBytes,
14
+ checkApiHealth,
15
+ normalizeApiBase,
16
+ safeArchiveName,
17
+ summarizeProject
18
+ } from "../lib/core.js";
19
+
20
+ const CONFIG_DIR = path.join(os.homedir(), ".demogo");
21
+ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
22
+
23
+ main().catch((error) => {
24
+ fail(error instanceof Error ? error.message : "DemoGo CLI 执行失败。");
25
+ });
26
+
27
+ async function main() {
28
+ const args = process.argv.slice(2);
29
+ const command = args[0];
30
+ const rest = args.slice(1);
31
+
32
+ if (!command || command === "help" || command === "--help" || command === "-h") {
33
+ printHelp();
34
+ return;
35
+ }
36
+
37
+ if (command === "version" || command === "--version" || command === "-v") {
38
+ console.log(VERSION);
39
+ return;
40
+ }
41
+
42
+ if (command === "config") {
43
+ await handleConfig(rest);
44
+ return;
45
+ }
46
+
47
+ if (command === "deploy") {
48
+ await handleDeploy(rest);
49
+ return;
50
+ }
51
+
52
+ if (command === "doctor") {
53
+ await handleDoctor(rest);
54
+ return;
55
+ }
56
+
57
+ fail(`未知命令:${command}\n运行 demogo help 查看可用命令。`);
58
+ }
59
+
60
+ async function handleConfig(args) {
61
+ const subcommand = args[0];
62
+ if (subcommand === "set") {
63
+ const options = parseOptions(args.slice(1));
64
+ const current = await readConfig();
65
+ const next = {
66
+ ...current,
67
+ ...(options.api ? { apiBase: normalizeApiBase(options.api) } : {}),
68
+ ...(options.token ? { token: String(options.token).trim() } : {})
69
+ };
70
+ await writeConfig(next);
71
+ console.log("DemoGo CLI 配置已保存。");
72
+ console.log(`API 地址:${next.apiBase || "未配置"}`);
73
+ console.log(`AI 发布口令:${next.token ? maskToken(next.token) : "未配置"}`);
74
+ return;
75
+ }
76
+
77
+ if (subcommand === "show") {
78
+ const config = await readConfig();
79
+ const token = config.token || process.env.DEMOGO_AGENT_TOKEN || "";
80
+ console.log(`API 地址:${config.apiBase || process.env.DEMOGO_API_BASE || "未配置"}`);
81
+ console.log(`AI 发布口令:${token ? maskToken(token) : "未配置"}`);
82
+ return;
83
+ }
84
+
85
+ if (subcommand === "clear") {
86
+ await clearConfig();
87
+ console.log("DemoGo CLI 本机配置已清除。");
88
+ return;
89
+ }
90
+
91
+ if (subcommand === "test") {
92
+ await handleDoctor(args.slice(1));
93
+ return;
94
+ }
95
+
96
+ fail("用法:demogo config set --api <地址> --token <AI发布口令>、demogo config show、demogo config clear,或 demogo doctor");
97
+ }
98
+
99
+ async function handleDeploy(args) {
100
+ const options = parseOptions(args);
101
+ const config = await readConfig();
102
+ const apiBase = normalizeApiBase(options.api || config.apiBase || process.env.DEMOGO_API_BASE);
103
+ const token = String(options.token || config.token || process.env.DEMOGO_AGENT_TOKEN || "").trim();
104
+ const projectDir = path.resolve(String(options.dir || "."));
105
+ const projectName = String(options.name || path.basename(projectDir)).trim();
106
+
107
+ if (!apiBase) {
108
+ fail([
109
+ "缺少 DemoGo 平台地址。",
110
+ "请先在 DemoGo 工作台复制平台地址,然后运行:",
111
+ "demogo config set --api <DemoGo平台地址> --token <你的AI发布口令>"
112
+ ].join("\n"));
113
+ }
114
+
115
+ if (!token) {
116
+ fail([
117
+ "缺少 DemoGo AI 发布口令。",
118
+ "请先在 DemoGo 工作台生成口令,然后运行:",
119
+ `demogo config set --api ${apiBase || "<DemoGo平台地址>"} --token <你的AI发布口令>`
120
+ ].join("\n"));
121
+ }
122
+
123
+ await assertDirectory(projectDir);
124
+ const fileList = await collectFiles(projectDir);
125
+ if (!fileList.length) fail("当前目录没有可发布文件。");
126
+
127
+ const archivePath = path.join(await mkdtemp(path.join(os.tmpdir(), "demogo-cli-")), `${safeArchiveName(projectName)}.tar.gz`);
128
+ try {
129
+ console.log("DemoGo 正在检查当前项目...");
130
+ printProjectSummary(summarizeProject(projectDir, fileList));
131
+ console.log("DemoGo 正在打包项目...");
132
+ await createProjectArchive(projectDir, fileList, archivePath);
133
+ const archiveStats = await stat(archivePath);
134
+ if (archiveStats.size > MAX_BYTES) {
135
+ fail(`打包后文件超过 50MB,请减少大文件后重试。当前约 ${formatBytes(archiveStats.size)}。`);
136
+ }
137
+ console.log(`项目包已生成:${formatBytes(archiveStats.size)}`);
138
+ console.log("DemoGo 正在生成试用链接...");
139
+ const result = await deployArchive({ apiBase, token, archivePath, projectName, source: "cli" });
140
+ printDeployResult(result);
141
+ } finally {
142
+ await rm(path.dirname(archivePath), { recursive: true, force: true });
143
+ }
144
+ }
145
+
146
+ async function handleDoctor(args) {
147
+ const options = parseOptions(args);
148
+ const config = await readConfig();
149
+ const apiBase = normalizeApiBase(options.api || config.apiBase || process.env.DEMOGO_API_BASE);
150
+ const token = String(options.token || config.token || process.env.DEMOGO_AGENT_TOKEN || "").trim();
151
+
152
+ if (!apiBase) {
153
+ fail("缺少 DemoGo 平台地址。请先运行 demogo config set --api <DemoGo地址> --token <AI发布口令>。");
154
+ }
155
+
156
+ console.log("DemoGo 正在检查平台地址...");
157
+ const health = await checkApiHealth(apiBase);
158
+ console.log(`平台连接正常:${health.service || "demogo-server"} ${health.version || ""}`.trim());
159
+ console.log(`AI 发布口令:${token ? "已配置" : "未配置"}`);
160
+ if (!token) {
161
+ console.log("提示:没有 AI 发布口令时只能检查平台地址,不能直接发布项目。");
162
+ }
163
+ }
164
+
165
+ function parseOptions(args) {
166
+ const options = {};
167
+ for (let index = 0; index < args.length; index += 1) {
168
+ const item = args[index];
169
+ if (!item.startsWith("--")) continue;
170
+ const key = item.slice(2);
171
+ const next = args[index + 1];
172
+ if (!next || next.startsWith("--")) {
173
+ options[key] = true;
174
+ continue;
175
+ }
176
+ options[key] = next;
177
+ index += 1;
178
+ }
179
+ return options;
180
+ }
181
+
182
+ async function readConfig() {
183
+ try {
184
+ return JSON.parse(await readFile(CONFIG_FILE, "utf8"));
185
+ } catch {
186
+ return {};
187
+ }
188
+ }
189
+
190
+ async function writeConfig(value) {
191
+ await mkdir(CONFIG_DIR, { recursive: true });
192
+ await writeFile(CONFIG_FILE, `${JSON.stringify(value, null, 2)}\n`, "utf8");
193
+ }
194
+
195
+ async function clearConfig() {
196
+ await unlink(CONFIG_FILE).catch((error) => {
197
+ if (error?.code !== "ENOENT") throw error;
198
+ });
199
+ }
200
+
201
+ function printDeployResult(result) {
202
+ console.log("");
203
+ console.log("DemoGo 试用链接已生成。");
204
+ console.log(`项目名称:${result.projectName || result.name || result.slug || "-"}`);
205
+ console.log(`访问链接:${result.publicUrl || "-"}`);
206
+ console.log(`发布方式:${result.deploySourceLabel || "DemoGo CLI"}`);
207
+ console.log(`页面类型:${result.detectedType || "-"}`);
208
+ console.log(`内容检查:${result.contentReview?.statusLabel || result.contentReviewStatus || "已通过"}`);
209
+ console.log(`报名/留言收集:${result.autoFormEnabled || result.inspection?.autoFormEnabled ? "已自动开启" : "未自动开启"}`);
210
+ if (result.nextStep) console.log(`下一步:${result.nextStep}`);
211
+ }
212
+
213
+ function printProjectSummary(summary) {
214
+ console.log(`项目目录:${summary.projectDir}`);
215
+ console.log(`准备上传:${summary.fileCount} 个文件,约 ${formatBytes(summary.totalBytes)}`);
216
+ console.log(`页面入口:${summary.hasReadyPage ? "已发现" : (summary.singleHtmlEntry ? `已发现单页 ${summary.singleHtmlEntry}` : "未直接发现,DemoGo 会继续检测")}`);
217
+ if (summary.hasPackageJson) console.log("源码项目:已发现 package.json");
218
+ }
219
+
220
+ function maskToken(token) {
221
+ const value = String(token || "");
222
+ if (value.length <= 14) return "已配置";
223
+ return `${value.slice(0, 10)}...${value.slice(-4)}`;
224
+ }
225
+
226
+ function printHelp() {
227
+ console.log(`
228
+ DemoGo CLI ${VERSION}
229
+
230
+ 常用命令:
231
+ demogo config set --api <DemoGo地址> --token <AI发布口令>
232
+ demogo config show
233
+ demogo config clear
234
+ demogo doctor
235
+ demogo deploy
236
+ demogo deploy --name <项目名称> --dir <项目目录>
237
+
238
+ 本地安装:
239
+ 解压 dist/demogo-cli-v${VERSION}.zip 后,在解压目录执行 npm install -g .
240
+ 安装完成后运行 demogo --version 验证。
241
+
242
+ 环境变量:
243
+ DEMOGO_API_BASE DemoGo API 地址
244
+ DEMOGO_AGENT_TOKEN DemoGo AI 发布口令
245
+
246
+ 示例:
247
+ demogo config set --api https://your-demogo.example.com --token dmg_xxx_xxx
248
+ demogo deploy --name 我的报名页
249
+ `.trim());
250
+ }
251
+
252
+ function fail(message) {
253
+ console.error(message);
254
+ process.exit(1);
255
+ }
package/lib/core.js ADDED
@@ -0,0 +1,315 @@
1
+ import { gzipSync } from "node:zlib";
2
+ import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ export const VERSION = "0.2.5";
6
+ export const MAX_FILES = 800;
7
+ export const MAX_BYTES = 50 * 1024 * 1024;
8
+
9
+ const EXCLUDED_DIRS = new Set([
10
+ ".git",
11
+ ".hg",
12
+ ".svn",
13
+ ".next",
14
+ ".nuxt",
15
+ ".output",
16
+ ".cache",
17
+ ".turbo",
18
+ ".vite",
19
+ "node_modules",
20
+ "coverage",
21
+ "tmp",
22
+ "temp"
23
+ ]);
24
+
25
+ const EXCLUDED_FILES = new Set([
26
+ ".env",
27
+ ".env.local",
28
+ ".env.development",
29
+ ".env.production",
30
+ ".npmrc",
31
+ ".yarnrc",
32
+ ".DS_Store",
33
+ "Thumbs.db"
34
+ ]);
35
+
36
+ const SENSITIVE_EXTENSIONS = [
37
+ ".pem",
38
+ ".key",
39
+ ".p12",
40
+ ".pfx",
41
+ ".crt",
42
+ ".cer"
43
+ ];
44
+
45
+ export async function assertDirectory(dir) {
46
+ try {
47
+ const stats = await stat(dir);
48
+ if (!stats.isDirectory()) throw new Error(`不是项目目录:${dir}`);
49
+ } catch {
50
+ throw new Error(`找不到项目目录:${dir}`);
51
+ }
52
+ }
53
+
54
+ export async function collectFiles(rootDir) {
55
+ const files = [];
56
+ let totalBytes = 0;
57
+
58
+ async function walk(currentDir, relativeDir = "") {
59
+ const entries = await readdir(currentDir, { withFileTypes: true });
60
+ entries.sort((left, right) => left.name.localeCompare(right.name));
61
+ for (const entry of entries) {
62
+ const fullPath = path.join(currentDir, entry.name);
63
+ const relativePath = toPosixPath(path.join(relativeDir, entry.name));
64
+ if (shouldExclude(entry, relativePath)) continue;
65
+ if (entry.isDirectory()) {
66
+ await walk(fullPath, relativePath);
67
+ continue;
68
+ }
69
+ if (!entry.isFile()) continue;
70
+ const stats = await stat(fullPath);
71
+ totalBytes += stats.size;
72
+ if (files.length >= MAX_FILES) throw new Error(`项目文件超过 ${MAX_FILES} 个,请精简后再发布。`);
73
+ if (totalBytes > MAX_BYTES) throw new Error("项目文件超过 50MB,请删除大文件后再发布。");
74
+ files.push({ fullPath, relativePath, size: stats.size });
75
+ }
76
+ }
77
+
78
+ await walk(rootDir);
79
+ return files;
80
+ }
81
+
82
+ export function summarizeProject(projectDir, files) {
83
+ const totalBytes = files.reduce((sum, file) => sum + file.size, 0);
84
+ const hasReadyPage = files.some((file) => [
85
+ "index.html",
86
+ "dist/index.html",
87
+ "build/index.html",
88
+ "out/index.html",
89
+ "public/index.html"
90
+ ].includes(file.relativePath));
91
+ const singleHtmlEntry = inferSingleHtmlEntry(files)?.relativePath || "";
92
+ const hasPackageJson = files.some((file) => file.relativePath === "package.json");
93
+ return {
94
+ projectDir,
95
+ fileCount: files.length,
96
+ totalBytes,
97
+ hasReadyPage,
98
+ singleHtmlEntry,
99
+ hasPackageJson
100
+ };
101
+ }
102
+
103
+ export async function createProjectArchive(rootDir, fileList, archivePath) {
104
+ const chunks = [];
105
+ const htmlEntry = inferSingleHtmlEntry(fileList);
106
+ for (const file of fileList) {
107
+ const data = await readFile(file.fullPath);
108
+ const archivePathName = htmlEntry && file.relativePath === htmlEntry.relativePath ? "index.html" : file.relativePath;
109
+ chunks.push(...createTarHeaders(archivePathName, data.length, 0o644));
110
+ chunks.push(data);
111
+ chunks.push(Buffer.alloc(padLength(data.length)));
112
+ }
113
+ chunks.push(Buffer.alloc(1024));
114
+ await mkdir(path.dirname(archivePath), { recursive: true });
115
+ await writeFile(archivePath, gzipSync(Buffer.concat(chunks)));
116
+ }
117
+
118
+ function inferSingleHtmlEntry(files) {
119
+ const hasIndex = files.some((file) => file.relativePath === "index.html");
120
+ if (hasIndex) return null;
121
+ const rootHtmlFiles = files.filter((file) => (
122
+ /^[^/]+\.html?$/i.test(file.relativePath) &&
123
+ !/^admin\.html$/i.test(file.relativePath) &&
124
+ !/^login\.html$/i.test(file.relativePath)
125
+ ));
126
+ return rootHtmlFiles.length === 1 ? rootHtmlFiles[0] : null;
127
+ }
128
+
129
+ export async function deployArchive({ apiBase, token, archivePath, projectName, source = "cli", userAgent = `demogo-cli/${VERSION}` }) {
130
+ const formData = new FormData();
131
+ formData.set("name", projectName);
132
+ formData.set("source", source);
133
+ formData.set(
134
+ "project",
135
+ new Blob([await readFile(archivePath)], { type: "application/gzip" }),
136
+ path.basename(archivePath)
137
+ );
138
+
139
+ let response;
140
+ try {
141
+ response = await fetch(`${normalizeApiBase(apiBase)}/api/agent/deploy`, {
142
+ method: "POST",
143
+ headers: {
144
+ Authorization: `Bearer ${token}`,
145
+ "User-Agent": userAgent,
146
+ "X-DemoGo-Deploy-Source": source
147
+ },
148
+ body: formData
149
+ });
150
+ } catch {
151
+ throw new Error(`无法连接 DemoGo:${normalizeApiBase(apiBase)}。请检查 API 地址是否正确,或稍后再试。`);
152
+ }
153
+
154
+ const text = await response.text();
155
+ const payload = parseJson(text);
156
+ if (!response.ok) {
157
+ const message = payload?.error || readableHttpError(response.status, text);
158
+ const shouldShowFixPrompt = response.status === 400 || payload?.inspection?.canPublish === false;
159
+ const fixPrompt = shouldShowFixPrompt
160
+ ? payload?.inspection?.fixPrompt || payload?.inspection?.ruleReport?.fixPrompt || ""
161
+ : "";
162
+ const details = fixPrompt ? `\n\n给 AI 工具的修改建议:\n${fixPrompt}` : "";
163
+ throw new Error(`${message}${details}`);
164
+ }
165
+ return payload;
166
+ }
167
+
168
+ export async function checkApiHealth(apiBase) {
169
+ let response;
170
+ try {
171
+ response = await fetch(`${normalizeApiBase(apiBase)}/api/health`, {
172
+ method: "GET",
173
+ headers: { "User-Agent": `demogo-cli/${VERSION}` }
174
+ });
175
+ } catch {
176
+ throw new Error(`无法连接 DemoGo:${normalizeApiBase(apiBase)}。请检查平台地址。`);
177
+ }
178
+ const text = await response.text();
179
+ const payload = parseJson(text);
180
+ if (!response.ok || !payload?.ok) {
181
+ throw new Error(`DemoGo 平台地址不可用:HTTP ${response.status}`);
182
+ }
183
+ return payload;
184
+ }
185
+
186
+ export function normalizeApiBase(value) {
187
+ return String(value || "").trim().replace(/\/+$/, "");
188
+ }
189
+
190
+ export function safeArchiveName(value) {
191
+ return String(value || "demogo-project").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "demogo-project";
192
+ }
193
+
194
+ export function formatBytes(bytes) {
195
+ if (bytes < 1024) return `${bytes} B`;
196
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
197
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
198
+ }
199
+
200
+ function shouldExclude(entry, relativePath) {
201
+ const name = entry.name;
202
+ if (entry.isDirectory() && EXCLUDED_DIRS.has(name)) return true;
203
+ if (EXCLUDED_FILES.has(name)) return true;
204
+ if (name.endsWith(".log")) return true;
205
+ if (SENSITIVE_EXTENSIONS.some((extension) => name.toLowerCase().endsWith(extension))) return true;
206
+ if (relativePath.startsWith("dist/") && relativePath.includes("/server-package/")) return true;
207
+ return false;
208
+ }
209
+
210
+ function createTarHeaders(name, size, mode) {
211
+ const normalizedName = toPosixPath(name);
212
+ const ustarPath = splitUstarPath(normalizedName);
213
+ if (ustarPath && isAscii(normalizedName)) {
214
+ return [createTarHeader({ name: ustarPath.name, prefix: ustarPath.prefix, size, mode })];
215
+ }
216
+
217
+ const paxBody = Buffer.from(createPaxRecord("path", normalizedName), "utf8");
218
+ const fallbackName = safeTarName(normalizedName);
219
+ return [
220
+ createTarHeader({ name: `PaxHeaders/${fallbackName}`.slice(0, 100), size: paxBody.length, mode: 0o644, type: "x" }),
221
+ paxBody,
222
+ Buffer.alloc(padLength(paxBody.length)),
223
+ createTarHeader({ name: fallbackName, size, mode })
224
+ ];
225
+ }
226
+
227
+ function createTarHeader({ name, size, mode, type = "0", prefix = "" }) {
228
+ const buffer = Buffer.alloc(512, 0);
229
+ writeString(buffer, name, 0, 100);
230
+ writeOctal(buffer, mode, 100, 8);
231
+ writeOctal(buffer, 0, 108, 8);
232
+ writeOctal(buffer, 0, 116, 8);
233
+ writeOctal(buffer, size, 124, 12);
234
+ writeOctal(buffer, Math.floor(Date.now() / 1000), 136, 12);
235
+ buffer.fill(0x20, 148, 156);
236
+ buffer[156] = type.charCodeAt(0);
237
+ writeString(buffer, "ustar", 257, 6);
238
+ writeString(buffer, "00", 263, 2);
239
+ writeString(buffer, prefix, 345, 155);
240
+ const checksum = buffer.reduce((sum, value) => sum + value, 0);
241
+ writeOctal(buffer, checksum, 148, 8);
242
+ return buffer;
243
+ }
244
+
245
+ function writeString(buffer, value, offset, length) {
246
+ const data = Buffer.from(String(value), "utf8");
247
+ data.subarray(0, length).copy(buffer, offset);
248
+ }
249
+
250
+ function writeOctal(buffer, value, offset, length) {
251
+ const text = value.toString(8).padStart(length - 1, "0").slice(0, length - 1);
252
+ buffer.write(text, offset, length - 1, "ascii");
253
+ buffer[offset + length - 1] = 0;
254
+ }
255
+
256
+ function padLength(size) {
257
+ return (512 - (size % 512)) % 512;
258
+ }
259
+
260
+ function toPosixPath(value) {
261
+ return value.split(path.sep).join("/");
262
+ }
263
+
264
+ function splitUstarPath(name) {
265
+ if (Buffer.byteLength(name, "utf8") <= 100) return { name, prefix: "" };
266
+ const parts = name.split("/");
267
+ for (let index = parts.length - 1; index > 0; index -= 1) {
268
+ const prefix = parts.slice(0, index).join("/");
269
+ const entryName = parts.slice(index).join("/");
270
+ if (Buffer.byteLength(entryName, "utf8") <= 100 && Buffer.byteLength(prefix, "utf8") <= 155) {
271
+ return { name: entryName, prefix };
272
+ }
273
+ }
274
+ return null;
275
+ }
276
+
277
+ function createPaxRecord(key, value) {
278
+ let record = ` ${key}=${value}\n`;
279
+ let length = Buffer.byteLength(record, "utf8") + String(Buffer.byteLength(record, "utf8")).length;
280
+ while (true) {
281
+ const next = `${length}${record}`;
282
+ const nextLength = Buffer.byteLength(next, "utf8");
283
+ if (nextLength === length) return next;
284
+ length = nextLength;
285
+ }
286
+ }
287
+
288
+ function safeTarName(value) {
289
+ const baseName = path.posix.basename(String(value || "file")) || "file";
290
+ const safe = safeArchiveName(baseName) || "file";
291
+ if (Buffer.byteLength(safe, "utf8") <= 100) return safe;
292
+ return safe.slice(0, 80) || "file";
293
+ }
294
+
295
+ function isAscii(value) {
296
+ return /^[\x00-\x7F]*$/.test(value);
297
+ }
298
+
299
+ function parseJson(text) {
300
+ try {
301
+ return JSON.parse(text);
302
+ } catch {
303
+ return null;
304
+ }
305
+ }
306
+
307
+ function readableHttpError(status, text) {
308
+ if (status === 401) return "AI 发布口令无效或已被重置。";
309
+ if (status === 403) return "当前账号额度不足,无法生成新的试用链接。";
310
+ if (status === 413) return "项目包太大,请压缩或删除大文件后重试。";
311
+ if (status === 400) return "项目暂时不能发布,请根据 DemoGo 返回的提示修改后再试。";
312
+ if (text?.trim().startsWith("<")) return "服务器返回了异常页面,本次发布没有完成。";
313
+ return `DemoGo 发布失败:HTTP ${status}`;
314
+ }
315
+
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@demogo-cn/cli",
3
+ "version": "0.2.5",
4
+ "description": "DemoGo CLI for generating shareable trial links from AI-built pages.",
5
+ "type": "module",
6
+ "bin": {
7
+ "demogo": "bin/demogo.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "lib"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18.0.0"
15
+ },
16
+ "license": "UNLICENSED"
17
+ }