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 CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- ![OpenYida](https://img.alicdn.com/imgextra/i4/O1CN017uyK3q1UUfbv7Z8oh_!!6000000002521-2-tps-2648-1382.png)
3
+ ![OpenYida](https://img.alicdn.com/imgextra/i3/O1CN01SKWbWu1aPzGXh293W_!!6000000003323-2-tps-1672-941.png)
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 Wukong, OpenYida returns a QR handoff when no valid cached login exists. The agent should show `qr_image_file` or `qr_url` in the conversation, ask the user to scan it with DingTalk, then run `poll_command` to finish the login and write the CLI Cookie cache. If the QR handoff is unavailable, use `openyida login --browser` as the fallback; it prefers local Chrome/Edge/Chromium CDP first and uses Playwright only as an optional fallback.
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 --codex-qr
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\|--codex\|--codex-qr\|--browser] [--corp-id <corpId>]` | Log in to Yida |
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 isBrowserHandoffEnvironment() {
248
+ function isAgentConversationEnvironment() {
249
249
  const { detectActiveTool } = require('../lib/core/utils');
250
- const activeTool = detectActiveTool();
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 shouldUseAgentQrLogin(cliArgs) {
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 isBrowserHandoffEnvironment();
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(getArgValue(args, '--codex-poll'), {
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(getArgValue(args, '--codex-select'), {
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 (shouldUseAgentQrLogin(args)) {
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 { startCodexQrLogin } = require('../lib/auth/qr-login');
388
- const result = await startCodexQrLogin({ corpId: getArgValue(args, '--corp-id') });
389
- printLoginResult(result);
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,失败时使用 Playwright 兜底)
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 中优先使用终端二维码或 AI 工具二维码 handoff。
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 登录;失败时再尝试用户安装的 Playwright。
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');
@@ -35,10 +35,43 @@ function getTargetCorpId(options = {}, session = {}) {
35
35
  }
36
36
 
37
37
  function buildCodexPollCommand(sessionFile, targetCorpId) {
38
- const baseCommand = `openyida login --codex-poll ${shellQuote(sessionFile)}`;
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 `![OpenYida login QR code](${String(qrImageFile).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
- status: 'need_qr_scan',
1093
- handoff_type: 'qr',
1094
- can_auto_use: false,
1095
- qr_url: 'https://login.example.test/qr?code=test',
1096
- qr_image_file: qrImageFile,
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
- status: 'need_qr_scan',
1144
- handoff_type: 'qr',
1145
- can_auto_use: false,
1146
- qr_url: qrUrl,
1147
- qr_image_file: imageWritten ? qrImageFile : null,
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|--codex|--codex-qr|--browser] [--corp-id <corpId>]', 'help.cmd_login', {
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
  }),
@@ -211,7 +211,7 @@ class EnvironmentChecker {
211
211
  label: '可选浏览器登录(Playwright 未安装)',
212
212
  passed: true,
213
213
  severity: Severity.INFO,
214
- message: '默认登录使用终端二维码或 AI 工具内置浏览器 handoff,不需要 Playwright',
214
+ message: '默认登录优先使用终端二维码或本地 CDP;AI 工具在 CDP 不可用时返回对话框二维码 handoff,不需要 Playwright',
215
215
  fixType: null,
216
216
  };
217
217
  }
@@ -10,7 +10,7 @@ module.exports = {
10
10
  usage: 'الاستخدام:',
11
11
  alias: 'الاسم المستعار:',
12
12
  group_auth: 'البيئة & المصادقة',
13
- cmd_login: 'تسجيل الدخول (الذاكرة المؤقتة أولاً، --qr أو --codex لتسجيل دخول متصفح Codex)',
13
+ cmd_login: 'تسجيل الدخول (الذاكرة المؤقتة أولاً، استخدم --browser أو --agent-qr عند الحاجة)',
14
14
  cmd_logout: 'تسجيل الخروج / تبديل الحساب',
15
15
  cmd_auth: 'إدارة حالة تسجيل الدخول',
16
16
  cmd_org: 'إدارة المنظمات (عرض / تبديل)',
@@ -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 bevorzugt, --qr oder --codex for browser login)',
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)',
@@ -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, --qr or --codex for browser login)',
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)',
@@ -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, --qr o --codex for browser login)',
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)',
@@ -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, --qr ou --codex pour navigateur Codex)',
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)',
@@ -10,7 +10,7 @@ module.exports = {
10
10
  usage: 'उपयोग:',
11
11
  alias: 'उपनाम:',
12
12
  group_auth: 'वातावरण & प्रमाणीकरण',
13
- cmd_login: 'लॉगिन (कैश प्राथमिकता, --qr या --codex for browser login)',
13
+ cmd_login: 'लॉगिन (पहले कैश, जरूरत हो तो --browser या --agent-qr)',
14
14
  cmd_logout: 'लॉगआउट / खाता बदलें',
15
15
  cmd_auth: 'लॉगिन स्थिति प्रबंधन',
16
16
  cmd_org: 'संगठन प्रबंधन (सूची / बदलें)',
@@ -12,7 +12,7 @@ module.exports = {
12
12
  usage: '使用方法:',
13
13
  alias: 'エイリアス:',
14
14
  group_auth: '環境 & 認証',
15
- cmd_login: 'ログイン(キャッシュ優先、--qr または --codex browser login)',
15
+ cmd_login: 'ログイン(キャッシュ優先、必要に応じて --browser または --agent-qr)',
16
16
  cmd_logout: 'ログアウト / アカウント切替',
17
17
  cmd_auth: 'ログイン状態管理',
18
18
  cmd_org: '組織管理(一覧 / 切替)',
@@ -10,7 +10,7 @@ module.exports = {
10
10
  usage: '사용법:',
11
11
  alias: '별칭:',
12
12
  group_auth: '환경 & 인증',
13
- cmd_login: '로그인 (캐시 우선, --qr 또는 --codex 스캔)',
13
+ cmd_login: '로그인 (캐시 우선, 필요 시 --browser 또는 --agent-qr)',
14
14
  cmd_logout: '로그아웃 / 계정 전환',
15
15
  cmd_auth: '로그인 상태 관리',
16
16
  cmd_org: '조직 관리 (목록 / 전환)',
@@ -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, --qr ou --codex for browser login)',
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)',
@@ -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, --qr hoặc --codex browser login)',
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)',
@@ -12,7 +12,7 @@ module.exports = {
12
12
  usage: '用法:',
13
13
  alias: '別名:',
14
14
  group_auth: '環境 & 認證',
15
- cmd_login: '登入(優先緩存,--qr 或 --codex 瀏覽器登入)',
15
+ cmd_login: '登入(優先緩存,按需使用 --browser 或 --agent-qr)',
16
16
  cmd_logout: '登出 / 切換帳戶',
17
17
  cmd_auth: '登入態管理',
18
18
  cmd_org: '組織管理(列出 / 切換)',
@@ -12,7 +12,7 @@ module.exports = {
12
12
  usage: '用法:',
13
13
  alias: '别名:',
14
14
  group_auth: '环境 & 认证',
15
- cmd_login: '登录(优先缓存,--qr 或 --codex 浏览器登录)',
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: '用 Codex 原生 MCP elicitation 单选控件选择宜搭组织,并完成 OpenYida Codex 二维码登录。',
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 --codex-poll 返回的 session_file 路径。',
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-beta.8",
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
+ ]