@szc-ft/mcp-szcd-client 0.30.0 → 0.32.0

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 (22) hide show
  1. package/opencode-extension/skills/local-browser-test/SKILL.md +229 -9
  2. package/opencode-extension/skills/local-browser-test/decrypt-creds.mjs +210 -0
  3. package/opencode-extension/skills/local-browser-test/lib/browser-engine.js +642 -5
  4. package/opencode-extension/skills/local-browser-test/lib/cdp-browser-config.js +115 -0
  5. package/opencode-extension/skills/local-browser-test/lib/decrypt-passwords.js +210 -0
  6. package/opencode-extension/skills/local-browser-test/lib/shared-deps.js +2 -2
  7. package/package.json +1 -1
  8. package/qwen-extension/qwen-extension.json +1 -1
  9. package/qwen-extension/skills/local-browser-test/SKILL.md +229 -9
  10. package/qwen-extension/skills/local-browser-test/decrypt-creds.mjs +210 -0
  11. package/qwen-extension/skills/local-browser-test/lib/browser-engine.js +642 -5
  12. package/qwen-extension/skills/local-browser-test/lib/cdp-browser-config.js +115 -0
  13. package/qwen-extension/skills/local-browser-test/lib/decrypt-passwords.js +210 -0
  14. package/qwen-extension/skills/local-browser-test/lib/shared-deps.js +2 -2
  15. package/scripts/lib/dev-browser-shortcut.js +230 -0
  16. package/scripts/postinstall.js +5 -0
  17. package/standard-skill/local-browser-test/SKILL.md +229 -9
  18. package/standard-skill/local-browser-test/decrypt-creds.mjs +210 -0
  19. package/standard-skill/local-browser-test/lib/browser-engine.js +642 -5
  20. package/standard-skill/local-browser-test/lib/cdp-browser-config.js +115 -0
  21. package/standard-skill/local-browser-test/lib/decrypt-passwords.js +210 -0
  22. package/standard-skill/local-browser-test/lib/shared-deps.js +2 -2
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 浏览器凭据解密工具
4
+ *
5
+ * 自动检测 Edge/Chrome 的 user-data-dir 配置并解密保存的密码。
6
+ * 解密结果缓存到 ~/.szcd-mcp/deps/decrypted-creds.json,避免重复解密。
7
+ *
8
+ * 用法:
9
+ * NODE_PATH=~/.szcd-mcp/deps/node_modules node decrypt-creds.mjs --url aicityos
10
+ * NODE_PATH=~/.szcd-mcp/deps/node_modules node decrypt-creds.mjs --json
11
+ * NODE_PATH=~/.szcd-mcp/deps/node_modules node decrypt-creds.mjs --refresh # 强制刷新缓存
12
+ *
13
+ * 前置条件:
14
+ * - 浏览器以 --remote-debugging-port 和 --user-data-dir 启动
15
+ * - ~/.szcd-mcp/deps/ 已安装 sql.js(共享缓存,跨 IDE 共享)
16
+ */
17
+ import { getFullBrowserConfig } from './lib/cdp-browser-config.js';
18
+ import { decryptAllPasswords } from './lib/decrypt-passwords.js';
19
+ import { existsSync, readFileSync, writeFileSync, statSync } from 'fs';
20
+ import { resolve, dirname, join } from 'path';
21
+ import { homedir } from 'os';
22
+ import { fileURLToPath } from 'url';
23
+
24
+ const __dirname = dirname(fileURLToPath(import.meta.url));
25
+
26
+ // 缓存文件路径(共享缓存目录)
27
+ const CACHE_DIR = join(homedir(), '.szcd-mcp', 'deps');
28
+ const CACHE_FILE = join(CACHE_DIR, 'decrypted-creds.json');
29
+
30
+ // 解析命令行参数
31
+ const args = process.argv.slice(2);
32
+ const options = {
33
+ url: null,
34
+ json: false,
35
+ refresh: false, // 强制刷新,忽略缓存
36
+ cdpUrl: 'http://localhost:9222',
37
+ userDataDir: null,
38
+ verbose: false,
39
+ };
40
+
41
+ for (let i = 0; i < args.length; i++) {
42
+ switch (args[i]) {
43
+ case '--url': options.url = args[++i]; break;
44
+ case '--json': options.json = true; break;
45
+ case '--refresh': case '-r': options.refresh = true; break;
46
+ case '--cdp': options.cdpUrl = args[++i]; break;
47
+ case '--user-data-dir': options.userDataDir = args[++i]; break;
48
+ case '--verbose': case '-v': options.verbose = true; break;
49
+ case '--help': case '-h':
50
+ console.log(`浏览器凭据解密工具(带缓存)
51
+
52
+ 用法:
53
+ node decrypt-creds.mjs [选项]
54
+
55
+ 选项:
56
+ --url <pattern> 按 URL 过滤(如 aicityos)
57
+ --json JSON 格式输出
58
+ --refresh, -r 强制刷新,忽略缓存重新解密
59
+ --cdp <url> CDP 地址(默认 http://localhost:9222)
60
+ --user-data-dir <path> 手动指定 user-data-dir 路径
61
+ --verbose, -v 输出详细信息
62
+ --help, -h 显示帮助
63
+
64
+ 缓存说明:
65
+ 解密结果自动缓存到 ${CACHE_FILE}
66
+ 缓存以 userDataDir + filter 为 key,监测 Login Data 文件修改时间判断是否过期
67
+ `);
68
+ process.exit(0);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * 尝试从缓存读取凭据
74
+ * @returns {Array|null} 缓存的凭据数组,null 表示缓存无效
75
+ */
76
+ function tryReadCache(userDataDir, filter) {
77
+ if (options.refresh) {
78
+ if (options.verbose) console.error('[decrypt-creds] --refresh 强制刷新,跳过缓存');
79
+ return null;
80
+ }
81
+ if (!existsSync(CACHE_FILE)) {
82
+ if (options.verbose) console.error('[decrypt-creds] 缓存文件不存在');
83
+ return null;
84
+ }
85
+
86
+ try {
87
+ const cache = JSON.parse(readFileSync(CACHE_FILE, 'utf8'));
88
+ const cacheKey = `${userDataDir}::${filter}`;
89
+
90
+ if (!cache[cacheKey]) {
91
+ if (options.verbose) console.error('[decrypt-creds] 缓存 key 不匹配');
92
+ return null;
93
+ }
94
+
95
+ // 检查缓存有效性:Login Data 未变 + 24 小时内
96
+ const CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
97
+ const entry = cache[cacheKey];
98
+ const age = Date.now() - entry.cachedAt;
99
+
100
+ const loginDataPath = join(userDataDir, 'Default', 'Login Data');
101
+ if (existsSync(loginDataPath)) {
102
+ const loginDataMtime = statSync(loginDataPath).mtimeMs;
103
+ if (loginDataMtime > entry.cachedAt) {
104
+ if (options.verbose) console.error('[decrypt-creds] Login Data 已更新,缓存过期');
105
+ return null;
106
+ }
107
+ }
108
+
109
+ if (age > CACHE_MAX_AGE_MS) {
110
+ if (options.verbose) console.error(`[decrypt-creds] 缓存已过期 (${Math.round(age / 3600000)}h),重新解密`);
111
+ return null;
112
+ }
113
+
114
+ if (options.verbose) {
115
+ console.error(`[decrypt-creds] ✓ 使用缓存(${Math.round(age / 60000)}min 前缓存,${entry.credentials.length} 条凭据)`);
116
+ }
117
+ return entry.credentials;
118
+ } catch (e) {
119
+ if (options.verbose) console.error('[decrypt-creds] 缓存读取失败:', e.message);
120
+ return null;
121
+ }
122
+ }
123
+
124
+ /**
125
+ * 将凭据写入缓存
126
+ */
127
+ function writeCache(userDataDir, filter, credentials) {
128
+ try {
129
+ let cache = {};
130
+ if (existsSync(CACHE_FILE)) {
131
+ try { cache = JSON.parse(readFileSync(CACHE_FILE, 'utf8')); } catch {}
132
+ }
133
+ const cacheKey = `${userDataDir}::${filter}`;
134
+ cache[cacheKey] = {
135
+ cachedAt: Date.now(),
136
+ userDataDir,
137
+ filter,
138
+ credentials,
139
+ };
140
+ writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), 'utf8');
141
+ if (options.verbose) console.error('[decrypt-creds] ✓ 已缓存到', CACHE_FILE);
142
+ } catch (e) {
143
+ if (options.verbose) console.error('[decrypt-creds] 缓存写入失败:', e.message);
144
+ }
145
+ }
146
+
147
+ async function main() {
148
+ console.error('[decrypt-creds] 检测浏览器配置...');
149
+
150
+ // 1. 获取 user-data-dir
151
+ let userDataDir = options.userDataDir;
152
+ if (!userDataDir) {
153
+ try {
154
+ const config = await getFullBrowserConfig(options.cdpUrl);
155
+ if (config) {
156
+ userDataDir = config.userDataDir;
157
+ if (options.verbose) {
158
+ console.error('[decrypt-creds] 浏览器:', config.browserInfo?.browser);
159
+ console.error('[decrypt-creds] CDP 端口:', config.remoteDebuggingPort);
160
+ console.error('[decrypt-creds] user-data-dir:', userDataDir);
161
+ }
162
+ }
163
+ } catch (e) {
164
+ console.error('[decrypt-creds] CDP 检测失败:', e.message);
165
+ }
166
+ }
167
+
168
+ if (!userDataDir) {
169
+ console.error('[decrypt-creds] 错误: 未检测到 user-data-dir,请用 --user-data-dir 指定');
170
+ process.exit(1);
171
+ }
172
+
173
+ const filter = options.url ? `%${options.url}%` : '%';
174
+
175
+ // 2. 尝试读缓存
176
+ let credentials = tryReadCache(userDataDir, filter);
177
+
178
+ // 3. 缓存未命中,执行解密
179
+ if (!credentials) {
180
+ console.error(`[decrypt-creds] 解密凭据 (filter: ${filter})...`);
181
+ try {
182
+ credentials = await decryptAllPasswords({
183
+ userDataDir,
184
+ filter,
185
+ verbose: options.verbose,
186
+ });
187
+ // 写入缓存
188
+ writeCache(userDataDir, filter, credentials);
189
+ } catch (e) {
190
+ console.error('[decrypt-creds] 解密失败:', e.message);
191
+ process.exit(1);
192
+ }
193
+ }
194
+
195
+ // 4. 输出结果
196
+ if (options.json) {
197
+ console.log(JSON.stringify(credentials, null, 2));
198
+ } else {
199
+ if (credentials.length === 0) {
200
+ console.log('(未找到匹配的凭据)');
201
+ } else {
202
+ const maxUserLen = Math.max(...credentials.map(c => c.username.length));
203
+ for (const { url, username, password } of credentials) {
204
+ console.log(`${username.padEnd(maxUserLen)} ${password} (${url})`);
205
+ }
206
+ }
207
+ }
208
+ }
209
+
210
+ main();