@icyouo/evt-cli 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,8 @@ evt-cli is a general-purpose HTTP CLI for running YAML-defined APIs and workflow
9
9
  - Runtime endpoints come only from `apis/*.yaml`.
10
10
  - Flows come only from `flows/*.yaml`.
11
11
  - Environments, base URLs, headers, and test fixtures come only from `profiles/*.json`.
12
- - The scanner is only for coverage comparison and generating missing YAML skeletons. It is not a runtime data source.
12
+ - Endpoint definitions are persisted as YAML. Scanner JSON is only an internal machine-readable handoff.
13
+ - Bundled skills are preferred for project data bootstrap. The built-in regex scanner remains as an endpoint-only fallback for users without an agent or skill workflow.
13
14
  - The default data directory is bundled with the CLI package. You can also point to your own project data directory with `--config-root` or `EVT_CLI_ROOT`.
14
15
 
15
16
  ## Quick Start
@@ -77,12 +78,33 @@ evt-cli ships with `data/**/*.example.*` for validation and reference. Real proj
77
78
 
78
79
  For compatibility with existing projects, evt also reads `apis/`, `flows/`, `profiles/`, and `scanner.config.json` directly under `--config-root` when those paths exist.
79
80
 
81
+ ## Bootstrap Project Data With Skills
82
+
83
+ evt-cli ships generic example data and bundled skills. A skill-aware agent can
84
+ turn an empty project data directory into runnable project data:
85
+
86
+ 1. Use `evt-profile-generator` to create `data/profiles/<env>.json` from source
87
+ config, docs, endpoint schemas, and user-provided test credentials.
88
+ 2. Use `evt-api-scanner` to discover API source paths, write
89
+ `data/scanner.json`, and generate `data/apis/*.yaml`.
90
+ 3. Use `evt-flow-generator` to create `data/flows/login.yaml`, smoke flows, and
91
+ feature flows from the generated endpoints and product workflow.
92
+ 4. Run `evt validate --config-root ./cli`.
93
+ 5. Run flows with `--dry-run` first, then against a safe test environment.
94
+
95
+ Profile and flow generation are AI-first tasks because base URLs, headers,
96
+ login state, test accounts, token response paths, and feature sequences are
97
+ project-specific. evt-cli keeps code fallback only for endpoint scanning.
98
+
80
99
  ## Local Check And Package
81
100
 
82
101
  ```bash
83
102
  npm run ci:local
84
103
  ```
85
104
 
105
+ The npm package includes source code, scripts, tests, data examples, and skills
106
+ so users can run local checks and build the standalone local binary package.
107
+
86
108
  This runs:
87
109
 
88
110
  - `npm test`
@@ -100,6 +122,7 @@ The local package directory contains only:
100
122
 
101
123
  - `evt`
102
124
  - `data/`
125
+ - `skills/`
103
126
  - `README.md`
104
127
  - `README.zh-CN.md`
105
128
 
@@ -146,7 +169,47 @@ Each endpoint should include:
146
169
  - `response`
147
170
  - `dangerous: true` when the endpoint is destructive or sensitive
148
171
 
149
- ## Scan And Coverage
172
+ ## Skills, Scan, Sync, And Coverage
173
+
174
+ evt-cli includes these bundled skills:
175
+
176
+ - `skills/evt-profile-generator/SKILL.md`
177
+ - `skills/evt-api-scanner/SKILL.md`
178
+ - `skills/evt-flow-generator/SKILL.md`
179
+
180
+ Agents can use these skills to create profile JSON, discover source
181
+ directories, write scanner config, generate YAML endpoint definitions, create
182
+ flow YAML, and audit coverage.
183
+
184
+ Endpoint definitions are written to YAML under `data/apis/*.yaml`. The scanner
185
+ may use JSON internally, but JSON is not the endpoint definition format.
186
+
187
+ Discover API source paths and write `data/scanner.json`:
188
+
189
+ ```bash
190
+ evt api discover --config-root ./cli
191
+ ```
192
+
193
+ Generate or update YAML endpoint definitions:
194
+
195
+ ```bash
196
+ evt api sync --config-root ./cli
197
+ ```
198
+
199
+ Audit YAML quality after sync:
200
+
201
+ ```bash
202
+ evt api audit --config-root ./cli --strict
203
+ ```
204
+
205
+ Check YAML coverage:
206
+
207
+ ```bash
208
+ evt api coverage --config-root ./cli
209
+ ```
210
+
211
+ The built-in regex scanner is still available as a fallback. It is less complete
212
+ than the skill-guided workflow, but it works without an agent.
150
213
 
151
214
  Scan source code for candidate endpoints:
152
215
 
@@ -166,7 +229,8 @@ Generate missing YAML skeletons:
166
229
  npm run sync:api -- --config-root /path/to/project/cli
167
230
  ```
168
231
 
169
- The scanner supports `kotlin`, `swift`, `js`, and `dart`. It also supports external skill or AST scanner commands:
232
+ The scanner supports `kotlin`, `swift`, `js`, and `dart`. It also supports the
233
+ bundled skill scanner and external command scanners:
170
234
 
171
235
  ```json
172
236
  {
@@ -179,15 +243,16 @@ The scanner supports `kotlin`, `swift`, `js`, and `dart`. It also supports exter
179
243
  },
180
244
  {
181
245
  "language": "skill",
182
- "name": "ast-scanner",
183
- "command": "node",
184
- "args": ["tools/scan-endpoints.js"]
246
+ "name": "evt-api-scanner",
247
+ "skill": "evt-api-scanner"
185
248
  }
186
249
  ]
187
250
  }
188
251
  ```
189
252
 
190
- An external scanner can output a JSON array or `{ "endpoints": [] }`. Each endpoint should include at least:
253
+ An external scanner can output a JSON array or `{ "endpoints": [] }` as an
254
+ internal scan result. `evt api sync` converts that scan result into YAML. Each
255
+ intermediate endpoint should include at least:
191
256
 
192
257
  ```json
193
258
  {
@@ -206,6 +271,10 @@ This JSON is used only for scanning, coverage, and sync. Runtime execution still
206
271
  ```bash
207
272
  evt profile list
208
273
  evt api list
274
+ evt api discover --config-root ./cli
275
+ evt api sync --config-root ./cli
276
+ evt api audit --config-root ./cli --strict
277
+ evt api coverage --config-root ./cli
209
278
  evt api call todo.list --dry-run
210
279
  evt flow run login --profile local
211
280
  evt cache show
package/README.zh-CN.md CHANGED
@@ -9,7 +9,8 @@ evt-cli 是一个通用 HTTP CLI,用 YAML 定义接口、用 YAML 定义 flow
9
9
  - 运行时端点只来自 `apis/*.yaml`。
10
10
  - flow 只来自 `flows/*.yaml`。
11
11
  - 环境、base URL、headers、测试 fixtures 只来自 `profiles/*.json`。
12
- - 扫描器只用于覆盖率对比和生成缺失 YAML 骨架,不是运行时数据源。
12
+ - endpoint 定义持久化格式始终是 YAML。scanner JSON 只作为内部机器中间结果。
13
+ - 内置 skills 优先用于项目 data 初始化。现有正则 scanner 只保留给没有 agent 或 skill workflow 的用户做 endpoint 扫描兜底。
13
14
  - 默认数据目录是当前 CLI 包内目录;也可以通过 `--config-root` 或 `EVT_CLI_ROOT` 指向项目自己的数据目录。
14
15
 
15
16
  ## 快速开始
@@ -77,12 +78,26 @@ evt-cli 自带 `data/**/*.example.*`,用于开箱验证和复制参考。真
77
78
 
78
79
  为了兼容已有项目,`--config-root` 下直接存在 `apis/`、`flows/`、`profiles/`、`scanner.config.json` 时也会被读取。
79
80
 
81
+ ## 用 Skills 初始化项目 Data
82
+
83
+ evt-cli 自带通用 example data 和内置 skills。支持 skill 的 agent 可以把一个空的项目 data 目录生成成可运行的项目 data:
84
+
85
+ 1. 使用 `evt-profile-generator` 根据源码配置、文档、endpoint schema 和用户提供的测试账号生成 `data/profiles/<env>.json`。
86
+ 2. 使用 `evt-api-scanner` 发现 API 源码路径,写入 `data/scanner.json`,并生成 `data/apis/*.yaml`。
87
+ 3. 使用 `evt-flow-generator` 根据生成的 endpoint 和产品流程生成 `data/flows/login.yaml`、smoke flow 和 feature flow。
88
+ 4. 执行 `evt validate --config-root ./cli`。
89
+ 5. 先用 `--dry-run` 跑 flow,再在安全测试环境里跑真实请求。
90
+
91
+ profile 和 flow 生成是 AI-first 任务,因为 base URL、headers、登录态、测试账号、token 返回路径和 feature 调用顺序都依赖具体项目语义。evt-cli 只保留 endpoint 扫描的代码兜底。
92
+
80
93
  ## 本地校验和打包
81
94
 
82
95
  ```bash
83
96
  npm run ci:local
84
97
  ```
85
98
 
99
+ npm 包会包含源码、脚本、测试、示例数据和 skills,用户可以自行运行本地校验并构建独立本地二进制包。
100
+
86
101
  执行内容:
87
102
 
88
103
  - `npm test`
@@ -100,6 +115,7 @@ npm run ci:local
100
115
 
101
116
  - `evt`
102
117
  - `data/`
118
+ - `skills/`
103
119
  - `README.md`
104
120
  - `README.zh-CN.md`
105
121
 
@@ -146,7 +162,46 @@ endpoints:
146
162
  - `response`
147
163
  - 必要时标记 `dangerous: true`
148
164
 
149
- ## 扫描和覆盖率
165
+ ## Skills、扫描、同步和覆盖率
166
+
167
+ evt-cli 内置这些 skills:
168
+
169
+ - `skills/evt-profile-generator/SKILL.md`
170
+ - `skills/evt-api-scanner/SKILL.md`
171
+ - `skills/evt-flow-generator/SKILL.md`
172
+
173
+ agent 可以使用这些 skills 生成 profile JSON、发现源码目录、写入 scanner 配置、
174
+ 生成 YAML endpoint 定义、生成 flow YAML,并做覆盖率审计。
175
+
176
+ endpoint 定义会写到 `data/apis/*.yaml`。scanner 可以在内部使用 JSON,但 JSON
177
+ 不是 endpoint 的最终定义格式。
178
+
179
+ 发现 API 源码路径并写入 `data/scanner.json`:
180
+
181
+ ```bash
182
+ evt api discover --config-root ./cli
183
+ ```
184
+
185
+ 生成或更新 YAML endpoint 定义:
186
+
187
+ ```bash
188
+ evt api sync --config-root ./cli
189
+ ```
190
+
191
+ 检查 YAML 质量门禁:
192
+
193
+ ```bash
194
+ evt api audit --config-root ./cli --strict
195
+ ```
196
+
197
+ 检查 YAML 覆盖率:
198
+
199
+ ```bash
200
+ evt api coverage --config-root ./cli
201
+ ```
202
+
203
+ 内置正则 scanner 仍然保留为兜底方案。它不如 skill workflow 完整,但不依赖
204
+ agent,也能让没有 agent 的用户勉强使用。
150
205
 
151
206
  扫描源码候选 endpoint:
152
207
 
@@ -166,7 +221,7 @@ npm run coverage:api -- --config-root /path/to/project/cli
166
221
  npm run sync:api -- --config-root /path/to/project/cli
167
222
  ```
168
223
 
169
- 扫描器支持 `kotlin`、`swift`、`js`、`dart`,也支持外部 skill/AST 扫描命令:
224
+ 扫描器支持 `kotlin`、`swift`、`js`、`dart`,也支持内置 skill scanner 和外部命令 scanner:
170
225
 
171
226
  ```json
172
227
  {
@@ -179,15 +234,15 @@ npm run sync:api -- --config-root /path/to/project/cli
179
234
  },
180
235
  {
181
236
  "language": "skill",
182
- "name": "ast-scanner",
183
- "command": "node",
184
- "args": ["tools/scan-endpoints.js"]
237
+ "name": "evt-api-scanner",
238
+ "skill": "evt-api-scanner"
185
239
  }
186
240
  ]
187
241
  }
188
242
  ```
189
243
 
190
- 外部 scanner 输出 JSON 数组,或 `{ "endpoints": [] }`。每个 endpoint 至少包含:
244
+ 外部 scanner 可以输出 JSON 数组,或 `{ "endpoints": [] }` 作为内部扫描结果。
245
+ `evt api sync` 会把这个扫描结果转换成 YAML。每个中间 endpoint 至少包含:
191
246
 
192
247
  ```json
193
248
  {
@@ -206,6 +261,10 @@ npm run sync:api -- --config-root /path/to/project/cli
206
261
  ```bash
207
262
  evt profile list
208
263
  evt api list
264
+ evt api discover --config-root ./cli
265
+ evt api sync --config-root ./cli
266
+ evt api audit --config-root ./cli --strict
267
+ evt api coverage --config-root ./cli
209
268
  evt api call todo.list --dry-run
210
269
  evt flow run login --profile local
211
270
  evt cache show
@@ -30,11 +30,8 @@
30
30
  },
31
31
  {
32
32
  "language": "skill",
33
- "name": "ast-scanner",
34
- "command": "node",
35
- "args": [
36
- "tools/scan-endpoints.js"
37
- ]
33
+ "name": "evt-api-scanner",
34
+ "skill": "evt-api-scanner"
38
35
  }
39
36
  ]
40
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icyouo/evt-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Run YAML-defined HTTP APIs and interactive workflows from your terminal.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -17,17 +17,21 @@
17
17
  "files": [
18
18
  "bin/",
19
19
  "src/",
20
+ "scripts/",
21
+ "test/",
20
22
  "data/",
23
+ "skills/",
21
24
  "README.md",
22
25
  "README.zh-CN.md"
23
26
  ],
24
27
  "scripts": {
25
28
  "ci:local": "node scripts/local-ci.js",
26
29
  "test": "node --test",
27
- "check": "node --check bin/evt.js && node --check src/index.js && node --check src/api/liveTester.js && node --check scripts/sync-api-coverage.js && node --check scripts/check-api-coverage.js && node --check scripts/local-ci.js && node --check scripts/build-package.js",
30
+ "check": "node --check bin/evt.js && node --check src/index.js && node --check src/api/liveTester.js && node --check scripts/sync-api-coverage.js && node --check scripts/check-api-coverage.js && node --check scripts/check-api-audit.js && node --check scripts/local-ci.js && node --check scripts/build-package.js && node --check skills/evt-api-scanner/scripts/discover.js && node --check skills/evt-api-scanner/scripts/scan.js",
28
31
  "package:local": "node scripts/build-package.js",
29
32
  "sync:api": "node scripts/sync-api-coverage.js",
30
- "coverage:api": "node scripts/check-api-coverage.js"
33
+ "coverage:api": "node scripts/check-api-coverage.js",
34
+ "audit:api": "node scripts/check-api-audit.js"
31
35
  },
32
36
  "engines": {
33
37
  "node": ">=20"
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const { spawnSync } = require("node:child_process");
6
+
7
+ const cliRoot = path.resolve(__dirname, "..");
8
+ const packageJson = require(path.join(cliRoot, "package.json"));
9
+ const distDir = path.join(cliRoot, "dist");
10
+ const packageName = "evt-cli";
11
+ const packageDir = path.join(distDir, packageName);
12
+ const entryId = "bin/evt.js";
13
+
14
+ function toPosix(file) {
15
+ return file.split(path.sep).join("/");
16
+ }
17
+
18
+ function normalizeModuleId(file) {
19
+ return path.posix.normalize(toPosix(path.relative(cliRoot, file)));
20
+ }
21
+
22
+ function resolveLocalModule(fromId, request) {
23
+ const base = path.posix.dirname(fromId);
24
+ const target = path.posix.normalize(path.posix.join(base, request));
25
+ const candidates = [
26
+ target,
27
+ `${target}.js`,
28
+ path.posix.join(target, "index.js")
29
+ ];
30
+ for (const candidate of candidates) {
31
+ const absolute = path.join(cliRoot, ...candidate.split("/"));
32
+ if (fs.existsSync(absolute) && fs.statSync(absolute).isFile()) {
33
+ return normalizeModuleId(absolute);
34
+ }
35
+ }
36
+ throw new Error(`Unable to resolve ${request} from ${fromId}`);
37
+ }
38
+
39
+ function findLocalRequires(source) {
40
+ const requires = [];
41
+ const pattern = /require\(\s*["']([^"']+)["']\s*\)/g;
42
+ let match;
43
+ while ((match = pattern.exec(source)) !== null) {
44
+ if (match[1].startsWith(".")) {
45
+ requires.push(match[1]);
46
+ }
47
+ }
48
+ return requires;
49
+ }
50
+
51
+ function collectModules(entry) {
52
+ const modules = new Map();
53
+ const queue = [entry];
54
+
55
+ while (queue.length > 0) {
56
+ const id = queue.shift();
57
+ if (modules.has(id)) continue;
58
+
59
+ const absolute = path.join(cliRoot, ...id.split("/"));
60
+ let source = fs.readFileSync(absolute, "utf8");
61
+ if (source.startsWith("#!")) {
62
+ source = source.replace(/^#!.*\n/, "");
63
+ }
64
+ modules.set(id, source);
65
+
66
+ for (const request of findLocalRequires(source)) {
67
+ const resolved = resolveLocalModule(id, request);
68
+ if (!modules.has(resolved)) {
69
+ queue.push(resolved);
70
+ }
71
+ }
72
+ }
73
+
74
+ return modules;
75
+ }
76
+
77
+ function renderExecutable(modules) {
78
+ const moduleEntries = Array.from(modules.entries())
79
+ .sort(([left], [right]) => left.localeCompare(right))
80
+ .map(([id, source]) => `modules[${JSON.stringify(id)}] = function(require, module, exports, __filename, __dirname) {\n${source}\n};`)
81
+ .join("\n\n");
82
+
83
+ return `#!/usr/bin/env node
84
+ const __nodeRequire = require;
85
+ const path = __nodeRequire("node:path");
86
+ const runtimeRoot = path.dirname(process.argv[1] ? path.resolve(process.argv[1]) : __filename);
87
+ const modules = Object.create(null);
88
+ const moduleCache = Object.create(null);
89
+
90
+ ${moduleEntries}
91
+
92
+ function resolveModule(fromId, request) {
93
+ if (!request.startsWith(".")) return request;
94
+ const base = path.posix.dirname(fromId);
95
+ const target = path.posix.normalize(path.posix.join(base, request));
96
+ const candidates = [target, target + ".js", path.posix.join(target, "index.js")];
97
+ for (const candidate of candidates) {
98
+ if (Object.prototype.hasOwnProperty.call(modules, candidate)) return candidate;
99
+ }
100
+ throw new Error("Unable to resolve " + request + " from " + fromId);
101
+ }
102
+
103
+ function loadModule(id) {
104
+ if (!Object.prototype.hasOwnProperty.call(modules, id)) {
105
+ return __nodeRequire(id);
106
+ }
107
+ if (moduleCache[id]) return moduleCache[id].exports;
108
+ const filename = path.join(runtimeRoot, ...id.split("/"));
109
+ const dirname = path.dirname(filename);
110
+ const module = { exports: {} };
111
+ moduleCache[id] = module;
112
+ const localRequire = (request) => loadModule(resolveModule(id, request));
113
+ modules[id](localRequire, module, module.exports, filename, dirname);
114
+ return module.exports;
115
+ }
116
+
117
+ loadModule(${JSON.stringify(entryId)});
118
+ `;
119
+ }
120
+
121
+ function copyDir(sourceDir, targetDir) {
122
+ fs.mkdirSync(targetDir, { recursive: true });
123
+ for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
124
+ if (entry.name === ".DS_Store") continue;
125
+ const source = path.join(sourceDir, entry.name);
126
+ const target = path.join(targetDir, entry.name);
127
+ if (entry.isDirectory()) {
128
+ copyDir(source, target);
129
+ } else if (entry.isFile()) {
130
+ fs.copyFileSync(source, target);
131
+ }
132
+ }
133
+ }
134
+
135
+ function run(command, args) {
136
+ const result = spawnSync(command, args, {
137
+ cwd: distDir,
138
+ stdio: "inherit",
139
+ shell: false
140
+ });
141
+ if (result.status !== 0) {
142
+ process.exit(result.status || 1);
143
+ }
144
+ }
145
+
146
+ function cleanDist() {
147
+ fs.rmSync(distDir, { recursive: true, force: true });
148
+ fs.mkdirSync(packageDir, { recursive: true });
149
+ }
150
+
151
+ function main() {
152
+ cleanDist();
153
+
154
+ const executablePath = path.join(packageDir, "evt");
155
+ fs.writeFileSync(executablePath, renderExecutable(collectModules(entryId)));
156
+ fs.chmodSync(executablePath, 0o755);
157
+
158
+ copyDir(path.join(cliRoot, "data"), path.join(packageDir, "data"));
159
+ copyDir(path.join(cliRoot, "skills"), path.join(packageDir, "skills"));
160
+ fs.copyFileSync(path.join(cliRoot, "README.md"), path.join(packageDir, "README.md"));
161
+ fs.copyFileSync(path.join(cliRoot, "README.zh-CN.md"), path.join(packageDir, "README.zh-CN.md"));
162
+
163
+ const artifactName = `${packageName}-${packageJson.version}-${process.platform}-${process.arch}.tar.gz`;
164
+ run("tar", ["-czf", artifactName, packageName]);
165
+
166
+ console.log("\nLocal package artifacts:");
167
+ console.log(`- dist/${packageName}/`);
168
+ console.log(`- dist/${artifactName}`);
169
+ console.log("\nPackage contents are limited to evt, data examples, skills, README.md, and README.zh-CN.md.");
170
+ console.log("No npm publish or remote upload was performed.");
171
+ }
172
+
173
+ main();