openyida 2026.5.17 → 2026.5.18

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
@@ -242,6 +242,8 @@ Use `npm run test:e2e:real:cleanup` to list recorded disposable resources. OpenY
242
242
  openyida connector smart-create --curl "curl https://api.example.com/users"
243
243
  openyida connector list
244
244
  openyida integration create APP_XXX FORM_XXX "Sync customer data"
245
+ openyida integration create APP_XXX FORM_XXX "Approval result notify" \
246
+ --events processFinish --approval-actions agree,disagree --receivers 123456
245
247
  openyida create-report APP_XXX "Sales Dashboard" .cache/openyida/reports/charts.json
246
248
  openyida append-chart APP_XXX REPORT_XXX .cache/openyida/reports/chart.json
247
249
  ```
@@ -258,13 +260,13 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
258
260
  | `openyida env setup` | Choose a customer-friendly login environment preset: public, overseas, Alibaba intranet, or private deployment |
259
261
  | `openyida env <list\|show\|switch\|add\|remove>` | Manage public/private Yida environment profiles |
260
262
  | `openyida commands [--json]` | Emit the machine-readable command manifest |
261
- | `openyida login [--qr\|--agent-qr\|--codex\|--browser] [--env <name>\|--overseas\|--yidaapps] [--corp-id <corpId>]` | Log in to Yida |
263
+ | `openyida login [--qr\|--agent-qr\|--codex\|--browser] [--env <name>\|--intl\|--overseas\|--global\|--yidaapps] [--corp-id <corpId>]` | Log in to Yida |
262
264
  | `openyida logout` | Log out or switch account |
263
265
  | `openyida auth <status\|login\|refresh\|logout>` | Manage login status |
264
266
  | `openyida org list` | List accessible organizations |
265
267
  | `openyida org switch --corp-id <corpId>` | Switch organization without logging in again |
266
268
 
267
- Environment selectors such as `--env intl`, `--overseas`, and `--yidaapps` can be used on login-required commands to choose the target Yida environment for that run.
269
+ Environment selectors such as `--env intl`, `--intl`, `--overseas`, `--global`, and `--yidaapps` can be used on login-required commands to choose the target Yida environment for that run. The `intl` preset targets Global YiDA at `https://www.yidaapps.com` and uses DingTalk International OAuth at `https://login.dingtalk.io`; use `openyida login --browser --intl` when you need cookies accepted by Global YiDA business APIs.
268
270
 
269
271
  ### Applications
270
272
 
@@ -274,6 +276,7 @@ Environment selectors such as `--env intl`, `--overseas`, and `--yidaapps` can b
274
276
  | `openyida corp-efficiency [overview\|details\|detail\|groups\|notify] [options] [--open\|--no-open]` | Query enterprise efficiency metrics, detail report entries, and related notification actions |
275
277
  | `openyida create-app "<name>"\|--name <name> [options] [--open\|--no-open]` | Create an application and output `appType` |
276
278
  | `openyida update-app <appType> --name "..."` | Update application metadata |
279
+ | `openyida app-permission <get\|set\|add\|remove\|search-user> ...` | Manage app primary admins, data admins, and developer members |
277
280
  | `openyida export <appType> [output]` | Export an application migration package |
278
281
  | `openyida import <file> [name]` | Import a migration package into a target environment |
279
282
 
@@ -319,13 +322,15 @@ Environment selectors such as `--env intl`, `--overseas`, and `--yidaapps` can b
319
322
  | `openyida create-report <appType> "<name>" <charts.json> [--open\|--no-open]` | Create a Yida report |
320
323
  | `openyida append-chart <appType> <reportId> <charts.json> [--open\|--no-open]` | Append a chart to an existing report |
321
324
  | `openyida connector <sub-command>` | Manage HTTP connectors, actions, tests, and auth accounts |
322
- | `openyida integration create <appType> <formUuid> <flowName> [options]` | Create an integration automation flow |
325
+ | `openyida integration create <appType> <formUuid> <flowName> [options]` | Create an integration automation flow, including form and approval-process events |
323
326
  | `openyida integration list <appType> [--form-uuid <uuid>] [--status y\|n] [--json]` | List automation flows in an app, optionally filtered by form/status |
324
327
  | `openyida integration enable <appType> <formUuid> <processCode>` | Enable an automation flow |
325
328
  | `openyida integration disable <appType> <formUuid> <processCode>` | Disable an automation flow |
326
329
  | `openyida dws <command> [args]` | Access DingTalk CLI capabilities such as contacts, calendar, todo, and approval |
327
330
  | `openyida dingtalk-link <url> [--target fullScreen] [--legacy-scheme] [--json]` | Generate DingTalk AppLink URLs for opening pages in DingTalk; use `--legacy-scheme` only when old `dingtalk://` links are required |
328
331
 
332
+ `openyida integration create` supports form events (`insert`, `update`, `delete`, `comment`) and approval events (`processFinish`, `activityTask`; aliases: `approval`, `approvalNode`). Approval events require `--approval-actions agree,disagree,terminated`; `activityTask` also requires `--approval-node-ids <nodeId,...>`.
333
+
329
334
  ### Utilities
330
335
 
331
336
  | Command | Description |
@@ -441,11 +446,15 @@ Scan the QR code to join the OpenYida DingTalk user group for updates and suppor
441
446
 
442
447
  Thanks to everyone who has contributed to OpenYida. Read the [Contributing Guide](./CONTRIBUTING.md) to get involved.
443
448
 
449
+ Latest contributors: [DDlixin1](https://github.com/DDlixin1), [fcloud](https://github.com/fcloud).
450
+
444
451
  <!-- openyida-contributors:start -->
445
452
 
446
453
  <p>
447
454
  <a href="https://github.com/yize"><img src="https://github.com/yize.png?size=48" width="48" height="48" alt="九神" title="九神" /></a>
448
455
  <a href="https://github.com/alex-mm"><img src="https://github.com/alex-mm.png?size=48" width="48" height="48" alt="天晟" title="天晟" /></a>
456
+ <a href="https://github.com/DDlixin1"><img src="https://github.com/DDlixin1.png?size=48" width="48" height="48" alt="DDlixin1" title="DDlixin1" /></a>
457
+ <a href="https://github.com/fcloud"><img src="https://github.com/fcloud.png?size=48" width="48" height="48" alt="Aiden Wu (fcloud)" title="Aiden Wu (fcloud)" /></a>
449
458
  <a href="https://github.com/nicky1108"><img src="https://github.com/nicky1108.png?size=48" width="48" height="48" alt="nicky1108" title="nicky1108" /></a>
450
459
  <a href="https://github.com/angelinheys"><img src="https://github.com/angelinheys.png?size=48" width="48" height="48" alt="angelinheys" title="angelinheys" /></a>
451
460
  <a href="https://github.com/yipengmu"><img src="https://github.com/yipengmu.png?size=48" width="48" height="48" alt="yipengmu" title="yipengmu" /></a>
package/bin/yida.js CHANGED
@@ -692,6 +692,12 @@ async function main() {
692
692
  break;
693
693
  }
694
694
 
695
+ case 'app-permission': {
696
+ const { run: runAppPermission } = require('../lib/app-permission/app-permission');
697
+ await runAppPermission(args);
698
+ break;
699
+ }
700
+
695
701
  case 'data': {
696
702
  if (args.length < 2) {
697
703
  warn('用法: openyida data <action> <resource> [args] [options]');
@@ -0,0 +1,459 @@
1
+ 'use strict';
2
+
3
+ const querystring = require('querystring');
4
+
5
+ const {
6
+ loadCookieData,
7
+ triggerLogin,
8
+ resolveBaseUrl,
9
+ httpGet,
10
+ httpPost,
11
+ requestWithAutoLogin,
12
+ } = require('../core/utils');
13
+ const { searchUsers } = require('../corp-manager/api');
14
+
15
+ const ROLE_CONFIGS = {
16
+ MAIN: {
17
+ key: 'main',
18
+ label: '应用主管理员',
19
+ memberField: 'managers',
20
+ idField: 'managerIdList',
21
+ required: true,
22
+ },
23
+ DATA: {
24
+ key: 'data',
25
+ label: '数据管理员',
26
+ memberField: 'dataManagers',
27
+ idField: 'dataManagerUserIdList',
28
+ required: false,
29
+ },
30
+ DEV: {
31
+ key: 'dev',
32
+ label: '开发成员',
33
+ memberField: 'devManagers',
34
+ idField: 'devManagerUserIdList',
35
+ required: false,
36
+ },
37
+ };
38
+
39
+ const ROLE_ALIASES = {
40
+ main: 'MAIN',
41
+ primary: 'MAIN',
42
+ owner: 'MAIN',
43
+ admin: 'MAIN',
44
+ manager: 'MAIN',
45
+ mainManagers: 'MAIN',
46
+ MAIN: 'MAIN',
47
+
48
+ data: 'DATA',
49
+ dataAdmin: 'DATA',
50
+ dataManager: 'DATA',
51
+ dataManagers: 'DATA',
52
+ DATA: 'DATA',
53
+
54
+ dev: 'DEV',
55
+ develop: 'DEV',
56
+ developer: 'DEV',
57
+ development: 'DEV',
58
+ devManager: 'DEV',
59
+ devManagers: 'DEV',
60
+ DEV: 'DEV',
61
+ };
62
+
63
+ const USAGE = `openyida app-permission - 应用级管理员设置
64
+
65
+ Usage:
66
+ openyida app-permission search-user <keyword> [--dept <text>] [--size N]
67
+ openyida app-permission get <appType>
68
+ openyida app-permission set <appType> <main|data|dev> --users <userId1,userId2>
69
+ openyida app-permission set <appType> <data|dev> --clear
70
+ openyida app-permission add <appType> <main|data|dev> --users <userId1,userId2>
71
+ openyida app-permission remove <appType> <main|data|dev> --users <userId1,userId2>
72
+
73
+ Examples:
74
+ openyida app-permission get APP_XXX
75
+ openyida app-permission add APP_XXX data --users manager7350
76
+ openyida app-permission set APP_XXX dev --users user001,user002
77
+ `;
78
+
79
+ function fail(message) {
80
+ console.error(message);
81
+ console.error(USAGE);
82
+ process.exit(1);
83
+ }
84
+
85
+ function printJson(payload) {
86
+ console.log(JSON.stringify(payload, null, 2));
87
+ }
88
+
89
+ function getAuthRef() {
90
+ let cookieData = loadCookieData();
91
+ if (!cookieData || !cookieData.cookies || !cookieData.csrf_token) {
92
+ cookieData = triggerLogin();
93
+ }
94
+
95
+ if (!cookieData || !cookieData.cookies || !cookieData.csrf_token) {
96
+ throw new Error('无法获取有效登录态或 CSRF Token');
97
+ }
98
+
99
+ return {
100
+ csrfToken: cookieData.csrf_token,
101
+ cookies: cookieData.cookies,
102
+ baseUrl: resolveBaseUrl(cookieData),
103
+ cookieData,
104
+ };
105
+ }
106
+
107
+ function assertSuccess(result, action) {
108
+ if (result && result.success) {
109
+ return result;
110
+ }
111
+ const message = result && (result.errorMsg || result.message || result.errorCode);
112
+ throw new Error(`${action}失败${message ? `:${message}` : ''}`);
113
+ }
114
+
115
+ function normalizeRole(role) {
116
+ const normalized = ROLE_ALIASES[String(role || '').trim()];
117
+ if (!normalized) {
118
+ throw new Error(`无效角色:${role},可用值:main, data, dev`);
119
+ }
120
+ return normalized;
121
+ }
122
+
123
+ function normalizeText(value) {
124
+ if (value === null || value === undefined) {
125
+ return '';
126
+ }
127
+ if (typeof value === 'string') {
128
+ return value;
129
+ }
130
+ if (typeof value === 'object') {
131
+ return value.zh_CN || value.pureEn_US || value.en_US || value.value || value.text || value.label || '';
132
+ }
133
+ return String(value);
134
+ }
135
+
136
+ function validateAppType(appType) {
137
+ if (!appType) {
138
+ throw new Error('缺少 appType');
139
+ }
140
+ if (/[/?#]/.test(appType)) {
141
+ throw new Error(`无效 appType:${appType}`);
142
+ }
143
+ return appType;
144
+ }
145
+
146
+ function splitList(value) {
147
+ if (!value || value === true) {
148
+ return [];
149
+ }
150
+ if (Array.isArray(value)) {
151
+ return value;
152
+ }
153
+ return String(value).split(',').map(item => item.trim()).filter(Boolean);
154
+ }
155
+
156
+ function unique(values) {
157
+ return [...new Set((values || []).map(value => String(value).trim()).filter(Boolean))];
158
+ }
159
+
160
+ function toPositiveInt(value, defaultValue) {
161
+ const parsed = Number.parseInt(value || `${defaultValue}`, 10);
162
+ if (!Number.isFinite(parsed) || parsed <= 0) {
163
+ return defaultValue;
164
+ }
165
+ return parsed;
166
+ }
167
+
168
+ function parseCliOptions(tokens) {
169
+ const positionals = [];
170
+ const options = {};
171
+
172
+ for (let i = 0; i < tokens.length; i += 1) {
173
+ const token = tokens[i];
174
+ if (token.startsWith('--')) {
175
+ const key = token.slice(2).replace(/-/g, '_');
176
+ const next = tokens[i + 1];
177
+ if (next !== undefined && !next.startsWith('--')) {
178
+ options[key] = next;
179
+ i += 1;
180
+ } else {
181
+ options[key] = true;
182
+ }
183
+ } else {
184
+ positionals.push(token);
185
+ }
186
+ }
187
+
188
+ return { positionals, options };
189
+ }
190
+
191
+ function buildCommonParams(authRef, params = {}) {
192
+ return {
193
+ _csrf_token: authRef.csrfToken,
194
+ _locale_time_zone_offset: '28800000',
195
+ _stamp: String(Date.now()),
196
+ ...params,
197
+ };
198
+ }
199
+
200
+ async function appGet(appType, path, params, authRef = getAuthRef()) {
201
+ const safeAppType = validateAppType(appType);
202
+ return requestWithAutoLogin(
203
+ auth => httpGet(
204
+ auth.baseUrl,
205
+ `/${safeAppType}/${path.replace(/^\/+/, '')}`,
206
+ buildCommonParams(auth, params),
207
+ auth.cookies,
208
+ ),
209
+ authRef,
210
+ );
211
+ }
212
+
213
+ async function appPost(appType, path, params, authRef = getAuthRef()) {
214
+ const safeAppType = validateAppType(appType);
215
+ return requestWithAutoLogin(
216
+ auth => httpPost(
217
+ auth.baseUrl,
218
+ `/${safeAppType}/${path.replace(/^\/+/, '')}`,
219
+ querystring.stringify(buildCommonParams(auth, params)),
220
+ auth.cookies,
221
+ ),
222
+ authRef,
223
+ );
224
+ }
225
+
226
+ function normalizeMember(record) {
227
+ const userId = record.userId || record.emplId || record.id || record.key || record.workNo || '';
228
+ const userName = normalizeText(
229
+ record.displayName ||
230
+ record.defaultName ||
231
+ record.name ||
232
+ record.nickName ||
233
+ record.label ||
234
+ record.userName,
235
+ );
236
+
237
+ return {
238
+ userId,
239
+ userName,
240
+ dingtalkId: record.dingtalkId || '',
241
+ avatar: record.personalPhoto || record.personalPhotoUrl || record.avatar || '',
242
+ companyNo: record.companyNo || '',
243
+ workStatus: record.workStatus || '',
244
+ };
245
+ }
246
+
247
+ function memberIds(members) {
248
+ return unique((members || []).map(member => member.userId || member.emplId || member.id || member.key));
249
+ }
250
+
251
+ function idsFromContent(content, config) {
252
+ const idValue = content[config.idField];
253
+ const idsFromField = typeof idValue === 'string' ? splitList(idValue) : [];
254
+ if (idsFromField.length > 0) {
255
+ return unique(idsFromField);
256
+ }
257
+ return memberIds(content[config.memberField] || []);
258
+ }
259
+
260
+ function normalizeRolePayload(content, roleType) {
261
+ const config = ROLE_CONFIGS[roleType];
262
+ const members = (content[config.memberField] || []).map(normalizeMember);
263
+ const ids = idsFromContent(content, config);
264
+
265
+ return {
266
+ role: config.key,
267
+ roleType,
268
+ roleLabel: config.label,
269
+ required: config.required,
270
+ userIds: ids,
271
+ members,
272
+ };
273
+ }
274
+
275
+ function normalizeAppPermission(content = {}) {
276
+ return {
277
+ success: true,
278
+ appType: content.appType || '',
279
+ appName: normalizeText(content.appName),
280
+ sentryMode: content.sentryMode || '',
281
+ isAccessControl: content.isAccessControl || '',
282
+ allowExternalAddressBook: content.allowExternalAddressBook || '',
283
+ newAllowExternalAddressBook: content.newAllowExternalAddressBook || '',
284
+ currentUserAdminType: content.adminType || '',
285
+ roles: {
286
+ main: normalizeRolePayload(content, 'MAIN'),
287
+ data: normalizeRolePayload(content, 'DATA'),
288
+ dev: normalizeRolePayload(content, 'DEV'),
289
+ },
290
+ };
291
+ }
292
+
293
+ async function getAppPermission(appType, authRef = getAuthRef()) {
294
+ const result = await appGet(
295
+ appType,
296
+ '/query/app/getAppIncludingAecpInfo.json',
297
+ { appKey: appType },
298
+ authRef,
299
+ );
300
+ assertSuccess(result, '查询应用管理员设置');
301
+ return normalizeAppPermission(result.content || {});
302
+ }
303
+
304
+ async function saveRoleManagers(options = {}, authRef = getAuthRef()) {
305
+ const appType = validateAppType(options.appType);
306
+ const roleType = normalizeRole(options.role || options.roleType);
307
+ const userIds = unique(options.userIds || options.users || []);
308
+
309
+ if (ROLE_CONFIGS[roleType].required && userIds.length === 0) {
310
+ throw new Error(`${ROLE_CONFIGS[roleType].label}不能为空`);
311
+ }
312
+
313
+ const result = await appPost(
314
+ appType,
315
+ '/query/app/updateAppAdmin.json',
316
+ {
317
+ adminType: roleType,
318
+ managers: userIds.join(','),
319
+ },
320
+ authRef,
321
+ );
322
+ assertSuccess(result, '保存应用管理员设置');
323
+
324
+ return {
325
+ success: true,
326
+ appType,
327
+ role: ROLE_CONFIGS[roleType].key,
328
+ roleType,
329
+ roleLabel: ROLE_CONFIGS[roleType].label,
330
+ userIds,
331
+ content: result.content,
332
+ };
333
+ }
334
+
335
+ async function updateRoleManagers(options = {}, authRef = getAuthRef()) {
336
+ const appType = validateAppType(options.appType);
337
+ const roleType = normalizeRole(options.role || options.roleType);
338
+ const action = options.action || 'set';
339
+ const inputUserIds = unique(options.userIds || options.users || []);
340
+
341
+ if (action === 'set') {
342
+ return saveRoleManagers({ appType, roleType, userIds: inputUserIds }, authRef);
343
+ }
344
+
345
+ if (inputUserIds.length === 0) {
346
+ throw new Error(`${action} 操作必须提供 --users`);
347
+ }
348
+
349
+ const current = await getAppPermission(appType, authRef);
350
+ const roleKey = ROLE_CONFIGS[roleType].key;
351
+ const previousUserIds = current.roles[roleKey].userIds;
352
+ let nextUserIds;
353
+
354
+ if (action === 'add') {
355
+ nextUserIds = unique(previousUserIds.concat(inputUserIds));
356
+ } else if (action === 'remove') {
357
+ const removeSet = new Set(inputUserIds);
358
+ nextUserIds = previousUserIds.filter(userId => !removeSet.has(userId));
359
+ } else {
360
+ throw new Error(`未知操作:${action}`);
361
+ }
362
+
363
+ const saved = await saveRoleManagers({ appType, roleType, userIds: nextUserIds }, authRef);
364
+ return {
365
+ ...saved,
366
+ action,
367
+ previousUserIds,
368
+ };
369
+ }
370
+
371
+ async function runSearchUser(positionals, options) {
372
+ const keyword = positionals[0];
373
+ if (!keyword) {
374
+ fail('缺少搜索关键词');
375
+ }
376
+
377
+ printJson(await searchUsers({
378
+ keyword,
379
+ dept: options.dept || options.department,
380
+ size: toPositiveInt(options.size, 50),
381
+ }));
382
+ }
383
+
384
+ async function runGet(positionals) {
385
+ const appType = positionals[0];
386
+ if (!appType) {
387
+ fail('缺少 appType');
388
+ }
389
+ printJson(await getAppPermission(appType));
390
+ }
391
+
392
+ async function runUpdate(action, positionals, options) {
393
+ const appType = positionals[0];
394
+ const role = positionals[1];
395
+ if (!appType) {
396
+ fail('缺少 appType');
397
+ }
398
+ if (!role) {
399
+ fail('缺少角色:main、data 或 dev');
400
+ }
401
+
402
+ const userIds = options.clear ? [] : splitList(options.users || options.user || options.user_ids);
403
+ if (!options.clear && userIds.length === 0) {
404
+ fail(`${action} 操作必须提供 --users <userId1,userId2>;清空 data/dev 请使用 --clear`);
405
+ }
406
+ if (options.clear && action !== 'set') {
407
+ fail('--clear 只支持 set 操作');
408
+ }
409
+
410
+ const saved = await updateRoleManagers({
411
+ action,
412
+ appType,
413
+ role,
414
+ userIds,
415
+ });
416
+ const current = await getAppPermission(appType);
417
+ const roleKey = ROLE_CONFIGS[saved.roleType].key;
418
+
419
+ printJson({
420
+ ...saved,
421
+ currentRole: current.roles[roleKey],
422
+ });
423
+ }
424
+
425
+ async function run(args) {
426
+ const { positionals, options } = parseCliOptions(args);
427
+ const action = positionals.shift();
428
+
429
+ if (!action || action === '--help' || action === '-h') {
430
+ console.log(USAGE);
431
+ return;
432
+ }
433
+
434
+ if (action === 'search-user') {
435
+ await runSearchUser(positionals, options);
436
+ } else if (action === 'get' || action === 'list') {
437
+ await runGet(positionals);
438
+ } else if (['set', 'add', 'remove'].includes(action)) {
439
+ await runUpdate(action, positionals, options);
440
+ } else {
441
+ fail(`未知 app-permission 子命令:${action}`);
442
+ }
443
+ }
444
+
445
+ module.exports = {
446
+ ROLE_CONFIGS,
447
+ ROLE_ALIASES,
448
+ USAGE,
449
+ parseCliOptions,
450
+ splitList,
451
+ normalizeRole,
452
+ normalizeText,
453
+ normalizeMember,
454
+ normalizeAppPermission,
455
+ getAppPermission,
456
+ saveRoleManagers,
457
+ updateRoleManagers,
458
+ run,
459
+ };
@@ -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] [--env <name>|--overseas|--yidaapps] [--corp-id <corpId>]', 'help.cmd_login', {
23
+ command('login', ['login'], 'login [--qr|--agent-qr|--codex|--browser] [--env <name>|--intl|--overseas|--global|--yidaapps] [--corp-id <corpId>]', 'help.cmd_login', {
24
24
  requiresLogin: false,
25
25
  output: 'json',
26
26
  }),
@@ -46,6 +46,9 @@ const COMMAND_GROUPS = [
46
46
  }),
47
47
  command('create-app', ['create-app'], 'create-app "<name>"|--name <name> [options] [--open|--no-open]', 'help.cmd_create_app'),
48
48
  command('update-app', ['update-app'], 'update-app <appType> --name "..."', 'help.cmd_update_app'),
49
+ command('app-permission', ['app-permission'], 'app-permission <get|set|add|remove|search-user> ...', 'help.cmd_app_permission', {
50
+ output: 'json',
51
+ }),
49
52
  command('export', ['export'], 'export <appType> [output]', 'help.cmd_export'),
50
53
  command('import', ['import'], 'import <file> [name]', 'help.cmd_import'),
51
54
  ],
@@ -133,6 +136,7 @@ const COMMAND_GROUPS = [
133
136
  command('integration.create', ['integration', 'create'], 'integration create <appType> ...', 'help.cmd_integration'),
134
137
  command('integration.check', ['integration', 'check'], 'integration check <appType...>', 'help.cmd_integration_check'),
135
138
  command('dws', ['dws'], 'dws <command> [args]', 'help.cmd_dws'),
139
+ command('dws.contact-user-search', ['dws', 'contact', 'user', 'search'], 'dws contact user search --keyword <text>', 'help.cmd_dws'),
136
140
  command('dingtalk-link', ['dingtalk-link'], 'dingtalk-link <url> [--target fullScreen] [--legacy-scheme] [--json]', 'help.cmd_dingtalk_link', {
137
141
  requiresLogin: false,
138
142
  output: 'text|json',
@@ -65,14 +65,23 @@ function buildDingtalkOAuthLoginUrl(options = {}) {
65
65
  scope: 'openid corpid',
66
66
  lang: options.lang || 'zh_CN',
67
67
  });
68
+ if (options.forceLogin) {
69
+ params.set('FEForceLogin', 'true');
70
+ }
68
71
 
69
72
  return `${loginOrigin}/oauth2/auth?${params.toString()}`;
70
73
  }
71
74
 
75
+ // 海外 YiDA / DingTalk International 登录入口。
76
+ // 必须满足三个条件才能让国际版钉钉扫码识别:
77
+ // 1. login origin 为 login.dingtalk.io
78
+ // 2. redirect_uri 落在 www.yidaapps.com(否则登完跳回国内域名,海外后端拿不到 session)
79
+ // 3. 追加 FEForceLogin=true,强制走国际版登录流程
72
80
  const INTERNATIONAL_LOGIN_URL = buildDingtalkOAuthLoginUrl({
73
81
  loginOrigin: DINGTALK_INTL_LOGIN_ORIGIN,
74
82
  baseUrl: INTERNATIONAL_BASE_URL,
75
83
  lang: 'en_US',
84
+ forceLogin: true,
76
85
  });
77
86
  const LEGACY_INTERNATIONAL_LOGIN_URL = buildDingtalkOAuthLoginUrl({
78
87
  loginOrigin: DINGTALK_INTL_LOGIN_ORIGIN,
@@ -96,11 +105,11 @@ const DEFAULT_ALIBABA_INTERNAL_ENV = {
96
105
  cookieFile: 'cookies-alibaba.json',
97
106
  };
98
107
 
99
- /** 海外版 DingTalk / Wukong 环境配置 */
108
+ /** 海外版 YiDA / DingTalk International 环境配置 */
100
109
  const DEFAULT_INTERNATIONAL_ENV = {
101
110
  baseUrl: INTERNATIONAL_BASE_URL,
102
111
  loginUrl: INTERNATIONAL_LOGIN_URL,
103
- description: '海外版 YiDA Apps / DingTalk / Wukonglogin.dingtalk.io)',
112
+ description: '海外版 YiDA Apps / DingTalk Internationalwww.yidaapps.com)',
104
113
  cookieFile: 'cookies-intl.json',
105
114
  };
106
115
 
@@ -115,27 +124,51 @@ const ENV_ALIASES = {
115
124
  aliyun: 'public',
116
125
  domestic: 'public',
117
126
  china: 'public',
127
+ '国内': 'public',
128
+ '国内版': 'public',
129
+ '中国': 'public',
130
+ '中国版': 'public',
131
+ '国内宜搭': 'public',
132
+ '中国宜搭': 'public',
118
133
  overseas: 'intl',
119
134
  oversea: 'intl',
120
135
  international: 'intl',
121
136
  global: 'intl',
122
137
  abroad: 'intl',
123
138
  intl: 'intl',
139
+ '海外': 'intl',
140
+ '海外版': 'intl',
141
+ '国际': 'intl',
142
+ '国际版': 'intl',
143
+ '全球': 'intl',
144
+ '全球版': 'intl',
145
+ '海外宜搭': 'intl',
146
+ '海外yida': 'intl',
147
+ '国际宜搭': 'intl',
148
+ '全球宜搭': 'intl',
149
+ '日本': 'intl',
150
+ '日本宜搭': 'intl',
151
+ '日本yida': 'intl',
124
152
  alibaba: 'alibaba',
125
153
  internal: 'alibaba',
126
154
  intranet: 'alibaba',
155
+ '阿里': 'alibaba',
156
+ '阿里内网': 'alibaba',
157
+ '内网': 'alibaba',
127
158
  };
128
159
 
129
160
  const SHARED_COOKIE_DOMAINS = new Set([
130
161
  'aliwork.com',
131
162
  'yidaapps.com',
132
163
  'alibaba-inc.com',
164
+ 'yidaapps.com',
133
165
  ]);
134
166
 
135
167
  const KNOWN_YIDA_HOSTS = new Set([
136
168
  'www.aliwork.com',
137
169
  'www.yidaapps.com',
138
170
  'yida-group.alibaba-inc.com',
171
+ 'www.yidaapps.com',
139
172
  ]);
140
173
 
141
174
  function cloneBuiltinEnvironments() {
@@ -255,6 +288,7 @@ function inferLoginUrlForBaseUrl(baseUrl, fallbackLoginUrl) {
255
288
  loginOrigin: DINGTALK_INTL_LOGIN_ORIGIN,
256
289
  baseUrl: normalizedBaseUrl,
257
290
  lang: 'en_US',
291
+ forceLogin: true,
258
292
  });
259
293
  }
260
294
  return fallbackLoginUrl || `${normalizedBaseUrl}/workPlatform`;
@@ -21,6 +21,7 @@ module.exports = {
21
21
  cmd_corp_efficiency: 'استعلام عن نظرة عامة على كفاءة المؤسسة وتقارير التفاصيل',
22
22
  cmd_create_app: 'إنشاء تطبيق Yida',
23
23
  cmd_update_app: 'تحديث معلومات التطبيق',
24
+ cmd_app_permission: 'إدارة مسؤولي التطبيق الأساسيين والبيانات والتطوير',
24
25
  cmd_export: 'تصدير التطبيق (حزمة الترحيل)',
25
26
  cmd_import: 'استيراد حزمة الترحيل، إعادة بناء التطبيق',
26
27
  group_form: 'النماذج & الصفحات',
@@ -21,6 +21,7 @@ module.exports = {
21
21
  cmd_corp_efficiency: 'Unternehmenseffizienz-Übersicht und Detailberichte abrufen',
22
22
  cmd_create_app: 'Yida-App erstellen',
23
23
  cmd_update_app: 'App-Informationen aktualisieren',
24
+ cmd_app_permission: 'App-Haupt-, Daten- und Entwicklungsadmins verwalten',
24
25
  cmd_export: 'App exportieren (Migrationspaket erstellen)',
25
26
  cmd_import: 'Migrationspaket importieren, App neu aufbauen',
26
27
  group_form: 'Formulare & Seiten',
@@ -23,6 +23,7 @@ module.exports = {
23
23
  cmd_corp_efficiency: 'Query enterprise efficiency overview and detail reports',
24
24
  cmd_create_app: 'Create a Yida app',
25
25
  cmd_update_app: 'Update app info',
26
+ cmd_app_permission: 'Manage app primary, data, and developer admins',
26
27
  cmd_export: 'Export app (generate migration package)',
27
28
  cmd_import: 'Import migration package, rebuild app',
28
29
  group_form: 'Forms & Pages',