yidaconnector 2026.6.11

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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +383 -0
  3. package/bin/yida.js +670 -0
  4. package/lib/app/form-navigation.js +58 -0
  5. package/lib/app/get-schema.js +538 -0
  6. package/lib/auth/auth.js +294 -0
  7. package/lib/auth/cdp-browser-login.js +390 -0
  8. package/lib/auth/codex-login.js +71 -0
  9. package/lib/auth/login.js +475 -0
  10. package/lib/auth/org.js +363 -0
  11. package/lib/auth/qr-login.js +1563 -0
  12. package/lib/core/chalk.js +384 -0
  13. package/lib/core/check-update.js +82 -0
  14. package/lib/core/cli-error.js +39 -0
  15. package/lib/core/command-manifest.js +106 -0
  16. package/lib/core/env-cmd.js +545 -0
  17. package/lib/core/env-manager.js +601 -0
  18. package/lib/core/env.js +287 -0
  19. package/lib/core/i18n.js +177 -0
  20. package/lib/core/locales/ar.js +805 -0
  21. package/lib/core/locales/de.js +805 -0
  22. package/lib/core/locales/en.js +1623 -0
  23. package/lib/core/locales/es.js +805 -0
  24. package/lib/core/locales/fr.js +805 -0
  25. package/lib/core/locales/hi.js +805 -0
  26. package/lib/core/locales/ja.js +1197 -0
  27. package/lib/core/locales/ko.js +807 -0
  28. package/lib/core/locales/pt.js +805 -0
  29. package/lib/core/locales/vi.js +805 -0
  30. package/lib/core/locales/zh-HK.js +1233 -0
  31. package/lib/core/locales/zh.js +1584 -0
  32. package/lib/core/query-data.js +781 -0
  33. package/lib/core/redact.js +100 -0
  34. package/lib/core/utils.js +799 -0
  35. package/lib/core/yida-client.js +117 -0
  36. package/package.json +94 -0
  37. package/project/config.json +4 -0
  38. package/project/pages/src/demo-birthday-game.oyd.jsx +832 -0
  39. package/project/pages/src/demo-chip-insight.oyd.jsx +983 -0
  40. package/project/pages/src/demo-compat-smoke.oyd.jsx +58 -0
  41. package/project/pages/src/demo-crm-batch-entry.oyd.jsx +805 -0
  42. package/project/pages/src/demo-crm-dashboard.oyd.jsx +677 -0
  43. package/project/pages/src/demo-future-vision-2026.oyd.jsx +1102 -0
  44. package/project/pages/src/demo-ppt.oyd.jsx +1192 -0
  45. package/project/pages/src/demo-salary-calculator.oyd.jsx +904 -0
  46. package/project/pages/src/yidaconnector-knowledge-doc.oyd.jsx +1714 -0
  47. package/project/prd/demo-birthday-game.md +39 -0
  48. package/project/prd/demo-crm.md +463 -0
  49. package/project/prd/demo-dingtalk-ai-solution-center.md +425 -0
  50. package/project/prd/demo-future-vision-2026.md +78 -0
  51. package/project/prd/demo-salary-calculator.md +101 -0
  52. package/scripts/build-skills-package.js +406 -0
  53. package/scripts/check-syntax.js +59 -0
  54. package/scripts/demo-dws.sh +106 -0
  55. package/scripts/e2e-real/cleanup.js +67 -0
  56. package/scripts/e2e-real/fixtures/form-fields.json +18 -0
  57. package/scripts/e2e-real/full-runner.js +1566 -0
  58. package/scripts/e2e-real/runner.js +293 -0
  59. package/scripts/e2e-real/skill-coverage.js +115 -0
  60. package/scripts/generate-command-docs.js +109 -0
  61. package/scripts/nightly-smoke.js +134 -0
  62. package/scripts/postinstall.js +545 -0
  63. package/scripts/solution-center-runner.js +368 -0
  64. package/scripts/validate-ci.sh +50 -0
  65. package/scripts/validate-command-manifest.js +119 -0
  66. package/scripts/validate-package-size.js +78 -0
  67. package/scripts/validate-skills.js +247 -0
  68. package/scripts/validate-structure.js +66 -0
  69. package/yida-skills/SKILL.md +163 -0
  70. package/yida-skills/references/yida-api.md +1309 -0
  71. package/yida-skills/skills/large-file-write/SKILL.md +91 -0
  72. package/yida-skills/skills/large-file-write/references/write-patterns.md +149 -0
  73. package/yida-skills/skills/large-file-write/scripts/write.js +157 -0
  74. package/yida-skills/skills/yida-data-management/SKILL.md +252 -0
  75. package/yida-skills/skills/yida-data-management/references/api-matrix.md +49 -0
  76. package/yida-skills/skills/yida-data-management/references/data-format-guide.md +159 -0
  77. package/yida-skills/skills/yida-data-management/references/verified-endpoints.md +62 -0
  78. package/yida-skills/skills/yida-login/SKILL.md +159 -0
  79. package/yida-skills/skills/yida-logout/SKILL.md +67 -0
@@ -0,0 +1,363 @@
1
+ /**
2
+ * org.js - 组织管理模块
3
+ *
4
+ * 提供组织切换能力,支持:
5
+ * - 列出用户可访问的组织
6
+ * - 切换组织(无需重新登录)
7
+ * - 交互式组织选择
8
+ *
9
+ * 切换组织原理:
10
+ * 通过一系列 HTTP 请求完成组织切换,无需重新登录。
11
+ * 流程:
12
+ * 1. GET /start.html?corpid={corpId}&switchCorp=true
13
+ * 2. GET /start.html?corpid={corpId}&
14
+ * 3. 跟随重定向获取新 Cookie
15
+ *
16
+ * 导出函数:
17
+ * listOrganizations() - 列出可访问的组织
18
+ * switchOrganization() - 切换到指定组织
19
+ * interactiveSwitch() - 交互式组织选择
20
+ */
21
+
22
+ 'use strict';
23
+
24
+ const https = require('https');
25
+ const http = require('http');
26
+ const { extractInfoFromCookies, resolveBaseUrl } = require('../core/utils');
27
+ const { saveCookieCache } = require('./login');
28
+ const { saveAuthConfig, loadAuthConfig } = require('./auth');
29
+ const { t } = require('../core/i18n');
30
+
31
+ // ── HTTP 请求工具 ─────────────────────────────────────
32
+
33
+ /**
34
+ * 发送 HTTP GET 请求并跟随重定向,提取响应中的 Cookie
35
+ * @param {string} url - 完整 URL
36
+ * @param {Array} cookies - 当前 Cookie 列表
37
+ * @param {boolean} _followRedirect - 是否跟随重定向(保留参数以保持 API 一致性)
38
+ * @returns {Promise<{statusCode: number, headers: object, cookies: Array}>}
39
+ */
40
+ function httpGetWithCookies(url, cookies, _followRedirect = true) {
41
+ return new Promise((resolve, reject) => {
42
+ const cookieHeader = cookies.map((c) => `${c.name}=${c.value}`).join('; ');
43
+ const parsedUrl = new URL(url);
44
+ const isHttps = parsedUrl.protocol === 'https:';
45
+ const requestModule = isHttps ? https : http;
46
+
47
+ const options = {
48
+ hostname: parsedUrl.hostname,
49
+ port: parsedUrl.port || (isHttps ? 443 : 80),
50
+ path: parsedUrl.pathname + parsedUrl.search,
51
+ method: 'GET',
52
+ headers: {
53
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
54
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
55
+ 'Cookie': cookieHeader,
56
+ },
57
+ timeout: 30000,
58
+ };
59
+
60
+ // 不自动跟随重定向,手动处理以提取 Cookie
61
+ const req = requestModule.request(options, (res) => {
62
+ let data = '';
63
+ res.on('data', (chunk) => { data += chunk; });
64
+ res.on('end', () => {
65
+ // 提取 Set-Cookie 中的新 Cookie
66
+ const setCookies = res.headers['set-cookie'] || [];
67
+ const newCookies = parseSetCookies(setCookies, cookies);
68
+
69
+ resolve({
70
+ statusCode: res.statusCode,
71
+ headers: res.headers,
72
+ cookies: newCookies,
73
+ location: res.headers.location,
74
+ body: data,
75
+ });
76
+ });
77
+ });
78
+
79
+ req.on('timeout', () => { req.destroy(); reject(new Error(t('common.request_timeout'))); });
80
+ req.on('error', reject);
81
+ req.end();
82
+ });
83
+ }
84
+
85
+ /**
86
+ * 解析 Set-Cookie 响应头,合并到现有 Cookie 中
87
+ * @param {Array<string>} setCookies - Set-Cookie 响应头数组
88
+ * @param {Array} existingCookies - 现有 Cookie 列表
89
+ * @returns {Array} 合并后的 Cookie 列表
90
+ */
91
+ function parseSetCookies(setCookies, existingCookies) {
92
+ const cookieMap = new Map();
93
+
94
+ // 先添加现有 Cookie
95
+ for (const cookie of existingCookies) {
96
+ cookieMap.set(cookie.name, cookie);
97
+ }
98
+
99
+ // 解析并更新 Cookie
100
+ for (const setCookie of setCookies) {
101
+ const parts = setCookie.split(';')[0].split('=');
102
+ if (parts.length >= 2) {
103
+ const name = parts[0].trim();
104
+ const value = parts.slice(1).join('=').trim();
105
+ cookieMap.set(name, { name, value });
106
+ }
107
+ }
108
+
109
+ return Array.from(cookieMap.values());
110
+ }
111
+
112
+ // ── 组织列表获取 ──────────────────────────────────────
113
+
114
+ /**
115
+ * 获取用户可访问的组织列表
116
+ * @param {object} cookieData - Cookie 数据
117
+ * @returns {Promise<Array>} 组织列表
118
+ */
119
+ async function listOrganizations(cookieData) {
120
+ const { c, banner, warn, sep } = require('../core/chalk');
121
+
122
+ banner(t('org.list_title'), { stderr: false });
123
+
124
+ const cookies = cookieData.cookies;
125
+
126
+ // 从 Cookie 中提取当前组织信息
127
+ const { corpId } = extractInfoFromCookies(cookies);
128
+
129
+ if (!corpId) {
130
+ warn(t('org.no_corp_id'), false);
131
+ console.log(` ${sep()}\n`);
132
+ return [];
133
+ }
134
+
135
+ // 尝试调用 API 获取组织列表
136
+ // 注意:宜搭没有直接的组织列表 API,我们从 Cookie 和 auth 配置中获取
137
+ const authConfig = loadAuthConfig();
138
+ const recentCorps = authConfig?.recentCorps || [];
139
+
140
+ // 构建组织列表
141
+ const organizations = [];
142
+
143
+ // 添加当前组织
144
+ organizations.push({
145
+ corpId,
146
+ name: t('org.current_org'),
147
+ isCurrent: true,
148
+ lastUsed: new Date().toISOString(),
149
+ });
150
+
151
+ // 添加历史组织
152
+ for (const org of recentCorps) {
153
+ if (org.corpId !== corpId) {
154
+ organizations.push({
155
+ corpId: org.corpId,
156
+ name: org.name || org.corpId,
157
+ isCurrent: false,
158
+ lastUsed: org.lastUsed,
159
+ });
160
+ }
161
+ }
162
+
163
+ // 显示组织列表
164
+ if (organizations.length === 0) {
165
+ warn(t('org.no_organizations'), false);
166
+ } else {
167
+ for (const org of organizations) {
168
+ const statusIcon = org.isCurrent ? `${c.green}✔${c.reset}` : `${c.dim}○${c.reset}`;
169
+ const current = org.isCurrent ? ` ${c.green}(${t('org.current')})${c.reset}` : '';
170
+ console.log(` ${statusIcon} ${org.name}${current}`);
171
+ console.log(` ${c.dim}corpId: ${org.corpId}${c.reset}`);
172
+ }
173
+ }
174
+
175
+ console.log(`\n ${sep()}\n`);
176
+ return organizations;
177
+ }
178
+
179
+ // ── 组织切换 ──────────────────────────────────────────
180
+
181
+ /**
182
+ * 切换到指定组织(无需重新登录)
183
+ * @param {string} targetCorpId - 目标组织 ID
184
+ * @param {object} cookieData - 当前 Cookie 数据
185
+ * @returns {Promise<object>} 切换结果
186
+ */
187
+ async function switchOrganization(targetCorpId, cookieData) {
188
+ const { c, banner, label, info, success: chalkSuccess, warn: chalkWarn, fail: chalkFail, step, sep } = require('../core/chalk');
189
+
190
+ banner(t('org.switch_title'), { stderr: false });
191
+
192
+ const cookies = cookieData.cookies;
193
+ const { corpId: currentCorpId } = extractInfoFromCookies(cookies);
194
+
195
+ label('From', currentCorpId || t('org.unknown'), { stderr: false });
196
+ label('To', `${c.green}${targetCorpId}${c.reset}`, { stderr: false });
197
+
198
+ if (currentCorpId === targetCorpId) {
199
+ chalkWarn(t('org.already_in_org'), false);
200
+ console.log(` ${sep()}\n`);
201
+ return {
202
+ success: true,
203
+ corpId: targetCorpId,
204
+ message: 'Already in target organization',
205
+ };
206
+ }
207
+
208
+ try {
209
+ const baseUrl = resolveBaseUrl(cookieData);
210
+
211
+ // Step 1: 发起切换请求
212
+ step(1, t('org.step1'), false);
213
+ const step1Url = `${baseUrl}/start.html?corpid=${targetCorpId}&switchCorp=true`;
214
+ const step1Result = await httpGetWithCookies(step1Url, cookies, false);
215
+
216
+ // Step 2: 确认切换
217
+ step(2, t('org.step2'), false);
218
+ const step2Url = `${baseUrl}/start.html?corpid=${targetCorpId}&`;
219
+ const step2Result = await httpGetWithCookies(step2Url, step1Result.cookies, false);
220
+
221
+ // Step 3-4: 获取新 Cookie(跟随重定向)
222
+ step(3, t('org.step3'), false);
223
+ const step3Url = `${baseUrl}/start.html?corpid=${targetCorpId}&`;
224
+ const step3Result = await httpGetWithCookies(step3Url, step2Result.cookies, false);
225
+
226
+ // 处理重定向
227
+ let finalCookies = step3Result.cookies;
228
+ let currentUrl = step3Result.location;
229
+
230
+ // 跟随重定向最多 5 次
231
+ let redirectCount = 0;
232
+ while (currentUrl && redirectCount < 5) {
233
+ redirectCount++;
234
+ info(`${t('org.redirect', redirectCount)}`, false);
235
+
236
+ // 构建完整 URL
237
+ if (!currentUrl.startsWith('http')) {
238
+ currentUrl = new URL(currentUrl, baseUrl).toString();
239
+ }
240
+
241
+ const redirectResult = await httpGetWithCookies(currentUrl, finalCookies, false);
242
+ finalCookies = redirectResult.cookies;
243
+ currentUrl = redirectResult.location;
244
+
245
+ // 如果到达工作台,停止
246
+ if (currentUrl && currentUrl.includes('workPlatform')) {
247
+ break;
248
+ }
249
+ }
250
+
251
+ // 提取新的 csrf_token 和 corpId
252
+ const { csrfToken, corpId: newCorpId, userId } = extractInfoFromCookies(finalCookies);
253
+
254
+ if (!csrfToken) {
255
+ chalkFail(t('org.switch_failed_no_csrf'), { exit: false });
256
+ console.log(` ${sep()}\n`);
257
+ return {
258
+ success: false,
259
+ message: 'No csrf_token in new cookies',
260
+ };
261
+ }
262
+
263
+ // 保存新的 Cookie
264
+ const newBaseUrl = baseUrl;
265
+ saveCookieCache(finalCookies, newBaseUrl);
266
+
267
+ // 更新 auth 配置
268
+ const authConfig = loadAuthConfig() || {};
269
+ authConfig.corpId = newCorpId;
270
+ authConfig.userId = userId;
271
+ authConfig.switchTime = new Date().toISOString();
272
+
273
+ // 更新历史组织列表
274
+ if (!authConfig.recentCorps) {
275
+ authConfig.recentCorps = [];
276
+ }
277
+
278
+ // 移除旧记录,添加新记录
279
+ authConfig.recentCorps = authConfig.recentCorps.filter((c) => c.corpId !== newCorpId);
280
+ authConfig.recentCorps.unshift({
281
+ corpId: newCorpId,
282
+ name: t('org.switched_org'),
283
+ lastUsed: new Date().toISOString(),
284
+ });
285
+
286
+ // 保留最近 10 个组织
287
+ authConfig.recentCorps = authConfig.recentCorps.slice(0, 10);
288
+ saveAuthConfig(authConfig);
289
+
290
+ chalkSuccess(t('org.switch_success'), false);
291
+ label('Corp ID', newCorpId, { stderr: false });
292
+ label('CSRF', `${csrfToken.slice(0, 16)}…`, { stderr: false });
293
+ console.log(` ${sep()}\n`);
294
+
295
+ return {
296
+ success: true,
297
+ corpId: newCorpId,
298
+ csrfToken,
299
+ userId,
300
+ baseUrl: newBaseUrl,
301
+ cookies: finalCookies,
302
+ };
303
+ } catch (error) {
304
+ chalkFail(t('org.switch_error', error.message), { exit: false });
305
+ console.log(` ${sep()}\n`);
306
+ return {
307
+ success: false,
308
+ message: error.message,
309
+ };
310
+ }
311
+ }
312
+
313
+ // ── 交互式组织选择 ────────────────────────────────────
314
+
315
+ /**
316
+ * 交互式组织选择
317
+ * @param {object} cookieData - Cookie 数据
318
+ * @returns {Promise<object>} 切换结果
319
+ */
320
+ async function interactiveSwitch(cookieData) {
321
+ const organizations = await listOrganizations(cookieData);
322
+
323
+ if (organizations.length === 0) {
324
+ return {
325
+ success: false,
326
+ message: 'No organizations available',
327
+ };
328
+ }
329
+
330
+ // 过滤掉当前组织
331
+ const switchableOrgs = organizations.filter((org) => !org.isCurrent);
332
+
333
+ if (switchableOrgs.length === 0) {
334
+ const { info: chalkInfo2 } = require('../core/chalk');
335
+ chalkInfo2(t('org.only_one_org'), false);
336
+ return {
337
+ success: false,
338
+ message: 'Only one organization available',
339
+ };
340
+ }
341
+
342
+ // 显示可选组织
343
+ const { c: oc, hint: chalkHint2 } = require('../core/chalk');
344
+ console.log(`\n ${oc.bold}${t('org.select_prompt')}${oc.reset}`);
345
+ switchableOrgs.forEach((org, index) => {
346
+ console.log(` ${oc.cyan}${index + 1}.${oc.reset} ${org.name} ${oc.dim}(${org.corpId})${oc.reset}`);
347
+ });
348
+
349
+ // 在非交互模式下,提示用户使用 --corp-id 参数
350
+ chalkHint2(t('org.use_corp_id_hint'), false);
351
+
352
+ return {
353
+ success: false,
354
+ message: 'Interactive mode not supported, please use --corp-id option',
355
+ organizations: switchableOrgs,
356
+ };
357
+ }
358
+
359
+ module.exports = {
360
+ listOrganizations,
361
+ switchOrganization,
362
+ interactiveSwitch,
363
+ };