dsh-plugin 1.1.2 → 1.2.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/client/client.js +4465 -1442
- package/client/client.js.map +1 -1
- package/lib/http/routes.js +438 -22
- package/lib/index.js +1 -1
- package/lib/services/install/install.js +4 -0
- package/lib/services/install/npm-check.js +55 -0
- package/lib/services/{npm-resolve.js → install/npm-resolve.js} +7 -5
- package/lib/services/{preflight.js → install/preflight.js} +15 -4
- package/lib/services/{task-queue.js → install/task-queue.js} +155 -66
- package/lib/services/loader.js +56 -1
- package/lib/services/log.js +138 -0
- package/lib/services/probe.js +157 -0
- package/lib/services/{profile.js → profile/profile.js} +29 -1
- package/lib/services/settings.js +60 -0
- package/lib/types/http/routes.d.ts +1 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/types/services/{install-types.d.ts → install/install-types.d.ts} +3 -0
- package/lib/types/services/{install.d.ts → install/install.d.ts} +5 -5
- package/lib/types/services/install/npm-check.d.ts +12 -0
- package/lib/types/services/{npm-resolve.d.ts → install/npm-resolve.d.ts} +2 -1
- package/lib/types/services/{task-queue.d.ts → install/task-queue.d.ts} +5 -1
- package/lib/types/services/loader.d.ts +14 -8
- package/lib/types/services/log.d.ts +49 -0
- package/lib/types/services/probe.d.ts +30 -0
- package/lib/types/services/{pending-restart.d.ts → profile/pending-restart.d.ts} +1 -1
- package/lib/types/services/{profile.d.ts → profile/profile.d.ts} +2 -0
- package/lib/types/services/settings.d.ts +24 -0
- package/package.json +1 -1
- package/src/client/components/PluginHubSection.tsx +341 -120
- package/src/client/components/{CatalogList.tsx → catalog/CatalogList.tsx} +11 -7
- package/src/client/components/{PluginCard.tsx → catalog/PluginCard.tsx} +5 -4
- package/src/client/components/layout/CatalogControls.tsx +80 -0
- package/src/client/components/{CatalogHeader.tsx → layout/CatalogHeader.tsx} +4 -4
- package/src/client/components/{CategoryTabs.tsx → layout/CategoryTabs.tsx} +3 -3
- package/src/client/components/layout/SectionTabs.tsx +60 -0
- package/src/client/components/{AboutModal.tsx → modals/AboutModal.tsx} +7 -7
- package/src/client/components/{HubUpdateModal.tsx → modals/HubUpdateModal.tsx} +5 -5
- package/src/client/components/modals/InstalledDetailModal.tsx +193 -0
- package/src/client/components/modals/LogsModal.tsx +301 -0
- package/src/client/components/modals/LogsPathModal.tsx +161 -0
- package/src/client/components/{NotificationsModal.tsx → modals/NotificationsModal.tsx} +47 -20
- package/src/client/components/{ProgressView.tsx → modals/ProgressView.tsx} +2 -2
- package/src/client/components/{modals.tsx → modals/modals.tsx} +117 -40
- package/src/client/components/{Dropdown.tsx → ui/Dropdown.tsx} +14 -6
- package/src/client/components/ui/Toggle.tsx +28 -0
- package/src/client/components/ui/icons.tsx +343 -0
- package/src/client/components/views/CustomInstallView.tsx +250 -0
- package/src/client/components/views/DiagnosticsView.tsx +172 -0
- package/src/client/components/views/InstalledView.tsx +328 -0
- package/src/client/components/views/LogsView.tsx +150 -0
- package/src/client/components/views/MarketView.tsx +66 -0
- package/src/client/components/views/SettingsView.tsx +273 -0
- package/src/client/data/catalog.ts +38 -0
- package/src/client/data/host.ts +107 -0
- package/src/client/data/hub.ts +41 -0
- package/src/client/hooks/useCatalog.ts +71 -115
- package/src/client/hooks/useIncrementalList.ts +46 -0
- package/src/client/hooks/useLanguage.ts +40 -0
- package/src/client/hooks/useSettings.ts +90 -0
- package/src/client/hooks/useTaskQueue.ts +156 -39
- package/src/client/locales.ts +358 -38
- package/src/client/logic/constants.ts +86 -0
- package/src/client/{lib → logic}/failures.ts +95 -74
- package/src/client/logic/install-command.ts +56 -0
- package/src/client/logic/installed.ts +207 -0
- package/src/client/logic/normalize.ts +52 -0
- package/src/client/logic/urls.ts +125 -0
- package/src/client/styles/CustomInstallView.module.css +191 -0
- package/src/client/styles/DiagnosticsView.module.css +231 -0
- package/src/client/styles/Dropdown.module.css +42 -41
- package/src/client/styles/Header.module.css +100 -132
- package/src/client/styles/InstalledView.module.css +447 -0
- package/src/client/styles/List.module.css +6 -0
- package/src/client/styles/LogsView.module.css +503 -0
- package/src/client/styles/Modal.module.css +352 -70
- package/src/client/styles/SectionTabs.module.css +142 -0
- package/src/client/styles/SettingsView.module.css +232 -0
- package/src/client/styles/Toggle.module.css +60 -0
- package/src/client/styles/tokens.module.css +1 -0
- package/src/client/types.ts +23 -3
- package/src/server/http/routes.ts +414 -22
- package/src/server/index.ts +1 -1
- package/src/server/services/{install-types.ts → install/install-types.ts} +3 -1
- package/src/server/services/{install.ts → install/install.ts} +5 -5
- package/src/server/services/install/npm-check.ts +53 -0
- package/src/server/services/{npm-resolve.ts → install/npm-resolve.ts} +7 -5
- package/src/server/services/{preflight.ts → install/preflight.ts} +16 -4
- package/src/server/services/{task-queue.ts → install/task-queue.ts} +157 -63
- package/src/server/services/loader.ts +55 -1
- package/src/server/services/log.ts +167 -0
- package/src/server/services/probe.ts +157 -0
- package/src/server/services/{pending-restart.ts → profile/pending-restart.ts} +1 -1
- package/src/server/services/{profile.ts → profile/profile.ts} +31 -1
- package/src/server/services/settings.ts +79 -0
- package/lib/services/install.js +0 -4
- package/src/client/components/CatalogControls.tsx +0 -108
- package/src/client/components/icons.tsx +0 -129
- package/src/client/lib/catalog.ts +0 -293
- package/src/client/lib/env.ts +0 -20
- /package/lib/services/{install-types.js → install/install-types.js} +0 -0
- /package/lib/services/{installed-versions.js → profile/installed-versions.js} +0 -0
- /package/lib/services/{pending-restart.js → profile/pending-restart.js} +0 -0
- /package/lib/services/{progress.js → profile/progress.js} +0 -0
- /package/lib/types/services/{preflight.d.ts → install/preflight.d.ts} +0 -0
- /package/lib/types/services/{installed-versions.d.ts → profile/installed-versions.d.ts} +0 -0
- /package/lib/types/services/{progress.d.ts → profile/progress.d.ts} +0 -0
- /package/src/client/{lib → logic}/format.ts +0 -0
- /package/src/client/{lib → logic}/renderMarkdown.ts +0 -0
- /package/src/server/services/{installed-versions.ts → profile/installed-versions.ts} +0 -0
- /package/src/server/services/{progress.ts → profile/progress.ts} +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
6
|
+
* 系统日志:把关键操作以 JSONL 追加到 `~/.dsh/profiles/<profile>/hub.log`。
|
|
7
|
+
*
|
|
8
|
+
* 每一条日志 = 级别(严重程度)× 类别(功能域):
|
|
9
|
+
* level debug / info / success / warn / error —— 区分报错与正常
|
|
10
|
+
* category install(安装)/ uninstall(卸载)/ update(更新,预留)/
|
|
11
|
+
* diagnostics(诊断)/ settings(设置)/ system(系统)
|
|
12
|
+
* 查看器按级别与类别过滤,就能针对性排查「安装时哪步不兼容」还是
|
|
13
|
+
* 「卸载时哪步不兼容」。读取时超行数上限就地裁剪,防文件无限膨胀;
|
|
14
|
+
* 写入失败绝不影响主流程(日志是辅助设施)。
|
|
15
|
+
*/
|
|
16
|
+
import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { dirname, join, resolve } from 'node:path';
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
import { loadSettings } from './settings.js';
|
|
20
|
+
/** 日志保留上限:超过后在读取时裁剪为最近 MAX_LOG_LINES 条 */
|
|
21
|
+
export const MAX_LOG_LINES = 2000;
|
|
22
|
+
const CATEGORIES = ['install', 'uninstall', 'update', 'diagnostics', 'settings', 'system'];
|
|
23
|
+
function profileDirectory(profile) {
|
|
24
|
+
return join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'profiles', profile);
|
|
25
|
+
}
|
|
26
|
+
/** `~` 开头路径展开为用户主目录(Node 不自动展开)。 */
|
|
27
|
+
function expandHome(p) {
|
|
28
|
+
if (p === '~')
|
|
29
|
+
return homedir();
|
|
30
|
+
if (p.startsWith('~/') || p.startsWith('~\\'))
|
|
31
|
+
return join(homedir(), p.slice(2));
|
|
32
|
+
return p;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 把用户填写的日志位置覆盖解析成实际文件路径:
|
|
36
|
+
* 以 .log 结尾视为文件本身,否则视为目录并在其中写 hub.log;相对路径按当前目录归一化。
|
|
37
|
+
*/
|
|
38
|
+
export function customLogFile(override) {
|
|
39
|
+
const trimmed = override.trim();
|
|
40
|
+
const expanded = expandHome(trimmed);
|
|
41
|
+
const file = /\.log$/i.test(expanded) ? expanded : join(expanded, 'hub.log');
|
|
42
|
+
return resolve(file);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 真正默认的日志文件路径:`~/.dsh/profiles/<profile>/hub.log`(不受设置覆盖影响)。
|
|
46
|
+
* 弹窗「恢复默认」、目录选择器起点等需要「未自定义时的位置」都从这里取 ——
|
|
47
|
+
* 不同机器/平台(用户主目录不同)这个值都不同,必须动态计算而非写死。
|
|
48
|
+
*/
|
|
49
|
+
export function defaultLogFilePath(profile) {
|
|
50
|
+
return join(profileDirectory(profile), 'hub.log');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 当前生效的日志文件路径:设置里填了 logPath 用自定义位置(目录或文件),
|
|
54
|
+
* 否则默认 `~/.dsh/profiles/<profile>/hub.log`。默认位置永远合法可用,无需用户干预。
|
|
55
|
+
*/
|
|
56
|
+
export function logFilePath(profile) {
|
|
57
|
+
const custom = loadSettings(profile).logPath?.trim() ?? '';
|
|
58
|
+
return custom === '' ? defaultLogFilePath(profile) : customLogFile(custom);
|
|
59
|
+
}
|
|
60
|
+
/** 追加一条系统日志(JSONL 一行一条)。 */
|
|
61
|
+
export function appendLog(profile, entry) {
|
|
62
|
+
try {
|
|
63
|
+
const file = logFilePath(profile);
|
|
64
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
65
|
+
appendFileSync(file, `${JSON.stringify(entry)}\n`);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// 日志失败不阻断主流程
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** 兼容旧格式:早期无 category(level 用 info/ok/fail/warn),归一化到新版。 */
|
|
72
|
+
function normalize(raw) {
|
|
73
|
+
if (typeof raw !== 'object' || raw === null)
|
|
74
|
+
return null;
|
|
75
|
+
const e = raw;
|
|
76
|
+
if (typeof e.at !== 'number')
|
|
77
|
+
return null;
|
|
78
|
+
let level = 'info';
|
|
79
|
+
if (e.level === 'ok' || e.level === 'success')
|
|
80
|
+
level = 'success';
|
|
81
|
+
else if (e.level === 'fail' || e.level === 'error')
|
|
82
|
+
level = 'error';
|
|
83
|
+
else if (e.level === 'warn')
|
|
84
|
+
level = 'warn';
|
|
85
|
+
else if (e.level === 'debug')
|
|
86
|
+
level = 'debug';
|
|
87
|
+
const category = CATEGORIES.includes(e.category) ? e.category : 'system';
|
|
88
|
+
return {
|
|
89
|
+
at: e.at,
|
|
90
|
+
level,
|
|
91
|
+
category,
|
|
92
|
+
event: typeof e.event === 'string' ? e.event : '',
|
|
93
|
+
message: typeof e.message === 'string' ? e.message : '',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** 读取系统日志(按时间正序,最新在末尾),超上限就地裁剪;支持过滤与分页。 */
|
|
97
|
+
export function readLog(profile, opts = {}) {
|
|
98
|
+
const { offset = 0, limit = 200 } = opts;
|
|
99
|
+
try {
|
|
100
|
+
const file = logFilePath(profile);
|
|
101
|
+
const raw = readFileSync(file, 'utf8');
|
|
102
|
+
let lines = raw.split('\n').filter((l) => l.trim() !== '');
|
|
103
|
+
if (lines.length > MAX_LOG_LINES) {
|
|
104
|
+
lines = lines.slice(-MAX_LOG_LINES);
|
|
105
|
+
writeFileSync(file, `${lines.join('\n')}\n`);
|
|
106
|
+
}
|
|
107
|
+
// 正序解析后倒序(最新在前)再过滤分页
|
|
108
|
+
let entries = [];
|
|
109
|
+
for (const line of lines) {
|
|
110
|
+
let parsed = null;
|
|
111
|
+
try {
|
|
112
|
+
parsed = JSON.parse(line);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const entry = normalize(parsed);
|
|
118
|
+
if (entry !== null)
|
|
119
|
+
entries.push(entry);
|
|
120
|
+
}
|
|
121
|
+
entries.reverse();
|
|
122
|
+
const needle = opts.query?.trim().toLowerCase() ?? '';
|
|
123
|
+
if (opts.category !== undefined && opts.category !== 'all') {
|
|
124
|
+
entries = entries.filter((e) => e.category === opts.category);
|
|
125
|
+
}
|
|
126
|
+
if (opts.level !== undefined && opts.level !== 'all') {
|
|
127
|
+
entries = entries.filter((e) => e.level === opts.level);
|
|
128
|
+
}
|
|
129
|
+
if (needle !== '') {
|
|
130
|
+
entries = entries.filter((e) => e.event.toLowerCase().includes(needle) || e.message.toLowerCase().includes(needle));
|
|
131
|
+
}
|
|
132
|
+
const total = entries.length;
|
|
133
|
+
return { entries: entries.slice(offset, offset + limit), total };
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return { entries: [], total: 0 };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
6
|
+
* 连通性探测:对单个 HTTPS 目标发 GET,测速并返回 HTTP 状态码。
|
|
7
|
+
*
|
|
8
|
+
* 实现方式:spawn 系统 curl 子进程并注入代理环境变量(HTTP_PROXY/HTTPS_PROXY),
|
|
9
|
+
* 与安装通道(pnpm/git 子进程)使用同一套代理环境。
|
|
10
|
+
*
|
|
11
|
+
* 代理来源由调用方决定(routes.ts):设置里的代理 → 系统代理 → 环境变量 → 直连。
|
|
12
|
+
* systemProxy() 读取操作系统级代理(macOS scutil / Windows 注册表),
|
|
13
|
+
* 使系统代理对 Node 进程可见。
|
|
14
|
+
*/
|
|
15
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
16
|
+
const probeFail = () => ({ ok: false, ms: null, status: null });
|
|
17
|
+
/** macOS 系统代理:scutil --proxy 输出里的 HTTP(S) 代理;未开启返回 null。 */
|
|
18
|
+
function macSystemProxy() {
|
|
19
|
+
try {
|
|
20
|
+
const out = spawnSync('scutil', ['--proxy'], { encoding: 'utf8', timeout: 1500 });
|
|
21
|
+
if (out.status !== 0)
|
|
22
|
+
return null;
|
|
23
|
+
const txt = out.stdout ?? '';
|
|
24
|
+
const val = (key) => {
|
|
25
|
+
const m = new RegExp(`^\\s*${key}\\s*:\\s*(.+)$`, 'm').exec(txt);
|
|
26
|
+
const v = m?.[1]?.trim();
|
|
27
|
+
return v !== undefined && v !== '' ? v : undefined;
|
|
28
|
+
};
|
|
29
|
+
const enabled = (val('HTTPSEnable') ?? val('HTTPEnable')) === '1';
|
|
30
|
+
const host = val('HTTPSProxy') ?? val('HTTPProxy');
|
|
31
|
+
if (!enabled || host === undefined)
|
|
32
|
+
return null;
|
|
33
|
+
const port = val('HTTPSPort') ?? val('HTTPPort') ?? '80';
|
|
34
|
+
if (!/^\d+$/.test(port))
|
|
35
|
+
return null;
|
|
36
|
+
return `http://${host}:${port}`;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Windows 系统代理:WinINET Internet 设置注册表;未开启返回 null。 */
|
|
43
|
+
function winSystemProxy() {
|
|
44
|
+
try {
|
|
45
|
+
const key = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings';
|
|
46
|
+
const enable = spawnSync('reg', ['query', key, '/v', 'ProxyEnable'], { encoding: 'utf8', timeout: 1500 });
|
|
47
|
+
const server = spawnSync('reg', ['query', key, '/v', 'ProxyServer'], { encoding: 'utf8', timeout: 1500 });
|
|
48
|
+
if (!/0x1/i.test(enable.stdout ?? ''))
|
|
49
|
+
return null;
|
|
50
|
+
const m = /([^:\s=]+):(\d+)/.exec(server.stdout ?? '');
|
|
51
|
+
if (m === null)
|
|
52
|
+
return null;
|
|
53
|
+
return `http://${m[1]}:${m[2]}`;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 操作系统级代理(macOS 系统网络设置 / Windows Internet 设置)。
|
|
61
|
+
* Node 内置 http(s) 不读系统代理,浏览器挂的代理 Node 看不见 —— 这里读出来
|
|
62
|
+
* 作为默认代理,保证「浏览器能开、安装/诊断就能通」。
|
|
63
|
+
* Linux 没有统一的系统代理入口,返回 null(交给环境变量 / 设置里的代理)。
|
|
64
|
+
*/
|
|
65
|
+
export function systemProxy() {
|
|
66
|
+
if (process.platform === 'darwin')
|
|
67
|
+
return macSystemProxy();
|
|
68
|
+
if (process.platform === 'win32')
|
|
69
|
+
return winSystemProxy();
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* curl 子进程探测:spawn curl -s -w '%{http_code}' 并注入代理 env。
|
|
74
|
+
* curl 读 HTTP_PROXY/HTTPS_PROXY 是原生行为,与 pnpm/git 安装通道同一套代理口径;
|
|
75
|
+
* 失败(curl 不存在 / 连接失败 / 超时)都归为不可达,全程不抛错。
|
|
76
|
+
*/
|
|
77
|
+
function curlProbe(url, proxy, timeoutMs) {
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
const started = Date.now();
|
|
80
|
+
const env = { ...process.env };
|
|
81
|
+
if (proxy !== '') {
|
|
82
|
+
env.HTTP_PROXY = proxy;
|
|
83
|
+
env.HTTPS_PROXY = proxy;
|
|
84
|
+
env.http_proxy = proxy;
|
|
85
|
+
env.https_proxy = proxy;
|
|
86
|
+
}
|
|
87
|
+
const discard = process.platform === 'win32' ? 'NUL' : '/dev/null';
|
|
88
|
+
const child = spawn('curl', [
|
|
89
|
+
'-s', '-o', discard, '-w', '%{http_code}',
|
|
90
|
+
'--max-time', String(Math.max(1, Math.round(timeoutMs / 1000))),
|
|
91
|
+
url,
|
|
92
|
+
], { env });
|
|
93
|
+
let code = '';
|
|
94
|
+
child.stdout.on('data', (c) => { code += c.toString(); });
|
|
95
|
+
let done = false;
|
|
96
|
+
const finish = (r) => {
|
|
97
|
+
if (done)
|
|
98
|
+
return;
|
|
99
|
+
done = true;
|
|
100
|
+
clearTimeout(timer);
|
|
101
|
+
resolve(r);
|
|
102
|
+
};
|
|
103
|
+
child.on('error', () => finish(probeFail()));
|
|
104
|
+
child.on('close', () => {
|
|
105
|
+
const status = /^\d+$/.test(code.trim()) ? Number(code.trim()) : null;
|
|
106
|
+
finish({ ok: status !== null && status >= 100 && status < 400, ms: Date.now() - started, status });
|
|
107
|
+
});
|
|
108
|
+
// 超时兜底:curl --max-time 到点自行退出,此处再设一个进程级超时防止悬挂
|
|
109
|
+
const timer = setTimeout(() => { child.kill(); finish(probeFail()); }, timeoutMs + 1000);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 探测一个 HTTPS 目标:proxy 非空注入给 curl 子进程走代理,否则直连。
|
|
114
|
+
* 供系统诊断 /diagnostics 使用;目标 URL 非法返回不可达。
|
|
115
|
+
*/
|
|
116
|
+
export function probeUrl(url, proxy, timeoutMs) {
|
|
117
|
+
let target;
|
|
118
|
+
try {
|
|
119
|
+
target = new URL(url);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return Promise.resolve(probeFail());
|
|
123
|
+
}
|
|
124
|
+
return curlProbe(target.href, proxy, timeoutMs);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* curl 子进程抓取响应体(与 probeUrl 同一套代理 env 注入)。
|
|
128
|
+
* 供服务端 /catalog 代理路由使用:目录/统计数据经此拉到服务端再转给浏览器,
|
|
129
|
+
* 使「目录数据请求走设置里的代理」与 npm / git 安装通道口径一致。
|
|
130
|
+
* 失败(curl 不存在 / 连接失败 / 超时 / 非零退出)时 ok=false,全程不抛错。
|
|
131
|
+
*/
|
|
132
|
+
export function fetchViaCurl(url, proxy, timeoutMs) {
|
|
133
|
+
return new Promise((resolve) => {
|
|
134
|
+
const env = { ...process.env };
|
|
135
|
+
if (proxy !== '') {
|
|
136
|
+
env.HTTP_PROXY = proxy;
|
|
137
|
+
env.HTTPS_PROXY = proxy;
|
|
138
|
+
env.http_proxy = proxy;
|
|
139
|
+
env.https_proxy = proxy;
|
|
140
|
+
}
|
|
141
|
+
const child = spawn('curl', ['-s', '--max-time', String(Math.max(1, Math.round(timeoutMs / 1000))), url], { env });
|
|
142
|
+
let body = '';
|
|
143
|
+
child.stdout.on('data', (c) => { body += c.toString(); });
|
|
144
|
+
let done = false;
|
|
145
|
+
const finish = (ok) => {
|
|
146
|
+
if (done)
|
|
147
|
+
return;
|
|
148
|
+
done = true;
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
resolve({ ok, body });
|
|
151
|
+
};
|
|
152
|
+
child.on('error', () => finish(false));
|
|
153
|
+
child.on('close', (code) => finish(code === 0));
|
|
154
|
+
// 超时兜底:curl --max-time 到点自行退出,此处再设一个进程级超时防止悬挂
|
|
155
|
+
const timer = setTimeout(() => { child.kill(); finish(false); }, timeoutMs + 1000);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
10
10
|
import { homedir } from 'node:os';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
|
-
import { PACKAGE_RE, REPO_RE } from '
|
|
12
|
+
import { PACKAGE_RE, REPO_RE } from '../install/install-types.js';
|
|
13
13
|
/** Resolve the active profile from the booted CLI args, falling back to `web`. */
|
|
14
14
|
export function readProfileArg(fallback = 'web') {
|
|
15
15
|
const index = process.argv.indexOf('--profile');
|
|
@@ -26,6 +26,34 @@ export function githubTarget(repo) {
|
|
|
26
26
|
return null;
|
|
27
27
|
return `git+https://github.com/${repo}.git`;
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* DSH 命令行前缀(`dsh plugin [--profile|-p <p>] add <target>`)—— 提取其中的安装目标;
|
|
31
|
+
* 非命令输入原样返回。兼容「直接粘贴完整命令」(防用户输错):--profile 段可选,
|
|
32
|
+
* 支持 `-p` 简写与 `--profile=web` 等号形式,命令大小写不敏感。
|
|
33
|
+
*/
|
|
34
|
+
const DSH_PLUGIN_CMD_RE = /^dsh\s+plugin\s+(?:(?:--profile|-p)(?:=|\s+)\S+\s*)?add\s+(.+)$/i;
|
|
35
|
+
export function installTargetOf(value) {
|
|
36
|
+
const input = typeof value === 'string' ? value.trim() : '';
|
|
37
|
+
const match = DSH_PLUGIN_CMD_RE.exec(input);
|
|
38
|
+
return match !== null ? match[1].trim() : input;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* npm 全局安装命令(官方 README 的 `npm install -g <pkgs>`,如 dsh-tui 的安装命令)——
|
|
42
|
+
* 提取 -g 之后的包列表。支持 `npm install|i` 与 `-g|--global`;任一包名非法(或格式
|
|
43
|
+
* 不匹配)返回 null,由调用方按「不支持的目标」处理。与客户端 CustomInstallView 同口径。
|
|
44
|
+
*/
|
|
45
|
+
const NPM_GLOBAL_CMD_RE = /^npm\s+(?:install|i)\s+(?:-g|--global)\s+(.+)$/i;
|
|
46
|
+
export function globalNpmPackagesOf(value) {
|
|
47
|
+
const input = typeof value === 'string' ? value.trim() : '';
|
|
48
|
+
const match = NPM_GLOBAL_CMD_RE.exec(input);
|
|
49
|
+
if (match === null)
|
|
50
|
+
return null;
|
|
51
|
+
const packages = match[1].trim().split(/\s+/).filter((p) => p !== '');
|
|
52
|
+
// 包名逐个校验 + 拒绝以 - 开头的 token(npm 会把 --xxx 当参数而非包名,防注入任意 CLI 参数)
|
|
53
|
+
if (packages.length === 0 || packages.some((p) => !validPackageName(p) || p.startsWith('-')))
|
|
54
|
+
return null;
|
|
55
|
+
return packages;
|
|
56
|
+
}
|
|
29
57
|
/** Extract an owner/repo identity from a catalog value or an installed Git spec. */
|
|
30
58
|
export function githubRepoOf(value) {
|
|
31
59
|
if (typeof value !== 'string')
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
+
* Website: https://dsh-plugin.org
|
|
4
|
+
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
+
*
|
|
6
|
+
* Hub settings persistence: one `hub-settings.json` per active profile
|
|
7
|
+
* (next to pnpm-workspace.yaml). All fields optional; missing keys fall
|
|
8
|
+
* back to DEFAULT_SETTINGS so old files stay readable as new options land.
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
|
11
|
+
import { dirname, join } from 'node:path';
|
|
12
|
+
import { profileDirectory } from './profile/profile.js';
|
|
13
|
+
export const DEFAULT_SETTINGS = {
|
|
14
|
+
checkUpdatesOnStart: true,
|
|
15
|
+
npmRegistry: '',
|
|
16
|
+
proxy: '',
|
|
17
|
+
enableNpmInstall: true,
|
|
18
|
+
enableGitInstall: true,
|
|
19
|
+
enableDshInstall: true,
|
|
20
|
+
logPath: '',
|
|
21
|
+
};
|
|
22
|
+
export function settingsFile(profile) {
|
|
23
|
+
return join(profileDirectory(profile), 'hub-settings.json');
|
|
24
|
+
}
|
|
25
|
+
/** 读取当前 profile 的设置;文件缺失/损坏时回退默认值(只读路径绝不抛错)。 */
|
|
26
|
+
export function loadSettings(profile) {
|
|
27
|
+
try {
|
|
28
|
+
const raw = JSON.parse(readFileSync(settingsFile(profile), 'utf8'));
|
|
29
|
+
return {
|
|
30
|
+
...DEFAULT_SETTINGS,
|
|
31
|
+
...(typeof raw === 'object' && raw !== null ? raw : {}),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return { ...DEFAULT_SETTINGS };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** 合并并持久化设置(部分更新),返回合并后的完整设置。 */
|
|
39
|
+
export function saveSettings(profile, patch) {
|
|
40
|
+
const next = { ...loadSettings(profile), ...patch };
|
|
41
|
+
const file = settingsFile(profile);
|
|
42
|
+
try {
|
|
43
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
44
|
+
writeFileSync(file, `${JSON.stringify(next, null, 2)}\n`);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// 持久化失败不阻断本次会话:内存值仍生效,下次写入再试
|
|
48
|
+
}
|
|
49
|
+
return next;
|
|
50
|
+
}
|
|
51
|
+
/** 重置为默认设置:删除设置文件,下次读取全部回退默认值(陈旧/白名单外键一并清除)。 */
|
|
52
|
+
export function resetSettings(profile) {
|
|
53
|
+
try {
|
|
54
|
+
rmSync(settingsFile(profile), { force: true });
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// 删除失败不阻断:loadSettings 仍回退默认值
|
|
58
|
+
}
|
|
59
|
+
return { ...DEFAULT_SETTINGS };
|
|
60
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
-
import { readProfileArg, type LoaderHandle } from '../services/install.ts';
|
|
2
|
+
import { readProfileArg, type LoaderHandle } from '../services/install/install.ts';
|
|
3
3
|
export interface WebRoute {
|
|
4
4
|
kind: 'exact';
|
|
5
5
|
path: string;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* web server service is available. The browser bundle (src/client) talks to
|
|
8
8
|
* these same-origin routes to perform real installs from inside the app.
|
|
9
9
|
*/
|
|
10
|
-
import { type LoaderHandle } from './services/install.ts';
|
|
10
|
+
import { type LoaderHandle } from './services/install/install.ts';
|
|
11
11
|
import { type WebServerService } from './http/routes.ts';
|
|
12
12
|
export declare const name = "dsh-plugin";
|
|
13
13
|
export interface Config {
|
|
@@ -23,6 +23,8 @@ export interface InstallTask {
|
|
|
23
23
|
* 失败提 Issue 时如实贴给作者,作者据此反推正确的 npm 包名(组织 scope 与 GitHub 用户名不一致时
|
|
24
24
|
* 仅凭仓库名猜不到,作者看到我们查过/试过的命令就能直接指认)。 */
|
|
25
25
|
attempts: string[];
|
|
26
|
+
/** 全局 npm 安装(官方 `npm install -g <pkgs>`):非空时任务执行全局 npm 安装,不进任何 profile */
|
|
27
|
+
globalNpm?: string[];
|
|
26
28
|
action: 'add' | 'remove';
|
|
27
29
|
/** 队列语义:pending 排队中 / running 执行中 / done / failed / cancelled(用户取消) */
|
|
28
30
|
status: 'pending' | 'running' | 'done' | 'failed' | 'cancelled';
|
|
@@ -51,6 +53,7 @@ export interface QueueItem {
|
|
|
51
53
|
target: string;
|
|
52
54
|
timeoutMs?: number;
|
|
53
55
|
env?: NodeJS.ProcessEnv;
|
|
56
|
+
globalNpm?: string[];
|
|
54
57
|
};
|
|
55
58
|
}
|
|
56
59
|
/**
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Install runner 的统一出口:执行真实插件变更(spawn 官方 dsh CLI 子进程)。
|
|
7
7
|
* 实现按职责拆分在同目录子模块,本文件只做公共 API 的 re-export,保持外部
|
|
8
|
-
* import 面稳定(routes.ts / index.ts 仍从 `services/install.ts` 引入)。
|
|
8
|
+
* import 面稳定(routes.ts / index.ts 仍从 `services/install/install.ts` 引入)。
|
|
9
9
|
*
|
|
10
10
|
* 模块划分:
|
|
11
11
|
* - install-types.ts 公共类型与常量
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* - task-queue.ts 任务注册表 + FIFO 队列 worker + 子进程生命周期
|
|
16
16
|
*/
|
|
17
17
|
export type { InstallResult, InstallTask, Invocation, PendingRestart, QueueItem } from './install-types.ts';
|
|
18
|
-
export type { LoaderHandle } from '
|
|
19
|
-
export { addPendingRestart, clearPendingRestart, listPendingRestarts } from '
|
|
20
|
-
export { dumpLoaderEntries, removeLoadedEntry } from '
|
|
21
|
-
export { addAllowBuildsKey, githubRepoOf, githubTarget, parseAllowBuildsKey, readProfileArg, validPackageName } from '
|
|
18
|
+
export type { LoaderHandle } from '../loader.ts';
|
|
19
|
+
export { addPendingRestart, clearPendingRestart, listPendingRestarts } from '../profile/pending-restart.ts';
|
|
20
|
+
export { dumpLoaderEntries, removeLoadedEntry } from '../loader.ts';
|
|
21
|
+
export { addAllowBuildsKey, githubRepoOf, githubTarget, globalNpmPackagesOf, installTargetOf, parseAllowBuildsKey, readProfileArg, validPackageName } from '../profile/profile.ts';
|
|
22
22
|
export { activeTask, cancelTask, getTask, hasQueuedTarget, hasRunningTask, runPluginMutation, startPluginMutation } from './task-queue.ts';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** npm arborist 内部崩溃特征:`Cannot read properties of null (reading 'edgesOut')`。 */
|
|
2
|
+
export declare const NPM_CRASH_EDGES_OUT_RE: RegExp;
|
|
3
|
+
/** 该缺陷在 npm 11.6.0 起不再复现;低于此版本遇到 edgesOut 崩溃即判「本机 npm 版本过低」。 */
|
|
4
|
+
export declare const NPM_MIN_VERSION: number[];
|
|
5
|
+
/** 读本机 npm 版本(major.minor.patch);npm 不在 PATH 或执行失败/超时时返回 null(不妄下结论)。 */
|
|
6
|
+
export declare function npmVersionOf(env: NodeJS.ProcessEnv | undefined): [number, number, number] | null;
|
|
7
|
+
/**
|
|
8
|
+
* 安装失败输出含 npm arborist edgesOut 崩溃特征,且本机 npm 版本低于阈值时,返回
|
|
9
|
+
* `[npm-too-low] npm@X.Y.Z …` 标记行;否则返回 null。前端据此展示「本机 npm 版本过低」
|
|
10
|
+
* 的准确原因,避免该报错被误判成插件打包/分发问题。
|
|
11
|
+
*/
|
|
12
|
+
export declare function npmTooLowMarker(output: string, env?: NodeJS.ProcessEnv): string | null;
|
|
@@ -2,5 +2,6 @@
|
|
|
2
2
|
* 反查 repo(`owner/repo`)对应的官方 npm 包名;未命中、网络异常或超时返回 null。
|
|
3
3
|
* 返回 null 不代表仓库一定没有 npm 包,只代表本次未能确认 —— 调用方应保留
|
|
4
4
|
* 原有错误路径,反查只是额外的一次尝试。
|
|
5
|
+
* @param registry - npm 镜像源地址(空串 = 官方 https://registry.npmjs.org)
|
|
5
6
|
*/
|
|
6
|
-
export declare function resolveNpmPackage(repo: string): Promise<string | null>;
|
|
7
|
+
export declare function resolveNpmPackage(repo: string, registry?: string): Promise<string | null>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InstallResult, InstallTask } from './install-types.ts';
|
|
2
|
-
import type { LoaderHandle } from '
|
|
2
|
+
import type { LoaderHandle } from '../loader.ts';
|
|
3
3
|
/** Snapshot of a task (keeps the live object untouched by consumers). */
|
|
4
4
|
export declare function getTask(id: number): InstallTask | undefined;
|
|
5
5
|
export interface ActiveTaskInfo {
|
|
@@ -42,6 +42,8 @@ export declare function startPluginMutation(options: {
|
|
|
42
42
|
target: string;
|
|
43
43
|
timeoutMs?: number;
|
|
44
44
|
env?: NodeJS.ProcessEnv;
|
|
45
|
+
/** 全局 npm 安装(官方 `npm install -g <pkgs>`):非空时执行全局安装,不进任何 profile、无需宿主重启 */
|
|
46
|
+
globalNpm?: string[];
|
|
45
47
|
/** 待重启行的展示目标(owner/repo):卸载时用于把 npm 包名映射回仓库名 */
|
|
46
48
|
displayTarget?: string;
|
|
47
49
|
/** 运行中 loader:卸载成功后主动移除条目、立即生效(缺失时卸载仍需重启清理) */
|
|
@@ -79,4 +81,6 @@ export declare function runPluginMutation(options: {
|
|
|
79
81
|
displayTarget?: string;
|
|
80
82
|
/** 运行中 loader:卸载成功后主动移除条目、立即生效(缺失时卸载仍需重启清理) */
|
|
81
83
|
uninstallLoader?: LoaderHandle;
|
|
84
|
+
/** 全局 npm 安装(官方 `npm install -g <pkgs>`):非空时执行全局安装,不进任何 profile、无需宿主重启 */
|
|
85
|
+
globalNpm?: string[];
|
|
82
86
|
}): Promise<InstallResult>;
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DSH-Plugin Hub — the community plugin marketplace for DeepSeek Harness.
|
|
3
|
-
* Website: https://dsh-plugin.org
|
|
4
|
-
* GitHub: https://github.com/dshplugin/dsh-plugin-hub
|
|
5
|
-
*
|
|
6
|
-
* 运行中 loader 的读取与停用:卸载成功后对匹配条目做 live-disable,
|
|
7
|
-
* 使卸载立即生效(刷新页面不再加载已卸载的 client bundle,也无需重启)。
|
|
8
|
-
*/
|
|
9
1
|
/**
|
|
10
2
|
* 运行中 loader 的最小接口:卸载成功后即时移除条目用。
|
|
11
3
|
* 对应官方 `ctx.loader`(cordis-plugin-loader 的 Loader 服务)的读取面与移除面。
|
|
@@ -35,6 +27,12 @@ export declare function dumpLoaderEntries(loader: LoaderHandle | undefined): Arr
|
|
|
35
27
|
id: string;
|
|
36
28
|
name?: string;
|
|
37
29
|
}>;
|
|
30
|
+
/**
|
|
31
|
+
* 判断某 npm 包名是否已加载进运行中 loader(宽匹配:条目名存在别名变体)。
|
|
32
|
+
* 官方 `ctx.loader.entries()` 遍历整棵树(reference.md §1.3),条目名 == 插件作者声明的名字。
|
|
33
|
+
* 安装完成但未重启时条目不在 loader → 返回 false,UI 据此标「待重启/未加载」。
|
|
34
|
+
*/
|
|
35
|
+
export declare function isEntryLoaded(loader: LoaderHandle | undefined, name: string): boolean;
|
|
38
36
|
/**
|
|
39
37
|
* 卸载成功后从运行中 loader 停用指定包的条目。做法:按 name 扫描
|
|
40
38
|
* `loader.entries()`(含嵌套子树),对每个匹配条目
|
|
@@ -47,3 +45,11 @@ export declare function dumpLoaderEntries(loader: LoaderHandle | undefined): Arr
|
|
|
47
45
|
* 宿主关键包(@deepseek-ai/* 与本插件自身)一律跳过。
|
|
48
46
|
*/
|
|
49
47
|
export declare function removeLoadedEntry(loader: LoaderHandle, name: string): Promise<boolean>;
|
|
48
|
+
/**
|
|
49
|
+
* 判断某已安装包是否是真正的 dsh 插件(宿主会加载 / 值得提示「待重启」):
|
|
50
|
+
* 1) 包名已写进 profile 的 `dsh.profile.bundles`(宿主启动时按清单加载);
|
|
51
|
+
* 2) 或包内 package.json 声明了 dsh 配置(`dsh.bundle` / `dsh.profile` 等)或 dsh 相关 keywords。
|
|
52
|
+
* 两者都满足不了(如 GitHub 官方示例仓库 octocat/Hello-World)说明它不是 dsh 插件,
|
|
53
|
+
* 装上也不会被宿主加载——UI 据此不再提示「待重启」,避免误导用户反复重启一个不会生效的东西。
|
|
54
|
+
*/
|
|
55
|
+
export declare function isDshPlugin(profile: string, name: string): boolean;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type LogCategory = 'install' | 'uninstall' | 'update' | 'diagnostics' | 'settings' | 'system';
|
|
2
|
+
export type LogLevel = 'debug' | 'info' | 'success' | 'warn' | 'error';
|
|
3
|
+
export interface LogEntry {
|
|
4
|
+
/** 毫秒时间戳 */
|
|
5
|
+
at: number;
|
|
6
|
+
level: LogLevel;
|
|
7
|
+
category: LogCategory;
|
|
8
|
+
/** 机器可读事件码,如 install.start / settings.update */
|
|
9
|
+
event: string;
|
|
10
|
+
/** 人类可读描述 */
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
13
|
+
/** 日志保留上限:超过后在读取时裁剪为最近 MAX_LOG_LINES 条 */
|
|
14
|
+
export declare const MAX_LOG_LINES = 2000;
|
|
15
|
+
export interface ReadLogOptions {
|
|
16
|
+
/** 分页偏移(过滤后倒序) */
|
|
17
|
+
offset?: number;
|
|
18
|
+
limit?: number;
|
|
19
|
+
category?: LogCategory | 'all';
|
|
20
|
+
level?: LogLevel | 'all';
|
|
21
|
+
/** 关键词:大小写不敏感,匹配事件码或描述 */
|
|
22
|
+
query?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ReadLogResult {
|
|
25
|
+
/** 倒序(最新在前) */
|
|
26
|
+
entries: LogEntry[];
|
|
27
|
+
/** 过滤后的总条数(分页前) */
|
|
28
|
+
total: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 把用户填写的日志位置覆盖解析成实际文件路径:
|
|
32
|
+
* 以 .log 结尾视为文件本身,否则视为目录并在其中写 hub.log;相对路径按当前目录归一化。
|
|
33
|
+
*/
|
|
34
|
+
export declare function customLogFile(override: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* 真正默认的日志文件路径:`~/.dsh/profiles/<profile>/hub.log`(不受设置覆盖影响)。
|
|
37
|
+
* 弹窗「恢复默认」、目录选择器起点等需要「未自定义时的位置」都从这里取 ——
|
|
38
|
+
* 不同机器/平台(用户主目录不同)这个值都不同,必须动态计算而非写死。
|
|
39
|
+
*/
|
|
40
|
+
export declare function defaultLogFilePath(profile: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* 当前生效的日志文件路径:设置里填了 logPath 用自定义位置(目录或文件),
|
|
43
|
+
* 否则默认 `~/.dsh/profiles/<profile>/hub.log`。默认位置永远合法可用,无需用户干预。
|
|
44
|
+
*/
|
|
45
|
+
export declare function logFilePath(profile: string): string;
|
|
46
|
+
/** 追加一条系统日志(JSONL 一行一条)。 */
|
|
47
|
+
export declare function appendLog(profile: string, entry: LogEntry): void;
|
|
48
|
+
/** 读取系统日志(按时间正序,最新在末尾),超上限就地裁剪;支持过滤与分页。 */
|
|
49
|
+
export declare function readLog(profile: string, opts?: ReadLogOptions): ReadLogResult;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface ProbeResult {
|
|
2
|
+
/** 是否可达(HTTP 状态码 100–399) */
|
|
3
|
+
ok: boolean;
|
|
4
|
+
/** 毫秒耗时;失败为 null */
|
|
5
|
+
ms: number | null;
|
|
6
|
+
/** HTTP 状态码;失败为 null */
|
|
7
|
+
status: number | null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 操作系统级代理(macOS 系统网络设置 / Windows Internet 设置)。
|
|
11
|
+
* Node 内置 http(s) 不读系统代理,浏览器挂的代理 Node 看不见 —— 这里读出来
|
|
12
|
+
* 作为默认代理,保证「浏览器能开、安装/诊断就能通」。
|
|
13
|
+
* Linux 没有统一的系统代理入口,返回 null(交给环境变量 / 设置里的代理)。
|
|
14
|
+
*/
|
|
15
|
+
export declare function systemProxy(): string | null;
|
|
16
|
+
/**
|
|
17
|
+
* 探测一个 HTTPS 目标:proxy 非空注入给 curl 子进程走代理,否则直连。
|
|
18
|
+
* 供系统诊断 /diagnostics 使用;目标 URL 非法返回不可达。
|
|
19
|
+
*/
|
|
20
|
+
export declare function probeUrl(url: string, proxy: string, timeoutMs: number): Promise<ProbeResult>;
|
|
21
|
+
/**
|
|
22
|
+
* curl 子进程抓取响应体(与 probeUrl 同一套代理 env 注入)。
|
|
23
|
+
* 供服务端 /catalog 代理路由使用:目录/统计数据经此拉到服务端再转给浏览器,
|
|
24
|
+
* 使「目录数据请求走设置里的代理」与 npm / git 安装通道口径一致。
|
|
25
|
+
* 失败(curl 不存在 / 连接失败 / 超时 / 非零退出)时 ok=false,全程不抛错。
|
|
26
|
+
*/
|
|
27
|
+
export declare function fetchViaCurl(url: string, proxy: string, timeoutMs: number): Promise<{
|
|
28
|
+
ok: boolean;
|
|
29
|
+
body: string;
|
|
30
|
+
}>;
|
|
@@ -4,6 +4,8 @@ export declare function readProfileArg(fallback?: string): string;
|
|
|
4
4
|
export declare function profileDirectory(profile: string): string;
|
|
5
5
|
/** Build a safe explicit HTTPS Git target, or null when the repo is unsafe. */
|
|
6
6
|
export declare function githubTarget(repo: string): string | null;
|
|
7
|
+
export declare function installTargetOf(value: string): string;
|
|
8
|
+
export declare function globalNpmPackagesOf(value: string): string[] | null;
|
|
7
9
|
/** Extract an owner/repo identity from a catalog value or an installed Git spec. */
|
|
8
10
|
export declare function githubRepoOf(value: string): string | null;
|
|
9
11
|
/** Validate an npm package name (uninstall target grammar). */
|