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.
- package/LICENSE +21 -0
- package/README.md +383 -0
- package/bin/yida.js +670 -0
- package/lib/app/form-navigation.js +58 -0
- package/lib/app/get-schema.js +538 -0
- package/lib/auth/auth.js +294 -0
- package/lib/auth/cdp-browser-login.js +390 -0
- package/lib/auth/codex-login.js +71 -0
- package/lib/auth/login.js +475 -0
- package/lib/auth/org.js +363 -0
- package/lib/auth/qr-login.js +1563 -0
- package/lib/core/chalk.js +384 -0
- package/lib/core/check-update.js +82 -0
- package/lib/core/cli-error.js +39 -0
- package/lib/core/command-manifest.js +106 -0
- package/lib/core/env-cmd.js +545 -0
- package/lib/core/env-manager.js +601 -0
- package/lib/core/env.js +287 -0
- package/lib/core/i18n.js +177 -0
- package/lib/core/locales/ar.js +805 -0
- package/lib/core/locales/de.js +805 -0
- package/lib/core/locales/en.js +1623 -0
- package/lib/core/locales/es.js +805 -0
- package/lib/core/locales/fr.js +805 -0
- package/lib/core/locales/hi.js +805 -0
- package/lib/core/locales/ja.js +1197 -0
- package/lib/core/locales/ko.js +807 -0
- package/lib/core/locales/pt.js +805 -0
- package/lib/core/locales/vi.js +805 -0
- package/lib/core/locales/zh-HK.js +1233 -0
- package/lib/core/locales/zh.js +1584 -0
- package/lib/core/query-data.js +781 -0
- package/lib/core/redact.js +100 -0
- package/lib/core/utils.js +799 -0
- package/lib/core/yida-client.js +117 -0
- package/package.json +94 -0
- package/project/config.json +4 -0
- package/project/pages/src/demo-birthday-game.oyd.jsx +832 -0
- package/project/pages/src/demo-chip-insight.oyd.jsx +983 -0
- package/project/pages/src/demo-compat-smoke.oyd.jsx +58 -0
- package/project/pages/src/demo-crm-batch-entry.oyd.jsx +805 -0
- package/project/pages/src/demo-crm-dashboard.oyd.jsx +677 -0
- package/project/pages/src/demo-future-vision-2026.oyd.jsx +1102 -0
- package/project/pages/src/demo-ppt.oyd.jsx +1192 -0
- package/project/pages/src/demo-salary-calculator.oyd.jsx +904 -0
- package/project/pages/src/yidaconnector-knowledge-doc.oyd.jsx +1714 -0
- package/project/prd/demo-birthday-game.md +39 -0
- package/project/prd/demo-crm.md +463 -0
- package/project/prd/demo-dingtalk-ai-solution-center.md +425 -0
- package/project/prd/demo-future-vision-2026.md +78 -0
- package/project/prd/demo-salary-calculator.md +101 -0
- package/scripts/build-skills-package.js +406 -0
- package/scripts/check-syntax.js +59 -0
- package/scripts/demo-dws.sh +106 -0
- package/scripts/e2e-real/cleanup.js +67 -0
- package/scripts/e2e-real/fixtures/form-fields.json +18 -0
- package/scripts/e2e-real/full-runner.js +1566 -0
- package/scripts/e2e-real/runner.js +293 -0
- package/scripts/e2e-real/skill-coverage.js +115 -0
- package/scripts/generate-command-docs.js +109 -0
- package/scripts/nightly-smoke.js +134 -0
- package/scripts/postinstall.js +545 -0
- package/scripts/solution-center-runner.js +368 -0
- package/scripts/validate-ci.sh +50 -0
- package/scripts/validate-command-manifest.js +119 -0
- package/scripts/validate-package-size.js +78 -0
- package/scripts/validate-skills.js +247 -0
- package/scripts/validate-structure.js +66 -0
- package/yida-skills/SKILL.md +163 -0
- package/yida-skills/references/yida-api.md +1309 -0
- package/yida-skills/skills/large-file-write/SKILL.md +91 -0
- package/yida-skills/skills/large-file-write/references/write-patterns.md +149 -0
- package/yida-skills/skills/large-file-write/scripts/write.js +157 -0
- package/yida-skills/skills/yida-data-management/SKILL.md +252 -0
- package/yida-skills/skills/yida-data-management/references/api-matrix.md +49 -0
- package/yida-skills/skills/yida-data-management/references/data-format-guide.md +159 -0
- package/yida-skills/skills/yida-data-management/references/verified-endpoints.md +62 -0
- package/yida-skills/skills/yida-login/SKILL.md +159 -0
- package/yida-skills/skills/yida-logout/SKILL.md +67 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* codex-login.js - 内置浏览器登录模式(Codex / Qoder / Wukong)
|
|
3
|
+
*
|
|
4
|
+
* Codex、Qoder 和悟空自带 in-app browser。CLI 进程不能直接调用其内置浏览器工具,
|
|
5
|
+
* 因此这里返回一个明确的浏览器登录 handoff,由 Agent 打开 URL 让用户扫码。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const { detectActiveTool } = require('../core/utils');
|
|
11
|
+
const { resolveLoginUrl } = require('../core/env-manager');
|
|
12
|
+
const { t } = require('../core/i18n');
|
|
13
|
+
|
|
14
|
+
/** 支持内置浏览器 handoff 的工具列表 */
|
|
15
|
+
const BROWSER_HANDOFF_TOOLS = ['codex', 'qoder', 'qoderwork', 'wukong'];
|
|
16
|
+
|
|
17
|
+
const BROWSER_HANDOFF_TOOL_META = {
|
|
18
|
+
codex: { browser: 'codex', displayName: 'Codex', flag: '--codex' },
|
|
19
|
+
qoder: { browser: 'qoder', displayName: 'Qoder', flag: '--qoder' },
|
|
20
|
+
qoderwork: { browser: 'qoderwork', displayName: 'QoderWork', flag: '--qoder' },
|
|
21
|
+
wukong: { browser: 'wukong', displayName: '悟空(Wukong)', flag: '--wukong' },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function resolveBrowserHandoffToolMeta(toolName) {
|
|
25
|
+
return BROWSER_HANDOFF_TOOL_META[toolName] || BROWSER_HANDOFF_TOOL_META.codex;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 内置浏览器登录入口:不依赖 Playwright,也不走终端二维码接口。
|
|
30
|
+
* 支持 Codex、Qoder 和悟空环境。
|
|
31
|
+
* @param {object} [options]
|
|
32
|
+
* @param {string} [options.tool] - 显式指定宿主工具:codex/qoder/wukong
|
|
33
|
+
* @returns {Promise<object>} loginResult
|
|
34
|
+
*/
|
|
35
|
+
async function codexLogin(options = {}) {
|
|
36
|
+
const { banner, info, warn, hint, label } = require('../core/chalk');
|
|
37
|
+
const activeTool = detectActiveTool();
|
|
38
|
+
|
|
39
|
+
const toolName = options.tool || (activeTool ? activeTool.tool : null);
|
|
40
|
+
if (!toolName || !BROWSER_HANDOFF_TOOLS.includes(toolName)) {
|
|
41
|
+
warn(t('codex_login.not_codex'));
|
|
42
|
+
}
|
|
43
|
+
const toolMeta = resolveBrowserHandoffToolMeta(toolName);
|
|
44
|
+
|
|
45
|
+
banner(t('codex_login.title', toolMeta.flag, toolMeta.displayName));
|
|
46
|
+
|
|
47
|
+
const loginUrl = options.loginUrl || resolveLoginUrl();
|
|
48
|
+
|
|
49
|
+
info(t('codex_login.no_playwright', toolMeta.displayName));
|
|
50
|
+
info(t('codex_login.using_browser', toolMeta.displayName));
|
|
51
|
+
label('URL', loginUrl);
|
|
52
|
+
hint(t('codex_login.browser_handoff_hint', toolMeta.displayName));
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
status: 'need_codex_browser_login',
|
|
56
|
+
handoff_type: 'browser',
|
|
57
|
+
can_auto_use: false,
|
|
58
|
+
agent_action: 'open_in_app_browser',
|
|
59
|
+
browser_open_strategy: 'browser_use_iab',
|
|
60
|
+
browser_use_local_redirect_fallback: true,
|
|
61
|
+
required_agent_tool: 'browser-use:browser',
|
|
62
|
+
required_runtime_tool: 'mcp__node_repl__js',
|
|
63
|
+
login_url: loginUrl,
|
|
64
|
+
browser: toolMeta.browser,
|
|
65
|
+
post_login_check_command: 'yidaconnector login --check-only --json',
|
|
66
|
+
fallback_command: 'yidaconnector login --browser',
|
|
67
|
+
message: t('codex_login.handoff_message', toolMeta.displayName),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = { codexLogin };
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* login.js - 宜搭登录态管理(Cookie 缓存 + 本地浏览器登录)
|
|
3
|
+
*
|
|
4
|
+
* 登录策略(按优先级):
|
|
5
|
+
* 1. 本地 Cookie 缓存(最快)
|
|
6
|
+
* 2. 本地 Chrome / Edge / Chromium CDP 浏览器登录
|
|
7
|
+
* 3. 可选 Playwright 浏览器登录(仅在用户自行安装 Playwright 时作为兜底)
|
|
8
|
+
*
|
|
9
|
+
* 导出函数:
|
|
10
|
+
* ensureLogin() - 确保有效登录态(优先缓存,否则尝试可选浏览器登录)
|
|
11
|
+
* checkLoginOnly() - 仅检查登录态,不触发登录
|
|
12
|
+
* refreshCsrfFromCache() - 从缓存 Cookie 重新提取 csrf_token
|
|
13
|
+
* interactiveLogin() - 打开浏览器扫码登录(优先本地 Chrome CDP,可选 Playwright 兜底)
|
|
14
|
+
* saveCookieCache() - 保存 Cookie 到本地缓存(供 qr-login.js 使用)
|
|
15
|
+
* logout() - 退出登录,清空 Cookie 缓存
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
'use strict';
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const os = require('os');
|
|
23
|
+
const { execSync } = require('child_process');
|
|
24
|
+
const { findProjectRoot, extractInfoFromCookies, loadCookieData, resolveBaseUrl, detectActiveTool } = require('../core/utils');
|
|
25
|
+
const { t } = require('../core/i18n');
|
|
26
|
+
|
|
27
|
+
const DEFAULT_BASE_URL = 'https://www.aliwork.com';
|
|
28
|
+
const DEFAULT_LOGIN_URL = 'https://www.aliwork.com/workPlatform';
|
|
29
|
+
|
|
30
|
+
// ── 配置读取 ──────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
function loadConfig() {
|
|
33
|
+
const { resolveLoginUrl } = require('../core/env-manager');
|
|
34
|
+
const loginUrl = resolveLoginUrl();
|
|
35
|
+
return {
|
|
36
|
+
loginUrl,
|
|
37
|
+
defaultBaseUrl: DEFAULT_BASE_URL,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Cookie 持久化 ─────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
function saveCookieCache(cookies, baseUrl) {
|
|
44
|
+
const projectRoot = findProjectRoot();
|
|
45
|
+
const cacheDir = path.join(projectRoot, '.cache');
|
|
46
|
+
|
|
47
|
+
// 使用当前激活环境的 Cookie 文件路径(环境隔离)
|
|
48
|
+
const { getCookieFilePath } = require('../core/env-manager');
|
|
49
|
+
const cookieFile = getCookieFilePath(projectRoot);
|
|
50
|
+
|
|
51
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
52
|
+
fs.writeFileSync(cookieFile, JSON.stringify({ cookies, base_url: baseUrl }, null, 2), 'utf-8');
|
|
53
|
+
const { c } = require('../core/chalk');
|
|
54
|
+
process.stderr.write(` ${c.green}✔${c.reset} Cookie saved to ${c.dim}${cookieFile}${c.reset}\n`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── 仅检查登录态 ──────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 仅检查登录态,不触发登录。
|
|
61
|
+
* @param {object} [options]
|
|
62
|
+
* @param {boolean} [options.includeSecrets=false] - 是否返回完整 Cookie/CSRF,默认脱敏
|
|
63
|
+
* @returns {object} 含 can_auto_use 字段的状态对象
|
|
64
|
+
*/
|
|
65
|
+
function checkLoginOnly(options = {}) {
|
|
66
|
+
const includeSecrets = !!options.includeSecrets;
|
|
67
|
+
const projectRoot = findProjectRoot();
|
|
68
|
+
const { getCurrentEnvConfig, getCookieFilePath } = require('../core/env-manager');
|
|
69
|
+
const activeTool = detectActiveTool();
|
|
70
|
+
const currentEnv = getCurrentEnvConfig(projectRoot);
|
|
71
|
+
const cookieFile = getCookieFilePath(projectRoot);
|
|
72
|
+
const legacyCookieFile = path.join(projectRoot, '.cache', 'cookies.json');
|
|
73
|
+
const cookieData = loadCookieData(projectRoot);
|
|
74
|
+
const cookies = cookieData && Array.isArray(cookieData.cookies) ? cookieData.cookies : [];
|
|
75
|
+
const baseDiagnostics = {
|
|
76
|
+
activeTool: activeTool ? activeTool.tool : null,
|
|
77
|
+
isWukong: !!(activeTool && activeTool.tool === 'wukong'),
|
|
78
|
+
projectRoot,
|
|
79
|
+
projectRootExists: fs.existsSync(projectRoot),
|
|
80
|
+
hasConfig: fs.existsSync(path.join(projectRoot, 'config.json')),
|
|
81
|
+
currentEnv: currentEnv.name,
|
|
82
|
+
cookieFile,
|
|
83
|
+
legacyCookieFile,
|
|
84
|
+
cookieFileFound: fs.existsSync(cookieFile) || fs.existsSync(legacyCookieFile),
|
|
85
|
+
usedLegacyCookieFile: !fs.existsSync(cookieFile) && fs.existsSync(legacyCookieFile),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if (!cookieData || !cookieData.cookies) {
|
|
89
|
+
return {
|
|
90
|
+
status: 'not_logged_in',
|
|
91
|
+
can_auto_use: false,
|
|
92
|
+
diagnostics: {
|
|
93
|
+
...baseDiagnostics,
|
|
94
|
+
cache_readable: false,
|
|
95
|
+
cookies_array_found: false,
|
|
96
|
+
csrf_token_found: false,
|
|
97
|
+
corp_id_found: false,
|
|
98
|
+
user_id_found: false,
|
|
99
|
+
base_url_found: false,
|
|
100
|
+
failure_reason: baseDiagnostics.cookieFileFound ? 'cookie_cache_unreadable_or_invalid' : 'cookie_cache_missing',
|
|
101
|
+
},
|
|
102
|
+
message: 'No local Cookie cache, QR scan login required',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const { csrfToken, corpId, userId } = extractInfoFromCookies(cookies);
|
|
107
|
+
const baseUrl = resolveBaseUrl(cookieData);
|
|
108
|
+
const diagnostics = {
|
|
109
|
+
...baseDiagnostics,
|
|
110
|
+
cache_readable: true,
|
|
111
|
+
cookies_array_found: Array.isArray(cookieData.cookies),
|
|
112
|
+
csrf_token_found: !!csrfToken,
|
|
113
|
+
corp_id_found: !!corpId,
|
|
114
|
+
user_id_found: !!userId,
|
|
115
|
+
base_url_found: !!baseUrl,
|
|
116
|
+
cookies_count: cookies.length,
|
|
117
|
+
failure_reason: csrfToken ? null : 'csrf_token_missing',
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
if (!csrfToken) {
|
|
121
|
+
return {
|
|
122
|
+
status: 'not_logged_in',
|
|
123
|
+
can_auto_use: false,
|
|
124
|
+
diagnostics,
|
|
125
|
+
message: 'No tianshu_csrf_token in Cookie, re-login required',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const result = {
|
|
130
|
+
status: 'ok',
|
|
131
|
+
can_auto_use: true,
|
|
132
|
+
csrf_token: includeSecrets ? csrfToken : `${csrfToken.slice(0, 8)}…`,
|
|
133
|
+
corp_id: corpId,
|
|
134
|
+
user_id: userId,
|
|
135
|
+
base_url: baseUrl,
|
|
136
|
+
cookies_count: cookies.length,
|
|
137
|
+
diagnostics,
|
|
138
|
+
message: `✅ Valid login credentials found\n Org: ${corpId}\n User: ${userId}\n Domain: ${baseUrl}`,
|
|
139
|
+
};
|
|
140
|
+
if (includeSecrets) {
|
|
141
|
+
result.cookies = cookies;
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ── 从缓存刷新 csrf_token ─────────────────────────────
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 从本地缓存 Cookie 中重新提取 csrf_token,无需重新扫码。
|
|
150
|
+
* @returns {object} loginResult
|
|
151
|
+
*/
|
|
152
|
+
function refreshCsrfFromCache() {
|
|
153
|
+
const cookieData = loadCookieData();
|
|
154
|
+
|
|
155
|
+
if (!cookieData || !cookieData.cookies) {
|
|
156
|
+
const { error: chalkError } = require('../core/chalk');
|
|
157
|
+
chalkError(t('login.no_cookie_cache'));
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const { csrfToken, corpId, userId } = extractInfoFromCookies(cookieData.cookies);
|
|
162
|
+
|
|
163
|
+
if (!csrfToken) {
|
|
164
|
+
const { error: chalkError2 } = require('../core/chalk');
|
|
165
|
+
chalkError2(t('login.no_csrf_in_cache'));
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const baseUrl = resolveBaseUrl(cookieData);
|
|
170
|
+
const { success: chalkSuccess } = require('../core/chalk');
|
|
171
|
+
chalkSuccess(t('login.csrf_extracted', csrfToken.slice(0, 16)));
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
csrf_token: csrfToken,
|
|
175
|
+
corp_id: corpId,
|
|
176
|
+
user_id: userId,
|
|
177
|
+
base_url: baseUrl,
|
|
178
|
+
cookies: cookieData.cookies,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── 确保登录态(优先缓存) ────────────────────────────
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 确保拥有有效的登录态。默认优先从本地缓存 Cookie 中提取,force=true 时跳过缓存重新登录。
|
|
186
|
+
* 默认 CLI 登录路径在 bin/yida.js 中负责选择终端二维码、AI 工具 CDP 或二维码 handoff。
|
|
187
|
+
* @param {object} [options]
|
|
188
|
+
* @param {boolean} [options.force=false] - 是否跳过本地缓存,强制重新登录
|
|
189
|
+
* @returns {object} loginResult
|
|
190
|
+
*/
|
|
191
|
+
function ensureLogin(options = {}) {
|
|
192
|
+
const force = !!options.force;
|
|
193
|
+
if (!force) {
|
|
194
|
+
const cookieData = loadCookieData();
|
|
195
|
+
|
|
196
|
+
if (cookieData && cookieData.cookies) {
|
|
197
|
+
const { csrfToken, corpId, userId } = extractInfoFromCookies(cookieData.cookies);
|
|
198
|
+
if (csrfToken) {
|
|
199
|
+
const { success: chalkSuccess2, label: chalkLabel } = require('../core/chalk');
|
|
200
|
+
chalkSuccess2(t('login.using_cache'));
|
|
201
|
+
chalkLabel('CSRF', `${csrfToken.slice(0, 16)}…`);
|
|
202
|
+
if (corpId) {chalkLabel('Corp ID', corpId);}
|
|
203
|
+
const baseUrl = resolveBaseUrl(cookieData);
|
|
204
|
+
return {
|
|
205
|
+
csrf_token: csrfToken,
|
|
206
|
+
corp_id: corpId,
|
|
207
|
+
user_id: userId,
|
|
208
|
+
base_url: baseUrl,
|
|
209
|
+
cookies: cookieData.cookies,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return interactiveLogin(options);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ── 本地浏览器扫码登录 ────────────────────────────────
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 获取 playwright 模块的绝对路径,用于临时脚本中 require。
|
|
222
|
+
*
|
|
223
|
+
* 查找策略(按优先级):
|
|
224
|
+
* 1. 用户项目或全局环境可解析的 playwright
|
|
225
|
+
* 2. 旧版本 yidaconnector 包自身的 node_modules/playwright(向后兼容)
|
|
226
|
+
* 3. 全局 npm root 路径
|
|
227
|
+
*
|
|
228
|
+
* @returns {string|null} playwright index.js 的绝对路径
|
|
229
|
+
*/
|
|
230
|
+
function getPlaywrightPath() {
|
|
231
|
+
// 策略1:require.resolve 标准解析(用户项目、npm link 或兼容安装场景)
|
|
232
|
+
try {
|
|
233
|
+
return require.resolve('playwright');
|
|
234
|
+
} catch {
|
|
235
|
+
// ignore
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 策略2:兼容旧版本 yidaconnector 包自身的 node_modules/playwright
|
|
239
|
+
const candidates = [
|
|
240
|
+
path.join(__dirname, '..', 'node_modules', 'playwright', 'index.js'),
|
|
241
|
+
path.join(__dirname, '..', '..', 'node_modules', 'playwright', 'index.js'),
|
|
242
|
+
];
|
|
243
|
+
for (const candidate of candidates) {
|
|
244
|
+
if (fs.existsSync(candidate)) {
|
|
245
|
+
return candidate;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// 策略3:全局 npm root 路径
|
|
250
|
+
try {
|
|
251
|
+
const globalRoot = execSync('npm root -g', { encoding: 'utf-8' }).trim();
|
|
252
|
+
const globalPlaywright = path.join(globalRoot, 'playwright', 'index.js');
|
|
253
|
+
if (fs.existsSync(globalPlaywright)) {
|
|
254
|
+
return globalPlaywright;
|
|
255
|
+
}
|
|
256
|
+
} catch {
|
|
257
|
+
// ignore
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 打开有头浏览器让用户扫码登录。
|
|
265
|
+
* 优先使用无依赖的 Chrome/Edge/Chromium CDP 登录;失败时可选择再尝试用户安装的 Playwright。
|
|
266
|
+
* @param {object} [options]
|
|
267
|
+
* @param {boolean} [options.playwrightFallback=true] - CDP 失败后是否尝试 Playwright
|
|
268
|
+
* @returns {object} loginResult
|
|
269
|
+
*/
|
|
270
|
+
function interactiveLogin(options = {}) {
|
|
271
|
+
const playwrightFallback = options.playwrightFallback !== false;
|
|
272
|
+
const config = loadConfig();
|
|
273
|
+
const loginUrl = config.loginUrl || DEFAULT_LOGIN_URL;
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
if (process.env.YIDACONNECTOR_DISABLE_CDP_LOGIN === '1') {
|
|
277
|
+
throw new Error('CDP login disabled by YIDACONNECTOR_DISABLE_CDP_LOGIN');
|
|
278
|
+
}
|
|
279
|
+
return cdpInteractiveLogin(loginUrl);
|
|
280
|
+
} catch (err) {
|
|
281
|
+
const { warn: chalkWarn3 } = require('../core/chalk');
|
|
282
|
+
chalkWarn3(err.message);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (!playwrightFallback) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (process.env.YIDACONNECTOR_DISABLE_PLAYWRIGHT_LOGIN === '1') {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const playwrightPath = getPlaywrightPath();
|
|
294
|
+
if (!playwrightPath) {
|
|
295
|
+
const { error: chalkError3 } = require('../core/chalk');
|
|
296
|
+
chalkError3(t('login.no_playwright'), { hint: `${t('login.playwright_install1')}\n ${t('login.playwright_install2')}` });
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return playwrightInteractiveLogin(loginUrl, playwrightPath);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function playwrightInteractiveLogin(loginUrl, playwrightPath) {
|
|
304
|
+
const { info: chalkInfo, label: chalkLabel2 } = require('../core/chalk');
|
|
305
|
+
chalkInfo(t('login.browser_opening'));
|
|
306
|
+
chalkLabel2('URL', loginUrl);
|
|
307
|
+
const envManagerPath = path.join(__dirname, '..', 'core', 'env-manager.js');
|
|
308
|
+
|
|
309
|
+
// 通过子进程运行异步 playwright 逻辑,避免顶层 await 兼容性问题
|
|
310
|
+
// 使用 require.resolve 获取的绝对路径,确保临时脚本能找到 playwright
|
|
311
|
+
const scriptContent = `
|
|
312
|
+
const playwright = require(${JSON.stringify(playwrightPath)});
|
|
313
|
+
const { chromium } = playwright;
|
|
314
|
+
const { deriveBaseUrlFromLoginState } = require(${JSON.stringify(envManagerPath)});
|
|
315
|
+
|
|
316
|
+
(async () => {
|
|
317
|
+
// 优先使用本地已安装的 Chrome,避免下载 Playwright 内置 Chromium
|
|
318
|
+
let browser;
|
|
319
|
+
try {
|
|
320
|
+
browser = await chromium.launch({ channel: 'chrome', headless: false });
|
|
321
|
+
} catch {
|
|
322
|
+
// 本地未安装 Chrome,降级为 Playwright 内置 Chromium
|
|
323
|
+
browser = await chromium.launch({ headless: false });
|
|
324
|
+
}
|
|
325
|
+
const context = await browser.newContext();
|
|
326
|
+
const page = await context.newPage();
|
|
327
|
+
await page.goto(${JSON.stringify(loginUrl)}, { timeout: 120000 });
|
|
328
|
+
|
|
329
|
+
console.error(' Waiting for login (up to 10 minutes)...');
|
|
330
|
+
// 轮询 tianshu_csrf_token cookie 出现来判断登录成功
|
|
331
|
+
// 兼容专属域名(your-company.aliwork.com)登录后跳转路径不固定的情况
|
|
332
|
+
let loginSuccess = false;
|
|
333
|
+
const deadline = Date.now() + 600000;
|
|
334
|
+
while (Date.now() < deadline) {
|
|
335
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
336
|
+
const cookies = await context.cookies();
|
|
337
|
+
if (cookies.some(c => c.name === 'tianshu_csrf_token' && c.value)) {
|
|
338
|
+
loginSuccess = true;
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (!loginSuccess) {
|
|
343
|
+
console.error(' ⏰ Login timed out (10 minutes). Please try again.');
|
|
344
|
+
await browser.close();
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
await page.waitForLoadState('networkidle').catch(() => {});
|
|
348
|
+
console.error(' ✅ Login successful!');
|
|
349
|
+
|
|
350
|
+
const currentUrl = page.url();
|
|
351
|
+
const cookies = await context.cookies();
|
|
352
|
+
const baseUrl = deriveBaseUrlFromLoginState(cookies, ${JSON.stringify(loginUrl)}, currentUrl);
|
|
353
|
+
await browser.close();
|
|
354
|
+
|
|
355
|
+
console.log(JSON.stringify({ cookies, base_url: baseUrl }));
|
|
356
|
+
})();
|
|
357
|
+
`;
|
|
358
|
+
|
|
359
|
+
const tmpScript = path.join(os.tmpdir(), `yidaconnector-login-${Date.now()}.js`);
|
|
360
|
+
fs.writeFileSync(tmpScript, scriptContent, 'utf-8');
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const stdout = execSync(`node "${tmpScript}"`, {
|
|
364
|
+
encoding: 'utf-8',
|
|
365
|
+
stdio: ['inherit', 'pipe', 'inherit'],
|
|
366
|
+
timeout: 660000,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const lines = stdout.trim().split('\n');
|
|
370
|
+
const jsonLine = lines[lines.length - 1];
|
|
371
|
+
const result = JSON.parse(jsonLine);
|
|
372
|
+
|
|
373
|
+
const { csrfToken, corpId, userId } = extractInfoFromCookies(result.cookies);
|
|
374
|
+
if (!csrfToken) {
|
|
375
|
+
const { error: chalkError4 } = require('../core/chalk');
|
|
376
|
+
chalkError4(t('login.no_csrf_in_cookie'));
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
saveCookieCache(result.cookies, result.base_url);
|
|
381
|
+
|
|
382
|
+
const { success: chalkSuccess3, label: chalkLabel3 } = require('../core/chalk');
|
|
383
|
+
chalkSuccess3(t('login.csrf_ok', csrfToken.slice(0, 16)));
|
|
384
|
+
if (corpId) {chalkLabel3('Corp ID', corpId);}
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
csrf_token: csrfToken,
|
|
388
|
+
corp_id: corpId,
|
|
389
|
+
user_id: userId,
|
|
390
|
+
base_url: result.base_url,
|
|
391
|
+
cookies: result.cookies,
|
|
392
|
+
};
|
|
393
|
+
} finally {
|
|
394
|
+
try { fs.unlinkSync(tmpScript); } catch { /* ignore */ }
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function cdpInteractiveLogin(loginUrl) {
|
|
399
|
+
const { cdpBrowserLogin } = require('./cdp-browser-login');
|
|
400
|
+
const { info: chalkInfo, label: chalkLabel, success: chalkSuccess } = require('../core/chalk');
|
|
401
|
+
|
|
402
|
+
chalkInfo(t('login.browser_opening'));
|
|
403
|
+
chalkLabel('URL', loginUrl);
|
|
404
|
+
chalkInfo(t('login.waiting_login'));
|
|
405
|
+
|
|
406
|
+
const result = cdpBrowserLogin({ loginUrl });
|
|
407
|
+
const { csrfToken, corpId, userId } = extractInfoFromCookies(result.cookies);
|
|
408
|
+
if (!csrfToken) {
|
|
409
|
+
const { error: chalkError } = require('../core/chalk');
|
|
410
|
+
chalkError(t('login.no_csrf_in_cookie'));
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
saveCookieCache(result.cookies, result.base_url);
|
|
415
|
+
chalkSuccess(t('login.csrf_ok', csrfToken.slice(0, 16)));
|
|
416
|
+
if (corpId) {chalkLabel('Corp ID', corpId);}
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
csrf_token: csrfToken,
|
|
420
|
+
corp_id: corpId,
|
|
421
|
+
user_id: userId,
|
|
422
|
+
base_url: result.base_url,
|
|
423
|
+
cookies: result.cookies,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ── 退出登录 ──────────────────────────────────────────
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* 退出登录:清空项目级 Cookie 文件。
|
|
431
|
+
*/
|
|
432
|
+
function logout() {
|
|
433
|
+
const { banner, label, success: chalkSuccess4, warn: chalkWarn, hint: chalkHint, sep } = require('../core/chalk');
|
|
434
|
+
|
|
435
|
+
banner(t('login.logout_title'));
|
|
436
|
+
|
|
437
|
+
const projectRoot = findProjectRoot();
|
|
438
|
+
|
|
439
|
+
// 删除当前环境对应的 Cookie 文件(多环境隔离)
|
|
440
|
+
const { getCookieFilePath } = require('../core/env-manager');
|
|
441
|
+
const envCookieFile = getCookieFilePath(projectRoot);
|
|
442
|
+
// 兼容旧版:同时删除 cookies.json(历史遗留文件)
|
|
443
|
+
const legacyCookieFile = path.join(projectRoot, '.cache', 'cookies.json');
|
|
444
|
+
|
|
445
|
+
label('Cookie (env)', envCookieFile);
|
|
446
|
+
if (envCookieFile !== legacyCookieFile) {
|
|
447
|
+
label('Cookie (legacy)', legacyCookieFile);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
let deleted = false;
|
|
451
|
+
for (const cookieFile of [envCookieFile, legacyCookieFile]) {
|
|
452
|
+
if (fs.existsSync(cookieFile)) {
|
|
453
|
+
fs.unlinkSync(cookieFile);
|
|
454
|
+
deleted = true;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (deleted) {
|
|
459
|
+
chalkSuccess4(t('login.logout_success'));
|
|
460
|
+
chalkHint(t('login.logout_hint'));
|
|
461
|
+
} else {
|
|
462
|
+
chalkWarn(t('login.logout_no_file'));
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
process.stderr.write(` ${sep()}\n\n`);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
module.exports = {
|
|
469
|
+
ensureLogin,
|
|
470
|
+
checkLoginOnly,
|
|
471
|
+
refreshCsrfFromCache,
|
|
472
|
+
interactiveLogin,
|
|
473
|
+
saveCookieCache,
|
|
474
|
+
logout,
|
|
475
|
+
};
|