openyida 2026.5.13 → 2026.5.15

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
@@ -167,6 +167,7 @@ openyida/
167
167
  openyida create-app "CRM"
168
168
  openyida create-app --name "CRM" --desc "Customer management" --theme deepBlue
169
169
  openyida app-list --size 20
170
+ openyida corp-efficiency
170
171
  openyida create-form create APP_XXX "Customer" .cache/openyida/forms/customer-fields.json
171
172
  openyida create-form update APP_XXX FORM_XXX .cache/openyida/forms/customer-changes.json
172
173
  openyida get-schema APP_XXX FORM_XXX
@@ -254,9 +255,10 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
254
255
  | Command | Description |
255
256
  |---------|-------------|
256
257
  | `openyida env [--json]` | Detect the active AI tool environment and login state |
258
+ | `openyida env setup` | Choose a customer-friendly login environment preset: public, overseas, Alibaba intranet, or private deployment |
257
259
  | `openyida env <list\|show\|switch\|add\|remove>` | Manage public/private Yida environment profiles |
258
260
  | `openyida commands [--json]` | Emit the machine-readable command manifest |
259
- | `openyida login [--qr\|--agent-qr\|--codex\|--browser] [--corp-id <corpId>]` | Log in to Yida |
261
+ | `openyida login [--qr\|--agent-qr\|--codex\|--browser] [--env <name>\|--overseas] [--corp-id <corpId>]` | Log in to Yida |
260
262
  | `openyida logout` | Log out or switch account |
261
263
  | `openyida auth <status\|login\|refresh\|logout>` | Manage login status |
262
264
  | `openyida org list` | List accessible organizations |
@@ -267,6 +269,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
267
269
  | Command | Description |
268
270
  |---------|-------------|
269
271
  | `openyida app-list [--size N]` | List Yida applications |
272
+ | `openyida corp-efficiency [overview\|details\|detail\|groups\|notify] [options] [--open\|--no-open]` | Query enterprise efficiency metrics, detail report entries, and related notification actions |
270
273
  | `openyida create-app "<name>"\|--name <name> [options] [--open\|--no-open]` | Create an application and output `appType` |
271
274
  | `openyida update-app <appType> --name "..."` | Update application metadata |
272
275
  | `openyida export <appType> [output]` | Export an application migration package |
package/bin/yida.js CHANGED
@@ -284,6 +284,39 @@ function getArgValue(cliArgs, name) {
284
284
  return cliArgs[index + 1];
285
285
  }
286
286
 
287
+ function applyLoginEnvironmentFlags(cliArgs) {
288
+ const envFlagMap = {
289
+ '--public': 'public',
290
+ '--intl': 'intl',
291
+ '--overseas': 'intl',
292
+ '--international': 'intl',
293
+ '--global': 'intl',
294
+ '--alibaba': 'alibaba',
295
+ '--internal': 'alibaba',
296
+ '--intranet': 'alibaba',
297
+ };
298
+ const filteredArgs = [];
299
+
300
+ for (let index = 0; index < cliArgs.length; index++) {
301
+ const arg = cliArgs[index];
302
+ if (arg === '--env') {
303
+ const envName = cliArgs[index + 1];
304
+ if (envName && !envName.startsWith('--')) {
305
+ process.env.OPENYIDA_ENV = envName;
306
+ index++;
307
+ }
308
+ continue;
309
+ }
310
+ if (envFlagMap[arg]) {
311
+ process.env.OPENYIDA_ENV = envFlagMap[arg];
312
+ continue;
313
+ }
314
+ filteredArgs.push(arg);
315
+ }
316
+
317
+ return filteredArgs;
318
+ }
319
+
287
320
  // 解析全局 --quiet 开关:从 args 中剔除并设置 YIDA_QUIET=1,让 chalk.js
288
321
  // 的所有装饰输出(banner/step/info/...)变 no-op,AI 即可直接 `... --quiet | jq`。
289
322
  function applyQuietFlag() {
@@ -352,40 +385,41 @@ async function main() {
352
385
 
353
386
  case 'login': {
354
387
  const { checkLoginOnly } = require('../lib/auth/login');
355
- if (args.includes('--agent-poll') || args.includes('--codex-poll')) {
356
- const sessionFile = getArgValue(args, '--agent-poll') || getArgValue(args, '--codex-poll');
388
+ const loginArgs = applyLoginEnvironmentFlags(args);
389
+ if (loginArgs.includes('--agent-poll') || loginArgs.includes('--codex-poll')) {
390
+ const sessionFile = getArgValue(loginArgs, '--agent-poll') || getArgValue(loginArgs, '--codex-poll');
357
391
  const { pollCodexQrLogin } = require('../lib/auth/qr-login');
358
392
  const result = await pollCodexQrLogin(sessionFile, {
359
- corpId: getArgValue(args, '--corp-id'),
393
+ corpId: getArgValue(loginArgs, '--corp-id'),
360
394
  });
361
395
  printLoginResult(result);
362
- } else if (args.includes('--agent-select') || args.includes('--codex-select')) {
363
- const sessionFile = getArgValue(args, '--agent-select') || getArgValue(args, '--codex-select');
396
+ } else if (loginArgs.includes('--agent-select') || loginArgs.includes('--codex-select')) {
397
+ const sessionFile = getArgValue(loginArgs, '--agent-select') || getArgValue(loginArgs, '--codex-select');
364
398
  const { selectCodexQrCorp } = require('../lib/auth/qr-login');
365
399
  const result = await selectCodexQrCorp(sessionFile, {
366
- corpId: getArgValue(args, '--corp-id'),
400
+ corpId: getArgValue(loginArgs, '--corp-id'),
367
401
  });
368
402
  printLoginResult(result);
369
- } else if (args[0] === '--check-only') {
370
- const result = checkLoginOnly({ includeSecrets: args.includes('--with-cookies') });
403
+ } else if (loginArgs[0] === '--check-only') {
404
+ const result = checkLoginOnly({ includeSecrets: loginArgs.includes('--with-cookies') });
371
405
  console.log(JSON.stringify(result, null, 2));
372
- } else if (shouldUseCodexQrLogin(args)) {
406
+ } else if (shouldUseCodexQrLogin(loginArgs)) {
373
407
  const { startCodexQrLogin } = require('../lib/auth/qr-login');
374
- const result = await startCodexQrLogin({ corpId: getArgValue(args, '--corp-id') });
408
+ const result = await startCodexQrLogin({ corpId: getArgValue(loginArgs, '--corp-id') });
375
409
  printLoginResult(result);
376
- } else if (args.includes('--browser')) {
410
+ } else if (loginArgs.includes('--browser')) {
377
411
  const { interactiveLogin } = require('../lib/auth/login');
378
412
  const result = interactiveLogin({ force: true });
379
413
  printLoginResult(result);
380
- } else if (args.includes('--qoder') || args.includes('--wukong')) {
414
+ } else if (loginArgs.includes('--qoder') || loginArgs.includes('--wukong')) {
381
415
  const { codexLogin } = require('../lib/auth/codex-login');
382
- const result = await codexLogin({ tool: args.includes('--qoder') ? 'qoder' : 'wukong' });
416
+ const result = await codexLogin({ tool: loginArgs.includes('--qoder') ? 'qoder' : 'wukong' });
383
417
  printLoginResult(result);
384
- } else if (args.includes('--qr')) {
418
+ } else if (loginArgs.includes('--qr')) {
385
419
  const { qrLogin } = require('../lib/auth/qr-login');
386
- const result = await qrLogin({ corpId: getArgValue(args, '--corp-id') });
420
+ const result = await qrLogin({ corpId: getArgValue(loginArgs, '--corp-id') });
387
421
  console.log(JSON.stringify(result));
388
- } else if (shouldUseAgentLogin(args)) {
422
+ } else if (shouldUseAgentLogin(loginArgs)) {
389
423
  const cachedResult = checkLoginOnly({ includeSecrets: true });
390
424
  if (cachedResult.status === 'ok') {
391
425
  printLoginResult(cachedResult);
@@ -398,17 +432,17 @@ async function main() {
398
432
  printLoginResult(browserResult);
399
433
  } else {
400
434
  const { startCodexQrLogin } = require('../lib/auth/qr-login');
401
- const result = await startCodexQrLogin({ corpId: getArgValue(args, '--corp-id') });
435
+ const result = await startCodexQrLogin({ corpId: getArgValue(loginArgs, '--corp-id') });
402
436
  printLoginResult(result);
403
437
  }
404
438
  }
405
- } else if (shouldUseBrowserHandoffLogin(args)) {
439
+ } else if (shouldUseBrowserHandoffLogin(loginArgs)) {
406
440
  const cachedResult = checkLoginOnly({ includeSecrets: true });
407
441
  if (cachedResult.status === 'ok') {
408
442
  printLoginResult(cachedResult);
409
443
  } else {
410
444
  const { codexLogin } = require('../lib/auth/codex-login');
411
- const result = await codexLogin({ tool: args.includes('--codex') ? 'codex' : undefined });
445
+ const result = await codexLogin({ tool: loginArgs.includes('--codex') ? 'codex' : undefined });
412
446
  printLoginResult(result);
413
447
  }
414
448
  } else {
@@ -418,7 +452,7 @@ async function main() {
418
452
  break;
419
453
  }
420
454
  const { qrLogin } = require('../lib/auth/qr-login');
421
- const result = await qrLogin({ corpId: getArgValue(args, '--corp-id') });
455
+ const result = await qrLogin({ corpId: getArgValue(loginArgs, '--corp-id') });
422
456
  console.log(JSON.stringify(result));
423
457
  }
424
458
  break;
@@ -437,8 +471,9 @@ async function main() {
437
471
  if (subCommand === 'status') {
438
472
  authStatus();
439
473
  } else if (subCommand === 'login') {
440
- const loginType = shouldUseBrowserHandoffLogin(args) ? 'browser' : 'qrcode';
441
- await authLogin({ type: loginType, corpId: getArgValue(args, '--corp-id') });
474
+ const authArgs = [subCommand, ...applyLoginEnvironmentFlags(args.slice(1))];
475
+ const loginType = shouldUseBrowserHandoffLogin(authArgs) ? 'browser' : 'qrcode';
476
+ await authLogin({ type: loginType, corpId: getArgValue(authArgs, '--corp-id') });
442
477
  } else if (subCommand === 'refresh') {
443
478
  authRefresh();
444
479
  } else if (subCommand === 'logout') {
@@ -493,6 +528,12 @@ async function main() {
493
528
  break;
494
529
  }
495
530
 
531
+ case 'corp-efficiency': {
532
+ const { run } = require('../lib/corp-efficiency/corp-efficiency');
533
+ await run(args);
534
+ break;
535
+ }
536
+
496
537
  case 'create-app': {
497
538
  const { run } = require('../lib/app/create-app');
498
539
  await run(args);
@@ -879,6 +920,9 @@ async function main() {
879
920
  } else if (subCommand === 'disable') {
880
921
  const { runDisable } = require('../lib/integration/integration-list');
881
922
  await runDisable(subArgs);
923
+ } else if (subCommand === 'check') {
924
+ const { run: runIntegrationCheck } = require('../lib/integration/integration-check');
925
+ await runIntegrationCheck(subArgs);
882
926
  } else {
883
927
  warn(t('cli.integration_unknown', subCommand));
884
928
  warn(t('cli.integration_help_hint'));
@@ -24,7 +24,7 @@ const { saveCookieCache } = require('./login');
24
24
  const { t } = require('../core/i18n');
25
25
  const { warn } = require('../core/chalk');
26
26
 
27
- const { resolveLoginUrl, resolveEndpoint, deriveBaseUrlFromUrl } = require('../core/env-manager');
27
+ const { resolveLoginUrl, resolveEndpoint, deriveBaseUrlFromUrl, getCurrentEnvConfig } = require('../core/env-manager');
28
28
 
29
29
  function shellQuote(value) {
30
30
  return `'${String(value).replace(/'/g, "'\\''")}'`;
@@ -34,9 +34,11 @@ function getTargetCorpId(options = {}, session = {}) {
34
34
  return options.corpId || options.targetCorpId || session.targetCorpId || null;
35
35
  }
36
36
 
37
- function buildCodexPollCommand(sessionFile, targetCorpId) {
37
+ function buildCodexPollCommand(sessionFile, targetCorpId, envName) {
38
38
  const baseCommand = `openyida login --agent-poll ${shellQuote(sessionFile)}`;
39
- return targetCorpId ? `${baseCommand} --corp-id ${shellQuote(targetCorpId)}` : baseCommand;
39
+ const envArg = envName ? ` --env ${shellQuote(envName)}` : '';
40
+ const corpArg = targetCorpId ? ` --corp-id ${shellQuote(targetCorpId)}` : '';
41
+ return `${baseCommand}${envArg}${corpArg}`;
40
42
  }
41
43
 
42
44
  function buildQrImageMarkdown(qrImageFile) {
@@ -55,7 +57,7 @@ function buildAgentQrResponseMarkdown(result) {
55
57
  return lines.join('\n');
56
58
  }
57
59
 
58
- function buildNeedQrScanResult({ qrUrl, qrImageFile, sessionFile, targetCorpId }) {
60
+ function buildNeedQrScanResult({ qrUrl, qrImageFile, sessionFile, targetCorpId, envName }) {
59
61
  const qrImageMarkdown = buildQrImageMarkdown(qrImageFile);
60
62
  const result = {
61
63
  status: 'need_qr_scan',
@@ -65,7 +67,7 @@ function buildNeedQrScanResult({ qrUrl, qrImageFile, sessionFile, targetCorpId }
65
67
  qr_image_file: qrImageFile || null,
66
68
  qr_image_markdown: qrImageMarkdown,
67
69
  session_file: sessionFile,
68
- poll_command: buildCodexPollCommand(sessionFile, targetCorpId),
70
+ poll_command: buildCodexPollCommand(sessionFile, targetCorpId, envName),
69
71
  message: 'Scan the QR code with DingTalk, then run poll_command.',
70
72
  };
71
73
  result.agent_response_markdown = buildAgentQrResponseMarkdown(result);
@@ -366,8 +368,9 @@ async function writeQrCodeImage(url, filePath, options = {}) {
366
368
  function isDingtalkOAuthChallengeUrl(url) {
367
369
  try {
368
370
  const parsedUrl = new URL(url);
369
- return parsedUrl.hostname.endsWith('dingtalk.com') &&
370
- parsedUrl.pathname.startsWith('/oauth2/');
371
+ const hostname = parsedUrl.hostname;
372
+ const isDingtalkDomain = hostname.endsWith('dingtalk.com') || hostname.endsWith('dingtalk.io');
373
+ return isDingtalkDomain && parsedUrl.pathname.startsWith('/oauth2/');
371
374
  } catch {
372
375
  return false;
373
376
  }
@@ -1012,7 +1015,8 @@ function buildCodexCorpInteraction(corpList) {
1012
1015
  };
1013
1016
  }
1014
1017
 
1015
- function buildNeedCorpSelectionResult(sessionFile, corpList) {
1018
+ function buildNeedCorpSelectionResult(sessionFile, corpList, envName) {
1019
+ const envArg = envName ? ` --env ${shellQuote(envName)}` : '';
1016
1020
  return {
1017
1021
  status: 'need_corp_selection',
1018
1022
  handoff_type: 'codex_native_select',
@@ -1024,7 +1028,7 @@ function buildNeedCorpSelectionResult(sessionFile, corpList) {
1024
1028
  main_org: !!corp.mainOrg,
1025
1029
  })),
1026
1030
  interaction: buildCodexCorpInteraction(corpList),
1027
- select_command_template: `openyida login --codex-select ${shellQuote(sessionFile)} --corp-id <corpId>`,
1031
+ select_command_template: `openyida login --codex-select ${shellQuote(sessionFile)}${envArg} --corp-id <corpId>`,
1028
1032
  };
1029
1033
  }
1030
1034
 
@@ -1086,7 +1090,7 @@ async function maybeReturnCorpSelectionAfterExchange(session, sessionFile, optio
1086
1090
  stage: 'pending_corp_switch',
1087
1091
  updatedAt: new Date().toISOString(),
1088
1092
  });
1089
- return buildNeedCorpSelectionResult(sessionFile, corpList);
1093
+ return buildNeedCorpSelectionResult(sessionFile, corpList, session.currentEnvName);
1090
1094
  }
1091
1095
 
1092
1096
  if (!selectedCorp && corpList.length === 1) {
@@ -1100,6 +1104,7 @@ function buildFakeCodexQrLoginResult(options = {}) {
1100
1104
  const sessionId = 'test-session';
1101
1105
  const { sessionFile, qrImageFile } = getCodexQrSessionPaths(sessionId);
1102
1106
  const targetCorpId = getTargetCorpId(options);
1107
+ const currentEnvName = getCurrentEnvConfig().name;
1103
1108
  saveCodexQrSession(sessionFile, {
1104
1109
  schema_version: 1,
1105
1110
  mode: 'codex_qr_login',
@@ -1110,6 +1115,7 @@ function buildFakeCodexQrLoginResult(options = {}) {
1110
1115
  cookieHeader: '',
1111
1116
  context: { type: 'legacy' },
1112
1117
  targetCorpId,
1118
+ currentEnvName,
1113
1119
  createdAt: new Date().toISOString(),
1114
1120
  });
1115
1121
 
@@ -1118,6 +1124,7 @@ function buildFakeCodexQrLoginResult(options = {}) {
1118
1124
  qrImageFile,
1119
1125
  sessionFile,
1120
1126
  targetCorpId,
1127
+ envName: currentEnvName,
1121
1128
  });
1122
1129
  }
1123
1130
 
@@ -1128,6 +1135,7 @@ async function startCodexQrLogin(options = {}) {
1128
1135
 
1129
1136
  const baseUrl = (options.baseUrl || resolveEndpoint(null)).replace(/\/+$/, '');
1130
1137
  const targetCorpId = getTargetCorpId(options);
1138
+ const currentEnvName = getCurrentEnvConfig().name;
1131
1139
  const sessionId = options.sessionId || createCodexQrSessionId();
1132
1140
  const { sessionFile, qrImageFile } = getCodexQrSessionPaths(sessionId);
1133
1141
 
@@ -1157,6 +1165,7 @@ async function startCodexQrLogin(options = {}) {
1157
1165
  cookieHeader,
1158
1166
  context,
1159
1167
  targetCorpId,
1168
+ currentEnvName,
1160
1169
  createdAt: new Date().toISOString(),
1161
1170
  });
1162
1171
 
@@ -1165,6 +1174,7 @@ async function startCodexQrLogin(options = {}) {
1165
1174
  qrImageFile: imageWritten ? qrImageFile : null,
1166
1175
  sessionFile,
1167
1176
  targetCorpId,
1177
+ envName: currentEnvName,
1168
1178
  });
1169
1179
  }
1170
1180
 
@@ -1199,9 +1209,10 @@ async function pollCodexQrLogin(sessionFile, options = {}) {
1199
1209
  corpList,
1200
1210
  stage: 'pending_dingtalk_oauth_org',
1201
1211
  targetCorpId,
1212
+ currentEnvName: session.currentEnvName,
1202
1213
  updatedAt: new Date().toISOString(),
1203
1214
  });
1204
- return buildNeedCorpSelectionResult(sessionFile, corpList);
1215
+ return buildNeedCorpSelectionResult(sessionFile, corpList, session.currentEnvName);
1205
1216
  }
1206
1217
  }
1207
1218
 
@@ -1436,5 +1447,6 @@ module.exports = {
1436
1447
  buildNeedQrScanResult,
1437
1448
  getTargetCorpId,
1438
1449
  deriveAliworkBaseUrl,
1450
+ isDingtalkOAuthChallengeUrl,
1439
1451
  },
1440
1452
  };
@@ -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|--agent-qr|--codex|--browser] [--corp-id <corpId>]', 'help.cmd_login', {
23
+ command('login', ['login'], 'login [--qr|--agent-qr|--codex|--browser] [--env <name>|--overseas] [--corp-id <corpId>]', 'help.cmd_login', {
24
24
  requiresLogin: false,
25
25
  output: 'json',
26
26
  }),
@@ -31,7 +31,7 @@ const COMMAND_GROUPS = [
31
31
  requiresLogin: false,
32
32
  output: 'text|json',
33
33
  }),
34
- command('env-management', ['env'], 'env <list|show|switch|add|remove>', 'help.cmd_env_management', {
34
+ command('env-management', ['env'], 'env <setup|list|show|switch|add|remove>', 'help.cmd_env_management', {
35
35
  requiresLogin: false,
36
36
  }),
37
37
  ],
@@ -41,6 +41,9 @@ const COMMAND_GROUPS = [
41
41
  titleKey: 'help.group_app',
42
42
  commands: [
43
43
  command('app-list', ['app-list'], 'app-list [--size N]', 'help.cmd_app_list'),
44
+ command('corp-efficiency', ['corp-efficiency'], 'corp-efficiency [overview|details|detail|groups|notify] [options] [--open|--no-open]', 'help.cmd_corp_efficiency', {
45
+ output: 'json',
46
+ }),
44
47
  command('create-app', ['create-app'], 'create-app "<name>"|--name <name> [options] [--open|--no-open]', 'help.cmd_create_app'),
45
48
  command('update-app', ['update-app'], 'update-app <appType> --name "..."', 'help.cmd_update_app'),
46
49
  command('export', ['export'], 'export <appType> [output]', 'help.cmd_export'),
@@ -128,6 +131,7 @@ const COMMAND_GROUPS = [
128
131
  titleKey: 'help.group_integration',
129
132
  commands: [
130
133
  command('integration.create', ['integration', 'create'], 'integration create <appType> ...', 'help.cmd_integration'),
134
+ command('integration.check', ['integration', 'check'], 'integration check <appType...>', 'help.cmd_integration_check'),
131
135
  command('dws', ['dws'], 'dws <command> [args]', 'help.cmd_dws'),
132
136
  command('dingtalk-link', ['dingtalk-link'], 'dingtalk-link <url> [--target fullScreen] [--legacy-scheme] [--json]', 'help.cmd_dingtalk_link', {
133
137
  requiresLogin: false,
@@ -11,6 +11,7 @@
11
11
 
12
12
  'use strict';
13
13
 
14
+ const { execSync } = require('child_process');
14
15
  const readline = require('readline');
15
16
  const { warn } = require('./chalk');
16
17
  const {
@@ -18,6 +19,10 @@ const {
18
19
  saveEnvsConfig,
19
20
  DEFAULT_BASE_URL,
20
21
  DEFAULT_LOGIN_URL,
22
+ DEFAULT_PUBLIC_ENV,
23
+ DEFAULT_INTERNATIONAL_ENV,
24
+ DEFAULT_ALIBABA_INTERNAL_ENV,
25
+ resolveEnvNameAlias,
21
26
  } = require('./env-manager');
22
27
 
23
28
  // ── 颜色常量 ──────────────────────────────────────────
@@ -67,6 +72,79 @@ function formatEnvLabel(envName, currentEnv) {
67
72
  return `${CYAN}${envName}${RESET}`;
68
73
  }
69
74
 
75
+ function getLoginHost(loginUrl) {
76
+ try {
77
+ return new URL(loginUrl || DEFAULT_LOGIN_URL).hostname;
78
+ } catch {
79
+ return loginUrl || DEFAULT_LOGIN_URL;
80
+ }
81
+ }
82
+
83
+ function isBuiltinEnvName(envName) {
84
+ return ['public', 'intl', 'alibaba'].includes(envName);
85
+ }
86
+
87
+ function isDingTalkWukongRunning() {
88
+ if (process.platform !== 'darwin') {return false;}
89
+ try {
90
+ return execSync('ps -axo args', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
91
+ .includes('/Applications/DingTalkWuKong.app/');
92
+ } catch {
93
+ return false;
94
+ }
95
+ }
96
+
97
+ function detectSetupRecommendation() {
98
+ if (process.env.OPENYIDA_ENV) {
99
+ return resolveEnvNameAlias(process.env.OPENYIDA_ENV);
100
+ }
101
+ if ((process.env.OPENYIDA_LOGIN_URL || '').includes('login.dingtalk.io')) {
102
+ return 'intl';
103
+ }
104
+ if ((process.env.OPENYIDA_ENDPOINT || '').includes('alibaba-inc.com')) {
105
+ return 'alibaba';
106
+ }
107
+ if (isDingTalkWukongRunning()) {
108
+ return 'intl';
109
+ }
110
+
111
+ let activeTool = null;
112
+ try {
113
+ activeTool = require('./utils').detectActiveTool();
114
+ } catch {
115
+ activeTool = null;
116
+ }
117
+ if (activeTool && activeTool.tool === 'wukong') {
118
+ return 'intl';
119
+ }
120
+
121
+ return 'public';
122
+ }
123
+
124
+ function switchToEnv(envName) {
125
+ const resolvedEnvName = resolveEnvNameAlias(envName);
126
+ const config = loadEnvsConfig();
127
+
128
+ if (!config.environments[resolvedEnvName]) {
129
+ warn(`${RED}错误:环境 "${envName}" 不存在${RESET}`);
130
+ warn(`使用 ${CYAN}openyida env list${RESET} 查看所有环境`);
131
+ process.exit(1);
132
+ }
133
+
134
+ const previousEnv = config.current;
135
+ config.current = resolvedEnvName;
136
+ saveEnvsConfig(config);
137
+
138
+ console.log('');
139
+ console.log(`${GREEN}✅ 已切换环境${RESET}`);
140
+ console.log(` ${DIM}${previousEnv}${RESET} → ${GREEN}${BOLD}${resolvedEnvName}${RESET}`);
141
+ console.log(` 地址:${config.environments[resolvedEnvName].baseUrl}`);
142
+ console.log(` 登录:${getLoginHost(config.environments[resolvedEnvName].loginUrl)}`);
143
+ console.log('');
144
+ console.log(`${DIM}下一步:运行 openyida login;在悟空中可运行 openyida login --wukong${RESET}`);
145
+ console.log('');
146
+ }
147
+
70
148
  // ── 子命令实现 ────────────────────────────────────────
71
149
 
72
150
  /**
@@ -88,6 +166,7 @@ function cmdList() {
88
166
  const label = formatEnvLabel(envName, config.current);
89
167
  console.log(` ${label}`);
90
168
  console.log(` ${DIM}地址:${RESET}${envConfig.baseUrl || DEFAULT_BASE_URL}`);
169
+ console.log(` ${DIM}登录:${RESET}${getLoginHost(envConfig.loginUrl || DEFAULT_LOGIN_URL)}`);
91
170
  if (envConfig.description) {
92
171
  console.log(` ${DIM}描述:${RESET}${envConfig.description}`);
93
172
  }
@@ -95,7 +174,7 @@ function cmdList() {
95
174
  }
96
175
 
97
176
  console.log('');
98
- console.log(`${DIM}使用 openyida env switch <name> 切换环境${RESET}`);
177
+ console.log(`${DIM}使用 openyida env setup 进入向导,或 openyida env switch <name> 切换环境${RESET}`);
99
178
  console.log(`${DIM}使用 openyida env add <name> 添加私有化环境${RESET}`);
100
179
  console.log('');
101
180
  }
@@ -106,7 +185,7 @@ function cmdList() {
106
185
  */
107
186
  function cmdShow(envName) {
108
187
  const config = loadEnvsConfig();
109
- const targetName = envName || config.current;
188
+ const targetName = resolveEnvNameAlias(envName || config.current);
110
189
  const envConfig = config.environments[targetName];
111
190
 
112
191
  if (!envConfig) {
@@ -146,30 +225,21 @@ function cmdSwitch(envName) {
146
225
  process.exit(1);
147
226
  }
148
227
 
228
+ const resolvedEnvName = resolveEnvNameAlias(envName);
149
229
  const config = loadEnvsConfig();
150
230
 
151
- if (!config.environments[envName]) {
231
+ if (!config.environments[resolvedEnvName]) {
152
232
  warn(`${RED}错误:环境 "${envName}" 不存在${RESET}`);
153
233
  warn(`使用 ${CYAN}openyida env list${RESET} 查看所有环境`);
154
234
  process.exit(1);
155
235
  }
156
236
 
157
- if (config.current === envName) {
158
- console.log(`${YELLOW}当前已在 "${envName}" 环境,无需切换${RESET}`);
237
+ if (config.current === resolvedEnvName) {
238
+ console.log(`${YELLOW}当前已在 "${resolvedEnvName}" 环境,无需切换${RESET}`);
159
239
  return;
160
240
  }
161
241
 
162
- const previousEnv = config.current;
163
- config.current = envName;
164
- saveEnvsConfig(config);
165
-
166
- console.log('');
167
- console.log(`${GREEN}✅ 已切换环境${RESET}`);
168
- console.log(` ${DIM}${previousEnv}${RESET} → ${GREEN}${BOLD}${envName}${RESET}`);
169
- console.log(` 地址:${config.environments[envName].baseUrl}`);
170
- console.log('');
171
- console.log(`${DIM}提示:登录态已自动切换,如需重新登录请运行 openyida login --qr${RESET}`);
172
- console.log('');
242
+ switchToEnv(resolvedEnvName);
173
243
  }
174
244
 
175
245
  /**
@@ -183,8 +253,9 @@ function cmdRemove(envName) {
183
253
  process.exit(1);
184
254
  }
185
255
 
186
- if (envName === 'public') {
187
- warn(`${RED}错误:不能删除默认的公有云环境 "public"${RESET}`);
256
+ envName = resolveEnvNameAlias(envName);
257
+ if (isBuiltinEnvName(envName)) {
258
+ warn(`${RED}错误:不能删除内置环境 "${envName}"${RESET}`);
188
259
  process.exit(1);
189
260
  }
190
261
 
@@ -235,6 +306,11 @@ async function cmdAdd(envName) {
235
306
  process.exit(1);
236
307
  }
237
308
 
309
+ if (resolveEnvNameAlias(envName) !== envName || isBuiltinEnvName(envName)) {
310
+ warn(`${RED}错误:"${envName}" 是内置环境或内置别名,不能作为私有化环境名称${RESET}`);
311
+ process.exit(1);
312
+ }
313
+
238
314
  const config = loadEnvsConfig();
239
315
  const existingConfig = config.environments[envName];
240
316
 
@@ -302,6 +378,75 @@ async function cmdAdd(envName) {
302
378
  console.log('');
303
379
  }
304
380
 
381
+ async function cmdSetup() {
382
+ const config = loadEnvsConfig();
383
+ const recommendedEnv = detectSetupRecommendation();
384
+ const options = [
385
+ {
386
+ key: 'public',
387
+ title: '阿里云公有云 / 国内钉钉',
388
+ baseUrl: DEFAULT_PUBLIC_ENV.baseUrl,
389
+ loginUrl: DEFAULT_PUBLIC_ENV.loginUrl,
390
+ },
391
+ {
392
+ key: 'intl',
393
+ title: '海外 DingTalk / Dingtalk Wukong',
394
+ baseUrl: DEFAULT_INTERNATIONAL_ENV.baseUrl,
395
+ loginUrl: DEFAULT_INTERNATIONAL_ENV.loginUrl,
396
+ },
397
+ {
398
+ key: 'alibaba',
399
+ title: '阿里内网员工环境',
400
+ baseUrl: DEFAULT_ALIBABA_INTERNAL_ENV.baseUrl,
401
+ loginUrl: DEFAULT_ALIBABA_INTERNAL_ENV.loginUrl,
402
+ },
403
+ {
404
+ key: 'custom',
405
+ title: '私有化部署 / 专属域名',
406
+ baseUrl: '自定义',
407
+ loginUrl: '自定义',
408
+ },
409
+ ];
410
+ const defaultIndex = Math.max(0, options.findIndex((item) => item.key === recommendedEnv));
411
+
412
+ console.log('');
413
+ console.log(`${BOLD}OpenYida 环境设置向导${RESET}`);
414
+ console.log(`${'─'.repeat(50)}`);
415
+ console.log(`${DIM}请选择客户实际登录入口。可随时用 openyida env switch 切换。${RESET}`);
416
+ console.log('');
417
+
418
+ options.forEach((item, index) => {
419
+ const isRecommended = index === defaultIndex;
420
+ console.log(` ${CYAN}${index + 1}.${RESET} ${item.title}${isRecommended ? ` ${GREEN}← 推荐${RESET}` : ''}`);
421
+ console.log(` ${DIM}地址:${item.baseUrl}${RESET}`);
422
+ console.log(` ${DIM}登录:${getLoginHost(item.loginUrl)}${RESET}`);
423
+ });
424
+ console.log('');
425
+
426
+ const answer = await askQuestion(`${BOLD}选择环境${RESET}(1-${options.length}):`, String(defaultIndex + 1));
427
+ const selectedIndex = Number.parseInt(answer, 10) - 1;
428
+ const selected = options[selectedIndex];
429
+ if (!selected) {
430
+ warn(`${RED}错误:无效选择${RESET}`);
431
+ process.exit(1);
432
+ }
433
+
434
+ if (selected.key === 'custom') {
435
+ const envName = await askQuestion(`${BOLD}私有化环境名称${RESET}(如 private-prod):`, '');
436
+ await cmdAdd(envName);
437
+ return;
438
+ }
439
+
440
+ if (config.current === selected.key) {
441
+ console.log('');
442
+ console.log(`${YELLOW}当前已在 "${selected.key}" 环境,无需切换${RESET}`);
443
+ console.log('');
444
+ return;
445
+ }
446
+
447
+ switchToEnv(selected.key);
448
+ }
449
+
305
450
  /**
306
451
  * 显示 env 命令帮助信息。
307
452
  */
@@ -314,6 +459,7 @@ function showHelp() {
314
459
  console.log('');
315
460
  console.log(`${CYAN}子命令:${RESET}`);
316
461
  console.log(` ${GREEN}list${RESET} 列出所有环境及当前激活环境`);
462
+ console.log(` ${GREEN}setup${RESET} 交互式选择公有云、海外、内网或私有化环境`);
317
463
  console.log(` ${GREEN}add <name>${RESET} 交互式添加私有化环境配置`);
318
464
  console.log(` ${GREEN}switch <name>${RESET} 切换当前激活环境`);
319
465
  console.log(` ${GREEN}remove <name>${RESET} 移除环境配置`);
@@ -321,6 +467,7 @@ function showHelp() {
321
467
  console.log('');
322
468
  console.log(`${CYAN}示例:${RESET}`);
323
469
  console.log(` ${BLUE}openyida env list${RESET} 查看所有环境`);
470
+ console.log(` ${BLUE}openyida env setup${RESET} 进入环境设置向导`);
324
471
  console.log(` ${BLUE}openyida env add private-prod${RESET} 添加私有化生产环境`);
325
472
  console.log(` ${BLUE}openyida env switch private-prod${RESET} 切换到私有化环境`);
326
473
  console.log(` ${BLUE}openyida env show private-prod${RESET} 查看环境详情`);
@@ -358,6 +505,12 @@ async function run(args) {
358
505
  cmdSwitch(subArgs[0]);
359
506
  break;
360
507
 
508
+ case 'setup':
509
+ case 'init':
510
+ case 'configure':
511
+ await cmdSetup();
512
+ break;
513
+
361
514
  case 'remove':
362
515
  case 'rm':
363
516
  case 'delete':