listpage_cli 0.0.311 → 0.0.313

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 (32) hide show
  1. package/bin/adapters/cli-interaction.js +6 -3
  2. package/bin/adapters/node-fs-adapter.js +8 -0
  3. package/bin/app/parse-args.js +1 -1
  4. package/bin/cli.js +3 -3
  5. package/bin/commands/lark/read-command.js +9 -1
  6. package/bin/commands/{build-project-command.js → project/build-project-command.js} +1 -1
  7. package/bin/commands/{deploy-project-command.js → project/deploy-project-command.js} +2 -2
  8. package/bin/commands/{release-project-command.js → project/release-project-command.js} +2 -2
  9. package/bin/commands/project-command.js +3 -3
  10. package/bin/commands/skill/add-command.js +9 -0
  11. package/bin/commands/skill/list-command.js +9 -0
  12. package/bin/commands/skill-command.js +21 -0
  13. package/bin/services/build-project-service.js +14 -0
  14. package/bin/services/{install-skill-service.js → skill-add-service.js} +8 -5
  15. package/bin/services/skill-list-service.js +27 -0
  16. package/package.json +1 -1
  17. package/templates/backend-template/package.json.tmpl +1 -1
  18. package/templates/backend-template/tsconfig.build.json +1 -0
  19. package/templates/backend-template/tsconfig.json +5 -2
  20. package/templates/frontend-template/package.json.tmpl +9 -13
  21. package/templates/frontend-template/src/api/index.ts +32 -4
  22. package/templates/frontend-template/src/api/request-config.ts +13 -0
  23. package/templates/frontend-template/src/api/user.ts +2 -13
  24. package/templates/rush-template/.gitignore.tmpl +3 -1
  25. package/templates/rush-template/listpage.config.json.tmpl +4 -0
  26. package/templates/skills-template/feishu-prd-reviewer/SKILL.md +124 -0
  27. package/templates/skills-template/feishu-prd-reviewer/example.md +36 -0
  28. package/templates/skills-template/listpage-cli-deploy/SKILL.md +123 -0
  29. package/bin/commands/install-skill-command.js +0 -9
  30. package/templates/frontend-template/src/api/config.tsx +0 -26
  31. package/templates/rush-template/.github/workflows/ci.yml +0 -27
  32. package/templates/rush-template/.trae/rules/project_rules.md +0 -25
@@ -156,9 +156,12 @@ function printHelp() {
156
156
  ` 用法: ${helpColor("listpage_cli init", "dim")}`,
157
157
  " 说明: 进入中文引导式交互,按提示填写即可",
158
158
  "",
159
- ` ${helpColor("install-skill", "green")}`,
160
- ` 用法: ${helpColor("listpage_cli install-skill [skillName] [--project]", "dim")}`,
161
- " 说明: 安装技能到 Cursor;默认 skillName 为 test,安装到当前命令执行目录的 .cursor/skills/",
159
+ ` ${helpColor("skill", "green")}`,
160
+ ` 用法: ${helpColor("listpage_cli skill list", "dim")}`,
161
+ " 说明: 列出 CLI 包内可安装的技能名称(模板目录下的子文件夹)",
162
+ "",
163
+ ` 用法: ${helpColor("listpage_cli skill add [技能名称]", "dim")}`,
164
+ " 说明: 安装技能到 Cursor;省略技能名时默认为 test,安装到当前命令执行目录的 .cursor/skills/",
162
165
  "",
163
166
  ` ${helpColor("project", "green")}`,
164
167
  ` 用法: ${helpColor("listpage_cli project build", "dim")}`,
@@ -88,6 +88,14 @@ function createNodeFsAdapter() {
88
88
  throw toFsPortError("writeBuffer", targetPath, error);
89
89
  }
90
90
  },
91
+ removeDirRecursive: (targetPath) => {
92
+ try {
93
+ (0, fs_1.rmSync)(targetPath, { recursive: true, force: true });
94
+ }
95
+ catch (error) {
96
+ throw toFsPortError("removeDirRecursive", targetPath, error);
97
+ }
98
+ },
91
99
  };
92
100
  }
93
101
  function toFsPortError(operation, targetPath, error) {
@@ -7,7 +7,7 @@ exports.parseCommandOptions = parseCommandOptions;
7
7
  exports.parseCommandPositionals = parseCommandPositionals;
8
8
  const KNOWN_COMMANDS = new Set([
9
9
  "init",
10
- "install-skill",
10
+ "skill",
11
11
  "project",
12
12
  "lark",
13
13
  ]);
package/bin/cli.js CHANGED
@@ -5,7 +5,7 @@ const cli_interaction_1 = require("./adapters/cli-interaction");
5
5
  const execute_1 = require("./app/execute");
6
6
  const command_result_1 = require("./domain/command-result");
7
7
  const init_command_1 = require("./commands/init-command");
8
- const install_skill_command_1 = require("./commands/install-skill-command");
8
+ const skill_command_1 = require("./commands/skill-command");
9
9
  const project_command_1 = require("./commands/project-command");
10
10
  const lark_command_1 = require("./commands/lark-command");
11
11
  const node_fs_adapter_1 = require("./adapters/node-fs-adapter");
@@ -23,7 +23,7 @@ const initCommandHandler = (0, init_command_1.createInitCommandHandler)({
23
23
  fs: fsAdapter,
24
24
  files: filesystemCapability,
25
25
  });
26
- const installSkillCommandHandler = (0, install_skill_command_1.createInstallSkillCommandHandler)({
26
+ const skillCommandHandler = (0, skill_command_1.createSkillCommandHandler)({
27
27
  fs: fsAdapter,
28
28
  files: filesystemCapability,
29
29
  config: {
@@ -45,7 +45,7 @@ async function main() {
45
45
  printVersion: cli_interaction_1.printVersion,
46
46
  handlers: {
47
47
  init: initCommandHandler,
48
- "install-skill": installSkillCommandHandler,
48
+ skill: skillCommandHandler,
49
49
  project: projectCommandHandler,
50
50
  lark: larkCommandHandler,
51
51
  },
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.handleReadCommand = handleReadCommand;
4
+ const node_fs_1 = require("node:fs");
4
5
  const command_result_1 = require("../../domain/command-result");
5
6
  const parse_doc_1 = require("./parse-doc");
6
7
  async function handleReadCommand(input, client, fs) {
@@ -23,9 +24,16 @@ async function handleReadCommand(input, client, fs) {
23
24
  const relativePath = `.listpage/lark/${docToken}/prd.md`;
24
25
  const absolutePath = fs.resolve(fs.cwd(), relativePath);
25
26
  const outputDir = fs.dirname(absolutePath);
26
- if (!fs.exists(outputDir)) {
27
+ try {
28
+ if ((0, node_fs_1.existsSync)(outputDir)) {
29
+ (0, node_fs_1.rmSync)(outputDir, { recursive: true, force: true });
30
+ }
27
31
  fs.ensureDir(outputDir);
28
32
  }
33
+ catch (error) {
34
+ const message = error instanceof Error ? error.message : String(error);
35
+ return (0, command_result_1.commandError)(`清理旧文档产物失败: ${message}`, "cleanup_lark_doc_dir_failed", 1);
36
+ }
29
37
  const docContent = await (0, parse_doc_1.parseBlocksToMarkdown)(blocks, {
30
38
  fs,
31
39
  outputDir,
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createBuildProjectCommandHandler = createBuildProjectCommandHandler;
4
- const build_project_service_1 = require("../services/build-project-service");
4
+ const build_project_service_1 = require("../../services/build-project-service");
5
5
  function createBuildProjectCommandHandler(deps) {
6
6
  return async (_input) => {
7
7
  return (0, build_project_service_1.runBuildProjectFlow)(deps);
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createDeployProjectCommandHandler = createDeployProjectCommandHandler;
4
- const parse_args_1 = require("../app/parse-args");
5
- const deploy_project_service_1 = require("../services/deploy-project-service");
4
+ const parse_args_1 = require("../../app/parse-args");
5
+ const deploy_project_service_1 = require("../../services/deploy-project-service");
6
6
  function createDeployProjectCommandHandler(deps) {
7
7
  return async (input) => {
8
8
  const firstPositional = (0, parse_args_1.parseProjectTailPositionals)(input.rawArgs)[0];
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createReleaseProjectCommandHandler = createReleaseProjectCommandHandler;
4
- const parse_args_1 = require("../app/parse-args");
5
- const release_project_service_1 = require("../services/release-project-service");
4
+ const parse_args_1 = require("../../app/parse-args");
5
+ const release_project_service_1 = require("../../services/release-project-service");
6
6
  function createReleaseProjectCommandHandler(deps) {
7
7
  return async (input) => {
8
8
  const firstPositional = (0, parse_args_1.parseProjectTailPositionals)(input.rawArgs)[0];
@@ -2,9 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createProjectCommandHandler = createProjectCommandHandler;
4
4
  const command_result_1 = require("../domain/command-result");
5
- const build_project_command_1 = require("./build-project-command");
6
- const release_project_command_1 = require("./release-project-command");
7
- const deploy_project_command_1 = require("./deploy-project-command");
5
+ const build_project_command_1 = require("./project/build-project-command");
6
+ const release_project_command_1 = require("./project/release-project-command");
7
+ const deploy_project_command_1 = require("./project/deploy-project-command");
8
8
  function createProjectCommandHandler(deps) {
9
9
  const build = (0, build_project_command_1.createBuildProjectCommandHandler)(deps.build);
10
10
  const release = (0, release_project_command_1.createReleaseProjectCommandHandler)(deps.release);
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSkillAddCommandHandler = createSkillAddCommandHandler;
4
+ const skill_add_service_1 = require("../../services/skill-add-service");
5
+ function createSkillAddCommandHandler(deps) {
6
+ return async (input) => {
7
+ return (0, skill_add_service_1.runSkillAddFlow)(input, deps);
8
+ };
9
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSkillListCommandHandler = createSkillListCommandHandler;
4
+ const skill_list_service_1 = require("../../services/skill-list-service");
5
+ function createSkillListCommandHandler(deps) {
6
+ return async (_input) => {
7
+ return (0, skill_list_service_1.runSkillListFlow)(deps);
8
+ };
9
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSkillCommandHandler = createSkillCommandHandler;
4
+ const command_result_1 = require("../domain/command-result");
5
+ const add_command_1 = require("./skill/add-command");
6
+ const list_command_1 = require("./skill/list-command");
7
+ function createSkillCommandHandler(deps) {
8
+ const add = (0, add_command_1.createSkillAddCommandHandler)(deps);
9
+ const list = (0, list_command_1.createSkillListCommandHandler)(deps);
10
+ return async (input) => {
11
+ const sub = input.positionals[0];
12
+ switch (sub) {
13
+ case "add":
14
+ return add(input);
15
+ case "list":
16
+ return list(input);
17
+ default:
18
+ return (0, command_result_1.commandError)("用法: listpage_cli skill add [技能名称]\n listpage_cli skill list", command_result_1.COMMAND_ERROR_CODES.unknownCommand, 1);
19
+ }
20
+ };
21
+ }
@@ -31,6 +31,13 @@ async function runBuildProjectFlow(deps) {
31
31
  const message = error instanceof Error ? error.message : String(error);
32
32
  return (0, command_result_1.commandError)(message, command_result_1.COMMAND_ERROR_CODES.invalidPath, 1);
33
33
  }
34
+ try {
35
+ removeOutputDirBeforeBuild(projectRoot);
36
+ }
37
+ catch (error) {
38
+ const message = error instanceof Error ? error.message : String(error);
39
+ return (0, command_result_1.commandError)(`[${command_runner_1.COMMAND_RUNNER_STAGE}] 清理输出目录失败: ${message}`, command_result_1.COMMAND_ERROR_CODES.executionFailed, 1);
40
+ }
34
41
  if (deps.executeBuild) {
35
42
  try {
36
43
  const result = await deps.executeBuild({
@@ -138,6 +145,13 @@ function resolveBuildTarget(projectRoot, projectDir) {
138
145
  ? projectDir
139
146
  : node_path_1.default.resolve(projectRoot, projectDir);
140
147
  }
148
+ /** 构建前删除 `.listpage/output`,避免遗留旧产物。 */
149
+ function removeOutputDirBeforeBuild(projectRoot) {
150
+ const outputRoot = node_path_1.default.resolve(projectRoot, ".listpage", "output");
151
+ if ((0, node_fs_1.existsSync)(outputRoot)) {
152
+ (0, node_fs_1.rmSync)(outputRoot, { recursive: true, force: true });
153
+ }
154
+ }
141
155
  function toCopySpecs(value) {
142
156
  if (!Array.isArray(value)) {
143
157
  return [];
@@ -1,18 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.runInstallSkillFlow = runInstallSkillFlow;
3
+ exports.runSkillAddFlow = runSkillAddFlow;
4
4
  const command_result_1 = require("../domain/command-result");
5
- function runInstallSkillFlow(input, deps) {
6
- const skillName = input.positionals[0] || deps.config.defaultSkillName;
5
+ function runSkillAddFlow(input, deps) {
6
+ const skillName = input.positionals[1] ?? deps.config.defaultSkillName;
7
7
  const sourceDir = deps.fs.join(deps.config.getSkillTemplateRoot(), skillName);
8
8
  if (!deps.fs.exists(sourceDir)) {
9
9
  return (0, command_result_1.commandError)(`技能 ${skillName} 不存在: ${sourceDir}`, command_result_1.COMMAND_ERROR_CODES.skillNotFound, 1);
10
10
  }
11
- const targetDirResult = resolveTargetDir(input, skillName, deps.fs);
11
+ const targetDirResult = resolveTargetDir(skillName, deps.fs);
12
12
  if (targetDirResult.kind === "error") {
13
13
  return targetDirResult.result;
14
14
  }
15
15
  try {
16
+ if (deps.fs.exists(targetDirResult.targetDir)) {
17
+ deps.fs.removeDirRecursive(targetDirResult.targetDir);
18
+ }
16
19
  deps.files.copySkillDir(sourceDir, targetDirResult.targetDir);
17
20
  return (0, command_result_1.commandOk)(`已安装 ${skillName} 技能到 ${targetDirResult.targetDir}`);
18
21
  }
@@ -21,7 +24,7 @@ function runInstallSkillFlow(input, deps) {
21
24
  return (0, command_result_1.commandError)(`安装技能失败(${skillName} -> ${targetDirResult.targetDir}): ${message}`, command_result_1.COMMAND_ERROR_CODES.fsOperationFailed, 1);
22
25
  }
23
26
  }
24
- function resolveTargetDir(input, skillName, fs) {
27
+ function resolveTargetDir(skillName, fs) {
25
28
  const baseDir = fs.cwd();
26
29
  if (!baseDir || !baseDir.trim()) {
27
30
  return {
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runSkillListFlow = runSkillListFlow;
4
+ const command_result_1 = require("../domain/command-result");
5
+ function runSkillListFlow(deps) {
6
+ const root = deps.config.getSkillTemplateRoot();
7
+ if (!deps.fs.exists(root)) {
8
+ return (0, command_result_1.commandError)(`技能模板目录不存在: ${root}`, command_result_1.COMMAND_ERROR_CODES.invalidPath, 1);
9
+ }
10
+ try {
11
+ const names = deps.fs
12
+ .readDir(root)
13
+ .filter((name) => {
14
+ const entry = deps.fs.join(root, name);
15
+ return deps.fs.exists(entry) && deps.fs.isDirectory(entry);
16
+ })
17
+ .sort((a, b) => a.localeCompare(b));
18
+ if (names.length === 0) {
19
+ return (0, command_result_1.commandOk)("(暂无可用技能)");
20
+ }
21
+ return (0, command_result_1.commandOk)(names.join("\n"));
22
+ }
23
+ catch (error) {
24
+ const message = error instanceof Error ? error.message : String(error);
25
+ return (0, command_result_1.commandError)(`列出技能失败: ${message}`, command_result_1.COMMAND_ERROR_CODES.fsOperationFailed, 1);
26
+ }
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "listpage_cli",
3
- "version": "0.0.311",
3
+ "version": "0.0.313",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "listpage_cli": "bin/cli.js"
@@ -25,7 +25,7 @@
25
25
  "class-transformer": "^0.5.1",
26
26
  "class-validator": "~0.14.2",
27
27
  "rxjs": "^7.8.1",
28
- "listpage-next-nest": "~0.0.311"
28
+ "listpage-next-nest": "~0.0.313"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@nestjs/schematics": "^11.0.0",
@@ -9,6 +9,7 @@
9
9
  "test",
10
10
  "**/*spec.ts",
11
11
  "prisma",
12
+ "dist",
12
13
  "prisma.config.ts"
13
14
  ]
14
15
  }
@@ -4,7 +4,7 @@
4
4
  "moduleResolution": "nodenext",
5
5
  "resolvePackageJsonExports": true,
6
6
  "esModuleInterop": true,
7
- "isolatedModules": true,
7
+ "isolatedModules": false,
8
8
  "declaration": true,
9
9
  "removeComments": true,
10
10
  "emitDecoratorMetadata": true,
@@ -20,6 +20,9 @@
20
20
  "forceConsistentCasingInFileNames": true,
21
21
  "noImplicitAny": false,
22
22
  "strictBindCallApply": false,
23
- "noFallthroughCasesInSwitch": false
23
+ "noFallthroughCasesInSwitch": false,
24
+ "paths": {
25
+ "exclude": ["node_modules", "dist"]
26
+ }
24
27
  }
25
28
  }
@@ -10,22 +10,18 @@
10
10
  "preview": "rsbuild preview"
11
11
  },
12
12
  "dependencies": {
13
- "react": "^19.2.0",
14
- "react-dom": "^19.2.0",
15
- "listpage-next": "~0.0.311",
16
- "react-router-dom": ">=6.0.0",
17
- "@ant-design/v5-patch-for-react-19": "~1.0.3",
13
+ "listpage-http": "0.0.313",
14
+ "listpage-components": "0.0.313",
15
+ "antd": "6.3.1",
18
16
  "ahooks": "^3.9.5",
19
- "antd": "^5.27.3",
20
- "axios": "^1.12.2",
17
+ "@ant-design/icons": "~6.0.2",
21
18
  "dayjs": "^1.11.18",
22
19
  "lodash": "^4.17.21",
23
- "styled-components": "^6.1.19",
24
- "mobx": "~6.15.0",
25
- "@ant-design/icons": "~6.0.2",
26
- "listpage-components": "~0.0.311",
27
- "lucide-react": "~0.575.0"
28
- "mobx-react-lite": "~4.1.1"
20
+ "lucide-react": "~0.575.0",
21
+ "recharts": "^3.7.0",
22
+ "react": "^19.2.0",
23
+ "react-dom": "^19.2.0",
24
+ "react-router-dom": ">=6.0.0"
29
25
  },
30
26
  "devDependencies": {
31
27
  "@rsbuild/core": "^1.6.0",
@@ -1,5 +1,33 @@
1
- import user from './user';
1
+ import { createApiClient } from 'listpage-http';
2
+ import { requestConfig, type RequestConfig } from './request-config';
3
+ import { TOKEN_STORAGE_KEY } from '@/constants/auth';
4
+ import { notification } from 'antd';
2
5
 
3
- export default {
4
- user,
5
- };
6
+ const isDev = process.env.NODE_ENV === 'development';
7
+
8
+ const api = createApiClient<RequestConfig>(requestConfig, {
9
+ baseURL: '/api/v1',
10
+ tokenKey: TOKEN_STORAGE_KEY,
11
+ defaultTimeout: 10_000,
12
+ successCodes: [20000, 200, 201],
13
+ unauthorizedCodes: [401],
14
+ unauthorizedRedirectPath: '/login',
15
+ server: {
16
+ protocol: undefined,
17
+ host: isDev ? 'localhost' : undefined,
18
+ port: isDev ? 3000 : undefined,
19
+ publicPath: '/api/v1',
20
+ },
21
+ onError: (code, msg) => {
22
+ // 统一错误处理
23
+ console.error('[API Error]', code, msg);
24
+ notification.open({
25
+ title: String(code),
26
+ description: msg || `请求失败 (${code})`,
27
+ placement: 'topRight',
28
+ type: 'error',
29
+ });
30
+ },
31
+ });
32
+
33
+ export default api;
@@ -0,0 +1,13 @@
1
+ import { defineEndpoint } from 'listpage-http';
2
+ import type { ReqLogin, ResLogin } from './user';
3
+
4
+ export const requestConfig = {
5
+ user: {
6
+ login: defineEndpoint<ReqLogin, ResLogin>({
7
+ method: 'POST',
8
+ path: '/auth/login',
9
+ }),
10
+ },
11
+ } as const;
12
+
13
+ export type RequestConfig = typeof requestConfig;
@@ -1,19 +1,8 @@
1
- import apiClient from './config';
2
-
3
- type ReqLogin = {
1
+ export type ReqLogin = {
4
2
  email: string;
5
3
  password: string;
6
4
  };
7
5
 
8
- type ResLogin = {
6
+ export type ResLogin = {
9
7
  token: string;
10
8
  };
11
-
12
- const login = async (values: ReqLogin) => {
13
- const response = await apiClient.post<ResLogin>('/auth/login', values);
14
- return response.data.data!;
15
- };
16
-
17
- export default {
18
- login,
19
- };
@@ -119,4 +119,6 @@ dist-storybook/
119
119
 
120
120
  # Heft temporary files
121
121
  .cache/
122
- .heft/
122
+ .heft/
123
+
124
+ .listpage
@@ -85,5 +85,9 @@
85
85
  },
86
86
  "volumes": []
87
87
  }
88
+ },
89
+ "lark": {
90
+ "APP_ID": "",
91
+ "APP_SECRET": ""
88
92
  }
89
93
  }
@@ -0,0 +1,124 @@
1
+ ---
2
+ name: feishu-prd-reviewer
3
+ description: 评审飞书 PRD(支持评审后结合待办反向澄清需求)
4
+ ---
5
+
6
+ # Feishu PRD Reviewer(产品视角)
7
+
8
+ ## 触发条件
9
+
10
+ - 用户要求处理该飞书 PRD(评审或补齐待办),并明确提供文档地址(URL)或 `doc_token`
11
+ - 通过 `npx listpage_cli lark read <doc_token>` 能成功读取到文档内容
12
+
13
+ ## 输入
14
+
15
+ - 飞书 PRD URL(用于解析 `doc_token`)
16
+ - 或用户直接提供 `doc_token`
17
+
18
+ ## 输出
19
+
20
+ - PRD 含待办:结合“AI 评审章节 + 待办问题”逐条处理待办,更新需求清单描述并回写对应区块、将待办标记为 done;随后结束
21
+ - PRD 不含待办:生成产品经理易读的评审结论并回写区块;随后结束
22
+
23
+ ## 核心原则
24
+
25
+ - **互斥执行**:一次仅执行一个模式。
26
+ - 有待办:只做“待办处理”,不做任何新的需求评审与结论生成(但可读取既有 AI 评审章节作为待办处理输入)。
27
+ - 无待办:只做“需求评审”,不进行待办处理流程(不创建 requirements.md)。
28
+ - **待办口径**:以“未完成待办”为准;若文档中的待办区块全部为 done(或没有待办条目),视为“无待办”。
29
+ - 产品视角优先:以“用户侧影响、业务闭环、风险与待确认点”为主,避免晦涩技术术语堆砌
30
+ - 与代码对齐:结合仓库现有实现推断隐含约束与异常场景,补齐开发需要的澄清点
31
+ - 约束强执行:待办处理阶段严格遵循标题层级与提示语移除规则(见“待办处理约束”)
32
+
33
+ ## 执行流程
34
+
35
+ 1. 获取 `doc_token`:从 URL 提取,或直接使用用户提供的 token
36
+ 2. 读取需求文档:在项目根目录执行
37
+ - `npx listpage_cli lark read <doc_token>`
38
+ - 该命令会把内容写入 `.listpage/lark/<doc_token>/prd.md`,并输出文件路径
39
+ 3. 判断待办是否存在
40
+ - 无待办:指“未完成待办”数量为 0(包括:文档没有待办、或所有待办均已标记 done)-> 进入步骤 6(代码评审 + 结论生成)
41
+ - 有待办:存在未完成待办 -> 进入步骤 4(待办逐条处理)
42
+ 4. 待办逐条处理(有待办时不进入新的代码评审与结论生成)
43
+ - 创建新文档
44
+ - 文件路径示例:`.listpage/lark/<doc_token>/requirements.md`
45
+ - 标题约束:严禁使用 H1/H2,所有标题必须从 H3 开始
46
+ - 内容约束:移除提示语“下面部分由产品经理编写,内容为准备评审的需求点”
47
+ - 内容来源:以原文档中“需求清单”作为基底,并结合“AI 评审章节(如 robot_face 回写的评审结论)”与待办问题进行澄清改写;去掉“需求清单”标题与相关提示语
48
+ - 循环处理每条待办
49
+ 1. 读取待办事项内容
50
+ 2. 同步读取对应需求点的 AI 评审内容(需求描述/问题/可行性/风险点)
51
+ 3. 以“待办问题 + AI 评审结论”共同作为输入,改写 `requirements.md` 中对应需求点,让需求描述更清晰、可执行
52
+ 4. 如 AI 评审与待办口径冲突,以“待办明确的新口径”优先,并在需求描述中消除歧义
53
+ 5. 回写时只更新需求清单内容,不改变既定回写命令和回写目标区块
54
+ 6. 直到所有待办处理完成
55
+ - 回写飞书文档(待办全部完成后)
56
+ - 执行:`npx listpage_cli lark update-block <doc_token> woman "<markdown_file_path>"`
57
+ - 更新待办状态
58
+ - 执行:`npx listpage_cli lark check-todo <doc_token> <index>`
59
+ - 将待办标记为完成(done)
60
+ 5. 流程结束:待办处理完成后停止,不再执行后续步骤
61
+ 6. 代码评审与结论生成(无待办时执行)
62
+
63
+ - 目标:先把“需求点列表”固定到 `review.md`,再对每条需求点按序逐条评审并回写,避免一次性把所有需求点塞进上下文导致爆炸
64
+ - 阶段 A:需求点提炼并写入 `review.md`(仅拆条,不写评审)
65
+ - 从步骤 2 读取到的 PRD 内容中,提取“需求清单”区段的全部需求点(包含图片/补充信息的隐含诉求)
66
+ - 将需求拆成 N 条“可执行条目”,为每条生成稳定编号(例如 `i=1..N`)
67
+ - 在本地创建/覆盖 Markdown:`.listpage/lark/<doc_token>/review.md`
68
+ - 在 `review.md` 顶部添加一个“执行待办清单”(仅用于阶段 B 驱动逐条处理)
69
+ - 格式示例(必须是 Markdown checklist):`- [ ] 需求点 1`
70
+ - 对每条生成一行:`- [ ] 需求点 i`
71
+ - 阶段 A 不把任何条目标为已完成
72
+ - 每条使用固定区块边界,格式示例:`### 需求点 i`(注意:仅要求禁止 H1/H2,H3 允许)
73
+ - 仅在每个 `### 需求点 i` 区块内写“需求描述”,并严格复用 `example.md` 的字段与缩进/子 bullet 结构(产品视角)
74
+ - 阶段 A 不写“问题/可行性/风险点”,只负责把每个需求点的“需求描述”结构固定下来,避免一次性输出过多评审内容导致上下文爆炸
75
+ - 格式约束:严禁使用 H1/H2 标题或表格;允许使用列表与加粗字段
76
+ - 阶段 B:逐条评审并回写 `review.md`(按序、只处理一条)
77
+ - 对 i = 1..N 循环执行,直到全部条目完成
78
+ 1. 只读取 `review.md` 中两处内容:
79
+ - 执行待办清单里第 i 行(`- [ ] 需求点 i` 或 `- [x] 需求点 i`)
80
+ - `### 需求点 i` 对应的区块(不要把其它条目的内容也纳入本轮处理)
81
+ 2. 只基于 PRD 需求本身进行判断,不做“结合代码去猜口径/猜实现”的推断;并且收紧“问题”触发条件:
82
+ - 只有当 PRD 信息不足以形成“开发可执行的需求描述”(关键落点/口径/范围/汇总规则无法确定)时,才输出 `**问题:**`(不写 `可行性/风险点`)
83
+ - 如果基于该需求点本身已足够做出“可以实现”的判断(即使仍存在非关键细节,也不阻塞落地),则不要问问题:直接输出 `**可行性:**`(成本等级 + 精简的必要说明)
84
+ 3. 将输出严格复用 `example.md` 的区块结构,并回写到对应 `### 需求点 i` 区块,保持其它区块不变:
85
+ - 不新增其它字段/标题/正文块
86
+ - 仅按需要填写 `需求描述` / `问题` / `可行性` / `风险点`(这些字段与缩进/子 bullet 结构必须与 `example.md` 一致)
87
+ - 不允许出现代码/组件/函数/文件名;不要根据代码猜口径(需求不清晰就只提问题)
88
+ 4. 把执行待办清单里第 i 行从 `- [ ] 需求点 i` 更新为 `- [x] 需求点 i`(作为“本条已完成”的唯一状态)
89
+ - 阶段 C:最终回写飞书
90
+ - 等循环全部完成后,在本地生成“回写版”文件:`review_writeback.md`
91
+ - `review_writeback.md` 最顶部新增 `### 评审人` 标题,并下一行写 `<当前模型名称>`(格式与 `example.md` 一致)
92
+ - 该文件同时保留 `### 评审人` 区块与所有 `### 需求点 i` 区块
93
+ - 必须删除顶部的“执行待办清单”(`- [ ]/- [x] 需求点 i` 那一段及其所在行),确保飞书里不出现 checklist 待办格式
94
+ - 执行飞书回写时,仅使用 `review_writeback.md`:
95
+ - `npx listpage_cli lark update-block <doc_token> robot_face "review_writeback.md"`
96
+
97
+ ## 评审方法(无待办时)
98
+
99
+ (无待办时执行第 6 步的“阶段 A/B/C”,本节仅保留总体输出原则)
100
+
101
+ - 需求点提炼:从“需求清单”区段理解全部需要落地的需求点(包括图片描述与补充信息中的隐含诉求),并拆成可逐条评审的 N 条条目
102
+ - 输出模板:每个 `### 需求点 i` 区块的呈现结构必须严格复用 `example.md`
103
+ - `- **需求描述:**:` 必须出现
104
+ - 仅当信息缺口会阻塞落地:才出现 `- **问题:**:`(此时不出现 `**可行性:**` 与 `**风险点:**`)
105
+ - 其余情况(能实现/不阻塞落地):出现 `- **可行性:**:`(可追加 `- **风险点:**:`,仅影响范围大时)
106
+ - 澄清优先(但不泛问):不阻塞落地时不提问;不做口径猜测
107
+ - 禁止代码猜测:不引用仓库代码/组件/函数/文件名;也不要根据代码推断不清晰的口径(应改为提问)
108
+
109
+ ## 写回文档(无待办时)
110
+
111
+ - 本地写回:通过第 6 步阶段 C 生成 `review_writeback.md`
112
+ - 格式要求:严禁使用(H1-H2)标题或表格;只保留 `### 评审人` 与所有 `### 需求点 i` 区块
113
+ - 同步飞书:执行 `npx listpage_cli lark update-block <doc_token> robot_face "review_writeback.md"`
114
+
115
+ ## 待办处理约束(强制)
116
+
117
+ - 新建 requirements.md 文档禁止 H1/H2,标题从 H3 开始
118
+ - 必须移除指定提示语,并去掉“需求清单”标题本身
119
+ - 必须把“AI 评审章节 + 待办问题”合并后再更新需求清单,不可仅依据待办原文机械改写
120
+
121
+ ## 成功标准
122
+
123
+ - 有待办:所有待办均回写并标记 done;已基于“AI 评审章节 + 待办问题”完成需求清单澄清更新;仅完成待办处理相关的文档更新;不生成新的需求评审结论(也不创建新的 review.md)
124
+ - 无待办:生成的评审结论回写成功;且回写内容满足“无 H1-H2 标题,无表格”的格式要求
@@ -0,0 +1,36 @@
1
+ ### 评审人
2
+
3
+ 模型名称
4
+
5
+ ### 需求点 1
6
+
7
+ - **需求描述:**: (一句话概括要做什么,产品视角)
8
+ - (关键点 1)
9
+ - (关键点 2)
10
+
11
+ ### 需求点 2
12
+
13
+ - **需求描述:**: (一句话概括要做什么,产品视角)
14
+
15
+ - **问题:**
16
+ - (问题 1:必须澄清“页面/表/口径/范围/汇总规则”等)
17
+ - (问题 2)
18
+ - (问题 3)
19
+
20
+ ### 需求点 3
21
+
22
+ - **需求描述:**: (一句话概括要做什么,产品视角)
23
+
24
+ - **可行性:**
25
+ - 成本:低/中/高
26
+ - (在不引入猜测的前提下,说明成本对应的业务/口径/环节调整)
27
+
28
+ ### 需求点 4
29
+
30
+ - **需求描述:**: (一句话概括要做什么,产品视角)
31
+ - **可行性:**
32
+ - 成本:中/高
33
+ - (在不引入猜测的前提下,说明成本对应的业务/口径/环节调整)
34
+ - **风险点:**
35
+ - (风险 1:可能影响的不止是展示文案,例如统计口径/汇总逻辑/跨模块联动等)
36
+ - (风险 2)
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: listpage-cli-deploy
3
+ description: Runs listpage_cli project build, release, and deploy in sequence (Docker build/push to registry and remote container). After a successful deploy, derive and tell the user the app access URL from listpage.config.json and backend listen port (see skill body). Use ONLY when the user explicitly asks to deploy using listpage_cli—e.g. mentions deploying with listpage_cli, listpage_cli 部署, or similar. Do NOT use for generic deployment, Docker/compose/K8s/CI, or Aliyun push without an explicit listpage_cli request. Default tag/environment is dev; users may name other environments in listpage.config.json docker.releaseProfiles (e.g. test, prod).
4
+ ---
5
+
6
+ # listpage_cli 自动化部署
7
+
8
+ ## 何时使用本技能
9
+
10
+ - **仅当用户明确说要通过 `listpage_cli` 部署**(例如:用 listpage_cli 部署、listpage_cli 发版等**且语境指向该 CLI**)时再应用本技能。
11
+ - **不要**因泛泛的「部署」「上线」「打镜像」「推阿里云」等就启用;除非用户同时点明 **listpage_cli**。
12
+
13
+ ## Tag 与环境
14
+
15
+ - `project release` / `project deploy` 的 **`<tag>`** 在习惯上对应**环境名**(开发 / 测试 / 生产等),与 `listpage.config.json` 里 **`docker.releaseProfiles`** 下的 key 一致;同一仓库可配置多套 profile(如 `dev`、`test`、`prod`),每套可有各自的 `tag`、`envFile` 等。
16
+ - **若用户未指定环境或 tag,默认使用 `dev`**。
17
+
18
+ 在**项目根目录**(需存在 `listpage.config.json`)按顺序执行三条命令即可完成构建、镜像推送与远程容器部署。
19
+
20
+ ## 前置条件
21
+
22
+ - 已安装并可执行 `listpage_cli`(可先运行 `listpage_cli --help` 核对子命令)。
23
+ - 本机已安装 **Docker**,且 **Docker 守护进程已启动**(`release` / `deploy` 会用到本地 Docker 与远程配置)。
24
+ - `listpage.config.json` 中 `docker.registry`、`docker.remote`、`docker.releaseProfiles` 等已按目标环境填好(见下文「常见坑」)。
25
+
26
+ ## 部署步骤(顺序固定)
27
+
28
+ 在项目根目录执行:
29
+
30
+ ```bash
31
+ listpage_cli project build
32
+ ```
33
+
34
+ - 校验当前目录为有效项目根,构建产物写入 **`.listpage/output`**。
35
+
36
+ ```bash
37
+ listpage_cli project release <tag>
38
+ ```
39
+
40
+ - 默认:`listpage_cli project release dev`(未指定环境时)。
41
+ - `<tag>` 与 `listpage.config.json` 中 `docker.releaseProfiles` 的环境 key 对齐(如 `dev` / `test` / `prod`);也可用 CLI 覆盖项,见 `listpage_cli --help` 中 `project release` 说明。
42
+ - 流程:校验 `.listpage/output` → 登录 / 构建 / 打 tag / 推送到配置的镜像仓库。
43
+
44
+ ```bash
45
+ listpage_cli project deploy <tag>
46
+ ```
47
+
48
+ - 默认:`listpage_cli project deploy dev`(与 `release` 使用同一环境名)。
49
+ - 使用 `docker.remote` + `docker.container` 部署;支持 `ports`、`envFile`、`env` 等合并。
50
+ - 若镜像已在目标环境且仅需更新容器步骤,可使用 CLI 中的 `--skip-image`(以 `listpage_cli --help` 为准)。
51
+
52
+ **CLI 参数优先级**(来自官方说明):`CLI 参数 > profile > 配置文件中的 base 项`。可选参数包括 `--profile`、`--platform`、`--env KEY=VALUE` 等,需要时查阅 `listpage_cli --help`。
53
+
54
+ ## 部署成功后:告知用户访问地址
55
+
56
+ 在 **`project deploy` 成功结束**后,必须结合**项目根目录的 `listpage.config.json`**(以及必要时后端监听端口)拼出**应用访问地址**,并用一句话告诉用户。**不要**把 `docker.registry` 密码、`envFile` 内容等敏感信息写入回复。
57
+
58
+ ### 推导规则(按顺序)
59
+
60
+ 1. **主机(host)**
61
+ 使用 `docker.remote.host`(远程部署目标机的 IP 或域名)。**不要**用 `docker.remote.port`(那是连接 Docker API 的端口,例如 2376,不是浏览器访问端口)。
62
+
63
+ 2. **协议(scheme)**
64
+ - **默认**:面向用户的应用 URL 使用 **`http`**。
65
+ - `docker.remote.protocol`(如 `https`)仅表示连接 **Docker daemon** 的方式,**不能**直接当作浏览器访问应用的协议。
66
+ - 若用户在前方挂了 **Nginx / 负载均衡 / 全站 HTTPS**,实际对外可能是 **`https`**;技能无法从配置文件唯一确定时,以 **`http` + host + 端口** 为主,并**简短说明**「若前面有 HTTPS 终止,请改用 https」。
67
+
68
+ 3. **路径(pathname / 前缀)**
69
+ - **一般情况**:路径前缀与 **`docker.container.name`** 一致,格式为 **`/<container.name>`**(注意前导 `/`,且通常**全小写**与配置一致)。
70
+ - **少数差异**:若前端 `assetPrefix` / `base`(如 `apps/fe` 的 `rsbuild.config.ts`)与 `docker.container.name` 不一致,应以**前端实际配置的 public 路径**为准(部署仍成功但路径不同时需核对前端)。默认在技能中优先 **`docker.container.name`**。
71
+
72
+ 4. **端口(浏览器访问的 host 端口)**
73
+ 读取 `docker.container.ports` 中每一项 **`"hostPort:containerPort"`** 字符串(与 Docker `-p` 一致)。
74
+ - 先确定**容器内后端 HTTP 监听端口**:查看 `servers/be`(或配置里 `artifacts.backend.projectDir`)中 `listen(...)`、`Dockerfile` 的 `EXPOSE` 或 `PORT` 环境变量;常见为 **3000**。
75
+ - 在 `ports` 数组里找到 **右侧(container)端口等于该后端端口** 的那一条,取**左侧(host)端口**作为 URL 中的端口。
76
+ - **多条映射时**:始终使用「**对应后端对外提供 Web 服务的那一个容器端口**」所映射到主机上的端口,而不是 Docker API 或其它服务端口。
77
+ - **单条映射**(如 `"3333:3000"`):后端在容器内监听 3000 → 浏览器访问 **`3333`**。
78
+
79
+ 5. **拼 URL**
80
+
81
+ `{scheme}://{docker.remote.host}:{上一步得到的 host 端口}/{路径前缀去掉多余斜杠}`
82
+
83
+ 路径前缀:若规则 3 得到 `/<name>`,则完整示例形态为:
84
+ `http://{host}:{hostPort}/{container.name}/`
85
+ 末尾是否带 `/` 可按习惯,保持与前端 `assetPrefix` 一致即可。
86
+
87
+ ### 示例(与配置字段对应)
88
+
89
+ 若 `docker.remote.host` 为 `localhost`,`docker.container.name` 为 `contract-payment-collection`,`docker.container.ports` 为 `["3333:3000"]`,且后端在容器内监听 **3000**,则访问地址为:
90
+
91
+ `http://localhost:3333/contract-payment-collection`
92
+
93
+ (若用户仅配置了 http 场景,上述即为默认输出;支持 https 时由用户或前置代理决定,可在说明中一句带过。)
94
+
95
+ ## 常见坑与配置要点
96
+
97
+ ### 1. Dockerfile 基础镜像(如 `FROM node:25-slim`)
98
+
99
+ 本地构建镜像时,若基础镜像未拉取过,`docker build` 可能失败或变慢。部署前可在本机先执行 `docker pull node:25-slim`(或与 `servers/be/Dockerfile` 中 `FROM` 一致的镜像),保证与构建环境一致。
100
+
101
+ ### 2. `docker.remote.tls`(ca / cert / key)
102
+
103
+ `listpage.config.json` 里 `docker.remote.tls` 的 `ca`、`cert`、`key` 路径指向的证书**与目标远程 Docker 主机绑定**;换一台部署机或重建 TLS 时,需替换为对应文件并保持路径可访问。
104
+
105
+ ### 3. `docker.build.platform`(如 `linux/amd64`)
106
+
107
+ 该字段影响**构建出的镜像架构**。例如:在 Apple Silicon Mac 上若要把镜像跑到 **x86 云服务器**,通常使用 `linux/amd64`;若目标为本机或 ARM 机器,需改为匹配的 `platform`(或通过 CLI 传 `--platform` 覆盖)。与运行环境不一致会导致容器无法运行或性能异常。
108
+
109
+ ### 4. 阿里云镜像仓库账号
110
+
111
+ `docker.registry` 中的 `url`、`namespace`、`username`、`password` 为**敏感信息**。技能与文档中只描述「在配置中填写」,不要在对话、提交记录或示例中粘贴真实密码;
112
+
113
+ ## 快速排错
114
+
115
+ | 现象 | 可检查项 |
116
+ |------|----------|
117
+ | `build` 失败 | 是否在项目根目录、`listpage.config.json` 是否完整、前后端构建脚本是否正常 |
118
+ | `release` 失败 | Docker 是否启动、基础镜像是否可拉取、registry 账号与网络、platform 是否与预期一致 |
119
+ | `deploy` 失败 | `docker.remote` 地址/端口/协议、TLS 证书是否匹配当前主机、`envFile` 路径是否存在 |
120
+
121
+ ## 参考命令说明
122
+
123
+ 执行 `listpage_cli --help` 可查看 `project build` / `project release` / `project deploy` 的完整参数与备注。
@@ -1,9 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createInstallSkillCommandHandler = createInstallSkillCommandHandler;
4
- const install_skill_service_1 = require("../services/install-skill-service");
5
- function createInstallSkillCommandHandler(deps) {
6
- return async (input) => {
7
- return (0, install_skill_service_1.runInstallSkillFlow)(input, deps);
8
- };
9
- }
@@ -1,26 +0,0 @@
1
- import { HttpClient } from 'listpage-next';
2
-
3
- const isDev = process.env.NODE_ENV === 'development';
4
-
5
- // 创建axios实例
6
- const apiClient = new HttpClient({
7
- token: 'admin',
8
- tokenKey: '',
9
- successCodes: [20000, 200],
10
- server: {
11
- protocol: undefined,
12
- host: isDev ? 'localhost' : undefined,
13
- port: isDev ? '3000' : undefined,
14
- publicPath: '/api/v1',
15
- },
16
- config: {
17
- timeout: 10000,
18
- headers: {
19
- 'Content-Type': 'application/json',
20
- },
21
- },
22
- });
23
-
24
- apiClient.setup();
25
-
26
- export default apiClient;
@@ -1,27 +0,0 @@
1
- name: CI
2
- on:
3
- push:
4
- branches: [ "main" ]
5
- pull_request:
6
- branches: [ "main" ]
7
- jobs:
8
- build:
9
- runs-on: ubuntu-latest
10
- steps:
11
- - uses: actions/checkout@v3
12
- with:
13
- fetch-depth: 2
14
- - name: Git config user
15
- uses: snow-actions/git-config-user@v1.0.0
16
- with:
17
- name: # Service Account's Name
18
- email: # Service Account's Email Address
19
- - uses: actions/setup-node@v3
20
- with:
21
- node-version: 16
22
- - name: Verify Change Logs
23
- run: node common/scripts/install-run-rush.js change --verify
24
- - name: Rush Install
25
- run: node common/scripts/install-run-rush.js install
26
- - name: Rush rebuild
27
- run: node common/scripts/install-run-rush.js rebuild --verbose --production
@@ -1,25 +0,0 @@
1
- # 项目规则
2
-
3
- ## 必须遵守的规则
4
-
5
- ### 技术方案编写规则
6
-
7
- 1. 存放目录: .trae/documents/ 下面。
8
- 2. 文件名称: xxx 技术方案.md
9
-
10
- ### 前端开发的规则
11
-
12
- 1. 前端组件导出必须使用具名导出,禁止 export default 的写法!!!
13
- 2. 当使用 ListPage 组件时,必须遵守项目内容文档:[ListPage-AI 生成规范](docs/ListPage-AI生成规范.md)。
14
- 3. 禁止使用 React.FC 来定义组件,组件的类型应该是 `(props: Props) => JSX.Element`, 不用显式声明。
15
- 4. 前端接口维护在 `api` 目录下, 所有的接口从 `api/index.ts` 中导出,严格遵守现在`api`目录的结构,并在需要的使用的时候从 `api/index.ts` 中引入
16
-
17
- ### 后端开发的规则
18
-
19
- 1. 后端接口如无特殊要求均采用 POST 请求
20
- 2. 对于列表查询的,默认请求体一定包含 current, pageSize 两个参数
21
- 3. 后端接口返回的格式要求如下:
22
- - 失败时,直接抛出异常,由拦截器自行处理,不用管
23
- - 成功时:
24
- - 常规数据的格式:{ code: 0, data: {} },控制器返回的数据直接就是 data,不需要再包一层
25
- - 列表数据的格式:{ code: 0, data: { list: [], total: 0, current: 1, pageSize: 10 } },控制器返回的数据直接就是 data,不需要再包一层