@demogo-cn/cli 0.4.0 → 0.5.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.
Files changed (4) hide show
  1. package/README.md +95 -78
  2. package/bin/demogo.js +117 -24
  3. package/lib/core.js +147 -84
  4. package/package.json +3 -2
package/README.md CHANGED
@@ -1,82 +1,99 @@
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.4.0.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
- Run the command from a clean project folder. Do not run it directly from Desktop, Downloads, Documents, or the user home folder.
36
-
37
- Use without installing:
38
-
39
- ```bash
40
- npx --yes @demogo-cn/cli deploy --api https://demogo.cn --token <DemoGo AI publish token>
41
- ```
42
-
43
- Or use the installed command:
44
-
45
- ```bash
46
- demogo deploy
47
- ```
48
-
49
- For a specific folder or project name:
50
-
51
- ```bash
52
- demogo deploy --dir <project-folder> --name <project-name>
53
- ```
54
-
55
- If you only have one HTML file, put that file in a clean folder and publish that folder. You do not need to rename the file to `index.html`.
56
-
57
- Project names are used for display in the DemoGo workbench. Every first publish receives an automatically assigned trial link path. Lite and Pro users can later customize the `/d/...` path in the workbench. Pro users can apply for a `xxx.demogo.cn` subdomain.
58
-
59
- ## Current Boundary
60
-
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.5.0.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
+ Run the command from a clean project folder. Do not run it directly from Desktop, Downloads, Documents, or the user home folder.
36
+
37
+ Use without installing:
38
+
39
+ ```bash
40
+ npx --yes @demogo-cn/cli deploy --api https://demogo.cn --token <DemoGo AI publish token>
41
+ ```
42
+
43
+ Or use the installed command:
44
+
45
+ ```bash
46
+ demogo deploy
47
+ ```
48
+
49
+ For a specific folder or project name:
50
+
51
+ ```bash
52
+ demogo deploy --dir <project-folder> --name <project-name>
53
+ ```
54
+
55
+ After a successful first publish, the CLI stores a small local record in `.demogo/project.json`. If the same project folder is published again with `demogo deploy`, DemoGo updates the original trial link by default. This does not consume a new Demo slot, but it does consume one publish/update count.
56
+
57
+ To force a brand-new trial link from the same folder:
58
+
59
+ ```bash
60
+ demogo deploy --new
61
+ ```
62
+
63
+ To update an existing link from another folder or another machine:
64
+
65
+ ```bash
66
+ demogo update --id https://demogo.cn/d/try-xxxxxx/
67
+ ```
68
+
69
+ If you only have one HTML file, put that file in a clean folder and publish that folder. You do not need to rename the file to `index.html`.
70
+
71
+ Project names are used for display in the DemoGo workbench. Every first publish receives an automatically assigned trial link path. Lite and Pro users can later customize the `/d/...` path in the workbench. Pro users can apply for a `xxx.demogo.cn` subdomain.
72
+
73
+ ## Current Boundary
74
+
61
75
  DemoGo supports static pages, single HTML pages, built frontend output, frontend source projects that can build static output, and Node.js single-service trial projects when the platform reports that the Node runtime is available. Node.js projects must provide a start command and listen on `process.env.PORT`.
62
76
 
63
- DemoGo can allocate an empty MySQL trial database for supported Node.js single-service projects. It does not currently run Redis, MongoDB, PostgreSQL, multi-service apps, WebSocket, or SSR runtimes such as Next/Nuxt/Remix server mode.
64
-
65
- ## npm / npx Status
66
-
67
- AI coding tools can run:
68
-
69
- ```bash
70
- npx --yes @demogo-cn/cli deploy --api https://demogo.cn --token <DemoGo AI publish token>
71
- ```
72
-
73
- Or install it globally:
74
-
75
- ```bash
76
- npm install -g @demogo-cn/cli
77
- demogo deploy
78
- ```
77
+ DemoGo can classify common frontend, backend, database, and environment-variable signals, including Next.js, TanStack Start, Nuxt, SvelteKit, Astro, Express, Fastify, Hono, Supabase, Postgres, MySQL, Prisma, and Drizzle.
79
78
 
80
- The package name is `@demogo-cn/cli`; the installed command remains `demogo`.
81
- 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.
79
+ DemoGo can allocate an empty MySQL trial database for supported Node.js single-service projects. It does not currently run Redis, MongoDB, PostgreSQL, multi-service apps, WebSocket, or SSR runtimes such as Next/Nuxt/Remix/TanStack Start/SvelteKit/Astro server mode.
80
+
81
+ ## npm / npx Status
82
+
83
+ AI coding tools can run:
84
+
85
+ ```bash
86
+ npx --yes @demogo-cn/cli deploy --api https://demogo.cn --token <DemoGo AI publish token>
87
+ ```
88
+
89
+ Or install it globally:
90
+
91
+ ```bash
92
+ npm install -g @demogo-cn/cli
93
+ demogo deploy
94
+ ```
95
+
96
+ The package name is `@demogo-cn/cli`; the installed command remains `demogo`.
97
+ 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.
98
+
82
99
 
package/bin/demogo.js CHANGED
@@ -16,7 +16,8 @@ import {
16
16
  checkApiHealth,
17
17
  normalizeApiBase,
18
18
  safeArchiveName,
19
- summarizeProject
19
+ summarizeProject,
20
+ updateArchive
20
21
  } from "../lib/core.js";
21
22
 
22
23
  const CONFIG_DIR = path.join(os.homedir(), ".demogo");
@@ -51,6 +52,11 @@ async function main() {
51
52
  return;
52
53
  }
53
54
 
55
+ if (command === "update") {
56
+ await handleUpdate(rest);
57
+ return;
58
+ }
59
+
54
60
  if (command === "doctor") {
55
61
  await handleDoctor(rest);
56
62
  return;
@@ -100,6 +106,62 @@ async function handleConfig(args) {
100
106
 
101
107
  async function handleDeploy(args) {
102
108
  const options = parseOptions(args);
109
+ const { apiBase, token, projectDir, projectName } = await resolvePublishContext(options, "deploy");
110
+ const existingProject = await readProjectRecord(projectDir);
111
+ if (existingProject?.demoRef && !options.new) {
112
+ console.log("DemoGo 发现当前项目目录已有发布记录,本次将更新原试用链接。");
113
+ console.log(`原访问链接:${existingProject.publicUrl || existingProject.demoRef}`);
114
+ await publishUpdate({
115
+ apiBase,
116
+ token,
117
+ projectDir,
118
+ projectName,
119
+ demoRef: existingProject.demoRef,
120
+ source: "cli"
121
+ });
122
+ return;
123
+ }
124
+
125
+ const archivePath = path.join(await mkdtemp(path.join(os.tmpdir(), "demogo-cli-")), `${safeArchiveName(projectName)}.tar.gz`);
126
+ try {
127
+ const archiveStats = await packProject(projectDir, archivePath);
128
+ console.log(`项目包已生成:${formatBytes(archiveStats.size)}`);
129
+ console.log("DemoGo 正在生成试用链接...");
130
+ const result = await deployArchive({ apiBase, token, archivePath, projectName, source: "cli" });
131
+ printDeployResult(result);
132
+ await writeProjectRecord(projectDir, result);
133
+ } finally {
134
+ await rm(path.dirname(archivePath), { recursive: true, force: true });
135
+ }
136
+ }
137
+
138
+ async function handleUpdate(args) {
139
+ const options = parseOptions(args);
140
+ const demoRef = String(options.id || options.demo || options.slug || options.url || "").trim();
141
+ if (!demoRef) {
142
+ fail("缺少要更新的项目。请使用:demogo update --id <Demo ID 或原试用链接>");
143
+ }
144
+
145
+ const { apiBase, token, projectDir, projectName } = await resolvePublishContext(options, "update");
146
+ await publishUpdate({ apiBase, token, projectDir, projectName, demoRef, source: "cli" });
147
+ }
148
+
149
+ async function publishUpdate({ apiBase, token, projectDir, projectName, demoRef, source }) {
150
+ const archivePath = path.join(await mkdtemp(path.join(os.tmpdir(), "demogo-cli-")), `${safeArchiveName(projectName || demoRef)}.tar.gz`);
151
+ try {
152
+ console.log("DemoGo 正在准备更新已有试用链接...");
153
+ const archiveStats = await packProject(projectDir, archivePath);
154
+ console.log(`项目包已生成:${formatBytes(archiveStats.size)}`);
155
+ console.log("DemoGo 正在更新试用项目,原链接会保持不变...");
156
+ const result = await updateArchive({ apiBase, token, archivePath, demoRef, source });
157
+ printDeployResult(result, { action: "update" });
158
+ await writeProjectRecord(projectDir, result);
159
+ } finally {
160
+ await rm(path.dirname(archivePath), { recursive: true, force: true });
161
+ }
162
+ }
163
+
164
+ async function resolvePublishContext(options, action = "deploy") {
103
165
  const config = await readConfig();
104
166
  const apiBase = normalizeApiBase(options.api || config.apiBase || process.env.DEMOGO_API_BASE);
105
167
  const token = String(options.token || config.token || process.env.DEMOGO_AGENT_TOKEN || "").trim();
@@ -110,7 +172,7 @@ async function handleDeploy(args) {
110
172
  fail([
111
173
  "缺少 DemoGo 平台地址。",
112
174
  "可以直接运行:",
113
- "demogo deploy --api <DemoGo平台地址> --token <你的AI发布口令>"
175
+ `demogo ${action} --api <DemoGo平台地址> --token <你的AI发布口令>`
114
176
  ].join("\n"));
115
177
  }
116
178
 
@@ -118,35 +180,31 @@ async function handleDeploy(args) {
118
180
  fail([
119
181
  "缺少 DemoGo AI 发布口令。",
120
182
  "可以直接运行:",
121
- `demogo deploy --api ${apiBase || "<DemoGo平台地址>"} --token <你的AI发布口令>`
183
+ `demogo ${action} --api ${apiBase || "<DemoGo平台地址>"} --token <你的AI发布口令>`
122
184
  ].join("\n"));
123
185
  }
124
186
 
125
187
  await assertDirectory(projectDir);
126
188
  assertSafeProjectDirectory(projectDir);
189
+ return { apiBase, token, projectDir, projectName };
190
+ }
191
+
192
+ async function packProject(projectDir, archivePath) {
127
193
  const fileList = await collectFiles(projectDir);
128
194
  if (!fileList.length) fail("当前目录没有可发布文件。");
129
195
 
130
- const archivePath = path.join(await mkdtemp(path.join(os.tmpdir(), "demogo-cli-")), `${safeArchiveName(projectName)}.tar.gz`);
131
- try {
132
- console.log("DemoGo 正在准备当前项目...");
133
- printProjectSummary(summarizeProject(projectDir, fileList));
134
- console.log("DemoGo 正在打包项目...");
135
- await createProjectArchive(projectDir, fileList, archivePath);
136
- const archiveStats = await stat(archivePath);
137
- if (archiveStats.size > MAX_BYTES) {
138
- fail([
139
- `打包后文件超过 50MB,当前约 ${formatBytes(archiveStats.size)}。`,
140
- "你可能在错误目录执行了发布。请切换到真正的项目目录,或使用 --dir 指定干净项目文件夹。"
141
- ].join("\n"));
142
- }
143
- console.log(`项目包已生成:${formatBytes(archiveStats.size)}`);
144
- console.log("DemoGo 正在生成试用链接...");
145
- const result = await deployArchive({ apiBase, token, archivePath, projectName, source: "cli" });
146
- printDeployResult(result);
147
- } finally {
148
- await rm(path.dirname(archivePath), { recursive: true, force: true });
196
+ console.log("DemoGo 正在准备当前项目...");
197
+ printProjectSummary(summarizeProject(projectDir, fileList));
198
+ console.log("DemoGo 正在打包项目...");
199
+ await createProjectArchive(projectDir, fileList, archivePath);
200
+ const archiveStats = await stat(archivePath);
201
+ if (archiveStats.size > MAX_BYTES) {
202
+ fail([
203
+ `打包后文件超过 50MB,当前约 ${formatBytes(archiveStats.size)}。`,
204
+ "你可能在错误目录执行了发布。请切换到真正的项目目录,或使用 --dir 指定干净项目文件夹。"
205
+ ].join("\n"));
149
206
  }
207
+ return archiveStats;
150
208
  }
151
209
 
152
210
  async function handleDoctor(args) {
@@ -213,11 +271,41 @@ async function clearConfig() {
213
271
  });
214
272
  }
215
273
 
216
- function printDeployResult(result) {
274
+ async function readProjectRecord(projectDir) {
275
+ try {
276
+ const record = JSON.parse(await readFile(projectRecordPath(projectDir), "utf8"));
277
+ const demoRef = String(record.demoId || record.id || record.publicUrl || record.slug || "").trim();
278
+ return demoRef ? { ...record, demoRef } : null;
279
+ } catch {
280
+ return null;
281
+ }
282
+ }
283
+
284
+ async function writeProjectRecord(projectDir, result) {
285
+ if (!result?.id && !result?.publicUrl) return;
286
+ const record = {
287
+ demoId: result.id || "",
288
+ slug: result.slug || "",
289
+ publicUrl: result.publicUrl || "",
290
+ projectName: result.projectName || result.name || "",
291
+ version: result.version || null,
292
+ updatedAt: new Date().toISOString()
293
+ };
294
+ const recordPath = projectRecordPath(projectDir);
295
+ await mkdir(path.dirname(recordPath), { recursive: true });
296
+ await writeFile(recordPath, `${JSON.stringify(record, null, 2)}\n`, "utf8");
297
+ }
298
+
299
+ function projectRecordPath(projectDir) {
300
+ return path.join(projectDir, ".demogo", "project.json");
301
+ }
302
+
303
+ function printDeployResult(result, options = {}) {
217
304
  console.log("");
218
- console.log("DemoGo 试用链接已生成。");
305
+ console.log(options.action === "update" ? "DemoGo 试用项目已更新,原链接保持不变。" : "DemoGo 试用链接已生成。");
219
306
  console.log(`项目名称:${result.projectName || result.name || result.slug || "-"}`);
220
307
  console.log(`访问链接:${result.publicUrl || "-"}`);
308
+ if (result.version) console.log(`当前版本:v${result.version}`);
221
309
  console.log(`发布方式:${result.deploySourceLabel || "DemoGo CLI"}`);
222
310
  console.log(`页面类型:${result.detectedType || "-"}`);
223
311
  console.log(`内容检查:${result.contentReview?.statusLabel || result.contentReviewStatus || "已通过"}`);
@@ -247,8 +335,11 @@ DemoGo CLI ${VERSION}
247
335
  demogo config clear
248
336
  demogo doctor
249
337
  demogo deploy
338
+ demogo deploy --new
339
+ demogo update --id <Demo ID 或原试用链接>
250
340
  demogo deploy --api <DemoGo地址> --token <AI发布口令>
251
341
  demogo deploy --name <项目名称> --dir <项目目录>
342
+ demogo update --id <Demo ID 或原试用链接> --dir <项目目录>
252
343
 
253
344
  本地安装:
254
345
  解压 dist/demogo-cli-v${VERSION}.zip 后,在解压目录执行 npm install -g .
@@ -261,6 +352,8 @@ DemoGo CLI ${VERSION}
261
352
  示例:
262
353
  demogo config set --api https://your-demogo.example.com --token dmg_xxx_xxx
263
354
  demogo deploy --api https://your-demogo.example.com --token dmg_xxx_xxx
355
+ demogo deploy --new
356
+ demogo update --id https://your-demogo.example.com/d/try-xxxxxx/ --dir ./my-project
264
357
  demogo deploy --name 我的报名页
265
358
  `.trim());
266
359
  }
package/lib/core.js CHANGED
@@ -1,22 +1,24 @@
1
1
  import { gzipSync } from "node:zlib";
2
2
  import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
3
+ import { readFileSync } from "node:fs";
3
4
  import path from "node:path";
4
5
 
5
- export const VERSION = "0.4.0";
6
- export const MAX_FILES = 800;
7
- export const MAX_BYTES = 50 * 1024 * 1024;
8
-
9
- export const unsafeProjectDirectoryNames = new Set([
10
- "desktop",
11
- "downloads",
12
- "documents",
13
- "onedrive",
14
- "桌面",
15
- "下载",
16
- "文档"
17
- ]);
6
+ export const VERSION = readVersion();
7
+ export const MAX_FILES = 800;
8
+ export const MAX_BYTES = 50 * 1024 * 1024;
9
+
10
+ export const unsafeProjectDirectoryNames = new Set([
11
+ "desktop",
12
+ "downloads",
13
+ "documents",
14
+ "onedrive",
15
+ "桌面",
16
+ "下载",
17
+ "文档"
18
+ ]);
18
19
 
19
20
  const EXCLUDED_DIRS = new Set([
21
+ ".demogo",
20
22
  ".git",
21
23
  ".hg",
22
24
  ".svn",
@@ -52,36 +54,53 @@ const SENSITIVE_EXTENSIONS = [
52
54
  ".cer"
53
55
  ];
54
56
 
55
- export async function assertDirectory(dir) {
56
- try {
57
- const stats = await stat(dir);
58
- if (!stats.isDirectory()) throw new Error(`不是项目目录:${dir}`);
59
- } catch {
60
- throw new Error(`找不到项目目录:${dir}`);
61
- }
62
- }
63
-
64
- export function assertSafeProjectDirectory(dir) {
65
- const resolved = path.resolve(dir);
66
- const home = path.resolve(process.env.USERPROFILE || process.env.HOME || "");
67
- const baseName = path.basename(resolved).toLowerCase();
68
-
69
- if (home && resolved === home) {
70
- throw new Error([
71
- "当前目录像是用户主目录,不适合直接发布。",
72
- "请新建一个干净文件夹,只放要发布的项目文件,然后在该文件夹里运行发布命令。",
73
- "也可以使用:npx --yes @demogo-cn/cli deploy --dir <项目目录>"
74
- ].join("\n"));
75
- }
76
-
77
- if (unsafeProjectDirectoryNames.has(baseName)) {
78
- throw new Error([
79
- `当前目录是 ${path.basename(resolved)},通常会包含很多无关文件,不适合直接发布。`,
80
- "请新建一个干净文件夹,只放要发布的项目文件;如果只有一个 HTML 文件,也可以让 AI 先创建临时目录再发布。",
81
- "也可以使用:npx --yes @demogo-cn/cli deploy --dir <项目目录>"
82
- ].join("\n"));
83
- }
84
- }
57
+ function readVersion() {
58
+ const candidates = [
59
+ path.resolve(process.cwd(), "VERSION"),
60
+ path.resolve(process.cwd(), "..", "VERSION"),
61
+ path.resolve(process.cwd(), "..", "..", "VERSION"),
62
+ path.resolve(path.dirname(new URL(import.meta.url).pathname), "..", "VERSION")
63
+ ];
64
+ for (const filePath of candidates) {
65
+ try {
66
+ return readFileSync(filePath, "utf8").trim();
67
+ } catch {
68
+ // Try the next packaged or repository-level VERSION file.
69
+ }
70
+ }
71
+ return "0.5.0";
72
+ }
73
+
74
+ export async function assertDirectory(dir) {
75
+ try {
76
+ const stats = await stat(dir);
77
+ if (!stats.isDirectory()) throw new Error(`不是项目目录:${dir}`);
78
+ } catch {
79
+ throw new Error(`找不到项目目录:${dir}`);
80
+ }
81
+ }
82
+
83
+ export function assertSafeProjectDirectory(dir) {
84
+ const resolved = path.resolve(dir);
85
+ const home = path.resolve(process.env.USERPROFILE || process.env.HOME || "");
86
+ const baseName = path.basename(resolved).toLowerCase();
87
+
88
+ if (home && resolved === home) {
89
+ throw new Error([
90
+ "当前目录像是用户主目录,不适合直接发布。",
91
+ "请新建一个干净文件夹,只放要发布的项目文件,然后在该文件夹里运行发布命令。",
92
+ "也可以使用:npx --yes @demogo-cn/cli deploy --dir <项目目录>"
93
+ ].join("\n"));
94
+ }
95
+
96
+ if (unsafeProjectDirectoryNames.has(baseName)) {
97
+ throw new Error([
98
+ `当前目录是 ${path.basename(resolved)},通常会包含很多无关文件,不适合直接发布。`,
99
+ "请新建一个干净文件夹,只放要发布的项目文件;如果只有一个 HTML 文件,也可以让 AI 先创建临时目录再发布。",
100
+ "也可以使用:npx --yes @demogo-cn/cli deploy --dir <项目目录>"
101
+ ].join("\n"));
102
+ }
103
+ }
85
104
 
86
105
  export async function collectFiles(rootDir) {
87
106
  const files = [];
@@ -101,24 +120,24 @@ export async function collectFiles(rootDir) {
101
120
  if (!entry.isFile()) continue;
102
121
  const stats = await stat(fullPath);
103
122
  totalBytes += stats.size;
104
- if (files.length >= MAX_FILES) throw new Error(projectTooLargeMessage(`项目文件超过 ${MAX_FILES} 个。`));
105
- if (totalBytes > MAX_BYTES) throw new Error(projectTooLargeMessage("项目文件超过 50MB。"));
123
+ if (files.length >= MAX_FILES) throw new Error(projectTooLargeMessage(`项目文件超过 ${MAX_FILES} 个。`));
124
+ if (totalBytes > MAX_BYTES) throw new Error(projectTooLargeMessage("项目文件超过 50MB。"));
106
125
  files.push({ fullPath, relativePath, size: stats.size });
107
126
  }
108
127
  }
109
128
 
110
- await walk(rootDir);
111
- return files;
112
- }
113
-
114
- export function projectTooLargeMessage(reason) {
115
- return [
116
- `${reason}你可能在桌面、下载、文档或用户根目录执行了发布。`,
117
- "请切换到真正的项目目录,或新建一个干净文件夹,只放要发布的项目文件。",
118
- "如果只有一个 HTML 文件,不需要改名为 index.html,可以把它单独放进干净目录后发布。",
119
- "也可以使用:npx --yes @demogo-cn/cli deploy --dir <项目目录>"
120
- ].join("\n");
121
- }
129
+ await walk(rootDir);
130
+ return files;
131
+ }
132
+
133
+ export function projectTooLargeMessage(reason) {
134
+ return [
135
+ `${reason}你可能在桌面、下载、文档或用户根目录执行了发布。`,
136
+ "请切换到真正的项目目录,或新建一个干净文件夹,只放要发布的项目文件。",
137
+ "如果只有一个 HTML 文件,不需要改名为 index.html,可以把它单独放进干净目录后发布。",
138
+ "也可以使用:npx --yes @demogo-cn/cli deploy --dir <项目目录>"
139
+ ].join("\n");
140
+ }
122
141
 
123
142
  export function summarizeProject(projectDir, files) {
124
143
  const totalBytes = files.reduce((sum, file) => sum + file.size, 0);
@@ -206,7 +225,7 @@ export async function deployArchive({ apiBase, token, archivePath, projectName,
206
225
  return payload;
207
226
  }
208
227
 
209
- export async function checkApiHealth(apiBase) {
228
+ export async function checkApiHealth(apiBase) {
210
229
  let response;
211
230
  try {
212
231
  response = await fetch(`${normalizeApiBase(apiBase)}/api/health`, {
@@ -221,31 +240,74 @@ export async function checkApiHealth(apiBase) {
221
240
  if (!response.ok || !payload?.ok) {
222
241
  throw new Error(`DemoGo 平台地址不可用:HTTP ${response.status}`);
223
242
  }
224
- return payload;
225
- }
226
-
227
- export async function checkAgentToken(apiBase, token, userAgent = `demogo-cli/${VERSION}`) {
228
- let response;
229
- try {
230
- response = await fetch(`${normalizeApiBase(apiBase)}/api/agent/token-check`, {
231
- method: "GET",
232
- headers: {
233
- Authorization: `Bearer ${token}`,
234
- "User-Agent": userAgent
235
- }
236
- });
237
- } catch {
238
- throw new Error(`无法连接 DemoGo:${normalizeApiBase(apiBase)}。请检查平台地址。`);
239
- }
240
- const text = await response.text();
241
- const payload = parseJson(text);
242
- if (!response.ok || !payload?.ok) {
243
- throw new Error(payload?.error || readableHttpError(response.status, text));
244
- }
245
- return payload;
246
- }
247
-
248
- export function normalizeApiBase(value) {
243
+ return payload;
244
+ }
245
+
246
+ export async function updateArchive({ apiBase, token, archivePath, demoRef, source = "cli", userAgent = `demogo-cli/${VERSION}` }) {
247
+ const formData = new FormData();
248
+ formData.set("source", source);
249
+ formData.set("demoId", demoRef);
250
+ formData.set(
251
+ "project",
252
+ new Blob([await readFile(archivePath)], { type: "application/gzip" }),
253
+ path.basename(archivePath)
254
+ );
255
+
256
+ let response;
257
+ try {
258
+ response = await fetch(`${normalizeApiBase(apiBase)}/api/agent/update`, {
259
+ method: "POST",
260
+ headers: {
261
+ Authorization: `Bearer ${token}`,
262
+ "User-Agent": userAgent,
263
+ "X-DemoGo-Deploy-Source": source
264
+ },
265
+ body: formData
266
+ });
267
+ } catch {
268
+ throw new Error(`无法连接 DemoGo:${normalizeApiBase(apiBase)}。请检查 API 地址是否正确,或稍后再试。`);
269
+ }
270
+
271
+ return parseDeploymentResponse(response);
272
+ }
273
+
274
+ async function parseDeploymentResponse(response) {
275
+ const text = await response.text();
276
+ const payload = parseJson(text);
277
+ if (!response.ok) {
278
+ const message = payload?.error || readableHttpError(response.status, text);
279
+ const shouldShowFixPrompt = response.status === 400 || payload?.inspection?.canPublish === false;
280
+ const fixPrompt = shouldShowFixPrompt
281
+ ? payload?.inspection?.fixPrompt || payload?.inspection?.ruleReport?.fixPrompt || ""
282
+ : "";
283
+ const details = fixPrompt ? `\n\n给 AI 工具的修改建议:\n${fixPrompt}` : "";
284
+ throw new Error(`${message}${details}`);
285
+ }
286
+ return payload;
287
+ }
288
+
289
+ export async function checkAgentToken(apiBase, token, userAgent = `demogo-cli/${VERSION}`) {
290
+ let response;
291
+ try {
292
+ response = await fetch(`${normalizeApiBase(apiBase)}/api/agent/token-check`, {
293
+ method: "GET",
294
+ headers: {
295
+ Authorization: `Bearer ${token}`,
296
+ "User-Agent": userAgent
297
+ }
298
+ });
299
+ } catch {
300
+ throw new Error(`无法连接 DemoGo:${normalizeApiBase(apiBase)}。请检查平台地址。`);
301
+ }
302
+ const text = await response.text();
303
+ const payload = parseJson(text);
304
+ if (!response.ok || !payload?.ok) {
305
+ throw new Error(payload?.error || readableHttpError(response.status, text));
306
+ }
307
+ return payload;
308
+ }
309
+
310
+ export function normalizeApiBase(value) {
249
311
  return String(value || "").trim().replace(/\/+$/, "");
250
312
  }
251
313
 
@@ -373,5 +435,6 @@ function readableHttpError(status, text) {
373
435
  if (status === 400) return "项目暂时不能发布,请根据 DemoGo 返回的提示修改后再试。";
374
436
  if (text?.trim().startsWith("<")) return "服务器返回了异常页面,本次发布没有完成。";
375
437
  return `DemoGo 发布失败:HTTP ${status}`;
376
- }
438
+ }
439
+
377
440
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@demogo-cn/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "DemoGo CLI for generating shareable trial links from AI-built pages.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,4 +14,5 @@
14
14
  "node": ">=18.0.0"
15
15
  },
16
16
  "license": "UNLICENSED"
17
- }
17
+ }
18
+