dsh-wsl-tool 1.10.9 → 1.10.10
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.md +36 -0
- package/README.zh-CN.md +26 -0
- package/index.js +178 -0
- package/lib/client.js +213 -4
- package/package.json +1 -2
- package/screenshots.json +1 -1
- package/assets/market-preview.html +0 -217
- package/assets/market-preview.png +0 -0
- package/assets/screenshot-1.png +0 -0
package/README.md
CHANGED
|
@@ -168,6 +168,15 @@ The settings surface needs `@deepseek-ai/schemastery`, which the plugin declares
|
|
|
168
168
|
an optional peer dependency: without it the three tools still run on their defaults
|
|
169
169
|
and only the panel is missing.
|
|
170
170
|
|
|
171
|
+
At the bottom of the panel there is one feedback entry, next to it a short
|
|
172
|
+
submission guide, and — see [Feedback](#feedback) — a 「复制插件信息」 button. The
|
|
173
|
+
button asks the host half on `/dsh-wsl-tool/info` for this plugin's own facts (name,
|
|
174
|
+
version and repository read from its manifest, Node, platform), the DSH build it is
|
|
175
|
+
running inside, and the WSL facts `wsl-env` reports (default distribution, kernel,
|
|
176
|
+
capability flags) — then copies the result together with the effective
|
|
177
|
+
configuration. Nothing here is a string a release has to remember to edit, and a
|
|
178
|
+
probe that fails says so instead of inventing a value.
|
|
179
|
+
|
|
171
180
|
## Optional: a WSL terminal in the sidebar
|
|
172
181
|
|
|
173
182
|
The desktop app's sidebar terminal can open WSL instead of a Windows shell. It is
|
|
@@ -427,3 +436,30 @@ documented in [`PUBLISHING.md`](PUBLISHING.md).
|
|
|
427
436
|
|
|
428
437
|
The repository carries the `dsh-plugin` topic and is listed under the `wsl`
|
|
429
438
|
category of the awesome-dsh-plugin community list.
|
|
439
|
+
|
|
440
|
+
## Feedback
|
|
441
|
+
|
|
442
|
+
The sidebar panel ends with one feedback entry, and it sends nothing by itself.
|
|
443
|
+
|
|
444
|
+
- **A bug or a concrete request** →
|
|
445
|
+
[open an issue](https://github.com/XINY11451/dsh-wsl/issues/new/choose). The
|
|
446
|
+
templates ask for the environment once, which is the difference between a report
|
|
447
|
+
that can be reproduced and a round trip.
|
|
448
|
+
- **Usage questions and ideas** →
|
|
449
|
+
[Discussions](https://github.com/XINY11451/dsh-wsl/discussions).
|
|
450
|
+
- **GitHub unreachable?** The panel's 「复制插件信息」 button asks the host half for
|
|
451
|
+
this package's own facts — name, version and repository straight out of its
|
|
452
|
+
`package.json`, plus Node, the platform and the effective configuration — and
|
|
453
|
+
copies them, together with the switch states the panel is already showing. Paste
|
|
454
|
+
that into the issue's 「补充」 field and only the prose is left to write. The
|
|
455
|
+
submission guide sits next to the button, in the panel.
|
|
456
|
+
|
|
457
|
+
Everything in that block is text you can read and edit before it goes anywhere: no
|
|
458
|
+
paths, host names or credentials are collected, and the plugin makes no request of
|
|
459
|
+
its own — the route the button reads is served by the local host, not the network.
|
|
460
|
+
Problems that belong elsewhere go to their own repositories — DSH itself →
|
|
461
|
+
[deepseek-harness](https://github.com/deepseek-ai/deepseek-harness/issues), the
|
|
462
|
+
market UI →
|
|
463
|
+
[dsh-market](https://github.com/dsh-market/dsh-market/issues), the catalog listing →
|
|
464
|
+
[awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/issues).
|
|
465
|
+
The full routing table is in [SUPPORT.md](SUPPORT.md).
|
package/README.zh-CN.md
CHANGED
|
@@ -143,6 +143,12 @@ DSH_SUBPROCESS_LOCAL=/path/to/dsh/node_modules npm run test:real
|
|
|
143
143
|
这个设置界面需要 `@deepseek-ai/schemastery`(插件把它声明为可选 peer 依赖):没有它三个工具照常按
|
|
144
144
|
默认值工作,只是没有面板。
|
|
145
145
|
|
|
146
|
+
面板**最底部**是一个反馈入口,旁边跟着一段简短的提交指南,以及一个「复制插件信息」按钮(详见
|
|
147
|
+
[反馈](#反馈))。按钮会向宿主半的 `/dsh-wsl-tool/info` 索取本插件自己的信息 —— 包名、版本、仓库
|
|
148
|
+
(都从 `package.json` 读出)、Node 与平台 —— 外加**它正运行在哪个 DSH 构建里**、以及 `wsl-env`
|
|
149
|
+
那一套 WSL 事实(默认发行版、内核、能力标记),然后连同当前生效的配置一起复制出来。
|
|
150
|
+
这些值没有一个是"发版时要记得手改"的字符串;探测失败时它写明失败,绝不编造。
|
|
151
|
+
|
|
146
152
|
## 可选:在侧边栏开一个 WSL 终端
|
|
147
153
|
|
|
148
154
|
桌面版侧边栏终端可以开 WSL 而不是 Windows shell。这是**可选**的 —— 装插件**不会**改你终端默认
|
|
@@ -387,3 +393,23 @@ npm run sync -- /path/to/profiles/<profile>/node_modules/<你的依赖名>
|
|
|
387
393
|
## 收录
|
|
388
394
|
|
|
389
395
|
本仓库带有 `dsh-plugin` topic,并已提交至 awesome-dsh-plugin 社区列表的 `wsl` 分类。
|
|
396
|
+
|
|
397
|
+
## 反馈
|
|
398
|
+
|
|
399
|
+
侧边栏面板最后一段就是反馈入口,它**不会自己发送任何东西**。
|
|
400
|
+
|
|
401
|
+
- **缺陷或明确的需求** → [开一个 Issue](https://github.com/XINY11451/dsh-wsl/issues/new/choose)。
|
|
402
|
+
模板会问一次环境,这决定了报告能不能被复现、要不要多来回一轮。
|
|
403
|
+
- **用法问题与想法** → [Discussions](https://github.com/XINY11451/dsh-wsl/discussions)。
|
|
404
|
+
- **GitHub 打不开?** 面板里的「复制插件信息」按钮会向宿主半索取这个包自己的信息 ——
|
|
405
|
+
包名、版本、仓库(直接读 `package.json`),加上 Node、平台与当前生效的配置 ——
|
|
406
|
+
连同面板正显示着的开关状态一起复制出来。粘进 Issue 的「补充」栏即可,只剩正文要自己写。
|
|
407
|
+
提交指南就在按钮旁边,也在面板里。
|
|
408
|
+
|
|
409
|
+
那段文本在发出去之前你都能看清、都能改:不采集路径、主机名或任何凭据,插件也不对外发起请求 ——
|
|
410
|
+
按钮读的那个路由由本机宿主提供,不走网络。
|
|
411
|
+
属于别处的问题请发到各自的仓库 —— DSH 本体 →
|
|
412
|
+
[deepseek-harness](https://github.com/deepseek-ai/deepseek-harness/issues),市场界面 →
|
|
413
|
+
[dsh-market](https://github.com/dsh-market/dsh-market/issues),收录与目录 →
|
|
414
|
+
[awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/issues)。
|
|
415
|
+
完整分流表见 [SUPPORT.md](SUPPORT.md)。
|
package/index.js
CHANGED
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
// A configuration change is a mount, not something a running session re-reads:
|
|
22
22
|
// every switch below is consulted once, in `apply`.
|
|
23
23
|
|
|
24
|
+
import { readFileSync } from 'node:fs'
|
|
25
|
+
import { dirname, join } from 'node:path'
|
|
26
|
+
|
|
27
|
+
import { CAPABILITY_PROBE, capabilityLines, parseFacts } from './lib/diagnostics.js'
|
|
28
|
+
import { parseDefaultDistro } from './lib/tools/wsl-env.js'
|
|
24
29
|
import { resolveConfig } from './lib/config.js'
|
|
25
30
|
import { pickSchemaBuilder } from './lib/schema.js'
|
|
26
31
|
import { createRunner } from './lib/runner.js'
|
|
@@ -92,6 +97,130 @@ export const Config = Schema?.object({
|
|
|
92
97
|
timeoutMs: Schema.number().default(0).description('默认命令超时毫秒数;0 表示用内置默认(也可用 DSH_WSL_TIMEOUT_MS)。').volatile(),
|
|
93
98
|
}).description('dsh-wsl 的功能开关与默认值。')
|
|
94
99
|
|
|
100
|
+
/**
|
|
101
|
+
* The read-only route the browser half fetches for 「复制插件信息」. Namespaced under
|
|
102
|
+
* the published package name so it cannot collide with another plugin's route.
|
|
103
|
+
*/
|
|
104
|
+
const INFO_ROUTE = '/dsh-wsl-tool/info'
|
|
105
|
+
|
|
106
|
+
/** Read this package's own manifest once; a broken manifest must not break the route. */
|
|
107
|
+
let manifestCache
|
|
108
|
+
function readManifest() {
|
|
109
|
+
if (manifestCache !== undefined) return manifestCache
|
|
110
|
+
try {
|
|
111
|
+
manifestCache = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'))
|
|
112
|
+
} catch {
|
|
113
|
+
manifestCache = null
|
|
114
|
+
}
|
|
115
|
+
return manifestCache
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Only a DSH package counts as the application: never this plugin's own manifest. */
|
|
119
|
+
const DSH_PACKAGE = /^@deepseek-ai\/dsh(?:-desktop|-desktop-host)?$/
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The DSH build this plugin is running inside, best effort.
|
|
123
|
+
*
|
|
124
|
+
* There is no environment variable for it — measured on the desktop build:
|
|
125
|
+
* `DSH_HOME`, `DSH_PROFILE`, `DSH_PROFILE_DIR`, `DSH_SESSION_ID`, `DSH_SHELL` and
|
|
126
|
+
* `DSH_WEB_URL`, and no version. So it is read from the application's own manifest
|
|
127
|
+
* instead: the host process is `<install>\DeepSeek Harness.exe`, whose asar sits at
|
|
128
|
+
* `resources/app.asar`, and the CLI entry in `argv[1]` leads into the same tree. Both
|
|
129
|
+
* routes are tried, and a candidate is accepted only when its manifest belongs to a
|
|
130
|
+
* DSH package, so a wrong guess degrades to "unreadable" rather than to a wrong
|
|
131
|
+
* version number.
|
|
132
|
+
*/
|
|
133
|
+
function dshVersion() {
|
|
134
|
+
const candidates = []
|
|
135
|
+
if (typeof process.execPath === 'string' && process.execPath !== '') {
|
|
136
|
+
const install = dirname(process.execPath)
|
|
137
|
+
candidates.push(join(install, 'resources', 'app.asar', 'package.json'))
|
|
138
|
+
candidates.push(join(install, 'resources', 'app', 'package.json'))
|
|
139
|
+
}
|
|
140
|
+
if (typeof process.argv[1] === 'string' && process.argv[1] !== '') {
|
|
141
|
+
let dir = dirname(process.argv[1])
|
|
142
|
+
for (let level = 0; level < 4; level += 1) {
|
|
143
|
+
candidates.push(join(dir, 'package.json'))
|
|
144
|
+
dir = dirname(dir)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for (const candidate of candidates) {
|
|
148
|
+
try {
|
|
149
|
+
const manifest = JSON.parse(readFileSync(candidate, 'utf8'))
|
|
150
|
+
if (typeof manifest?.name !== 'string' || !DSH_PACKAGE.test(manifest.name)) continue
|
|
151
|
+
if (typeof manifest.version === 'string' && manifest.version !== '') {
|
|
152
|
+
return { name: manifest.name, version: manifest.version }
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
// Missing, unreadable or not JSON: the next candidate's turn.
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The WSL facts worth copying — the default distribution, the kernel and the
|
|
163
|
+
* capability flags that `wsl-env` reports. Probes run concurrently (one WSL round
|
|
164
|
+
* trip each) and every one of them is allowed to fail: a machine without WSL still
|
|
165
|
+
* produces the rest of the block, and the copied text never invents a value.
|
|
166
|
+
*/
|
|
167
|
+
async function wslFacts(runner, config) {
|
|
168
|
+
const attempt = (promise) => Promise.resolve(promise).catch(() => null)
|
|
169
|
+
const opts = { distro: undefined, timeoutMs: config.internalTimeoutMs, exec: undefined }
|
|
170
|
+
const [uname, list, caps] = await Promise.all([
|
|
171
|
+
attempt(runner.runWsl('uname -srm', opts)),
|
|
172
|
+
attempt(runner.spawnWsl(['wsl.exe', '-l', '-v'], config.internalTimeoutMs)),
|
|
173
|
+
attempt(runner.runWsl(CAPABILITY_PROBE, opts)),
|
|
174
|
+
])
|
|
175
|
+
const facts = {
|
|
176
|
+
defaultDistro: list !== null && list.exitCode === 0 ? parseDefaultDistro(list.stdout) : null,
|
|
177
|
+
kernel: uname !== null && uname.exitCode === 0 ? uname.stdout.trim() : null,
|
|
178
|
+
capabilities: caps !== null && caps.exitCode === 0 ? capabilityLines(parseFacts(caps.stdout)) : null,
|
|
179
|
+
}
|
|
180
|
+
if (facts.defaultDistro === null && facts.kernel === null && facts.capabilities === null) return null
|
|
181
|
+
return facts
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* This plugin's own facts, as the panel copies them out.
|
|
186
|
+
*
|
|
187
|
+
* The version comes from the manifest next to this file rather than from a string
|
|
188
|
+
* written here, so a release cannot forget to update it. Everything else is the
|
|
189
|
+
* effective configuration — the same resolved object the tools read — so the copied
|
|
190
|
+
* block cannot disagree with the running plugin. `dsh` and `wsl` are the two facts
|
|
191
|
+
* the browser half cannot reach on its own, and both degrade to `null` rather than
|
|
192
|
+
* to a guess.
|
|
193
|
+
*
|
|
194
|
+
* No paths, host names or credentials: this route is served by the local web server
|
|
195
|
+
* and a browser (or anything else on the machine) can fetch it.
|
|
196
|
+
*/
|
|
197
|
+
async function selfInfo(config, runner) {
|
|
198
|
+
const manifest = readManifest()
|
|
199
|
+
const repository = typeof manifest?.repository?.url === 'string'
|
|
200
|
+
? manifest.repository.url.replace(/^git\+/, '')
|
|
201
|
+
: null
|
|
202
|
+
return {
|
|
203
|
+
name: manifest?.name ?? 'dsh-wsl-tool',
|
|
204
|
+
version: manifest?.version ?? null,
|
|
205
|
+
repository,
|
|
206
|
+
node: process.version,
|
|
207
|
+
platform: process.platform,
|
|
208
|
+
arch: process.arch,
|
|
209
|
+
dsh: dshVersion(),
|
|
210
|
+
wsl: await wslFacts(runner, config),
|
|
211
|
+
config: {
|
|
212
|
+
tools: { ...config.tools },
|
|
213
|
+
backgroundJobs: config.backgroundJobs,
|
|
214
|
+
translatePaths: config.translatePaths,
|
|
215
|
+
startInSessionWorkspace: config.startInSessionWorkspace,
|
|
216
|
+
dangerGuard: config.dangerGuard,
|
|
217
|
+
distro: config.distro,
|
|
218
|
+
commandTimeoutMs: config.commandTimeoutMs,
|
|
219
|
+
maxOutputBytes: config.maxOutputBytes,
|
|
220
|
+
},
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
95
224
|
export function apply(ctx, settings = {}) {
|
|
96
225
|
// Resolved once per mount. Behaviour reads this object at call time, so the
|
|
97
226
|
// volatile-update hook below can change it in place without remounting.
|
|
@@ -104,6 +233,55 @@ export function apply(ctx, settings = {}) {
|
|
|
104
233
|
if (config.tools.path) ctx.tools.register(createWslPathTool({ config, runner }))
|
|
105
234
|
if (config.tools.env) ctx.tools.register(createWslEnvTool({ config, runner }))
|
|
106
235
|
|
|
236
|
+
// ---------------------------------------------------------------- self info
|
|
237
|
+
//
|
|
238
|
+
// The panel's 「复制插件信息」 needs facts this half already has and the browser
|
|
239
|
+
// half cannot know: the package name and version (read from this plugin's own
|
|
240
|
+
// manifest, so a release never has to edit a string) and the effective
|
|
241
|
+
// configuration. It is published on a read-only route the browser can fetch,
|
|
242
|
+
// namespaced so it cannot collide, and it deliberately carries no paths, host
|
|
243
|
+
// names or credentials — only versions, platform and the switch states the panel
|
|
244
|
+
// already shows.
|
|
245
|
+
try {
|
|
246
|
+
if (typeof ctx.inject === 'function') {
|
|
247
|
+
ctx.inject(['webServer'], (inner) => {
|
|
248
|
+
const webServer = inner.webServer
|
|
249
|
+
if (webServer === undefined || webServer === null || typeof webServer.register !== 'function') return
|
|
250
|
+
const disposer = webServer.register({
|
|
251
|
+
kind: 'exact',
|
|
252
|
+
path: INFO_ROUTE,
|
|
253
|
+
handler: async (request, response) => {
|
|
254
|
+
if (request.method !== 'GET') {
|
|
255
|
+
response.writeHead(405, { allow: 'GET' })
|
|
256
|
+
response.end()
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
let body
|
|
260
|
+
try {
|
|
261
|
+
// The WSL probes cost a few WSL round trips; they run concurrently and
|
|
262
|
+
// every one of them may fail without failing the request.
|
|
263
|
+
body = JSON.stringify(await selfInfo(config, runner))
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.warn('dsh-wsl-tool: reading self info failed', error)
|
|
266
|
+
body = JSON.stringify({ name: 'dsh-wsl-tool', version: null, dsh: null, wsl: null })
|
|
267
|
+
}
|
|
268
|
+
response.writeHead(200, {
|
|
269
|
+
'content-type': 'application/json; charset=utf-8',
|
|
270
|
+
// A copied block must describe this moment, not a cached one.
|
|
271
|
+
'cache-control': 'no-store',
|
|
272
|
+
})
|
|
273
|
+
response.end(body)
|
|
274
|
+
},
|
|
275
|
+
})
|
|
276
|
+
if (typeof inner.effect === 'function') {
|
|
277
|
+
inner.effect(() => disposer, 'dsh-wsl-tool: self-info route')
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
}
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.warn('dsh-wsl-tool: could not publish the self-info route', error)
|
|
283
|
+
}
|
|
284
|
+
|
|
107
285
|
// A volatile edit is written into the same object the loader handed us and then
|
|
108
286
|
// announced, so re-resolving in place is what makes the behaviour switches
|
|
109
287
|
// (background jobs, path translation, the guard, workdir, distro, timeout) take
|
package/lib/client.js
CHANGED
|
@@ -62,7 +62,6 @@ window.__ModuleLoader__.load({
|
|
|
62
62
|
const WARN_LABEL = 'var(--dsw-alias-state-warn-label, #b45309)'
|
|
63
63
|
const LINK_LABEL = 'var(--dsw-alias-link, inherit)'
|
|
64
64
|
const CODE_FILL = 'var(--dsw-alias-markdown-code-block, rgba(127, 127, 127, 0.12))'
|
|
65
|
-
const CODE_LABEL = 'var(--dsw-alias-markdown-inline-code, inherit)'
|
|
66
65
|
const RADIUS = 'var(--dsw-radius-xs, 4px)'
|
|
67
66
|
|
|
68
67
|
/**
|
|
@@ -151,6 +150,20 @@ window.__ModuleLoader__.load({
|
|
|
151
150
|
name: WSL
|
|
152
151
|
args: ['-e', 'bash', '-l']`
|
|
153
152
|
|
|
153
|
+
/** The submission guide shown beside the copy button.
|
|
154
|
+
*
|
|
155
|
+
* It lives in the panel because "where does this go" is the question a copied
|
|
156
|
+
* block raises at exactly that moment; SUPPORT.md is the long form of the same
|
|
157
|
+
* list, with the repository for each kind of problem.
|
|
158
|
+
*/
|
|
159
|
+
const GUIDE = [
|
|
160
|
+
'标题写清现象,例如「[Bug] 后台任务在带 systemd 的发行版里报无属主」;模板会自动加 [Bug] 前缀。',
|
|
161
|
+
'正文写三段:发生了什么(贴原文)、你期望什么、怎么复现。',
|
|
162
|
+
'「补充」栏直接粘上面复制的内容 —— 版本号、发行版、开关状态都在里面,不用手抄。',
|
|
163
|
+
'提问或想法走 Discussions;DSH 本体、市场界面、收录目录的问题各有各的仓库(见 SUPPORT.md)。',
|
|
164
|
+
'提交前把不想公开的路径、主机名、密钥删掉:Issue 是公开的。',
|
|
165
|
+
]
|
|
166
|
+
|
|
154
167
|
/** A setting nobody can read yet; shaped like a scope snapshot so the panel
|
|
155
168
|
* can treat it uniformly. `status: 'unavailable'` is the platform's own word
|
|
156
169
|
* for "this Host does not serve that configuration entry". */
|
|
@@ -374,6 +387,16 @@ window.__ModuleLoader__.load({
|
|
|
374
387
|
padding: '6px 0',
|
|
375
388
|
}
|
|
376
389
|
const HINT_STYLE = { margin: 0, color: LABEL_TERTIARY, fontSize: '12px', lineHeight: '18px', maxWidth: '66ch' }
|
|
390
|
+
/** The submission guide beside the copy button: an ordered list, not prose. */
|
|
391
|
+
const GUIDE_STYLE = {
|
|
392
|
+
margin: 0,
|
|
393
|
+
paddingLeft: '18px',
|
|
394
|
+
color: LABEL_SECONDARY,
|
|
395
|
+
fontSize: '12px',
|
|
396
|
+
lineHeight: '18px',
|
|
397
|
+
maxWidth: '72ch',
|
|
398
|
+
}
|
|
399
|
+
const GUIDE_ITEM_STYLE = { marginTop: '3px' }
|
|
377
400
|
const BADGE_STYLE = {
|
|
378
401
|
border: '0.5px solid ' + BORDER_BADGE,
|
|
379
402
|
borderRadius: RADIUS,
|
|
@@ -411,10 +434,13 @@ window.__ModuleLoader__.load({
|
|
|
411
434
|
padding: '12px',
|
|
412
435
|
background: CODE_FILL,
|
|
413
436
|
borderRadius: RADIUS,
|
|
414
|
-
|
|
437
|
+
// Body text, not the inline-code tint: this is a whole block a reader has to
|
|
438
|
+
// read character by character (a path with backslashes, an argv list), and the
|
|
439
|
+
// inline-code token is dimmed against the very fill it sits on.
|
|
440
|
+
color: LABEL_PRIMARY,
|
|
415
441
|
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, monospace',
|
|
416
|
-
fontSize: '
|
|
417
|
-
lineHeight: '
|
|
442
|
+
fontSize: '12.5px',
|
|
443
|
+
lineHeight: '19px',
|
|
418
444
|
overflowX: 'auto',
|
|
419
445
|
whiteSpace: 'pre',
|
|
420
446
|
}
|
|
@@ -485,6 +511,11 @@ window.__ModuleLoader__.load({
|
|
|
485
511
|
// Host did not accept the last one, so the panel can say so.
|
|
486
512
|
const [busy, setBusy] = React.useState(undefined)
|
|
487
513
|
const [refused, setRefused] = React.useState(false)
|
|
514
|
+
// Feedback copying: `ok` when the clipboard took it, `manual` when the browser
|
|
515
|
+
// refused (no clipboard API, or no permission) so the text is shown to select.
|
|
516
|
+
const [feedbackCopy, setFeedbackCopy] = React.useState(undefined)
|
|
517
|
+
const [feedbackText, setFeedbackText] = React.useState('')
|
|
518
|
+
const [selfInfo, setSelfInfo] = React.useState(undefined)
|
|
488
519
|
|
|
489
520
|
React.useEffect(
|
|
490
521
|
() =>
|
|
@@ -668,6 +699,184 @@ window.__ModuleLoader__.load({
|
|
|
668
699
|
),
|
|
669
700
|
)
|
|
670
701
|
|
|
702
|
+
// ------------------------------------------------------- feedback (one entry)
|
|
703
|
+
//
|
|
704
|
+
// One place, two ways out: the issue chooser for anyone who can reach GitHub,
|
|
705
|
+
// and this plugin's own facts as text for anyone who cannot (measured on this
|
|
706
|
+
// machine: github.com is not always reachable). The guide sits beside the
|
|
707
|
+
// button, because "where does this go" is the question the copied block raises.
|
|
708
|
+
// Nothing is sent by this plugin: the bytes leave only when the user pastes them.
|
|
709
|
+
const FEEDBACK_URL = 'https://github.com/XINY11451/dsh-wsl/issues/new/choose'
|
|
710
|
+
// Kept in step with index.js: the Host half publishes this exact path.
|
|
711
|
+
const INFO_ROUTE = '/dsh-wsl-tool/info'
|
|
712
|
+
|
|
713
|
+
const switchLines = () => SECTIONS.flatMap((group) =>
|
|
714
|
+
group.rows.map((row) => {
|
|
715
|
+
const effective = readPath(snapshot.value, row.path)
|
|
716
|
+
const checked = effective === undefined ? row.when : effective === true
|
|
717
|
+
return `- ${row.label}:${checked ? '开' : '关'}`
|
|
718
|
+
}),
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* The block the button copies: the plugin's own facts.
|
|
723
|
+
*
|
|
724
|
+
* `info` is what the Host half answered on `/dsh-wsl-tool/info`, read from this
|
|
725
|
+
* package's manifest, so no version is ever copied by hand. When it is missing
|
|
726
|
+
* — an older host, a route that never mounted, a blocked request — the block
|
|
727
|
+
* says so instead of inventing a number, and still carries what this panel knows.
|
|
728
|
+
*/
|
|
729
|
+
const infoText = (info) => {
|
|
730
|
+
const hostConfig = info !== null && typeof info === 'object' ? info.config : undefined
|
|
731
|
+
const lines = ['### 插件信息(由插件自己读出,可直接粘贴)']
|
|
732
|
+
if (info === undefined || info === null) {
|
|
733
|
+
lines.push('- 版本:未能读取(见市场条目,或 `npm ls dsh-wsl-tool`)')
|
|
734
|
+
lines.push('- DSH:(未能读取,设置里能看到)')
|
|
735
|
+
lines.push('- WSL 发行版与内核:(未能读取;可跑一次 wsl-env 工具)')
|
|
736
|
+
} else {
|
|
737
|
+
lines.push(`- 插件:${info.name} ${info.version ?? '(版本未知)'}`)
|
|
738
|
+
if (typeof info.repository === 'string' && info.repository !== '') lines.push(`- 仓库:${info.repository}`)
|
|
739
|
+
if (typeof info.node === 'string') lines.push(`- Node ${info.node} 平台 ${info.platform} ${info.arch}`)
|
|
740
|
+
// Both of these are read by the host half; a value that could not be read
|
|
741
|
+
// says so rather than being invented.
|
|
742
|
+
const dsh = info.dsh
|
|
743
|
+
lines.push(dsh !== null && dsh !== undefined && typeof dsh.version === 'string'
|
|
744
|
+
? `- DSH:${dsh.version}(${dsh.name})`
|
|
745
|
+
: '- DSH:(未能读取,设置里能看到)')
|
|
746
|
+
const wsl = info.wsl
|
|
747
|
+
if (wsl !== null && wsl !== undefined && typeof wsl === 'object') {
|
|
748
|
+
const parts = []
|
|
749
|
+
if (typeof wsl.defaultDistro === 'string' && wsl.defaultDistro !== '') {
|
|
750
|
+
parts.push(`${wsl.defaultDistro}(默认发行版)`)
|
|
751
|
+
}
|
|
752
|
+
if (typeof wsl.kernel === 'string' && wsl.kernel !== '') parts.push(`内核 ${wsl.kernel}`)
|
|
753
|
+
lines.push(`- WSL:${parts.length > 0 ? parts.join(';') : '未探测到发行版或内核'}`)
|
|
754
|
+
if (Array.isArray(wsl.capabilities) && wsl.capabilities.length > 0) {
|
|
755
|
+
lines.push(`- WSL 能力:${wsl.capabilities.join(' ')}`)
|
|
756
|
+
}
|
|
757
|
+
} else {
|
|
758
|
+
lines.push('- WSL:(未能读取;可跑一次 wsl-env 工具)')
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
lines.push('')
|
|
762
|
+
lines.push('### 面板里的开关')
|
|
763
|
+
lines.push(...switchLines())
|
|
764
|
+
const distro = hostConfig?.distro ?? readPath(snapshot.value, ['distro'])
|
|
765
|
+
const timeoutMs = hostConfig?.commandTimeoutMs ?? readPath(snapshot.value, ['timeoutMs'])
|
|
766
|
+
lines.push(`- 发行版 distro:${typeof distro === 'string' && distro.trim() !== '' ? distro : '系统默认'}`)
|
|
767
|
+
lines.push(`- 命令超时 timeoutMs:${typeof timeoutMs === 'number' && Number.isFinite(timeoutMs) ? `${timeoutMs} 毫秒` : '—'}`)
|
|
768
|
+
return lines.join('\n')
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const writeFeedback = (text) => {
|
|
772
|
+
const clipboard = typeof navigator === 'object' && navigator !== null ? navigator.clipboard : undefined
|
|
773
|
+
if (clipboard === undefined || typeof clipboard.writeText !== 'function') {
|
|
774
|
+
// No clipboard API (or no permission): show the text so it can be selected.
|
|
775
|
+
setFeedbackText(text)
|
|
776
|
+
setFeedbackCopy('manual')
|
|
777
|
+
return
|
|
778
|
+
}
|
|
779
|
+
try {
|
|
780
|
+
Promise.resolve(clipboard.writeText(text)).then(
|
|
781
|
+
() => setFeedbackCopy('ok'),
|
|
782
|
+
() => {
|
|
783
|
+
setFeedbackText(text)
|
|
784
|
+
setFeedbackCopy('manual')
|
|
785
|
+
},
|
|
786
|
+
)
|
|
787
|
+
} catch {
|
|
788
|
+
setFeedbackText(text)
|
|
789
|
+
setFeedbackCopy('manual')
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const copyInfo = () => {
|
|
794
|
+
// The Host half's answer is cached after the first success: the second click
|
|
795
|
+
// copies without a round trip.
|
|
796
|
+
if (selfInfo !== undefined) {
|
|
797
|
+
writeFeedback(infoText(selfInfo))
|
|
798
|
+
return undefined
|
|
799
|
+
}
|
|
800
|
+
const fetcher = typeof fetch === 'function' ? fetch : undefined
|
|
801
|
+
if (fetcher === undefined) {
|
|
802
|
+
writeFeedback(infoText(undefined))
|
|
803
|
+
return undefined
|
|
804
|
+
}
|
|
805
|
+
// Bounded, but generously: the host half probes WSL (a few round trips) before
|
|
806
|
+
// it answers, and aborting early would paste a block full of "unreadable".
|
|
807
|
+
const controller = typeof AbortController === 'function' ? new AbortController() : undefined
|
|
808
|
+
const timer = typeof setTimeout === 'function'
|
|
809
|
+
? setTimeout(() => {
|
|
810
|
+
if (controller !== undefined) controller.abort()
|
|
811
|
+
}, 12000)
|
|
812
|
+
: undefined
|
|
813
|
+
setFeedbackCopy('busy')
|
|
814
|
+
// Returned so a test can await the round trip; a click handler may ignore it.
|
|
815
|
+
return Promise.resolve(fetcher(INFO_ROUTE, {
|
|
816
|
+
headers: { accept: 'application/json' },
|
|
817
|
+
...(controller !== undefined ? { signal: controller.signal } : {}),
|
|
818
|
+
}))
|
|
819
|
+
.then((response) => (response !== undefined && response !== null && response.ok === true
|
|
820
|
+
? response.json()
|
|
821
|
+
: undefined))
|
|
822
|
+
.then((info) => {
|
|
823
|
+
if (timer !== undefined) clearTimeout(timer)
|
|
824
|
+
if (info !== undefined && info !== null && typeof info === 'object') setSelfInfo(info)
|
|
825
|
+
writeFeedback(infoText(info ?? undefined))
|
|
826
|
+
})
|
|
827
|
+
.catch(() => {
|
|
828
|
+
if (timer !== undefined) clearTimeout(timer)
|
|
829
|
+
writeFeedback(infoText(undefined))
|
|
830
|
+
})
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
children.push(
|
|
834
|
+
section(
|
|
835
|
+
'feedback',
|
|
836
|
+
'意见反馈 / 提升建议',
|
|
837
|
+
React.createElement('p', { style: HINT_STYLE },
|
|
838
|
+
'「复制插件信息」由插件自己读出下面的内容并复制到剪贴板:版本、仓库、Node、以及你面板里的开关状态。插件不会自己发送任何东西 —— 由你粘、你提交。'),
|
|
839
|
+
React.createElement(
|
|
840
|
+
'div',
|
|
841
|
+
{ style: OVERRIDE_BAR_STYLE },
|
|
842
|
+
React.createElement(
|
|
843
|
+
'a',
|
|
844
|
+
{
|
|
845
|
+
href: FEEDBACK_URL,
|
|
846
|
+
target: '_blank',
|
|
847
|
+
rel: 'noreferrer noopener',
|
|
848
|
+
style: { color: LINK_LABEL, fontSize: '12px' },
|
|
849
|
+
},
|
|
850
|
+
'打开反馈页(GitHub)',
|
|
851
|
+
),
|
|
852
|
+
React.createElement(
|
|
853
|
+
'button',
|
|
854
|
+
{
|
|
855
|
+
type: 'button',
|
|
856
|
+
style: RESET_STYLE,
|
|
857
|
+
disabled: feedbackCopy === 'busy',
|
|
858
|
+
onClick: copyInfo,
|
|
859
|
+
},
|
|
860
|
+
feedbackCopy === 'busy'
|
|
861
|
+
? '正在读取…'
|
|
862
|
+
: feedbackCopy === 'ok' ? '已复制 ✓' : '复制插件信息',
|
|
863
|
+
),
|
|
864
|
+
),
|
|
865
|
+
React.createElement('p', { style: HINT_STYLE }, FEEDBACK_URL),
|
|
866
|
+
React.createElement(
|
|
867
|
+
'ol',
|
|
868
|
+
{ style: GUIDE_STYLE },
|
|
869
|
+
...GUIDE.map((line, index) =>
|
|
870
|
+
React.createElement('li', { key: `guide-${index}`, style: GUIDE_ITEM_STYLE }, line)),
|
|
871
|
+
),
|
|
872
|
+
feedbackCopy === 'manual' ? React.createElement('pre', { style: PRE_STYLE }, feedbackText) : null,
|
|
873
|
+
feedbackCopy === 'manual'
|
|
874
|
+
? React.createElement('p', { style: HINT_STYLE },
|
|
875
|
+
'(浏览器不允许自动复制:全选上面这段,粘到你想粘的地方即可)')
|
|
876
|
+
: null,
|
|
877
|
+
),
|
|
878
|
+
)
|
|
879
|
+
|
|
671
880
|
return React.createElement('div', { style: PANEL_STYLE }, children)
|
|
672
881
|
}
|
|
673
882
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-wsl-tool",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.10",
|
|
4
4
|
"description": "WSL tools for DSH: run Linux commands (fresh shell per call, exit codes and signals, timeouts, truncated output spilled to a file, optional stdin), convert Windows↔WSL paths, report the WSL environment (kernel, systemd, docker, drives), send long builds to background jobs, and guard destructive commands. A left-sidebar panel switches each capability; an opt-in patch points the sidebar terminal at WSL. WSL calls run below the DSH sandbox.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
"PUBLISHING.md",
|
|
47
47
|
"extras",
|
|
48
48
|
"screenshots.json",
|
|
49
|
-
"assets",
|
|
50
49
|
"LICENSE"
|
|
51
50
|
],
|
|
52
51
|
"scripts": {
|
package/screenshots.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
["assets/market-preview.png", "assets/screenshot-1.png"]
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<title>dsh-wsl 功能介绍</title>
|
|
6
|
-
<style>
|
|
7
|
-
:root {
|
|
8
|
-
--bg: #0d1017;
|
|
9
|
-
--panel: #141922;
|
|
10
|
-
--panel-2: #1a212c;
|
|
11
|
-
--line: #263041;
|
|
12
|
-
--text: #e7ecf3;
|
|
13
|
-
--dim: #9aa7b8;
|
|
14
|
-
--dim-2: #6d7a8c;
|
|
15
|
-
--accent: #4f8cff;
|
|
16
|
-
--accent-2: #46d3a4;
|
|
17
|
-
--warn: #f0a33c;
|
|
18
|
-
}
|
|
19
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
20
|
-
html, body { width: 1280px; height: 800px; overflow: hidden; }
|
|
21
|
-
body {
|
|
22
|
-
background: radial-gradient(1100px 520px at 12% -12%, #1b2740 0%, var(--bg) 62%), var(--bg);
|
|
23
|
-
color: var(--text);
|
|
24
|
-
font-family: "Segoe UI", "Microsoft YaHei", system-ui, -apple-system, sans-serif;
|
|
25
|
-
padding: 40px 44px;
|
|
26
|
-
display: flex; flex-direction: column; gap: 22px;
|
|
27
|
-
}
|
|
28
|
-
header { display: flex; align-items: flex-start; justify-content: space-between; }
|
|
29
|
-
.title { display: flex; align-items: center; gap: 14px; }
|
|
30
|
-
.mark {
|
|
31
|
-
width: 46px; height: 46px; border-radius: 12px;
|
|
32
|
-
background: linear-gradient(140deg, #2d6ae0, #46d3a4);
|
|
33
|
-
display: flex; align-items: center; justify-content: center;
|
|
34
|
-
font-size: 22px; font-weight: 700; color: #071018; letter-spacing: -1px;
|
|
35
|
-
}
|
|
36
|
-
h1 { font-size: 27px; letter-spacing: .3px; }
|
|
37
|
-
h1 span { color: var(--accent); }
|
|
38
|
-
.sub { color: var(--dim); font-size: 13.5px; margin-top: 5px; }
|
|
39
|
-
.badges { display: flex; gap: 8px; }
|
|
40
|
-
.badge {
|
|
41
|
-
border: 1px solid var(--line); background: var(--panel);
|
|
42
|
-
border-radius: 999px; padding: 5px 11px; font-size: 12px; color: var(--dim);
|
|
43
|
-
}
|
|
44
|
-
.badge.on { color: var(--accent-2); border-color: #2b5c4c; background: #10231d; }
|
|
45
|
-
main { display: grid; grid-template-columns: 1fr 430px; gap: 26px; flex: 1; min-height: 0; }
|
|
46
|
-
.col { display: flex; flex-direction: column; gap: 18px; min-height: 0; }
|
|
47
|
-
section .head {
|
|
48
|
-
font-size: 12px; letter-spacing: 2px; color: var(--dim-2);
|
|
49
|
-
text-transform: uppercase; margin-bottom: 9px;
|
|
50
|
-
}
|
|
51
|
-
.cards { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
|
52
|
-
.card {
|
|
53
|
-
background: linear-gradient(180deg, var(--panel), #121721);
|
|
54
|
-
border: 1px solid var(--line); border-radius: 10px; padding: 12px 13px;
|
|
55
|
-
}
|
|
56
|
-
.card b { font-size: 14px; display: block; }
|
|
57
|
-
.card code {
|
|
58
|
-
font-family: Consolas, "Cascadia Mono", monospace; font-size: 12.5px;
|
|
59
|
-
color: var(--accent-2); background: #0e1a18; border: 1px solid #1e3d36;
|
|
60
|
-
border-radius: 5px; padding: 1px 5px;
|
|
61
|
-
}
|
|
62
|
-
.card p { color: var(--dim); font-size: 12px; margin-top: 6px; line-height: 1.55; }
|
|
63
|
-
.card.wide { grid-column: 1 / -1; }
|
|
64
|
-
.limits { border-color: #3a2f1d; background: linear-gradient(180deg, #1b1710, #14120c); }
|
|
65
|
-
.limits b { color: var(--warn); }
|
|
66
|
-
.panel {
|
|
67
|
-
background: linear-gradient(180deg, #151b25, #10151d);
|
|
68
|
-
border: 1px solid var(--line); border-radius: 14px; padding: 15px 16px;
|
|
69
|
-
display: flex; flex-direction: column; gap: 11px; min-height: 0;
|
|
70
|
-
}
|
|
71
|
-
.panel .bar { display: flex; justify-content: space-between; align-items: center; }
|
|
72
|
-
.panel .bar .t { font-size: 15px; font-weight: 600; }
|
|
73
|
-
.panel .bar .r { font-size: 11.5px; color: var(--accent); border: 1px solid #24406e; background: #101b2e; border-radius: 6px; padding: 3px 8px; }
|
|
74
|
-
.panel .note { font-size: 11.5px; color: var(--dim-2); }
|
|
75
|
-
.row { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
|
|
76
|
-
.row .l { min-width: 0; }
|
|
77
|
-
.row .l b { font-size: 13px; font-weight: 600; display: block; }
|
|
78
|
-
.row .l p { font-size: 11.5px; color: var(--dim); margin-top: 3px; line-height: 1.5; }
|
|
79
|
-
.sw {
|
|
80
|
-
flex: none; width: 34px; height: 19px; border-radius: 999px;
|
|
81
|
-
background: #263041; position: relative; margin-top: 2px;
|
|
82
|
-
}
|
|
83
|
-
.sw::after { content: ""; position: absolute; top: 2px; left: 2px; width: 15px; height: 15px; border-radius: 50%; background: #6d7a8c; }
|
|
84
|
-
.sw.on { background: #1d4ed8; }
|
|
85
|
-
.sw.on::after { left: 17px; background: #dce8ff; }
|
|
86
|
-
.sw.risk { background: #4a3416; }
|
|
87
|
-
.sw.risk::after { left: 17px; background: #f0c37a; }
|
|
88
|
-
.group { display: flex; flex-direction: column; gap: 9px; }
|
|
89
|
-
.group + .group { border-top: 1px dashed var(--line); padding-top: 10px; }
|
|
90
|
-
.reset { display: flex; justify-content: flex-end; align-items: center; gap: 8px; font-size: 11.5px; color: var(--dim); }
|
|
91
|
-
.reset .pill { border: 1px solid #24406e; background: #101b2e; color: #b9cdf5; border-radius: 6px; padding: 3px 8px; }
|
|
92
|
-
footer {
|
|
93
|
-
display: flex; justify-content: space-between; align-items: center;
|
|
94
|
-
border-top: 1px solid var(--line); padding-top: 14px;
|
|
95
|
-
color: var(--dim-2); font-size: 12px;
|
|
96
|
-
}
|
|
97
|
-
footer code {
|
|
98
|
-
font-family: Consolas, "Cascadia Mono", monospace; color: var(--text);
|
|
99
|
-
background: #0b0f15; border: 1px solid var(--line); border-radius: 6px; padding: 4px 8px;
|
|
100
|
-
}
|
|
101
|
-
footer .right { text-align: right; }
|
|
102
|
-
</style>
|
|
103
|
-
</head>
|
|
104
|
-
<body>
|
|
105
|
-
<header>
|
|
106
|
-
<div class="title">
|
|
107
|
-
<div class="mark">W​SL</div>
|
|
108
|
-
<div>
|
|
109
|
-
<h1>DSH <span>×</span> WSL</h1>
|
|
110
|
-
<div class="sub">在 Windows 上执行 Linux 命令 —— 命令执行层基本对齐原生 Linux DSH</div>
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
113
|
-
<div class="badges">
|
|
114
|
-
<div class="badge on">左侧栏开关面板</div>
|
|
115
|
-
<div class="badge">可选:侧边栏 WSL 终端</div>
|
|
116
|
-
<div class="badge">零依赖</div>
|
|
117
|
-
</div>
|
|
118
|
-
</header>
|
|
119
|
-
|
|
120
|
-
<main>
|
|
121
|
-
<div class="col">
|
|
122
|
-
<section>
|
|
123
|
-
<div class="head">三个工具</div>
|
|
124
|
-
<div class="cards">
|
|
125
|
-
<div class="card">
|
|
126
|
-
<b><code>wsl</code> 命令执行</b>
|
|
127
|
-
<p>每次调用一个全新 shell;可喂 stdin、可设超时;退出码 / 信号 / 截断都如实回传。</p>
|
|
128
|
-
</div>
|
|
129
|
-
<div class="card">
|
|
130
|
-
<b><code>wsl-path</code> 路径互转</b>
|
|
131
|
-
<p>Windows 路径 ↔ <code>/mnt/...</code>、<code>\\wsl.localhost\</code> 双向转换。</p>
|
|
132
|
-
</div>
|
|
133
|
-
<div class="card">
|
|
134
|
-
<b><code>wsl-env</code> 能力体检</b>
|
|
135
|
-
<p>发行版、内核、systemd、cgroup、GPU 直通、docker、挂载盘与两侧配置一次看清。</p>
|
|
136
|
-
</div>
|
|
137
|
-
<div class="card">
|
|
138
|
-
<b>长任务后台跑</b>
|
|
139
|
-
<p><code>runInBackground</code> 交给内置 job 工具读回 / 中止,前台超时不再拖死。</p>
|
|
140
|
-
</div>
|
|
141
|
-
<div class="card wide">
|
|
142
|
-
<b>安全与可观测</b>
|
|
143
|
-
<p>危险命令守卫(删除 / 分区 / 关机需显式放行);输出按流上限截断,超限会自动落盘完整文件并提供路径。</p>
|
|
144
|
-
</div>
|
|
145
|
-
</div>
|
|
146
|
-
</section>
|
|
147
|
-
|
|
148
|
-
<section>
|
|
149
|
-
<div class="head">如实说明的限制</div>
|
|
150
|
-
<div class="cards">
|
|
151
|
-
<div class="card wide limits">
|
|
152
|
-
<b>两条边界</b>
|
|
153
|
-
<p>① WSL 调用发生在 DSH 沙箱层之下,文件策略约束不到它;② 项目放在 Windows 盘时仍是 Windows 文件系统语义(9p:慢、无 inotify、权限位不生效)。</p>
|
|
154
|
-
</div>
|
|
155
|
-
</div>
|
|
156
|
-
</section>
|
|
157
|
-
|
|
158
|
-
<section>
|
|
159
|
-
<div class="head">一次真实调用</div>
|
|
160
|
-
<div class="card wide">
|
|
161
|
-
<b><code>wsl</code> · <code>wsl "cd /mnt/d/proj && npm test"</code></b>
|
|
162
|
-
<p>返回 stdout / stderr / exitCode,并标记信号、超时与截断;长任务加 <code>runInBackground: true</code> 拿 jobId,用 <code>job_output</code> / <code>job_kill</code> 读回或中止。可选一条 patch 还能把桌面版「新建终端」指向 WSL(真 PTY,默认不开)。</p>
|
|
163
|
-
</div>
|
|
164
|
-
</section>
|
|
165
|
-
</div>
|
|
166
|
-
|
|
167
|
-
<div class="panel">
|
|
168
|
-
<div class="bar">
|
|
169
|
-
<div class="t">WSL</div>
|
|
170
|
-
<div class="r">全部恢复默认</div>
|
|
171
|
-
</div>
|
|
172
|
-
<div class="note">工具开关下次启动生效;其余开关即时生效。</div>
|
|
173
|
-
|
|
174
|
-
<div class="group">
|
|
175
|
-
<div class="row">
|
|
176
|
-
<div class="l"><b>wsl 命令执行</b><p>注册 <code>wsl</code> 工具:在 WSL 里执行 Linux 命令</p></div>
|
|
177
|
-
<div class="sw on"></div>
|
|
178
|
-
</div>
|
|
179
|
-
<div class="row">
|
|
180
|
-
<div class="l"><b>wsl-path 路径转换</b><p>注册 <code>wsl-path</code> 工具:路径互转</p></div>
|
|
181
|
-
<div class="sw on"></div>
|
|
182
|
-
</div>
|
|
183
|
-
<div class="row">
|
|
184
|
-
<div class="l"><b>wsl-env 能力体检</b><p>注册 <code>wsl-env</code> 工具:汇总 WSL 能力</p></div>
|
|
185
|
-
<div class="sw on"></div>
|
|
186
|
-
</div>
|
|
187
|
-
</div>
|
|
188
|
-
|
|
189
|
-
<div class="group">
|
|
190
|
-
<div class="row">
|
|
191
|
-
<div class="l"><b>后台任务</b><p>允许 <code>runInBackground</code>,由 job 工具读回</p></div>
|
|
192
|
-
<div class="sw on"></div>
|
|
193
|
-
</div>
|
|
194
|
-
<div class="row">
|
|
195
|
-
<div class="l"><b>自动转换路径</b><p>命令里的 Windows 路径自动转成 <code>/mnt/...</code></p></div>
|
|
196
|
-
<div class="sw on"></div>
|
|
197
|
-
</div>
|
|
198
|
-
<div class="row">
|
|
199
|
-
<div class="l"><b>默认跟随会话工作区</b><p>未传 workdir 时从会话目录开始</p></div>
|
|
200
|
-
<div class="sw"></div>
|
|
201
|
-
</div>
|
|
202
|
-
<div class="row">
|
|
203
|
-
<div class="l"><b>危险命令守卫</b><p>删除、分区、关机等必须显式放行</p></div>
|
|
204
|
-
<div class="sw on risk"></div>
|
|
205
|
-
</div>
|
|
206
|
-
</div>
|
|
207
|
-
|
|
208
|
-
<div class="reset"><span>已覆盖项可一键清除</span><span class="pill">每行只留「已覆盖」标记</span></div>
|
|
209
|
-
</div>
|
|
210
|
-
</main>
|
|
211
|
-
|
|
212
|
-
<footer>
|
|
213
|
-
<div>安装:<code>dsh plugin --profile web add dsh-wsl-tool</code></div>
|
|
214
|
-
<div class="right">github.com/XINY11451/dsh-wsl · dsh-wsl-tool</div>
|
|
215
|
-
</footer>
|
|
216
|
-
</body>
|
|
217
|
-
</html>
|
|
Binary file
|
package/assets/screenshot-1.png
DELETED
|
Binary file
|