dsh-github-flow 0.1.7 → 0.2.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.
- package/README.en.md +287 -69
- package/README.md +290 -68
- package/docs/architecture.md +71 -14
- package/docs/images/1.png +0 -0
- package/docs/images/2.png +0 -0
- package/docs/images/3.png +0 -0
- package/docs/images/4.png +0 -0
- package/docs/npm-publish-guide.md +12 -0
- package/lib/api/route-helpers.d.ts +47 -0
- package/lib/api/route-helpers.js +134 -0
- package/lib/api/routes-actions.d.ts +9 -0
- package/lib/api/routes-actions.js +115 -0
- package/lib/api/routes-lifecycle.d.ts +9 -0
- package/lib/api/routes-lifecycle.js +75 -0
- package/lib/api/routes-overview.d.ts +11 -0
- package/lib/api/routes-overview.js +227 -0
- package/lib/api/routes-proxy.d.ts +18 -0
- package/lib/api/routes-proxy.js +63 -0
- package/lib/api/routes.d.ts +17 -5
- package/lib/api/routes.js +18 -417
- package/lib/client.js +1179 -40
- package/lib/commands/gh.d.ts +2 -1
- package/lib/commands/gh.js +40 -3
- package/lib/executor.d.ts +17 -0
- package/lib/executor.js +40 -2
- package/lib/main.d.ts +12 -0
- package/lib/main.js +73 -4
- package/lib/proxy.d.ts +87 -0
- package/lib/proxy.js +275 -0
- package/lib/renderers/markdown.d.ts +24 -0
- package/lib/renderers/markdown.js +493 -0
- package/lib/tools/api.d.ts +4 -0
- package/lib/tools/api.js +14 -4
- package/lib/tools/help.d.ts +31 -0
- package/lib/tools/help.js +212 -0
- package/lib/tools/issue.d.ts +32 -1
- package/lib/tools/issue.js +94 -17
- package/lib/tools/pr.d.ts +22 -1
- package/lib/tools/pr.js +67 -15
- package/lib/tools/release.d.ts +89 -0
- package/lib/tools/release.js +219 -0
- package/lib/tools/repo.d.ts +13 -1
- package/lib/tools/repo.js +46 -14
- package/lib/tools/run.d.ts +22 -1
- package/lib/tools/run.js +74 -14
- package/lib/tools/secret.d.ts +60 -0
- package/lib/tools/secret.js +175 -0
- package/lib/tools/skill.d.ts +63 -0
- package/lib/tools/skill.js +146 -0
- package/lib/types.d.ts +57 -0
- package/lib/updater.d.ts +49 -0
- package/lib/updater.js +169 -0
- package/lib/upgrade-manager.d.ts +58 -0
- package/lib/upgrade-manager.js +309 -0
- package/lib/version.d.ts +13 -0
- package/lib/version.js +13 -0
- package/package.json +3 -2
- package/lib/client/icons.d.ts +0 -13
- package/lib/client/icons.js +0 -41
- package/lib/client/index.d.ts +0 -3
- package/lib/client/index.js +0 -36
- package/lib/client/panel.d.ts +0 -1
- package/lib/client/panel.js +0 -102
- package/lib/client/style.d.ts +0 -1
- package/lib/client/style.js +0 -555
package/lib/proxy.js
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 网络代理管理器:插件全部外联流量的唯一代理状态权威源。
|
|
3
|
+
*
|
|
4
|
+
* 设计依据(docs/plans/proxy-support-plan.md):
|
|
5
|
+
* - gh 子进程链路(9 工具 + Web API 数据)经标准环境变量 HTTPS_PROXY/HTTP_PROXY/NO_PROXY 驱动,
|
|
6
|
+
* 实测 gh CLI 完整遵循 Go net/http 约定;
|
|
7
|
+
* - Node fetch 链路(updater 的 npm registry 探测)无视环境变量,需 undici ProxyAgent
|
|
8
|
+
* dispatcher 驱动——插件自身不依赖 undici,运行时从 DSH 宿主 require 链动态解析;
|
|
9
|
+
* - 不走 Cordis Config 承载:配置变更会触发插件全量 reload(工具注销重注册),
|
|
10
|
+
* 而代理是高频试错型设置。持久化用独立文件,保存即生效、零重载。
|
|
11
|
+
* - 未设置时零干预:不注入任何环境变量、不动 fetch dispatcher,
|
|
12
|
+
* gh 与 fetch 自然回落到系统环境变量(若宿主本身配置了 HTTPS_PROXY)或直连。
|
|
13
|
+
*/
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import os from 'node:os';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { createRequire } from 'node:module';
|
|
18
|
+
const DEFAULT_CONFIG = {
|
|
19
|
+
url: '',
|
|
20
|
+
noProxy: 'localhost,127.0.0.1',
|
|
21
|
+
applyToPackageManager: true,
|
|
22
|
+
};
|
|
23
|
+
/** 持久化路径:~/.dsh/data/dsh-github-flow/proxy.json(与 DSH 用户数据目录规范对齐) */
|
|
24
|
+
function resolveStorePath() {
|
|
25
|
+
return path.join(os.homedir(), '.dsh', 'data', 'dsh-github-flow', 'proxy.json');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 校验用户输入的代理地址。
|
|
29
|
+
* 为什么白名单 scheme:地址将注入到全部 gh/npm 子进程环境变量并可能驱动 fetch dispatcher,
|
|
30
|
+
* 放行 file://、gopher:// 等怪异 scheme 属于扩大攻击面;内嵌凭据会随进程 env 暴露,禁止保存。
|
|
31
|
+
*/
|
|
32
|
+
export function validateProxyUrl(input) {
|
|
33
|
+
if (typeof input !== 'string' || !input.trim())
|
|
34
|
+
return { ok: false, error: '代理地址不能为空' };
|
|
35
|
+
// 控制字符预处理:WHATWG URL 会静默剥离 NUL 等控制符后继续解析(实测 '\x00http://x'
|
|
36
|
+
// 被接受且 hostname='x'),但注入到子进程 env 的必须是原始串——剥离后 env 与
|
|
37
|
+
// 校验结果不一致,且控制符在环境变量里属滥用输入。直接前置拒绝最干净。
|
|
38
|
+
if (/[\x00-\x1f\x7f]/.test(input))
|
|
39
|
+
return { ok: false, error: '代理地址包含非法控制字符' };
|
|
40
|
+
const raw = input.trim();
|
|
41
|
+
let parsed;
|
|
42
|
+
try {
|
|
43
|
+
// WHATWG URL 原生识别 socks5/socks5h scheme(实测),无需改写即可完成结构校验;
|
|
44
|
+
// 返回给调用方的始终保持用户原始串——注入 env 的值必须与用户输入一致。
|
|
45
|
+
parsed = new URL(raw);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return { ok: false, error: '代理地址格式无效,应为 http://host:port 或 socks5://host:port' };
|
|
49
|
+
}
|
|
50
|
+
// socks5h:// 与 socks5:// 的本质差异在 DNS 归属:socks5 在本机解析(系统 DNS 可能被污染),
|
|
51
|
+
// socks5h 把域名原样交给代理端解析(抗污染更强)。两者均为合法代理形态,一并放行。
|
|
52
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:' && parsed.protocol !== 'socks5:' && parsed.protocol !== 'socks5h:') {
|
|
53
|
+
return { ok: false, error: `不支持的协议 ${parsed.protocol},仅允许 http / https / socks5 / socks5h` };
|
|
54
|
+
}
|
|
55
|
+
if (parsed.username || parsed.password) {
|
|
56
|
+
return { ok: false, error: '暂不支持内嵌凭据的代理地址(凭据会随子进程环境变量暴露)' };
|
|
57
|
+
}
|
|
58
|
+
// WHATWG URL 对 "http://" 与 "socks5://" 这类空 host 会静默填空串 hostname,
|
|
59
|
+
// 必须显式拦截;超长主机名同样拒绝(正常代理地址不可能 4KB+,属滥用输入)。
|
|
60
|
+
if (!parsed.hostname)
|
|
61
|
+
return { ok: false, error: '代理地址缺少主机名' };
|
|
62
|
+
if (parsed.hostname.length > 253)
|
|
63
|
+
return { ok: false, error: '代理主机名异常超长' };
|
|
64
|
+
return { ok: true, url: raw };
|
|
65
|
+
}
|
|
66
|
+
/** 归一化 NO_PROXY:强制 localhost,127.0.0.1 置首(本机 Web API 回环请求绝不能被代理劫持) */
|
|
67
|
+
function normalizeNoProxy(input) {
|
|
68
|
+
const raw = typeof input === 'string' ? input : '';
|
|
69
|
+
const parts = raw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
70
|
+
for (const must of ['localhost', '127.0.0.1']) {
|
|
71
|
+
if (!parts.some((p) => p.toLowerCase() === must))
|
|
72
|
+
parts.unshift(must);
|
|
73
|
+
}
|
|
74
|
+
return parts.join(',');
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 三级降级解析 undici(仅 fetch dispatcher 需要;gh/npm 子进程链路完全无关)。
|
|
78
|
+
* 1. DSH 宿主运行时 require 链(desktop profile 实测可用);
|
|
79
|
+
* 2. 未来 Node 开放 node:undici 内置模块(22.18 仍为 ERR_UNKNOWN_BUILTIN_MODULE);
|
|
80
|
+
* 3. 均失败 → 返回 null,npm 探测链路保持直连,不炸任何主流程。
|
|
81
|
+
*/
|
|
82
|
+
function resolveUndici() {
|
|
83
|
+
try {
|
|
84
|
+
const cordisPkg = path.join(os.homedir(), '.dsh', 'profiles', 'desktop', 'node_modules', '@deepseek-ai', 'cordis', 'package.json');
|
|
85
|
+
const req = createRequire(cordisPkg);
|
|
86
|
+
const u = req('undici');
|
|
87
|
+
if (u && typeof u.ProxyAgent === 'function' && typeof u.setGlobalDispatcher === 'function' && typeof u.getGlobalDispatcher === 'function')
|
|
88
|
+
return u;
|
|
89
|
+
}
|
|
90
|
+
catch { /* 宿主路径不存在(web/CLI profile)→ 走下一级 */ }
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
export class ProxyManager {
|
|
94
|
+
config = { ...DEFAULT_CONFIG };
|
|
95
|
+
lastProbe;
|
|
96
|
+
fetchProxyAvailable = false;
|
|
97
|
+
activeDispatcherUrl = null;
|
|
98
|
+
/** 安装代理 dispatcher 前的进程默认 dispatcher 快照(关闭代理时恢复,防残留) */
|
|
99
|
+
defaultDispatcher = null;
|
|
100
|
+
constructor(presetUrl) {
|
|
101
|
+
this.load(presetUrl);
|
|
102
|
+
this.applyFetchDispatcher();
|
|
103
|
+
}
|
|
104
|
+
/** 读取持久化文件;不存在时回落 cordis.patch.yml 预置值;再无则保持默认直连 */
|
|
105
|
+
load(presetUrl) {
|
|
106
|
+
try {
|
|
107
|
+
const storePath = resolveStorePath();
|
|
108
|
+
if (fs.existsSync(storePath)) {
|
|
109
|
+
const raw = JSON.parse(fs.readFileSync(storePath, 'utf8'));
|
|
110
|
+
if (raw && typeof raw === 'object') {
|
|
111
|
+
const cfg = typeof raw.config === 'object' && raw.config ? raw.config : raw;
|
|
112
|
+
const url = typeof cfg.url === 'string' ? cfg.url.trim() : '';
|
|
113
|
+
// 文件中的 url 仍走校验:文件可能被手改出非法值,持久化不能成为绕过校验的后门
|
|
114
|
+
this.config = {
|
|
115
|
+
url: validateProxyUrl(url).ok ? url : '',
|
|
116
|
+
noProxy: normalizeNoProxy(cfg.noProxy),
|
|
117
|
+
applyToPackageManager: cfg.applyToPackageManager !== false,
|
|
118
|
+
};
|
|
119
|
+
if (typeof raw.lastProbe === 'object' && raw.lastProbe)
|
|
120
|
+
this.lastProbe = raw.lastProbe;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch { /* 文件损坏/不可读 → 视为未配置,不阻断启动 */ }
|
|
126
|
+
// 无持久化文件:使用 Cordis Config 预置(若管理员在 cordis.patch.yml 配置且合法)
|
|
127
|
+
if (presetUrl && validateProxyUrl(presetUrl).ok) {
|
|
128
|
+
this.config = { ...DEFAULT_CONFIG, url: presetUrl.trim() };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 原子写持久化文件:先写临时文件再 rename,杜绝并发/崩溃产生半截 JSON。
|
|
133
|
+
* 写盘失败(目录权限/沙箱)不抛出:代理是「尽力持久化」语义——内存态已生效,
|
|
134
|
+
* 仅丢失重启恢复能力;用一次 console.warn 提示用户,UI 仍能正常工作。
|
|
135
|
+
*/
|
|
136
|
+
persist() {
|
|
137
|
+
try {
|
|
138
|
+
const storePath = resolveStorePath();
|
|
139
|
+
const dir = path.dirname(storePath);
|
|
140
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
141
|
+
const tmp = `${storePath}.${process.pid}.tmp`;
|
|
142
|
+
fs.writeFileSync(tmp, JSON.stringify({ config: this.config, lastProbe: this.lastProbe }, null, 2), 'utf8');
|
|
143
|
+
fs.renameSync(tmp, storePath);
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
console.warn(`[dsh-github-flow] 代理配置写盘失败(本次会话仍生效,重启后需重新设置): ${e?.message || e}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 生成注入子进程的代理环境变量。
|
|
151
|
+
* 未启用时返回 {}——绝不覆盖 process.env 中既有的系统级代理设置(用户不设插件代理
|
|
152
|
+
* 时 gh 自然继承宿主环境变量,即「默认走系统设置」语义)。
|
|
153
|
+
* socks5 对 gh(Go runtime)原生有效;对 npm 同样有效(pacote/agent-base 支持)。
|
|
154
|
+
*/
|
|
155
|
+
getEnv(scope = 'gh') {
|
|
156
|
+
if (!this.config.url)
|
|
157
|
+
return {};
|
|
158
|
+
if (scope === 'package-manager' && !this.config.applyToPackageManager)
|
|
159
|
+
return {};
|
|
160
|
+
return {
|
|
161
|
+
HTTPS_PROXY: this.config.url,
|
|
162
|
+
HTTP_PROXY: this.config.url,
|
|
163
|
+
https_proxy: this.config.url,
|
|
164
|
+
http_proxy: this.config.url,
|
|
165
|
+
NO_PROXY: this.config.noProxy,
|
|
166
|
+
no_proxy: this.config.noProxy,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* 应用 fetch dispatcher(npm registry 探测链路)。
|
|
171
|
+
* undici 的 ProxyAgent 不支持 socks5:该场景显式标记不可用并保持直连,
|
|
172
|
+
* UI 需如实提示差异(GitHub 链路正常走 socks5),不做静默降级。
|
|
173
|
+
* dispatcher 按 url 缓存重建,url 未变化时零开销。
|
|
174
|
+
*
|
|
175
|
+
* 关闭代理时必须恢复默认全局 dispatcher:setGlobalDispatcher 是进程级副作用,
|
|
176
|
+
* 若只清空本实例状态而不回滚 dispatcher,此前安装的 ProxyAgent 会永久残留,
|
|
177
|
+
* 「启用→关闭」后 npm 探测仍走死代理直到进程重启。首次安装前先快照原 dispatcher。
|
|
178
|
+
*/
|
|
179
|
+
applyFetchDispatcher() {
|
|
180
|
+
const url = this.config.url;
|
|
181
|
+
if (!url || url.startsWith('socks5://') || url.startsWith('socks5h://')) {
|
|
182
|
+
// 回滚残留的代理 dispatcher(仅在我们曾经安装过时)
|
|
183
|
+
if (this.activeDispatcherUrl !== null) {
|
|
184
|
+
const undici = resolveUndici();
|
|
185
|
+
if (undici && this.defaultDispatcher) {
|
|
186
|
+
try {
|
|
187
|
+
undici.setGlobalDispatcher(this.defaultDispatcher);
|
|
188
|
+
}
|
|
189
|
+
catch { /* 回滚失败保持现状,下次保存重试 */ }
|
|
190
|
+
}
|
|
191
|
+
this.activeDispatcherUrl = null;
|
|
192
|
+
}
|
|
193
|
+
this.fetchProxyAvailable = false;
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
const undici = resolveUndici();
|
|
197
|
+
if (!undici) {
|
|
198
|
+
this.fetchProxyAvailable = false;
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
// url 变更才重建 dispatcher:高频 apply()(如 UI 反复保存)不会泄漏 agent 连接池
|
|
203
|
+
if (this.activeDispatcherUrl !== url) {
|
|
204
|
+
if (!this.defaultDispatcher) {
|
|
205
|
+
this.defaultDispatcher = undici.getGlobalDispatcher();
|
|
206
|
+
}
|
|
207
|
+
undici.setGlobalDispatcher(new undici.ProxyAgent(url));
|
|
208
|
+
this.activeDispatcherUrl = url;
|
|
209
|
+
}
|
|
210
|
+
this.fetchProxyAvailable = true;
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
this.fetchProxyAvailable = false;
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/** 保存新配置并立即生效(写文件 + 重应用 dispatcher),返回完整状态快照 */
|
|
219
|
+
save(input) {
|
|
220
|
+
if (input.url !== undefined && input.url !== '') {
|
|
221
|
+
const v = validateProxyUrl(input.url);
|
|
222
|
+
if (!v.ok)
|
|
223
|
+
return { ok: false, error: v.error, state: this.snapshot() };
|
|
224
|
+
}
|
|
225
|
+
const nextUrl = input.url !== undefined ? (typeof input.url === 'string' ? input.url.trim() : '') : this.config.url;
|
|
226
|
+
this.config = {
|
|
227
|
+
url: nextUrl,
|
|
228
|
+
noProxy: normalizeNoProxy(input.noProxy !== undefined ? input.noProxy : this.config.noProxy),
|
|
229
|
+
applyToPackageManager: typeof input.applyToPackageManager === 'boolean' ? input.applyToPackageManager : this.config.applyToPackageManager,
|
|
230
|
+
};
|
|
231
|
+
this.applyFetchDispatcher();
|
|
232
|
+
this.lastProbe = undefined; // 配置变更后旧探测结果失效
|
|
233
|
+
this.persist();
|
|
234
|
+
return { ok: true, state: this.snapshot() };
|
|
235
|
+
}
|
|
236
|
+
/** 经当前代理配置探测 GitHub API 连通性(2.5s 超时)。失败不回滚保存——离线预配置是合法场景 */
|
|
237
|
+
async probe() {
|
|
238
|
+
const started = Date.now();
|
|
239
|
+
if (!this.config.url) {
|
|
240
|
+
this.lastProbe = { ok: false, at: started, error: '未配置代理地址(当前为系统默认直连)' };
|
|
241
|
+
return this.lastProbe;
|
|
242
|
+
}
|
|
243
|
+
const controller = new AbortController();
|
|
244
|
+
const timer = setTimeout(() => controller.abort(), 2_500);
|
|
245
|
+
try {
|
|
246
|
+
const res = await fetch('https://api.github.com/rate_limit', {
|
|
247
|
+
signal: controller.signal,
|
|
248
|
+
headers: { 'User-Agent': 'dsh-github-flow-proxy-probe' },
|
|
249
|
+
});
|
|
250
|
+
// 探测走的是 Node fetch:只有 http/https 代理且 undici 可用时才真经代理。
|
|
251
|
+
// socks5 场景 fetch 仍直连——探测结果仅代表「GitHub 可达性」,代理有效性以 gh 链路为准。
|
|
252
|
+
this.lastProbe = res.ok
|
|
253
|
+
? { ok: true, at: started, latencyMs: Date.now() - started }
|
|
254
|
+
: { ok: false, at: started, error: `GitHub API 响应 ${res.status}` };
|
|
255
|
+
}
|
|
256
|
+
catch (e) {
|
|
257
|
+
this.lastProbe = { ok: false, at: started, error: String(e?.cause?.message || e?.message || e).slice(0, 200) };
|
|
258
|
+
}
|
|
259
|
+
finally {
|
|
260
|
+
clearTimeout(timer);
|
|
261
|
+
try {
|
|
262
|
+
this.persist();
|
|
263
|
+
}
|
|
264
|
+
catch { /* 探测结果写盘失败不影响内存态 */ }
|
|
265
|
+
}
|
|
266
|
+
return this.lastProbe;
|
|
267
|
+
}
|
|
268
|
+
snapshot() {
|
|
269
|
+
return {
|
|
270
|
+
config: { ...this.config, noProxy: this.config.noProxy },
|
|
271
|
+
fetchProxyAvailable: this.fetchProxyAvailable,
|
|
272
|
+
lastProbe: this.lastProbe ? { ...this.lastProbe } : undefined,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH GitHub Flow - Native Markdown 格式化渲染引擎
|
|
3
|
+
*
|
|
4
|
+
* 核心目标:
|
|
5
|
+
* 1. 终结机械多行 JSON 输出,为列表和详情产出高密度 Markdown 表格与卡片;
|
|
6
|
+
* 2. 节省 70%+ 的会话上下文 Token,极大加速长对话推理速度;
|
|
7
|
+
* 3. 嵌入可点击超链接与直观的状态药丸 Emoji,提升人类与模型阅读体验;
|
|
8
|
+
* 4. 严谨防御 Markdown 表格竖线破坏、缺失值回退与异常对象降级。
|
|
9
|
+
*/
|
|
10
|
+
/** 安全清洗表格单元格文本:转义竖线,压缩换行,限制最大长度 */
|
|
11
|
+
export declare function sanitizeCell(text: any, maxLen?: number): string;
|
|
12
|
+
/** 格式化 PR / Issue 状态药丸徽章 */
|
|
13
|
+
export declare function formatStateBadge(state?: string, isDraft?: boolean): string;
|
|
14
|
+
/** 格式化 CI 工作流状态徽章 */
|
|
15
|
+
export declare function formatCiBadge(status?: string, conclusion?: string): string;
|
|
16
|
+
/** 格式化合并冲突状态 */
|
|
17
|
+
export declare function formatConflictBadge(status?: string): string;
|
|
18
|
+
export declare function renderPrMarkdown(args: any, value: any): string;
|
|
19
|
+
export declare function renderIssueMarkdown(args: any, value: any): string;
|
|
20
|
+
export declare function renderRunMarkdown(args: any, value: any): string;
|
|
21
|
+
export declare function renderRepoMarkdown(args: any, value: any): string;
|
|
22
|
+
export declare function formatReleaseBadge(isDraft?: boolean, isPrerelease?: boolean): string;
|
|
23
|
+
export declare function renderReleaseMarkdown(args: any, value: any): string;
|
|
24
|
+
export declare function renderSkillMarkdown(args: any, value: any): string;
|