openyida 2026.5.9 → 2026.5.12-beta.1

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
@@ -296,6 +296,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
296
296
  | `openyida task-center <type> [options]` | Query todo, created, processed, CC, or proxy-submitted tasks |
297
297
  | `openyida get-permission <appType> <formUuid>` | Query form permission configuration |
298
298
  | `openyida save-permission <appType> <formUuid> [options]` | Save form permission configuration |
299
+ | `openyida corp-manager <sub-command>` | Manage platform admins, sub-admins, app admins, and address book visibility |
299
300
  | `openyida verify-short-url <appType> <formUuid> <url>` | Verify a short URL |
300
301
  | `openyida save-share-config <appType> <formUuid> <url> <isOpen> [openAuth]` | Save public access or sharing configuration |
301
302
  | `openyida get-page-config <appType> <formUuid>` | Query public access or sharing configuration |
package/bin/yida.js CHANGED
@@ -827,6 +827,12 @@ async function main() {
827
827
  break;
828
828
  }
829
829
 
830
+ case 'corp-manager': {
831
+ const { run: runCorpManager } = require('../lib/corp-manager/corp-manager');
832
+ await runCorpManager(args);
833
+ break;
834
+ }
835
+
830
836
  case 'flash-to-prd': {
831
837
  const { run: runFlashToPrd } = require('../lib/flash-note/flash-to-prd');
832
838
  await runFlashToPrd(args);
@@ -72,6 +72,7 @@ const COMMAND_GROUPS = [
72
72
  command('task-center', ['task-center'], 'task-center <type> [options]', 'help.cmd_task_center'),
73
73
  command('get-permission', ['get-permission'], 'get-permission <appType> <formUuid>', 'help.cmd_get_permission'),
74
74
  command('save-permission', ['save-permission'], 'save-permission <appType> <formUuid> ...', 'help.cmd_save_permission'),
75
+ command('corp-manager', ['corp-manager'], 'corp-manager <search-user|list|add|remove|address-book> ...', 'help.cmd_corp_manager', { output: 'json' }),
75
76
  ],
76
77
  },
77
78
  {
@@ -38,6 +38,7 @@ module.exports = {
38
38
  cmd_task_center: 'مركز المهام العالمي (معلق/معالج/نسخة إلخ)',
39
39
  cmd_get_permission: 'استعلام إعدادات أذونات النموذج',
40
40
  cmd_save_permission: 'حفظ إعدادات أذونات النموذج',
41
+ cmd_corp_manager: 'إدارة أذونات منصة المؤسسة',
41
42
  group_process: 'العمليات',
42
43
  cmd_configure_process: 'تكوين ونشر قواعد العملية',
43
44
  cmd_create_process: 'إنشاء نموذج عملية (متكامل)',
@@ -38,6 +38,7 @@ module.exports = {
38
38
  cmd_task_center: 'Globales Aufgabenzentrum (Aufgaben/Bearbeitet/CC etc.)',
39
39
  cmd_get_permission: 'Formularberechtigungen abfragen',
40
40
  cmd_save_permission: 'Formularberechtigungen speichern',
41
+ cmd_corp_manager: 'Plattformberechtigungen verwalten',
41
42
  group_process: 'Prozesse',
42
43
  cmd_configure_process: 'Prozessregeln konfigurieren & veröffentlichen',
43
44
  cmd_create_process: 'Prozessformular erstellen (All-in-One)',
@@ -41,6 +41,7 @@ module.exports = {
41
41
  cmd_task_center: 'Global task center (todo/processed/cc etc.)',
42
42
  cmd_get_permission: 'Query form permission config',
43
43
  cmd_save_permission: 'Save form permission config',
44
+ cmd_corp_manager: 'Manage platform admins and address book permissions',
44
45
  group_process: 'Process',
45
46
  cmd_configure_process: 'Configure and publish process rules',
46
47
  cmd_create_process: 'Create process form (all-in-one)',
@@ -38,6 +38,7 @@ module.exports = {
38
38
  cmd_task_center: 'Centro de tareas global (pendiente/procesado/CC etc.)',
39
39
  cmd_get_permission: 'Consultar configuración de permisos',
40
40
  cmd_save_permission: 'Guardar configuración de permisos',
41
+ cmd_corp_manager: 'Gestionar permisos de plataforma',
41
42
  group_process: 'Procesos',
42
43
  cmd_configure_process: 'Configurar y publicar reglas de proceso',
43
44
  cmd_create_process: 'Crear formulario de proceso (todo en uno)',
@@ -38,6 +38,7 @@ module.exports = {
38
38
  cmd_task_center: 'Centre de tâches global (à faire/traité/CC etc.)',
39
39
  cmd_get_permission: 'Consulter la configuration des permissions',
40
40
  cmd_save_permission: 'Enregistrer la configuration des permissions',
41
+ cmd_corp_manager: 'Gérer les permissions de plateforme',
41
42
  group_process: 'Processus',
42
43
  cmd_configure_process: 'Configurer et publier les règles de processus',
43
44
  cmd_create_process: 'Créer un formulaire de processus (tout-en-un)',
@@ -38,6 +38,7 @@ module.exports = {
38
38
  cmd_task_center: 'वैश्विक कार्य केंद्र (लंबित/संसाधित/CC आदि)',
39
39
  cmd_get_permission: 'फॉर्म अनुमति कॉन्फ़िगरेशन पूछें',
40
40
  cmd_save_permission: 'फॉर्म अनुमति कॉन्फ़िगरेशन सहेजें',
41
+ cmd_corp_manager: 'प्लेटफ़ॉर्म अनुमतियां प्रबंधित करें',
41
42
  group_process: 'प्रक्रिया',
42
43
  cmd_configure_process: 'प्रक्रिया नियम कॉन्फ़िगर और प्रकाशित करें',
43
44
  cmd_create_process: 'प्रक्रिया फॉर्म बनाएं (एकीकृत)',
@@ -40,6 +40,7 @@ module.exports = {
40
40
  cmd_task_center: 'グローバルタスクセンター(未処理/処理済/CC等)',
41
41
  cmd_get_permission: 'フォーム権限設定を照会',
42
42
  cmd_save_permission: 'フォーム権限設定を保存',
43
+ cmd_corp_manager: 'プラットフォーム権限を管理',
43
44
  group_process: 'プロセス',
44
45
  cmd_configure_process: 'プロセスルールを設定&公開',
45
46
  cmd_create_process: 'プロセスフォームを作成(一体型)',
@@ -38,6 +38,7 @@ module.exports = {
38
38
  cmd_task_center: '글로벌 작업 센터 (할일/처리됨/참조 등)',
39
39
  cmd_get_permission: '양식 권한 설정 조회',
40
40
  cmd_save_permission: '양식 권한 설정 저장',
41
+ cmd_corp_manager: '플랫폼 권한 관리',
41
42
  group_process: '프로세스',
42
43
  cmd_configure_process: '프로세스 규칙 설정 및 게시',
43
44
  cmd_create_process: '프로세스 양식 생성 (통합형)',
@@ -38,6 +38,7 @@ module.exports = {
38
38
  cmd_task_center: 'Centro de tarefas global (pendente/processado/CC etc.)',
39
39
  cmd_get_permission: 'Consultar configuração de permissões',
40
40
  cmd_save_permission: 'Salvar configuração de permissões',
41
+ cmd_corp_manager: 'Gerenciar permissões da plataforma',
41
42
  group_process: 'Processos',
42
43
  cmd_configure_process: 'Configurar e publicar regras de processo',
43
44
  cmd_create_process: 'Criar formulário de processo (tudo-em-um)',
@@ -38,6 +38,7 @@ module.exports = {
38
38
  cmd_task_center: 'Trung tâm tác vụ toàn cầu (cần làm/đã xử lý/CC v.v.)',
39
39
  cmd_get_permission: 'Truy vấn cấu hình quyền biểu mẫu',
40
40
  cmd_save_permission: 'Lưu cấu hình quyền biểu mẫu',
41
+ cmd_corp_manager: 'Quản lý quyền nền tảng',
41
42
  group_process: 'Quy trình',
42
43
  cmd_configure_process: 'Cấu hình và xuất bản quy tắc quy trình',
43
44
  cmd_create_process: 'Tạo biểu mẫu quy trình (tích hợp)',
@@ -40,6 +40,7 @@ module.exports = {
40
40
  cmd_task_center: '全域任務中心(待辦/已處理/抄送等)',
41
41
  cmd_get_permission: '查詢表單權限設定',
42
42
  cmd_save_permission: '儲存表單權限設定',
43
+ cmd_corp_manager: '管理平台管理員與通訊錄權限',
43
44
  group_process: '流程',
44
45
  cmd_configure_process: '設定並發布流程規則',
45
46
  cmd_create_process: '建立流程表單(一體化)',
@@ -41,6 +41,7 @@ module.exports = {
41
41
  cmd_task_center: '全局任务中心(待办/已处理/抄送等)',
42
42
  cmd_get_permission: '查询表单权限配置',
43
43
  cmd_save_permission: '保存表单权限配置',
44
+ cmd_corp_manager: '管理平台管理员与通讯录权限',
44
45
  group_process: '流程',
45
46
  cmd_configure_process: '配置并发布流程规则',
46
47
  cmd_create_process: '创建流程表单(一体化)',
@@ -0,0 +1,311 @@
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
+
14
+ const ROLE_ALIASES = {
15
+ app: 'applicationCreateRole',
16
+ application: 'applicationCreateRole',
17
+ applicationCreateRole: 'applicationCreateRole',
18
+ platform: 'corpAdminRole',
19
+ main: 'corpAdminRole',
20
+ corp: 'corpAdminRole',
21
+ corpAdminRole: 'corpAdminRole',
22
+ sub: 'subCorpAdminRole',
23
+ subPlatform: 'subCorpAdminRole',
24
+ subCorpAdminRole: 'subCorpAdminRole',
25
+ };
26
+
27
+ const ROLE_LABELS = {
28
+ applicationCreateRole: '应用管理员',
29
+ corpAdminRole: '平台管理员',
30
+ subCorpAdminRole: '平台子管理员',
31
+ };
32
+
33
+ const SCENE_LABELS = {
34
+ appManage: '应用管理',
35
+ bulletinBoard: '公告栏定制',
36
+ };
37
+
38
+ function getAuthRef() {
39
+ let cookieData = loadCookieData();
40
+ if (!cookieData || !cookieData.cookies || !cookieData.csrf_token) {
41
+ cookieData = triggerLogin();
42
+ }
43
+
44
+ if (!cookieData || !cookieData.cookies || !cookieData.csrf_token) {
45
+ throw new Error('无法获取有效登录态或 CSRF Token');
46
+ }
47
+
48
+ return {
49
+ csrfToken: cookieData.csrf_token,
50
+ cookies: cookieData.cookies,
51
+ baseUrl: resolveBaseUrl(cookieData),
52
+ cookieData,
53
+ };
54
+ }
55
+
56
+ function normalizeRole(role) {
57
+ const normalized = ROLE_ALIASES[role];
58
+ if (!normalized) {
59
+ throw new Error(`无效角色:${role},可用值:app, platform, sub`);
60
+ }
61
+ return normalized;
62
+ }
63
+
64
+ function normalizeText(value) {
65
+ if (value === null || value === undefined) {
66
+ return '';
67
+ }
68
+ if (typeof value === 'string') {
69
+ return value;
70
+ }
71
+ if (typeof value === 'object') {
72
+ return value.zh_CN || value.pureEn_US || value.en_US || value.value || value.text || value.label || '';
73
+ }
74
+ return String(value);
75
+ }
76
+
77
+ function buildCommonParams(authRef, params = {}) {
78
+ return {
79
+ _csrf_token: authRef.csrfToken,
80
+ _locale_time_zone_offset: '28800000',
81
+ _stamp: String(Date.now()),
82
+ ...params,
83
+ };
84
+ }
85
+
86
+ async function corpGet(path, params, authRef = getAuthRef()) {
87
+ return requestWithAutoLogin(
88
+ (auth) => httpGet(auth.baseUrl, path, buildCommonParams(auth, params), auth.cookies),
89
+ authRef,
90
+ );
91
+ }
92
+
93
+ async function corpPost(path, params, authRef = getAuthRef()) {
94
+ return requestWithAutoLogin(
95
+ (auth) => httpPost(auth.baseUrl, path, querystring.stringify(buildCommonParams(auth, params)), auth.cookies),
96
+ authRef,
97
+ );
98
+ }
99
+
100
+ function assertSuccess(result, action) {
101
+ if (result && result.success) {
102
+ return result;
103
+ }
104
+ const message = result && (result.errorMsg || result.message || result.errorCode);
105
+ throw new Error(`${action}失败${message ? `:${message}` : ''}`);
106
+ }
107
+
108
+ function normalizeAdmin(record) {
109
+ return {
110
+ userId: record.userId || record.adminWorkNo || '',
111
+ userName: normalizeText(record.userName),
112
+ businessWorkNo: record.businessWorkNo || '',
113
+ departmentId: record.departmentId || '',
114
+ departmentNamePath: normalizeText(record.departmentNamePath),
115
+ roleType: record.roleType || '',
116
+ roleLabel: ROLE_LABELS[record.roleType] || record.roleType || '',
117
+ mainAdmin: !!record.mainAdmin,
118
+ orgAdmin: !!record.orgAdmin,
119
+ manageDeptIds: record.manageDeptIds || [],
120
+ manageDeptNames: record.manageDeptNames || [],
121
+ manageScene: record.manageScene || [],
122
+ manageSceneLabels: (record.manageScene || []).map(scene => SCENE_LABELS[scene] || scene),
123
+ };
124
+ }
125
+
126
+ function normalizeUser(record) {
127
+ const depts = Array.isArray(record.depts) ? record.depts : [];
128
+ return {
129
+ userId: record.id || record.emplId || record.userId || record.workNo || '',
130
+ userName: normalizeText(record.name || record.text || record.userName || record.displayName),
131
+ departmentNamePath: normalizeText(record.deptDesc || record.deptFullPath || record.departmentNamePath),
132
+ departmentIds: depts.map(dept => String(dept.id || '')).filter(Boolean),
133
+ departments: depts.map(dept => ({
134
+ id: String(dept.id || ''),
135
+ name: normalizeText(dept.deptName || dept.deptPathName || dept.name || dept.text),
136
+ path: normalizeText(dept.deptPathName || dept.deptName || dept.name || dept.text),
137
+ })),
138
+ avatar: record.personalPhoto || record.avatar || '',
139
+ sourceIdentifier: record.sourceIdentifier || '',
140
+ };
141
+ }
142
+
143
+ function parseAdminList(result) {
144
+ const content = (result && result.content) || {};
145
+ return {
146
+ success: true,
147
+ currentPage: Number(content.currentPage || 1),
148
+ pageSize: Number(content.limit || content.pageSize || 0),
149
+ totalCount: Number(content.totalCount || 0),
150
+ admins: (content.values || []).map(normalizeAdmin),
151
+ };
152
+ }
153
+
154
+ async function listAdmins(options = {}, authRef = getAuthRef()) {
155
+ const roleType = normalizeRole(options.role || options.roleType || 'app');
156
+ const params = {
157
+ currentPage: String(options.page || 1),
158
+ pageIndex: String(options.page || 1),
159
+ pageSize: String(options.size || 20),
160
+ roleType,
161
+ };
162
+
163
+ if (options.userId) {
164
+ params.adminWorkNos = options.userId;
165
+ }
166
+
167
+ const result = await corpPost('/query/corpadmin/listCorpAppOrSubAdmins.json', params, authRef);
168
+ assertSuccess(result, '查询管理员列表');
169
+ return parseAdminList(result);
170
+ }
171
+
172
+ async function searchUsers(options = {}, authRef = getAuthRef()) {
173
+ const keyword = options.keyword || options.key || '';
174
+ if (!keyword) {
175
+ throw new Error('缺少搜索关键词');
176
+ }
177
+
178
+ const result = await corpGet('/query/userservice/searchUsersOrDepts.json', {
179
+ key: keyword,
180
+ start: '0',
181
+ size: String(options.size || 50),
182
+ option: 'employee',
183
+ }, authRef);
184
+ assertSuccess(result, '搜索人员');
185
+
186
+ const content = result.content || {};
187
+ const values = content.values || content.data || [];
188
+ const users = values
189
+ .filter(item => !item.dept)
190
+ .map(normalizeUser);
191
+
192
+ const deptFilter = options.dept || options.department;
193
+ const filteredUsers = deptFilter
194
+ ? users.filter(user => user.departmentNamePath.includes(deptFilter))
195
+ : users;
196
+
197
+ return {
198
+ success: true,
199
+ totalCount: filteredUsers.length,
200
+ users: filteredUsers,
201
+ };
202
+ }
203
+
204
+ function buildSubAdminConfig(options = {}) {
205
+ const deptIds = options.deptIds || options.departmentIds || [];
206
+ if (!Array.isArray(deptIds) || deptIds.length === 0) {
207
+ throw new Error('添加或更新平台子管理员时必须提供 --dept-ids');
208
+ }
209
+
210
+ const scenes = options.scenes || ['appManage', 'bulletinBoard'];
211
+ return JSON.stringify({
212
+ deptList: deptIds.map(id => String(id)),
213
+ scene: scenes.map(scene => String(scene)).filter(Boolean),
214
+ });
215
+ }
216
+
217
+ async function saveAdmin(options = {}, authRef = getAuthRef()) {
218
+ const roleType = normalizeRole(options.role || options.roleType || 'app');
219
+ const userId = options.userId || options.user || options.adminWorkNos;
220
+ if (!userId) {
221
+ throw new Error('缺少成员 userId');
222
+ }
223
+
224
+ const params = {
225
+ adminWorkNos: userId,
226
+ roleType,
227
+ };
228
+
229
+ if (roleType === 'subCorpAdminRole') {
230
+ params.config = buildSubAdminConfig(options);
231
+ }
232
+
233
+ const result = await corpPost('/query/corpadmin/saveAppOrSubAdmins.json', params, authRef);
234
+ assertSuccess(result, '保存管理员');
235
+ return {
236
+ success: true,
237
+ roleType,
238
+ roleLabel: ROLE_LABELS[roleType],
239
+ userId,
240
+ content: result.content || {},
241
+ };
242
+ }
243
+
244
+ async function removeAdmin(options = {}, authRef = getAuthRef()) {
245
+ const roleType = normalizeRole(options.role || options.roleType || 'app');
246
+ const userId = options.userId || options.user || options.adminWorkNos;
247
+ if (!userId) {
248
+ throw new Error('缺少成员 userId');
249
+ }
250
+
251
+ const result = await corpPost('/query/corpadmin/batchDeleteAdmins.json', {
252
+ adminWorkNos: userId,
253
+ roleType,
254
+ }, authRef);
255
+ assertSuccess(result, '移除管理员');
256
+ return {
257
+ success: true,
258
+ roleType,
259
+ roleLabel: ROLE_LABELS[roleType],
260
+ userId,
261
+ content: result.content || {},
262
+ };
263
+ }
264
+
265
+ async function getAddressBookVisible(authRef = getAuthRef()) {
266
+ const result = await corpGet('/query/corpadmin/getAddressBookVisible.json', {}, authRef);
267
+ assertSuccess(result, '查询通讯录权限');
268
+ const content = result.content || {};
269
+ return {
270
+ success: true,
271
+ isAllVisible: content.isAllVisible || 'n',
272
+ isAdminVisible: content.isAdminVisible || 'n',
273
+ };
274
+ }
275
+
276
+ async function saveAddressBookVisible(options = {}, authRef = getAuthRef()) {
277
+ const current = await getAddressBookVisible(authRef);
278
+ const isAllVisible = options.isAllVisible || options.allVisible || current.isAllVisible;
279
+ const isAdminVisible = options.isAdminVisible || options.adminVisible || current.isAdminVisible;
280
+
281
+ const result = await corpPost('/query/corpadmin/saveAddressBoolVisible.json', {
282
+ isAllVisible,
283
+ isAdminVisible,
284
+ }, authRef);
285
+ assertSuccess(result, '保存通讯录权限');
286
+
287
+ return {
288
+ success: true,
289
+ isAllVisible,
290
+ isAdminVisible,
291
+ content: result.content || {},
292
+ };
293
+ }
294
+
295
+ module.exports = {
296
+ ROLE_ALIASES,
297
+ ROLE_LABELS,
298
+ SCENE_LABELS,
299
+ getAuthRef,
300
+ normalizeRole,
301
+ normalizeText,
302
+ normalizeAdmin,
303
+ normalizeUser,
304
+ buildSubAdminConfig,
305
+ listAdmins,
306
+ searchUsers,
307
+ saveAdmin,
308
+ removeAdmin,
309
+ getAddressBookVisible,
310
+ saveAddressBookVisible,
311
+ };
@@ -0,0 +1,210 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ buildSubAdminConfig,
5
+ listAdmins,
6
+ searchUsers,
7
+ saveAdmin,
8
+ removeAdmin,
9
+ getAddressBookVisible,
10
+ saveAddressBookVisible,
11
+ } = require('./api');
12
+
13
+ const USAGE = `openyida corp-manager - 平台权限管理
14
+
15
+ Usage:
16
+ openyida corp-manager search-user <keyword> [--dept <text>] [--size N]
17
+ openyida corp-manager list <app|platform|sub> [--user <userId>] [--page N] [--size N]
18
+ openyida corp-manager add <app|platform|sub> --user <userId> [--dept-ids <id1,id2>] [--scenes appManage,bulletinBoard]
19
+ openyida corp-manager remove <app|platform|sub> --user <userId>
20
+ openyida corp-manager address-book get
21
+ openyida corp-manager address-book set [--all-visible y|n] [--admin-visible y|n]
22
+
23
+ Examples:
24
+ openyida corp-manager search-user "余浩" --dept "宜搭,钉钉官方同学"
25
+ openyida corp-manager add sub --user 014734242419657712 --dept-ids 848712658 --scenes appManage,bulletinBoard
26
+ openyida corp-manager remove sub --user 014734242419657712
27
+ `;
28
+
29
+ function fail(message) {
30
+ console.error(message);
31
+ console.error(USAGE);
32
+ process.exit(1);
33
+ }
34
+
35
+ function parseCliOptions(tokens) {
36
+ const positionals = [];
37
+ const options = {};
38
+
39
+ for (let i = 0; i < tokens.length; i += 1) {
40
+ const token = tokens[i];
41
+ if (token.startsWith('--')) {
42
+ const key = token.slice(2).replace(/-/g, '_');
43
+ const next = tokens[i + 1];
44
+ if (next && !next.startsWith('--')) {
45
+ options[key] = next;
46
+ i += 1;
47
+ } else {
48
+ options[key] = true;
49
+ }
50
+ } else {
51
+ positionals.push(token);
52
+ }
53
+ }
54
+
55
+ return { positionals, options };
56
+ }
57
+
58
+ function splitList(value) {
59
+ if (!value) {
60
+ return [];
61
+ }
62
+ if (Array.isArray(value)) {
63
+ return value;
64
+ }
65
+ return String(value).split(',').map(item => item.trim()).filter(Boolean);
66
+ }
67
+
68
+ function toPositiveInt(value, defaultValue) {
69
+ const parsed = Number.parseInt(value || `${defaultValue}`, 10);
70
+ if (!Number.isFinite(parsed) || parsed <= 0) {
71
+ return defaultValue;
72
+ }
73
+ return parsed;
74
+ }
75
+
76
+ function normalizeVisible(value, flagName) {
77
+ if (value === undefined || value === true) {
78
+ return undefined;
79
+ }
80
+ const normalized = String(value).trim().toLowerCase();
81
+ if (['y', 'yes', 'true', '1', 'on'].includes(normalized)) {
82
+ return 'y';
83
+ }
84
+ if (['n', 'no', 'false', '0', 'off'].includes(normalized)) {
85
+ return 'n';
86
+ }
87
+ throw new Error(`${flagName} 只支持 y/n`);
88
+ }
89
+
90
+ function printJson(payload) {
91
+ console.log(JSON.stringify(payload, null, 2));
92
+ }
93
+
94
+ async function runSearchUser(positionals, options) {
95
+ const keyword = positionals[0];
96
+ if (!keyword) {
97
+ fail('缺少搜索关键词');
98
+ }
99
+
100
+ const result = await searchUsers({
101
+ keyword,
102
+ dept: options.dept || options.department,
103
+ size: toPositiveInt(options.size, 50),
104
+ });
105
+ printJson(result);
106
+ }
107
+
108
+ async function runList(positionals, options) {
109
+ const role = positionals[0];
110
+ if (!role) {
111
+ fail('缺少角色:app、platform 或 sub');
112
+ }
113
+
114
+ const result = await listAdmins({
115
+ role,
116
+ userId: options.user || options.user_id,
117
+ page: toPositiveInt(options.page, 1),
118
+ size: toPositiveInt(options.size, 20),
119
+ });
120
+ printJson(result);
121
+ }
122
+
123
+ async function runAdd(positionals, options) {
124
+ const role = positionals[0];
125
+ const userId = options.user || options.user_id;
126
+ if (!role) {
127
+ fail('缺少角色:app、platform 或 sub');
128
+ }
129
+ if (!userId) {
130
+ fail('缺少 --user <userId>');
131
+ }
132
+
133
+ const result = await saveAdmin({
134
+ role,
135
+ userId,
136
+ deptIds: splitList(options.dept_ids || options.department_ids),
137
+ scenes: splitList(options.scenes || 'appManage,bulletinBoard'),
138
+ });
139
+ printJson(result);
140
+ }
141
+
142
+ async function runRemove(positionals, options) {
143
+ const role = positionals[0];
144
+ const userId = options.user || options.user_id;
145
+ if (!role) {
146
+ fail('缺少角色:app、platform 或 sub');
147
+ }
148
+ if (!userId) {
149
+ fail('缺少 --user <userId>');
150
+ }
151
+
152
+ const result = await removeAdmin({ role, userId });
153
+ printJson(result);
154
+ }
155
+
156
+ async function runAddressBook(positionals, options) {
157
+ const action = positionals[0];
158
+ if (action === 'get') {
159
+ printJson(await getAddressBookVisible());
160
+ return;
161
+ }
162
+
163
+ if (action === 'set') {
164
+ const allVisible = normalizeVisible(options.all_visible, '--all-visible');
165
+ const adminVisible = normalizeVisible(options.admin_visible, '--admin-visible');
166
+ if (allVisible === undefined && adminVisible === undefined) {
167
+ fail('address-book set 至少需要 --all-visible 或 --admin-visible');
168
+ }
169
+ printJson(await saveAddressBookVisible({
170
+ allVisible,
171
+ adminVisible,
172
+ }));
173
+ return;
174
+ }
175
+
176
+ fail('address-book 子命令只支持 get 或 set');
177
+ }
178
+
179
+ async function run(args) {
180
+ const { positionals, options } = parseCliOptions(args);
181
+ const action = positionals.shift();
182
+
183
+ if (!action || action === '--help' || action === '-h') {
184
+ console.log(USAGE);
185
+ return;
186
+ }
187
+
188
+ if (action === 'search-user') {
189
+ await runSearchUser(positionals, options);
190
+ } else if (action === 'list') {
191
+ await runList(positionals, options);
192
+ } else if (action === 'add') {
193
+ await runAdd(positionals, options);
194
+ } else if (action === 'remove') {
195
+ await runRemove(positionals, options);
196
+ } else if (action === 'address-book') {
197
+ await runAddressBook(positionals, options);
198
+ } else {
199
+ fail(`未知 corp-manager 子命令:${action}`);
200
+ }
201
+ }
202
+
203
+ module.exports = {
204
+ USAGE,
205
+ parseCliOptions,
206
+ splitList,
207
+ normalizeVisible,
208
+ buildSubAdminConfig,
209
+ run,
210
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openyida",
3
- "version": "2026.5.9",
3
+ "version": "2026.5.12-beta.1",
4
4
  "description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
5
5
  "bin": {
6
6
  "openyida": "bin/yida.js",
@@ -13,6 +13,7 @@ const SKILL_COVERAGE = {
13
13
  'yida-app': { level: 'real-e2e', stages: ['app', 'form', 'page', 'data', 'report', 'dashboard'] },
14
14
  'yida-chart': { level: 'real-e2e', stages: ['report', 'dashboard'], tests: ['report chart config generation'] },
15
15
  'yida-connector': { level: 'offline', stages: ['connector-local'], commands: ['connector gen-template', 'connector parse-api'] },
16
+ 'yida-corp-manager': { level: 'offline-unit', tests: ['tests/corp-manager.test.js'], reason: 'enterprise admin mutations are not safe for shared real org E2E' },
16
17
  'yida-create-app': { level: 'real-e2e', stages: ['app'], commands: ['create-app'] },
17
18
  'yida-create-form-page': { level: 'real-e2e', stages: ['form'], commands: ['create-form create', 'create-form update', 'create-form add-option'] },
18
19
  'yida-create-page': { level: 'real-e2e', stages: ['page', 'dashboard'], commands: ['create-page --mode dashboard'] },
@@ -171,6 +171,7 @@ openyida copy
171
171
  | `yida-publish-page` | `skills/yida-publish-page/SKILL.md` | 编译并发布自定义页面 | `openyida publish <源文件路径> <appType> <formUuid> [--health-check]` |
172
172
  | `yida-page-config` | `skills/yida-page-config/SKILL.md` | 页面公开访问/组织内分享配置 | `openyida verify-short-url <appType> <formUuid> <url>` |
173
173
  | `yida-form-permission` | `skills/yida-form-permission/SKILL.md` | 表单权限查询与保存 | `openyida get-permission <appType> <formUuid>` |
174
+ | `yida-corp-manager` | `skills/yida-corp-manager/SKILL.md` | 平台管理员、应用管理员、子管理员与通讯录权限 | `openyida corp-manager <子命令>` |
174
175
  | `yida-form-detail` | `skills/yida-form-detail/SKILL.md` | 表单详情页 formDetail 样式优化 | 详见 SKILL.md |
175
176
  | `yida-data-management` | `skills/yida-data-management/SKILL.md` | 表单/流程/任务数据查询与变更 | `openyida data query form <appType> <formUuid>` |
176
177
  | `yida-table-form` | `skills/yida-table-form/SKILL.md` | 表格形态批量录入页面 | 详见 SKILL.md |
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: yida-corp-manager
3
+ description: 宜搭平台权限管理。查询和维护应用管理员、平台管理员、平台子管理员,以及通讯录可见性开关。适用于用户提到平台权限管理、corpManager、应用管理员、平台管理员、子管理员、通讯录权限。不适用于表单权限组(应使用 yida-form-permission)。
4
+ ---
5
+
6
+ # 平台权限管理
7
+
8
+ ## 严格要求 (MUST DO)
9
+
10
+ - 修改前先查询当前状态:人员先 `search-user`,角色先 `list`,通讯录开关先 `address-book get`。
11
+ - 增删改管理员会影响真实组织权限,执行前向用户确认目标人员、角色、部门范围和管理场景。
12
+ - 同名人员必须用 `search-user` 的 `departmentNamePath` 区分,不得只凭姓名操作。
13
+ - 平台子管理员必须指定 `--dept-ids`,场景默认 `appManage,bulletinBoard`。
14
+
15
+ ## 不适用场景
16
+
17
+ - 表单权限组、数据权限、操作权限:使用 `yida-form-permission`。
18
+ - 流程节点字段权限:使用 `yida-process-rule`。
19
+ - 页面公开访问或组织内分享:使用 `yida-page-config`。
20
+
21
+ ## 常用命令
22
+
23
+ 搜索人员,确认 userId:
24
+
25
+ ```bash
26
+ openyida corp-manager search-user "余浩" --dept "宜搭,钉钉官方同学"
27
+ ```
28
+
29
+ 查询管理员:
30
+
31
+ ```bash
32
+ openyida corp-manager list app --user <userId>
33
+ openyida corp-manager list platform --user <userId>
34
+ openyida corp-manager list sub --user <userId>
35
+ ```
36
+
37
+ 添加或更新管理员:
38
+
39
+ ```bash
40
+ openyida corp-manager add app --user <userId>
41
+ openyida corp-manager add platform --user <userId>
42
+ openyida corp-manager add sub --user <userId> --dept-ids 848712658 --scenes appManage,bulletinBoard
43
+ ```
44
+
45
+ 移除管理员:
46
+
47
+ ```bash
48
+ openyida corp-manager remove app --user <userId>
49
+ openyida corp-manager remove platform --user <userId>
50
+ openyida corp-manager remove sub --user <userId>
51
+ ```
52
+
53
+ 通讯录权限:
54
+
55
+ ```bash
56
+ openyida corp-manager address-book get
57
+ openyida corp-manager address-book set --all-visible n --admin-visible y
58
+ ```
59
+
60
+ ## 角色映射
61
+
62
+ | CLI 角色 | 页面含义 | 接口 roleType |
63
+ |---------|----------|---------------|
64
+ | `app` | 应用管理员 | `applicationCreateRole` |
65
+ | `platform` | 平台管理员 | `corpAdminRole` |
66
+ | `sub` | 平台子管理员 | `subCorpAdminRole` |
67
+
68
+ ## 子管理员参数
69
+
70
+ `--dept-ids` 传部门 ID,多个用逗号分隔。可先通过人员搜索结果里的 `departmentIds` 获取常用部门 ID。`--scenes` 支持:
71
+
72
+ | scene | 含义 |
73
+ |-------|------|
74
+ | `appManage` | 应用管理 |
75
+ | `bulletinBoard` | 公告栏定制 |
76
+
77
+ ## 安全检查清单
78
+
79
+ 1. `search-user` 确认目标人员 userId 和部门路径。
80
+ 2. `list <role> --user <userId>` 确认当前角色状态。
81
+ 3. 展示将要执行的 add/remove/address-book set 命令。
82
+ 4. 用户确认后执行。
83
+ 5. 再次 `list` 或 `address-book get` 验证结果。