zero-ai 1.0.71 → 1.0.73
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/package.json
CHANGED
|
@@ -1,53 +1,54 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
2
|
+
"name": "zero-ai",
|
|
3
|
+
"version": "1.0.73",
|
|
4
|
+
"description": "Zero Ecotope AI",
|
|
5
|
+
"main": "src/ai.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ai": "./src/ai.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "src/index.test.js"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/silentbalanceyh/vertx-ai.git"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"Zero",
|
|
18
|
+
"UI"
|
|
19
|
+
],
|
|
20
|
+
"author": "Lang",
|
|
21
|
+
"license": "ISC",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/silentbalanceyh/vertx-ai/issues"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"bluebird": "^3.7.2",
|
|
27
|
+
"child_process": "^1.0.2",
|
|
28
|
+
"clipboard-copy": "^4.0.1",
|
|
29
|
+
"co": "^4.6.0",
|
|
30
|
+
"colors": "^1.4.0",
|
|
31
|
+
"commander": "^11.0.0",
|
|
32
|
+
"crypto-js": "^4.1.1",
|
|
33
|
+
"dox": "^1.0.0",
|
|
34
|
+
"ejs": "^3.1.10",
|
|
35
|
+
"exceljs": "^4.3.0",
|
|
36
|
+
"extend": "^3.0.2",
|
|
37
|
+
"handlebars": "^4.7.7",
|
|
38
|
+
"i": "^0.3.7",
|
|
39
|
+
"immutable": "^4.3.0",
|
|
40
|
+
"inquirer": "^8.2.5",
|
|
41
|
+
"linebyline": "^1.3.0",
|
|
42
|
+
"lodash": "^4.17.21",
|
|
43
|
+
"mkdirp": "^3.0.1",
|
|
44
|
+
"mockjs": "^1.1.0",
|
|
45
|
+
"random-js": "^2.1.0",
|
|
46
|
+
"superagent": "^8.0.9",
|
|
47
|
+
"taffydb": "^2.7.3",
|
|
48
|
+
"underscore": "^1.13.6",
|
|
49
|
+
"uuid": "^9.0.0",
|
|
50
|
+
"mysql2": "^3.11.0"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "http://www.vertxai.cn",
|
|
53
|
+
"github": "https://github.com/silentbalanceyh/vertx-ai.git"
|
|
53
54
|
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
const Ec = require("../epic");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const Ut = require("../commander-shared");
|
|
5
|
+
|
|
6
|
+
const REF_ROLE_ID = "e501b47a-c08b-4c83-b12b-95ad82873e96";
|
|
7
|
+
const REQUIRED_ENV_KEYS = ["Z_DB_TYPE", "Z_DBS_INSTANCE", "Z_DB_APP_USER", "Z_DB_APP_PASS"];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 从 pom.xml 读取当前项目的 artifactId(排除 <parent> 内的)
|
|
11
|
+
*/
|
|
12
|
+
function getArtifactIdFromPom(cwd) {
|
|
13
|
+
const pomPath = path.resolve(cwd, "pom.xml");
|
|
14
|
+
if (!fs.existsSync(pomPath)) return null;
|
|
15
|
+
let content = fs.readFileSync(pomPath, "utf-8");
|
|
16
|
+
content = content.replace(/<parent>[\s\S]*?<\/parent>/i, "");
|
|
17
|
+
const m = content.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
18
|
+
return m ? m[1].trim() : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 解析 app.env:export KEY="value" 或 export KEY='value',写入 process.env
|
|
23
|
+
*/
|
|
24
|
+
function loadAppEnv(filePath) {
|
|
25
|
+
if (!fs.existsSync(filePath)) return false;
|
|
26
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
27
|
+
const lines = content.split(/\r?\n/);
|
|
28
|
+
for (const line of lines) {
|
|
29
|
+
const trimmed = line.trim();
|
|
30
|
+
if (trimmed.startsWith("#") || !trimmed.startsWith("export ")) continue;
|
|
31
|
+
const match = trimmed.match(/^export\s+([A-Za-z0-9_]+)=["']?([^"'\n]*)["']?/);
|
|
32
|
+
if (match) process.env[match[1]] = match[2].trim();
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 解析 .r2mo/app.env 路径:
|
|
39
|
+
* ONE:当前目录 .r2mo/app.env
|
|
40
|
+
* DPA:{id}-api/.r2mo/app.env,id 来自 pom.xml 或当前目录名
|
|
41
|
+
* 支持两种布局:api 在项目内 (cwd/{id}-api) 或 与项目并列 (cwd/../{id}-api)
|
|
42
|
+
*/
|
|
43
|
+
function resolveAppEnvPath(cwd) {
|
|
44
|
+
const primary = path.resolve(cwd, ".r2mo", "app.env");
|
|
45
|
+
if (fs.existsSync(primary)) return primary;
|
|
46
|
+
|
|
47
|
+
let artifactId = getArtifactIdFromPom(cwd);
|
|
48
|
+
if (!artifactId) {
|
|
49
|
+
const base = path.basename(cwd);
|
|
50
|
+
if (base && base !== ".") artifactId = base;
|
|
51
|
+
}
|
|
52
|
+
if (artifactId) {
|
|
53
|
+
const apiDir = `${artifactId}-api`;
|
|
54
|
+
const nested = path.resolve(cwd, apiDir, ".r2mo", "app.env");
|
|
55
|
+
if (fs.existsSync(nested)) return nested;
|
|
56
|
+
const sibling = path.resolve(cwd, "..", apiDir, ".r2mo", "app.env");
|
|
57
|
+
if (fs.existsSync(sibling)) return sibling;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = async (options) => {
|
|
63
|
+
try {
|
|
64
|
+
const parsed = Ut.parseArgument(options);
|
|
65
|
+
const roleInput = parsed.role;
|
|
66
|
+
if (!roleInput || !String(roleInput).trim()) {
|
|
67
|
+
Ec.error("请使用 -r 指定角色(NAME 或 CODE)");
|
|
68
|
+
Ec.info("示例:ai perm -r 管理员 或 ai perm -r ADMIN");
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
Ec.execute(`ai perm:目标角色(-r)= ${roleInput}`);
|
|
73
|
+
|
|
74
|
+
const cwd = process.cwd();
|
|
75
|
+
const appEnvPath = resolveAppEnvPath(cwd);
|
|
76
|
+
if (!appEnvPath) {
|
|
77
|
+
const tried = [path.resolve(cwd, ".r2mo", "app.env")];
|
|
78
|
+
const id = getArtifactIdFromPom(cwd) || path.basename(cwd);
|
|
79
|
+
if (id) {
|
|
80
|
+
tried.push(path.resolve(cwd, `${id}-api`, ".r2mo", "app.env"));
|
|
81
|
+
tried.push(path.resolve(cwd, "..", `${id}-api`, ".r2mo", "app.env"));
|
|
82
|
+
}
|
|
83
|
+
Ec.error(".r2mo/app.env 不存在;DPA 下也未找到 {id}-api/.r2mo/app.env");
|
|
84
|
+
Ec.info("已尝试路径(id=" + (id || "未解析") + "):");
|
|
85
|
+
tried.forEach((p) => Ec.info(` - ${p}`));
|
|
86
|
+
Ec.info("请确认:1) 在项目根执行 2) 存在 .r2mo/app.env 或 {id}-api/.r2mo/app.env(嵌套或与项目并列)");
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
loadAppEnv(appEnvPath);
|
|
90
|
+
Ec.info(`已加载环境变量:${appEnvPath}`);
|
|
91
|
+
|
|
92
|
+
const missing = REQUIRED_ENV_KEYS.filter((k) => !process.env[k] || !String(process.env[k]).trim());
|
|
93
|
+
if (missing.length > 0) {
|
|
94
|
+
Ec.error(`环境变量不齐,缺少:${missing.join(", ")},已跳过并给出警告。`);
|
|
95
|
+
Ec.info("请在 .r2mo/app.env 中配置:Z_DB_TYPE、Z_DBS_INSTANCE、Z_DB_APP_USER、Z_DB_APP_PASS");
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const mysql = require("mysql2/promise");
|
|
100
|
+
const dbConfig = {
|
|
101
|
+
host: process.env.Z_DB_HOST || "localhost",
|
|
102
|
+
port: parseInt(process.env.Z_DB_PORT || "3306", 10),
|
|
103
|
+
user: process.env.Z_DB_APP_USER,
|
|
104
|
+
password: process.env.Z_DB_APP_PASS,
|
|
105
|
+
database: process.env.Z_DBS_INSTANCE
|
|
106
|
+
};
|
|
107
|
+
Ec.execute(`连接数据库:${dbConfig.database} @ ${dbConfig.host}:${dbConfig.port}(用户 ${dbConfig.user})`);
|
|
108
|
+
|
|
109
|
+
const conn = await mysql.createConnection(dbConfig);
|
|
110
|
+
try {
|
|
111
|
+
Ec.execute("查询 S_ROLE 表:按 NAME 或 CODE 匹配角色…");
|
|
112
|
+
const [rowsRole] = await conn.execute(
|
|
113
|
+
"SELECT ID, NAME, CODE FROM S_ROLE WHERE NAME = ? OR CODE = ? LIMIT 1",
|
|
114
|
+
[roleInput.trim(), roleInput.trim()]
|
|
115
|
+
);
|
|
116
|
+
if (!rowsRole || rowsRole.length === 0) {
|
|
117
|
+
Ec.error(`查询不到角色:${roleInput}`);
|
|
118
|
+
Ec.info("请确认 S_ROLE 表中存在该 NAME 或 CODE;可在库中执行:SELECT ID, NAME, CODE FROM S_ROLE; 查看已有角色。");
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const targetRoleId = rowsRole[0].ID;
|
|
122
|
+
Ec.info(`已找到角色:ID=${targetRoleId},NAME=${rowsRole[0].NAME || "-"},CODE=${rowsRole[0].CODE || "-"}`);
|
|
123
|
+
|
|
124
|
+
Ec.execute(`查询参考角色权限:R_ROLE_PERM,ROLE_ID = ${REF_ROLE_ID}`);
|
|
125
|
+
const [refPerms] = await conn.execute(
|
|
126
|
+
"SELECT * FROM R_ROLE_PERM WHERE ROLE_ID = ?",
|
|
127
|
+
[REF_ROLE_ID]
|
|
128
|
+
);
|
|
129
|
+
if (!refPerms || refPerms.length === 0) {
|
|
130
|
+
Ec.info(`参考角色 ${REF_ROLE_ID} 在 R_ROLE_PERM 中无记录,无需复制。`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
Ec.info(`参考角色共 ${refPerms.length} 条权限,开始复制到角色 ${targetRoleId}…`);
|
|
134
|
+
|
|
135
|
+
const columns = Object.keys(refPerms[0]);
|
|
136
|
+
const placeholders = columns.map(() => "?").join(", ");
|
|
137
|
+
const colList = columns.map((c) => "`" + c + "`").join(", ");
|
|
138
|
+
let inserted = 0;
|
|
139
|
+
let skipped = 0;
|
|
140
|
+
|
|
141
|
+
for (const row of refPerms) {
|
|
142
|
+
const values = columns.map((col) => (col === "ROLE_ID" ? targetRoleId : row[col]));
|
|
143
|
+
try {
|
|
144
|
+
const [result] = await conn.execute(
|
|
145
|
+
`INSERT IGNORE INTO R_ROLE_PERM (${colList}) VALUES (${placeholders})`,
|
|
146
|
+
values
|
|
147
|
+
);
|
|
148
|
+
if (result && result.affectedRows > 0) inserted++;
|
|
149
|
+
else skipped++;
|
|
150
|
+
} catch (err) {
|
|
151
|
+
if (err.code === "ER_DUP_ENTRY" || err.errno === 1062) skipped++;
|
|
152
|
+
else throw err;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Report:详细汇总
|
|
157
|
+
const sep = "----------------------------------------";
|
|
158
|
+
Ec.info(sep);
|
|
159
|
+
Ec.info(" ai perm 执行报告");
|
|
160
|
+
Ec.info(sep);
|
|
161
|
+
Ec.info(" ⚙️ 环境");
|
|
162
|
+
Ec.info(` app.env : ${appEnvPath}`);
|
|
163
|
+
Ec.info(` 数据库类型 : ${process.env.Z_DB_TYPE || "-"}`);
|
|
164
|
+
Ec.info(` 数据库实例 : ${dbConfig.database}`);
|
|
165
|
+
Ec.info(` 连接地址 : ${dbConfig.host}:${dbConfig.port}`);
|
|
166
|
+
Ec.info(` 数据库用户 : ${dbConfig.user}`);
|
|
167
|
+
Ec.info(" 👤 目标角色(-r 指定)");
|
|
168
|
+
Ec.info(` 输入 : ${roleInput}`);
|
|
169
|
+
Ec.info(` ID : ${targetRoleId}`);
|
|
170
|
+
Ec.info(` NAME : ${rowsRole[0].NAME ?? "-"}`);
|
|
171
|
+
Ec.info(` CODE : ${rowsRole[0].CODE ?? "-"}`);
|
|
172
|
+
Ec.info(" 📋 参考角色(复制来源)");
|
|
173
|
+
Ec.info(` ROLE_ID : ${REF_ROLE_ID}`);
|
|
174
|
+
Ec.info(` R_ROLE_PERM 条数 : ${refPerms.length}`);
|
|
175
|
+
Ec.info(" ✅ 权限复制结果");
|
|
176
|
+
Ec.info(` 本次插入 : ${inserted} 条`);
|
|
177
|
+
Ec.info(` 重复跳过 : ${skipped} 条`);
|
|
178
|
+
Ec.info(` 合计处理 : ${inserted + skipped} 条`);
|
|
179
|
+
Ec.info(sep);
|
|
180
|
+
} finally {
|
|
181
|
+
await conn.end();
|
|
182
|
+
}
|
|
183
|
+
} catch (err) {
|
|
184
|
+
Ec.error(`执行失败:${err.message}`);
|
|
185
|
+
if (err.code) Ec.info(`错误码:${err.code}`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
@@ -181,7 +181,7 @@ module.exports = async (options) => {
|
|
|
181
181
|
Ec.info(`安装位置:${targetLabels.join("、")}`);
|
|
182
182
|
Ec.info(`已安装 ${selectedFiles.length} 个规则文件`);
|
|
183
183
|
|
|
184
|
-
// work-claude
|
|
184
|
+
// work-claude/:从笔记 frontmatter 提取 title 构造菜单,用户选择后拷贝该文件为项目根目录 CLAUDE.md
|
|
185
185
|
const workClaudeDir = path.join(repoCache, "work-claude");
|
|
186
186
|
if (fs.existsSync(workClaudeDir)) {
|
|
187
187
|
const workClaudeFiles = fs.readdirSync(workClaudeDir).filter((file) => {
|
|
@@ -192,10 +192,16 @@ module.exports = async (options) => {
|
|
|
192
192
|
const choicesWithTitle = workClaudeFiles.map((file) => {
|
|
193
193
|
const fullPath = path.join(workClaudeDir, file);
|
|
194
194
|
const content = fs.readFileSync(fullPath, "utf-8");
|
|
195
|
-
const
|
|
196
|
-
|
|
195
|
+
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
196
|
+
let title = path.basename(file, ".md");
|
|
197
|
+
if (fmMatch) {
|
|
198
|
+
const block = fmMatch[1];
|
|
199
|
+
const titleMatch = block.match(/title:\s*["']([^"']+)["']/);
|
|
200
|
+
if (titleMatch) title = titleMatch[1].trim();
|
|
201
|
+
}
|
|
197
202
|
return { name: title, value: file };
|
|
198
203
|
});
|
|
204
|
+
choicesWithTitle.sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true }));
|
|
199
205
|
const { selectedClaude } = await inquirer.prompt([
|
|
200
206
|
{
|
|
201
207
|
type: "list",
|
|
@@ -208,21 +214,6 @@ module.exports = async (options) => {
|
|
|
208
214
|
const targetClaude = path.resolve(outputPath, "CLAUDE.md");
|
|
209
215
|
fs.copyFileSync(sourceClaude, targetClaude);
|
|
210
216
|
Ec.info(`已安装:${selectedClaude} → CLAUDE.md`);
|
|
211
|
-
// 将 CLAUDE.md 追加到 .gitignore
|
|
212
|
-
const gitignorePathClaude = path.resolve(outputPath, ".gitignore");
|
|
213
|
-
const claudeIgnoreEntry = "CLAUDE.md";
|
|
214
|
-
if (fs.existsSync(gitignorePathClaude)) {
|
|
215
|
-
const content = fs.readFileSync(gitignorePathClaude, "utf-8");
|
|
216
|
-
const hasClaude = content.split("\n").some((line) => line.trim() === claudeIgnoreEntry);
|
|
217
|
-
if (!hasClaude) {
|
|
218
|
-
const newContent = content.endsWith("\n") ? `${content}${claudeIgnoreEntry}\n` : `${content}\n${claudeIgnoreEntry}\n`;
|
|
219
|
-
fs.writeFileSync(gitignorePathClaude, newContent, "utf-8");
|
|
220
|
-
Ec.info(`已将 ${claudeIgnoreEntry} 添加到 .gitignore`);
|
|
221
|
-
}
|
|
222
|
-
} else {
|
|
223
|
-
fs.writeFileSync(gitignorePathClaude, `${claudeIgnoreEntry}\n`, "utf-8");
|
|
224
|
-
Ec.info(`已创建 .gitignore 并添加 ${claudeIgnoreEntry}`);
|
|
225
|
-
}
|
|
226
217
|
}
|
|
227
218
|
}
|
|
228
219
|
|
|
@@ -8,6 +8,7 @@ const executeWeb = require('./fn.source.front');
|
|
|
8
8
|
const executeSpring = require('./fn.source.spring');
|
|
9
9
|
const executeZero = require('./fn.source.zero');
|
|
10
10
|
const executeApply = require('./fn.source.apply');
|
|
11
|
+
const executePerm = require('./fn.perm');
|
|
11
12
|
const exported = {
|
|
12
13
|
executeUuid, // ai uuid
|
|
13
14
|
executeString, // ai str
|
|
@@ -21,6 +22,7 @@ const exported = {
|
|
|
21
22
|
executeZero, // ai zero
|
|
22
23
|
// Cursor 规则安装
|
|
23
24
|
executeApply, // ai apply
|
|
25
|
+
executePerm, // ai perm
|
|
24
26
|
};
|
|
25
27
|
module.exports = exported;
|
|
26
28
|
/**
|