openyida 2026.5.9-beta.8 → 2026.5.9
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 +38 -4
- package/bin/yida.js +23 -16
- package/lib/auth/login.js +14 -4
- package/lib/auth/qr-login.js +49 -21
- package/lib/core/command-manifest.js +1 -1
- package/lib/core/doctor.js +1 -1
- package/lib/core/locales/ar.js +1 -1
- package/lib/core/locales/de.js +1 -1
- package/lib/core/locales/en.js +1 -1
- package/lib/core/locales/es.js +1 -1
- package/lib/core/locales/fr.js +1 -1
- package/lib/core/locales/hi.js +1 -1
- package/lib/core/locales/ja.js +1 -1
- package/lib/core/locales/ko.js +1 -1
- package/lib/core/locales/pt.js +1 -1
- package/lib/core/locales/vi.js +1 -1
- package/lib/core/locales/zh-HK.js +1 -1
- package/lib/core/locales/zh.js +1 -1
- package/lib/mcp/server.js +2 -2
- package/package.json +5 -1
- package/scripts/e2e-real/cleanup.js +67 -0
- package/scripts/e2e-real/fixtures/form-fields.json +18 -0
- package/scripts/e2e-real/full-runner.js +1317 -0
- package/scripts/e2e-real/runner.js +293 -0
- package/scripts/e2e-real/skill-coverage.js +104 -0
- package/yida-skills/SKILL.md +2 -2
- package/yida-skills/skills/yida-login/SKILL.md +15 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
# OpenYida
|
|
6
6
|
|
|
@@ -69,12 +69,12 @@ OpenYida detects the active agent environment, workspace path, login state, and
|
|
|
69
69
|
openyida login
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
In Codex, Qoder, and
|
|
72
|
+
In Codex, Qoder, Wukong, Claude Code, OpenCode, Cursor, and other detected AI tools, OpenYida first tries local Chrome/Edge/Chromium CDP when no valid cached login exists. If local CDP is unavailable, it falls back to an AI-dialog QR handoff. The agent should render `qr_image_markdown` or paste `agent_response_markdown` directly in the conversation so the QR code is visible, then run `poll_command` after the user scans it with DingTalk. If image rendering is unavailable, fall back to `qr_url`. The explicit `openyida login --browser` command still prefers CDP first and uses Playwright as an optional browser fallback.
|
|
73
73
|
|
|
74
74
|
The explicit QR polling command remains available:
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
|
-
openyida login --
|
|
77
|
+
openyida login --agent-qr
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
For terminal QR login, use:
|
|
@@ -199,6 +199,40 @@ openyida get-permission APP_XXX FORM_XXX
|
|
|
199
199
|
|
|
200
200
|
When creating or updating test data with `openyida data`, Yida date fields must use 13-digit millisecond timestamps, for example `"dateField_xxx": 1719705600000`. Do not submit `YYYY-MM-DD` strings for `DateField` or `CascadeDateField` values.
|
|
201
201
|
|
|
202
|
+
### Real Environment E2E
|
|
203
|
+
|
|
204
|
+
Most checks should stay offline, but OpenYida also includes an explicit real-environment smoke path for release and nightly validation:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
OPENYIDA_E2E=1 npm run test:e2e:real
|
|
208
|
+
OPENYIDA_E2E=1 npm run test:e2e:real:full
|
|
209
|
+
npm run test:e2e:real:skills
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The runner creates a disposable app, form, and custom page with an `OY_E2E_*` prefix, then verifies login, app listing, schema fetch, data query, and page publish. It writes a registry to `project/.cache/e2e-real/` so created resources can be audited later. To inject CI cookies without relying on a local login cache, pass `OPENYIDA_E2E_COOKIES_BASE64` as a base64 encoded cookie array or `{ "cookies": [...] }` object.
|
|
213
|
+
|
|
214
|
+
`test:e2e:real:full` extends the smoke path into a broad deterministic feature matrix: auth/env, app update, form update and option mutation, page build/compile/generate/publish, data create/get/update/query, permission read, page config and short URL check, report create/append, dashboard skill verification, export/import, batch, task-center, formula/doctor/sample/CDN config, and local connector parsing/template generation. AI-backed commands such as `flash-to-prd` are available as the optional `ai` stage because they depend on remote model availability.
|
|
215
|
+
|
|
216
|
+
`test:e2e:real:skills` enforces coverage for every directory under `yida-skills/skills/`. Each skill must be classified as real E2E, offline/unit, opt-in, or deprecated with an explicit reason. This prevents new skills from quietly bypassing the real-environment test plan.
|
|
217
|
+
|
|
218
|
+
Each successful full run leaves a human-inspectable result app in the target organization. The final step publishes a dedicated `Full E2E Dashboard` custom page, renames the app to `OY_E2E_*_PASSED` by default, and prints direct links for the app, form, dashboard page, and report; the same links are saved under `resultApp` in the registry JSON.
|
|
219
|
+
|
|
220
|
+
Useful options:
|
|
221
|
+
|
|
222
|
+
| Env var | Purpose |
|
|
223
|
+
|---------|---------|
|
|
224
|
+
| `OPENYIDA_E2E_PREFIX` | Override the disposable resource name prefix |
|
|
225
|
+
| `OPENYIDA_E2E_CORP_ID` | Switch to the dedicated test organization before creating resources |
|
|
226
|
+
| `OPENYIDA_E2E_RESULT_APP_NAME` | Override the final app name shown as the full-run result |
|
|
227
|
+
| `OPENYIDA_E2E_BASE_URL` | Override the Yida base URL for private deployments |
|
|
228
|
+
| `OPENYIDA_E2E_FIELDS_FILE` | Use a custom form fields fixture |
|
|
229
|
+
| `OPENYIDA_E2E_PAGE_SOURCE` | Use a custom page source for publish verification |
|
|
230
|
+
| `OPENYIDA_E2E_SKIP_PUBLISH=1` | Skip custom page creation and publish |
|
|
231
|
+
| `OPENYIDA_E2E_REGISTRY_DIR` | Write registries outside `project/.cache/e2e-real/` |
|
|
232
|
+
| `OPENYIDA_E2E_FULL_STAGES` | Comma-separated stage list for `test:e2e:real:full`; use `all` or omit for the default broad matrix |
|
|
233
|
+
|
|
234
|
+
Use `npm run test:e2e:real:cleanup` to list recorded disposable resources. OpenYida does not yet expose a safe app/form deletion command, so cleanup is intentionally a registry-backed audit step rather than an automatic destructive action.
|
|
235
|
+
|
|
202
236
|
### Connectors, Integrations, and Reports
|
|
203
237
|
|
|
204
238
|
```bash
|
|
@@ -220,7 +254,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
220
254
|
| `openyida env [--json]` | Detect the active AI tool environment and login state |
|
|
221
255
|
| `openyida env <list\|show\|switch\|add\|remove>` | Manage public/private Yida environment profiles |
|
|
222
256
|
| `openyida commands [--json]` | Emit the machine-readable command manifest |
|
|
223
|
-
| `openyida login [--qr\|--
|
|
257
|
+
| `openyida login [--qr\|--agent-qr\|--codex\|--browser] [--corp-id <corpId>]` | Log in to Yida |
|
|
224
258
|
| `openyida logout` | Log out or switch account |
|
|
225
259
|
| `openyida auth <status\|login\|refresh\|logout>` | Manage login status |
|
|
226
260
|
| `openyida org list` | List accessible organizations |
|
package/bin/yida.js
CHANGED
|
@@ -245,26 +245,25 @@ function printLoginResult(result) {
|
|
|
245
245
|
console.log(JSON.stringify(summary));
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
function
|
|
248
|
+
function isAgentConversationEnvironment() {
|
|
249
249
|
const { detectActiveTool } = require('../lib/core/utils');
|
|
250
|
-
|
|
251
|
-
return !!activeTool && (activeTool.tool === 'codex' || activeTool.tool === 'qoder' || activeTool.tool === 'wukong');
|
|
250
|
+
return !!detectActiveTool();
|
|
252
251
|
}
|
|
253
252
|
|
|
254
253
|
function shouldUseBrowserHandoffLogin(cliArgs) {
|
|
255
|
-
if (cliArgs.includes('--qr') || cliArgs.includes('--codex-qr')) {return false;}
|
|
254
|
+
if (cliArgs.includes('--qr') || cliArgs.includes('--codex-qr') || cliArgs.includes('--agent-qr')) {return false;}
|
|
256
255
|
if (cliArgs.includes('--browser') || cliArgs.includes('--codex') || cliArgs.includes('--qoder') || cliArgs.includes('--wukong')) {return true;}
|
|
257
256
|
return false;
|
|
258
257
|
}
|
|
259
258
|
|
|
260
|
-
function
|
|
261
|
-
if (cliArgs.includes('--qr') || cliArgs.includes('--codex-qr')) {return false;}
|
|
259
|
+
function shouldUseAgentLogin(cliArgs) {
|
|
260
|
+
if (cliArgs.includes('--qr') || cliArgs.includes('--codex-qr') || cliArgs.includes('--agent-qr')) {return false;}
|
|
262
261
|
if (shouldUseBrowserHandoffLogin(cliArgs)) {return false;}
|
|
263
|
-
return
|
|
262
|
+
return isAgentConversationEnvironment();
|
|
264
263
|
}
|
|
265
264
|
|
|
266
265
|
function shouldUseCodexQrLogin(cliArgs) {
|
|
267
|
-
if (cliArgs.includes('--codex-qr')) {return true;}
|
|
266
|
+
if (cliArgs.includes('--codex-qr') || cliArgs.includes('--agent-qr')) {return true;}
|
|
268
267
|
return false;
|
|
269
268
|
}
|
|
270
269
|
|
|
@@ -338,15 +337,17 @@ async function main() {
|
|
|
338
337
|
|
|
339
338
|
case 'login': {
|
|
340
339
|
const { checkLoginOnly } = require('../lib/auth/login');
|
|
341
|
-
if (args.includes('--codex-poll')) {
|
|
340
|
+
if (args.includes('--agent-poll') || args.includes('--codex-poll')) {
|
|
341
|
+
const sessionFile = getArgValue(args, '--agent-poll') || getArgValue(args, '--codex-poll');
|
|
342
342
|
const { pollCodexQrLogin } = require('../lib/auth/qr-login');
|
|
343
|
-
const result = await pollCodexQrLogin(
|
|
343
|
+
const result = await pollCodexQrLogin(sessionFile, {
|
|
344
344
|
corpId: getArgValue(args, '--corp-id'),
|
|
345
345
|
});
|
|
346
346
|
printLoginResult(result);
|
|
347
|
-
} else if (args.includes('--codex-select')) {
|
|
347
|
+
} else if (args.includes('--agent-select') || args.includes('--codex-select')) {
|
|
348
|
+
const sessionFile = getArgValue(args, '--agent-select') || getArgValue(args, '--codex-select');
|
|
348
349
|
const { selectCodexQrCorp } = require('../lib/auth/qr-login');
|
|
349
|
-
const result = await selectCodexQrCorp(
|
|
350
|
+
const result = await selectCodexQrCorp(sessionFile, {
|
|
350
351
|
corpId: getArgValue(args, '--corp-id'),
|
|
351
352
|
});
|
|
352
353
|
printLoginResult(result);
|
|
@@ -379,14 +380,20 @@ async function main() {
|
|
|
379
380
|
const { qrLogin } = require('../lib/auth/qr-login');
|
|
380
381
|
const result = await qrLogin({ corpId: getArgValue(args, '--corp-id') });
|
|
381
382
|
console.log(JSON.stringify(result));
|
|
382
|
-
} else if (
|
|
383
|
+
} else if (shouldUseAgentLogin(args)) {
|
|
383
384
|
const cachedResult = checkLoginOnly({ includeSecrets: true });
|
|
384
385
|
if (cachedResult.status === 'ok') {
|
|
385
386
|
printLoginResult(cachedResult);
|
|
386
387
|
} else {
|
|
387
|
-
const {
|
|
388
|
-
const
|
|
389
|
-
|
|
388
|
+
const { interactiveLogin } = require('../lib/auth/login');
|
|
389
|
+
const browserResult = interactiveLogin({ playwrightFallback: false });
|
|
390
|
+
if (browserResult) {
|
|
391
|
+
printLoginResult(browserResult);
|
|
392
|
+
} else {
|
|
393
|
+
const { startCodexQrLogin } = require('../lib/auth/qr-login');
|
|
394
|
+
const result = await startCodexQrLogin({ corpId: getArgValue(args, '--corp-id') });
|
|
395
|
+
printLoginResult(result);
|
|
396
|
+
}
|
|
390
397
|
}
|
|
391
398
|
} else if (shouldUseBrowserHandoffLogin(args)) {
|
|
392
399
|
const cachedResult = checkLoginOnly({ includeSecrets: true });
|
package/lib/auth/login.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* ensureLogin() - 确保有效登录态(优先缓存,否则尝试可选浏览器登录)
|
|
11
11
|
* checkLoginOnly() - 仅检查登录态,不触发登录
|
|
12
12
|
* refreshCsrfFromCache() - 从缓存 Cookie 重新提取 csrf_token
|
|
13
|
-
* interactiveLogin() - 打开浏览器扫码登录(优先本地 Chrome CDP
|
|
13
|
+
* interactiveLogin() - 打开浏览器扫码登录(优先本地 Chrome CDP,可选 Playwright 兜底)
|
|
14
14
|
* saveCookieCache() - 保存 Cookie 到本地缓存(供 qr-login.js 使用)
|
|
15
15
|
* logout() - 退出登录,清空 Cookie 缓存
|
|
16
16
|
*/
|
|
@@ -183,7 +183,7 @@ function refreshCsrfFromCache() {
|
|
|
183
183
|
|
|
184
184
|
/**
|
|
185
185
|
* 确保拥有有效的登录态。优先从本地缓存 Cookie 中提取,否则尝试本地浏览器登录。
|
|
186
|
-
* 默认 CLI 登录路径在 bin/yida.js
|
|
186
|
+
* 默认 CLI 登录路径在 bin/yida.js 中负责选择终端二维码、AI 工具 CDP 或二维码 handoff。
|
|
187
187
|
* @returns {object} loginResult
|
|
188
188
|
*/
|
|
189
189
|
function ensureLogin() {
|
|
@@ -257,20 +257,30 @@ function getPlaywrightPath() {
|
|
|
257
257
|
|
|
258
258
|
/**
|
|
259
259
|
* 打开有头浏览器让用户扫码登录。
|
|
260
|
-
* 优先使用无依赖的 Chrome/Edge/Chromium CDP
|
|
260
|
+
* 优先使用无依赖的 Chrome/Edge/Chromium CDP 登录;失败时可选择再尝试用户安装的 Playwright。
|
|
261
|
+
* @param {object} [options]
|
|
262
|
+
* @param {boolean} [options.playwrightFallback=true] - CDP 失败后是否尝试 Playwright
|
|
261
263
|
* @returns {object} loginResult
|
|
262
264
|
*/
|
|
263
|
-
function interactiveLogin() {
|
|
265
|
+
function interactiveLogin(options = {}) {
|
|
266
|
+
const playwrightFallback = options.playwrightFallback !== false;
|
|
264
267
|
const config = loadConfig();
|
|
265
268
|
const loginUrl = config.loginUrl || DEFAULT_LOGIN_URL;
|
|
266
269
|
|
|
267
270
|
try {
|
|
271
|
+
if (process.env.OPENYIDA_DISABLE_CDP_LOGIN === '1') {
|
|
272
|
+
throw new Error('CDP login disabled by OPENYIDA_DISABLE_CDP_LOGIN');
|
|
273
|
+
}
|
|
268
274
|
return cdpInteractiveLogin(loginUrl);
|
|
269
275
|
} catch (err) {
|
|
270
276
|
const { warn: chalkWarn3 } = require('../core/chalk');
|
|
271
277
|
chalkWarn3(err.message);
|
|
272
278
|
}
|
|
273
279
|
|
|
280
|
+
if (!playwrightFallback) {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
|
|
274
284
|
const playwrightPath = getPlaywrightPath();
|
|
275
285
|
if (!playwrightPath) {
|
|
276
286
|
const { error: chalkError3 } = require('../core/chalk');
|
package/lib/auth/qr-login.js
CHANGED
|
@@ -35,10 +35,43 @@ function getTargetCorpId(options = {}, session = {}) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function buildCodexPollCommand(sessionFile, targetCorpId) {
|
|
38
|
-
const baseCommand = `openyida login --
|
|
38
|
+
const baseCommand = `openyida login --agent-poll ${shellQuote(sessionFile)}`;
|
|
39
39
|
return targetCorpId ? `${baseCommand} --corp-id ${shellQuote(targetCorpId)}` : baseCommand;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
function buildQrImageMarkdown(qrImageFile) {
|
|
43
|
+
if (!qrImageFile) {return null;}
|
|
44
|
+
return `.replace(/\\/g, '/')})`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function buildAgentQrResponseMarkdown(result) {
|
|
48
|
+
const lines = [t('qr_login.scan_hint').trim(), ''];
|
|
49
|
+
if (result.qr_image_markdown) {
|
|
50
|
+
lines.push(result.qr_image_markdown, '');
|
|
51
|
+
}
|
|
52
|
+
lines.push(t('qr_login.qr_url_label', result.qr_url).trim());
|
|
53
|
+
lines.push('');
|
|
54
|
+
lines.push(`poll_command: \`${result.poll_command}\``);
|
|
55
|
+
return lines.join('\n');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildNeedQrScanResult({ qrUrl, qrImageFile, sessionFile, targetCorpId }) {
|
|
59
|
+
const qrImageMarkdown = buildQrImageMarkdown(qrImageFile);
|
|
60
|
+
const result = {
|
|
61
|
+
status: 'need_qr_scan',
|
|
62
|
+
handoff_type: 'qr',
|
|
63
|
+
can_auto_use: false,
|
|
64
|
+
qr_url: qrUrl,
|
|
65
|
+
qr_image_file: qrImageFile || null,
|
|
66
|
+
qr_image_markdown: qrImageMarkdown,
|
|
67
|
+
session_file: sessionFile,
|
|
68
|
+
poll_command: buildCodexPollCommand(sessionFile, targetCorpId),
|
|
69
|
+
message: 'Scan the QR code with DingTalk, then run poll_command.',
|
|
70
|
+
};
|
|
71
|
+
result.agent_response_markdown = buildAgentQrResponseMarkdown(result);
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
|
|
42
75
|
// ── HTTP 工具 ─────────────────────────────────────────
|
|
43
76
|
|
|
44
77
|
/**
|
|
@@ -1088,16 +1121,12 @@ function buildFakeCodexQrLoginResult(options = {}) {
|
|
|
1088
1121
|
createdAt: new Date().toISOString(),
|
|
1089
1122
|
});
|
|
1090
1123
|
|
|
1091
|
-
return {
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
session_file: sessionFile,
|
|
1098
|
-
poll_command: buildCodexPollCommand(sessionFile, targetCorpId),
|
|
1099
|
-
message: 'Scan the QR code with DingTalk, then run poll_command.',
|
|
1100
|
-
};
|
|
1124
|
+
return buildNeedQrScanResult({
|
|
1125
|
+
qrUrl: 'https://login.example.test/qr?code=test',
|
|
1126
|
+
qrImageFile,
|
|
1127
|
+
sessionFile,
|
|
1128
|
+
targetCorpId,
|
|
1129
|
+
});
|
|
1101
1130
|
}
|
|
1102
1131
|
|
|
1103
1132
|
async function startCodexQrLogin(options = {}) {
|
|
@@ -1139,16 +1168,12 @@ async function startCodexQrLogin(options = {}) {
|
|
|
1139
1168
|
createdAt: new Date().toISOString(),
|
|
1140
1169
|
});
|
|
1141
1170
|
|
|
1142
|
-
return {
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
session_file: sessionFile,
|
|
1149
|
-
poll_command: buildCodexPollCommand(sessionFile, targetCorpId),
|
|
1150
|
-
message: 'Scan the QR code with DingTalk, then run poll_command.',
|
|
1151
|
-
};
|
|
1171
|
+
return buildNeedQrScanResult({
|
|
1172
|
+
qrUrl,
|
|
1173
|
+
qrImageFile: imageWritten ? qrImageFile : null,
|
|
1174
|
+
sessionFile,
|
|
1175
|
+
targetCorpId,
|
|
1176
|
+
});
|
|
1152
1177
|
}
|
|
1153
1178
|
|
|
1154
1179
|
async function pollCodexQrLogin(sessionFile, options = {}) {
|
|
@@ -1414,6 +1439,9 @@ module.exports = {
|
|
|
1414
1439
|
isDingtalkOAuthPassResult,
|
|
1415
1440
|
buildCodexCorpInteraction,
|
|
1416
1441
|
buildCodexPollCommand,
|
|
1442
|
+
buildQrImageMarkdown,
|
|
1443
|
+
buildAgentQrResponseMarkdown,
|
|
1444
|
+
buildNeedQrScanResult,
|
|
1417
1445
|
getTargetCorpId,
|
|
1418
1446
|
},
|
|
1419
1447
|
};
|
|
@@ -20,7 +20,7 @@ const COMMAND_GROUPS = [
|
|
|
20
20
|
id: 'auth',
|
|
21
21
|
titleKey: 'help.group_auth',
|
|
22
22
|
commands: [
|
|
23
|
-
command('login', ['login'], 'login [--qr|--
|
|
23
|
+
command('login', ['login'], 'login [--qr|--agent-qr|--codex|--browser] [--corp-id <corpId>]', 'help.cmd_login', {
|
|
24
24
|
requiresLogin: false,
|
|
25
25
|
output: 'json',
|
|
26
26
|
}),
|
package/lib/core/doctor.js
CHANGED
|
@@ -211,7 +211,7 @@ class EnvironmentChecker {
|
|
|
211
211
|
label: '可选浏览器登录(Playwright 未安装)',
|
|
212
212
|
passed: true,
|
|
213
213
|
severity: Severity.INFO,
|
|
214
|
-
message: '
|
|
214
|
+
message: '默认登录优先使用终端二维码或本地 CDP;AI 工具在 CDP 不可用时返回对话框二维码 handoff,不需要 Playwright',
|
|
215
215
|
fixType: null,
|
|
216
216
|
};
|
|
217
217
|
}
|
package/lib/core/locales/ar.js
CHANGED
|
@@ -10,7 +10,7 @@ module.exports = {
|
|
|
10
10
|
usage: 'الاستخدام:',
|
|
11
11
|
alias: 'الاسم المستعار:',
|
|
12
12
|
group_auth: 'البيئة & المصادقة',
|
|
13
|
-
cmd_login: 'تسجيل الدخول (الذاكرة المؤقتة أولاً، --
|
|
13
|
+
cmd_login: 'تسجيل الدخول (الذاكرة المؤقتة أولاً، استخدم --browser أو --agent-qr عند الحاجة)',
|
|
14
14
|
cmd_logout: 'تسجيل الخروج / تبديل الحساب',
|
|
15
15
|
cmd_auth: 'إدارة حالة تسجيل الدخول',
|
|
16
16
|
cmd_org: 'إدارة المنظمات (عرض / تبديل)',
|
package/lib/core/locales/de.js
CHANGED
|
@@ -10,7 +10,7 @@ module.exports = {
|
|
|
10
10
|
usage: 'Verwendung:',
|
|
11
11
|
alias: 'Alias:',
|
|
12
12
|
group_auth: 'Umgebung & Authentifizierung',
|
|
13
|
-
cmd_login: 'Anmelden (Cache
|
|
13
|
+
cmd_login: 'Anmelden (Cache zuerst, bei Bedarf --browser oder --agent-qr)',
|
|
14
14
|
cmd_logout: 'Abmelden / Konto wechseln',
|
|
15
15
|
cmd_auth: 'Anmeldestatus-Verwaltung',
|
|
16
16
|
cmd_org: 'Organisationsverwaltung (auflisten / wechseln)',
|
package/lib/core/locales/en.js
CHANGED
|
@@ -12,7 +12,7 @@ module.exports = {
|
|
|
12
12
|
usage: 'Usage:',
|
|
13
13
|
alias: 'Alias:',
|
|
14
14
|
group_auth: 'Auth & Environment',
|
|
15
|
-
cmd_login: 'Login (cache first, --
|
|
15
|
+
cmd_login: 'Login (cache first, --browser or --agent-qr when needed)',
|
|
16
16
|
cmd_logout: 'Logout / switch account',
|
|
17
17
|
cmd_auth: 'Login state management',
|
|
18
18
|
cmd_org: 'Organization management (list / switch)',
|
package/lib/core/locales/es.js
CHANGED
|
@@ -10,7 +10,7 @@ module.exports = {
|
|
|
10
10
|
usage: 'Uso:',
|
|
11
11
|
alias: 'Alias:',
|
|
12
12
|
group_auth: 'Entorno & Autenticación',
|
|
13
|
-
cmd_login: 'Iniciar sesión (caché primero, --
|
|
13
|
+
cmd_login: 'Iniciar sesión (caché primero, --browser o --agent-qr si hace falta)',
|
|
14
14
|
cmd_logout: 'Cerrar sesión / cambiar cuenta',
|
|
15
15
|
cmd_auth: 'Gestión del estado de sesión',
|
|
16
16
|
cmd_org: 'Gestión de organizaciones (listar / cambiar)',
|
package/lib/core/locales/fr.js
CHANGED
|
@@ -10,7 +10,7 @@ module.exports = {
|
|
|
10
10
|
usage: 'Utilisation :',
|
|
11
11
|
alias: 'Alias :',
|
|
12
12
|
group_auth: 'Environnement & Authentification',
|
|
13
|
-
cmd_login: 'Connexion (cache prioritaire, --
|
|
13
|
+
cmd_login: 'Connexion (cache prioritaire, --browser ou --agent-qr si nécessaire)',
|
|
14
14
|
cmd_logout: 'Déconnexion / changer de compte',
|
|
15
15
|
cmd_auth: 'Gestion de l\'état de connexion',
|
|
16
16
|
cmd_org: 'Gestion des organisations (lister / changer)',
|
package/lib/core/locales/hi.js
CHANGED
|
@@ -10,7 +10,7 @@ module.exports = {
|
|
|
10
10
|
usage: 'उपयोग:',
|
|
11
11
|
alias: 'उपनाम:',
|
|
12
12
|
group_auth: 'वातावरण & प्रमाणीकरण',
|
|
13
|
-
cmd_login: 'लॉगिन (
|
|
13
|
+
cmd_login: 'लॉगिन (पहले कैश, जरूरत हो तो --browser या --agent-qr)',
|
|
14
14
|
cmd_logout: 'लॉगआउट / खाता बदलें',
|
|
15
15
|
cmd_auth: 'लॉगिन स्थिति प्रबंधन',
|
|
16
16
|
cmd_org: 'संगठन प्रबंधन (सूची / बदलें)',
|
package/lib/core/locales/ja.js
CHANGED
|
@@ -12,7 +12,7 @@ module.exports = {
|
|
|
12
12
|
usage: '使用方法:',
|
|
13
13
|
alias: 'エイリアス:',
|
|
14
14
|
group_auth: '環境 & 認証',
|
|
15
|
-
cmd_login: '
|
|
15
|
+
cmd_login: 'ログイン(キャッシュ優先、必要に応じて --browser または --agent-qr)',
|
|
16
16
|
cmd_logout: 'ログアウト / アカウント切替',
|
|
17
17
|
cmd_auth: 'ログイン状態管理',
|
|
18
18
|
cmd_org: '組織管理(一覧 / 切替)',
|
package/lib/core/locales/ko.js
CHANGED
|
@@ -10,7 +10,7 @@ module.exports = {
|
|
|
10
10
|
usage: '사용법:',
|
|
11
11
|
alias: '별칭:',
|
|
12
12
|
group_auth: '환경 & 인증',
|
|
13
|
-
cmd_login: '로그인 (캐시 우선, --
|
|
13
|
+
cmd_login: '로그인 (캐시 우선, 필요 시 --browser 또는 --agent-qr)',
|
|
14
14
|
cmd_logout: '로그아웃 / 계정 전환',
|
|
15
15
|
cmd_auth: '로그인 상태 관리',
|
|
16
16
|
cmd_org: '조직 관리 (목록 / 전환)',
|
package/lib/core/locales/pt.js
CHANGED
|
@@ -10,7 +10,7 @@ module.exports = {
|
|
|
10
10
|
usage: 'Uso:',
|
|
11
11
|
alias: 'Alias:',
|
|
12
12
|
group_auth: 'Ambiente & Autenticação',
|
|
13
|
-
cmd_login: 'Login (cache primeiro, --
|
|
13
|
+
cmd_login: 'Login (cache primeiro, use --browser ou --agent-qr se necessário)',
|
|
14
14
|
cmd_logout: 'Logout / trocar conta',
|
|
15
15
|
cmd_auth: 'Gerenciamento do estado de login',
|
|
16
16
|
cmd_org: 'Gerenciamento de organizações (listar / trocar)',
|
package/lib/core/locales/vi.js
CHANGED
|
@@ -10,7 +10,7 @@ module.exports = {
|
|
|
10
10
|
usage: 'Cách dùng:',
|
|
11
11
|
alias: 'Bí danh:',
|
|
12
12
|
group_auth: 'Môi trường & Xác thực',
|
|
13
|
-
cmd_login: 'Đăng nhập (ưu tiên cache, --
|
|
13
|
+
cmd_login: 'Đăng nhập (ưu tiên cache, dùng --browser hoặc --agent-qr khi cần)',
|
|
14
14
|
cmd_logout: 'Đăng xuất / chuyển tài khoản',
|
|
15
15
|
cmd_auth: 'Quản lý trạng thái đăng nhập',
|
|
16
16
|
cmd_org: 'Quản lý tổ chức (liệt kê / chuyển)',
|
package/lib/core/locales/zh.js
CHANGED
|
@@ -12,7 +12,7 @@ module.exports = {
|
|
|
12
12
|
usage: '用法:',
|
|
13
13
|
alias: '别名:',
|
|
14
14
|
group_auth: '环境 & 认证',
|
|
15
|
-
cmd_login: '
|
|
15
|
+
cmd_login: '登录(优先缓存,按需使用 --browser 或 --agent-qr)',
|
|
16
16
|
cmd_logout: '退出登录 / 切换账号',
|
|
17
17
|
cmd_auth: '登录态管理',
|
|
18
18
|
cmd_org: '组织管理(列出 / 切换)',
|
package/lib/mcp/server.js
CHANGED
|
@@ -129,13 +129,13 @@ function listTools() {
|
|
|
129
129
|
{
|
|
130
130
|
name: SELECT_ORGANIZATION_TOOL,
|
|
131
131
|
title: '选择宜搭登录组织',
|
|
132
|
-
description: '用
|
|
132
|
+
description: '用 AI 工具原生 MCP elicitation 单选控件选择宜搭组织,并完成 OpenYida 二维码登录。',
|
|
133
133
|
inputSchema: {
|
|
134
134
|
type: 'object',
|
|
135
135
|
properties: {
|
|
136
136
|
session_file: {
|
|
137
137
|
type: 'string',
|
|
138
|
-
description: 'openyida login --
|
|
138
|
+
description: 'openyida login --agent-poll 返回的 session_file 路径。兼容旧的 --codex-poll。',
|
|
139
139
|
},
|
|
140
140
|
message: {
|
|
141
141
|
type: 'string',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openyida",
|
|
3
|
-
"version": "2026.5.9
|
|
3
|
+
"version": "2026.5.9",
|
|
4
4
|
"description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
|
|
5
5
|
"bin": {
|
|
6
6
|
"openyida": "bin/yida.js",
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
"scripts": {
|
|
23
23
|
"test": "jest",
|
|
24
24
|
"test:unit": "jest",
|
|
25
|
+
"test:e2e:real": "node scripts/e2e-real/runner.js",
|
|
26
|
+
"test:e2e:real:full": "node scripts/e2e-real/full-runner.js",
|
|
27
|
+
"test:e2e:real:skills": "node scripts/e2e-real/skill-coverage.js",
|
|
28
|
+
"test:e2e:real:cleanup": "node scripts/e2e-real/cleanup.js",
|
|
25
29
|
"test:coverage": "jest --coverage",
|
|
26
30
|
"lint": "eslint bin/ lib/ scripts/ tests/ --ext .js",
|
|
27
31
|
"lint:fix": "eslint bin/ lib/ scripts/ tests/ --ext .js --fix",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const ROOT = path.resolve(__dirname, '..', '..');
|
|
9
|
+
const DEFAULT_REGISTRY_DIR = path.join(ROOT, 'project', '.cache', 'e2e-real');
|
|
10
|
+
|
|
11
|
+
function getRegistryDir(env = process.env) {
|
|
12
|
+
return env.OPENYIDA_E2E_REGISTRY_DIR || DEFAULT_REGISTRY_DIR;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function listRegistries(registryDir = getRegistryDir()) {
|
|
16
|
+
if (!fs.existsSync(registryDir)) {return [];}
|
|
17
|
+
return fs.readdirSync(registryDir)
|
|
18
|
+
.filter((file) => file.endsWith('.json'))
|
|
19
|
+
.map((file) => {
|
|
20
|
+
const registryPath = path.join(registryDir, file);
|
|
21
|
+
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
22
|
+
return { registryPath, registry };
|
|
23
|
+
})
|
|
24
|
+
.sort((a, b) => String(a.registry.startedAt || '').localeCompare(String(b.registry.startedAt || '')));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function printSummary(items) {
|
|
28
|
+
if (items.length === 0) {
|
|
29
|
+
console.log('No real E2E registries found.');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const item of items) {
|
|
34
|
+
const registry = item.registry;
|
|
35
|
+
console.log(`\n${registry.runId || path.basename(item.registryPath)} [${registry.status || 'unknown'}]`);
|
|
36
|
+
console.log(`Registry: ${item.registryPath}`);
|
|
37
|
+
for (const resource of registry.resources || []) {
|
|
38
|
+
const id = resource.appType || resource.formUuid || resource.pageId || 'unknown';
|
|
39
|
+
const secondary = resource.formUuid || resource.pageId || '';
|
|
40
|
+
console.log(`- ${resource.type}: ${id}${secondary && secondary !== id ? ` / ${secondary}` : ''} ${resource.name || ''}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log('\nOpenYida does not yet expose a safe app/form deletion command, so this script lists disposable resources recorded by real E2E runs for manual cleanup.');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function run(options = {}) {
|
|
48
|
+
const registryDir = options.registryDir || getRegistryDir(options.env || process.env);
|
|
49
|
+
const items = listRegistries(registryDir);
|
|
50
|
+
printSummary(items);
|
|
51
|
+
return items;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (require.main === module) {
|
|
55
|
+
try {
|
|
56
|
+
run();
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error(error.message);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
getRegistryDir,
|
|
65
|
+
listRegistries,
|
|
66
|
+
run,
|
|
67
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"type": "TextField",
|
|
4
|
+
"label": "E2E Text",
|
|
5
|
+
"required": true,
|
|
6
|
+
"placeholder": "Created by OpenYida real E2E"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"type": "NumberField",
|
|
10
|
+
"label": "E2E Number",
|
|
11
|
+
"placeholder": "123"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"type": "SelectField",
|
|
15
|
+
"label": "E2E Status",
|
|
16
|
+
"options": ["New", "Done"]
|
|
17
|
+
}
|
|
18
|
+
]
|