@xiaoso/dsh-tool-plus 0.1.0-beta.7 → 0.1.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/README.en.md +9 -5
- package/README.md +9 -5
- package/lib/client.js +88 -19
- package/lib/index.mjs +300 -16
- package/lib/{scrapers-DrSYgPyf.mjs → scrapers-DI8yWlu_.mjs} +1 -1
- package/lib/trash-cli.mjs +281 -0
- package/lib/{utils-DSEEKy3G.mjs → utils-D1p8-H8L.mjs} +1 -1
- package/package.json +2 -1
- package/presets/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
[简体中文](./README.md) | English
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@xiaoso/dsh-tool-plus)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://nodejs.org)
|
|
8
|
+
[]()
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
Essential tools enhancement for DeepSeek Harness: persistent bash, structured read, multi-mode edit, atomic write, full-text search, and direct image reading — one plugin covers it all. Ported from the [Oh My Pi](https://github.com/can1357/oh-my-pi) core; once installed it automatically takes over the official bash / pwsh / file / search tools, with optional `ast_grep` / `ast_edit` structural search and rewrite.
|
|
8
11
|
|
|
9
12
|
## Features
|
|
10
13
|
|
|
11
14
|
- **bash**: persistent shell; `cd` and `export` keep state across calls; verbose logs (git/npm/cargo…) are condensed automatically; overlong output keeps only head and tail, with the full content written to disk for later retrieval; long-running commands are moved to the background automatically; optionally intercepts `cat`, `grep`, `find`, and `sed -i` to steer you toward the dedicated tools
|
|
15
|
+
- **Safe delete**: `rm` moves files to the system trash by default instead of deleting permanently, so accidental deletes are recoverable; use `command rm` for a real delete
|
|
12
16
|
- **read**: precise line-range reading (supports `:N-M`, `:raw`, and multiple ranges); large code files return a structural summary by default, with details expanded on demand; zip/tar archives, SQLite, notebooks, and PDFs read directly; PNG / JPEG / WebP / GIF images read directly, oversized ones scaled down automatically; can fetch web content (including images embedded in the page); SPA sites (content rendered dynamically by JS, e.g. excalidraw.com) are automatically fetched via local browser rendering
|
|
13
17
|
- **write**: atomic writes that return a diff of the changes; supports patch-style writing, and can write directly into zip/tar archive members and SQLite data
|
|
14
18
|
- **edit**: `replace` by default, with patch / hashline / apply-patch formats also supported; multi-hunk edits, uniqueness validation, fuzzy matching tolerant of whitespace differences
|
|
@@ -30,12 +34,12 @@ Install **both** the plugin and the presets:
|
|
|
30
34
|
### Install from npm (recommended)
|
|
31
35
|
|
|
32
36
|
```sh
|
|
33
|
-
dsh plugin --profile web add @xiaoso/dsh-tool-plus
|
|
37
|
+
dsh plugin --profile web add @xiaoso/dsh-tool-plus
|
|
34
38
|
```
|
|
35
39
|
|
|
36
40
|
### Install from GitHub
|
|
37
41
|
|
|
38
|
-
Track the latest development build
|
|
42
|
+
Track the latest development build:
|
|
39
43
|
|
|
40
44
|
```sh
|
|
41
45
|
dsh plugin --profile web add github:xiaoso456/dsh-tool-plus
|
|
@@ -52,7 +56,7 @@ dsh plugin --profile web add link:<path to this repo>
|
|
|
52
56
|
**Option 1 · Install with the npx script**
|
|
53
57
|
|
|
54
58
|
```sh
|
|
55
|
-
npx @xiaoso/dsh-tool-plus-presets
|
|
59
|
+
npx @xiaoso/dsh-tool-plus-presets
|
|
56
60
|
```
|
|
57
61
|
|
|
58
62
|
**Option 2 · Have an AI session configure the presets** (no dependency on the package above) — paste the following into an AI session that can edit files on your machine:
|
package/README.md
CHANGED
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
简体中文 | [English](./README.en.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@xiaoso/dsh-tool-plus)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://nodejs.org)
|
|
8
|
+
[]()
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
DeepSeek Harness 基础工具增强:持久 bash、结构化 read、多模式 edit、原子 write、全文搜索、图像直读,一个插件全覆盖。基于 [Oh My Pi](https://github.com/can1357/oh-my-pi) 内核移植,安装后自动接管官方 bash / pwsh / 文件 / 搜索工具,另有可选的 `ast_grep` / `ast_edit` 结构化搜索与重写。
|
|
8
11
|
|
|
9
12
|
## 功能特性
|
|
10
13
|
|
|
11
14
|
- **bash**:持久 shell,`cd`、`export` 跨调用保持状态;冗长日志(git/npm/cargo…)自动压缩;超长输出只保留头尾,完整内容落盘可回读;长命令自动转后台;可选拦截 `cat`、`grep`、`find`、`sed -i`,引导改用专用工具
|
|
15
|
+
- **安全删除**:`rm` 默认把文件移入系统回收站而非永久删除,误删可恢复;需要真删时用 `command rm`
|
|
12
16
|
- **read**:按行区间精准读取(支持 `:N-M`、`:raw`、多区间);大代码文件默认返回结构摘要,细节按需展开;zip/tar 归档、SQLite、notebook、PDF 直接读;PNG / JPEG / WebP / GIF 图片直读,超大图自动缩放;可抓取网页内容(含网页内图片);SPA 站点(内容由 JS 动态渲染,如 excalidraw.com)自动改用本机浏览器渲染后抓取
|
|
13
17
|
- **write**:原子写入,返回修改 diff;支持补丁式写入,可直接写 zip/tar 归档成员与 SQLite 数据
|
|
14
18
|
- **edit**:replace 默认,另支持 patch / hashline / apply-patch 三种补丁格式;多段编辑、唯一性校验、空白差异模糊匹配
|
|
@@ -30,12 +34,12 @@ DeepSeek Harness 基础工具增强:持久 bash、结构化 read、多模式 e
|
|
|
30
34
|
### 从 npm 安装(推荐)
|
|
31
35
|
|
|
32
36
|
```sh
|
|
33
|
-
dsh plugin --profile web add @xiaoso/dsh-tool-plus
|
|
37
|
+
dsh plugin --profile web add @xiaoso/dsh-tool-plus
|
|
34
38
|
```
|
|
35
39
|
|
|
36
40
|
### 从 GitHub 安装
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
跟踪最新开发版:
|
|
39
43
|
|
|
40
44
|
```sh
|
|
41
45
|
dsh plugin --profile web add github:xiaoso456/dsh-tool-plus
|
|
@@ -52,7 +56,7 @@ dsh plugin --profile web add link:<本仓库路径>
|
|
|
52
56
|
**方法一 · 使用 npx 脚本安装**
|
|
53
57
|
|
|
54
58
|
```sh
|
|
55
|
-
npx @xiaoso/dsh-tool-plus-presets
|
|
59
|
+
npx @xiaoso/dsh-tool-plus-presets
|
|
56
60
|
```
|
|
57
61
|
|
|
58
62
|
**方法二 · 使用大模型配置预设**(不依赖上面的包)——把下面这段发给一个能改你电脑上文件的 AI 会话:
|
package/lib/client.js
CHANGED
|
@@ -75,6 +75,15 @@ window.__ModuleLoader__.load({
|
|
|
75
75
|
group: "behavior",
|
|
76
76
|
tool: "bash"
|
|
77
77
|
},
|
|
78
|
+
{
|
|
79
|
+
name: "rmSafe",
|
|
80
|
+
kind: "boolean",
|
|
81
|
+
default: true,
|
|
82
|
+
labelKey: "rmSafe",
|
|
83
|
+
hintKey: "rmSafeHint",
|
|
84
|
+
group: "behavior",
|
|
85
|
+
tool: "bash"
|
|
86
|
+
},
|
|
78
87
|
{
|
|
79
88
|
name: "useShellCommandWrapper",
|
|
80
89
|
kind: "boolean",
|
|
@@ -745,7 +754,6 @@ window.__ModuleLoader__.load({
|
|
|
745
754
|
const SETTINGS_ROWS_CSS = `
|
|
746
755
|
.tp-row{display:flex;align-items:center;gap:12px;padding:12px 0;border-bottom:1px solid var(--dsw-alias-border-l1);transition:background .16s ease}
|
|
747
756
|
.tp-row:last-child{border-bottom:none}
|
|
748
|
-
.tp-rowClickable{cursor:pointer}
|
|
749
757
|
.tp-rowText{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;padding-right:24px}
|
|
750
758
|
.tp-rowTitle{font-size:14px;font-weight:400;line-height:22px;color:var(--dsw-alias-label-primary)}
|
|
751
759
|
.tp-rowDesc{font-size:12px;font-weight:400;line-height:18px;color:var(--dsw-alias-label-tertiary)}
|
|
@@ -824,18 +832,16 @@ window.__ModuleLoader__.load({
|
|
|
824
832
|
}
|
|
825
833
|
/**
|
|
826
834
|
* Number row: same left-text / right-control anatomy as toggle and select
|
|
827
|
-
* rows, with the input right-aligned at a fixed width. The
|
|
828
|
-
* the input (htmlFor)
|
|
835
|
+
* rows, with the input right-aligned at a fixed width. The title is wired to
|
|
836
|
+
* the input (htmlFor), so clicking the title focuses the input; the
|
|
837
|
+
* description and the rest of the row are inert — each side manages its own
|
|
838
|
+
* interaction.
|
|
829
839
|
*/
|
|
830
840
|
function NumberRow(props) {
|
|
831
841
|
const { t, control, disabled, onEdit, onReset } = props;
|
|
832
842
|
const inputId = (0, react.useId)();
|
|
833
843
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
834
|
-
className: "tp-row"
|
|
835
|
-
onClick: (event) => {
|
|
836
|
-
if (event.target.closest("input,button,select,textarea,a")) return;
|
|
837
|
-
event.currentTarget.querySelector(".tp-input")?.focus();
|
|
838
|
-
},
|
|
844
|
+
className: "tp-row",
|
|
839
845
|
children: [
|
|
840
846
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
841
847
|
className: "tp-rowText",
|
|
@@ -883,11 +889,7 @@ window.__ModuleLoader__.load({
|
|
|
883
889
|
}));
|
|
884
890
|
const close = () => onOpenChange(null);
|
|
885
891
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
886
|
-
className: "tp-row"
|
|
887
|
-
onClick: (event) => {
|
|
888
|
-
if (event.target.closest("input,button,select,textarea,a")) return;
|
|
889
|
-
event.currentTarget.querySelector(".tp-selectTrigger")?.click();
|
|
890
|
-
},
|
|
892
|
+
className: "tp-row",
|
|
891
893
|
children: [
|
|
892
894
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
893
895
|
className: "tp-rowText",
|
|
@@ -941,11 +943,7 @@ window.__ModuleLoader__.load({
|
|
|
941
943
|
function ToggleRow(props) {
|
|
942
944
|
const { t, control, disabled, onToggle, onReset } = props;
|
|
943
945
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
944
|
-
className: "tp-row"
|
|
945
|
-
onClick: (event) => {
|
|
946
|
-
if (event.target.closest("input,button,select,textarea,a")) return;
|
|
947
|
-
event.currentTarget.querySelector(".tp-switch input")?.click();
|
|
948
|
-
},
|
|
946
|
+
className: "tp-row",
|
|
949
947
|
children: [
|
|
950
948
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
951
949
|
className: "tp-rowText",
|
|
@@ -1641,6 +1639,8 @@ window.__ModuleLoader__.load({
|
|
|
1641
1639
|
*/
|
|
1642
1640
|
/** Logical RPC channel registered by the host half of this plugin. */
|
|
1643
1641
|
const TOOL_PLUS_RPC_CHANNEL = "/tool-plus";
|
|
1642
|
+
/** Endpoint that reports the rmSafe injection status (query = ensure + verify). */
|
|
1643
|
+
const RM_SAFE_STATUS_ENDPOINT = "rmSafe/status";
|
|
1644
1644
|
//#endregion
|
|
1645
1645
|
//#region src/client/web-connection-rpc.ts
|
|
1646
1646
|
/**
|
|
@@ -2029,6 +2029,27 @@ window.__ModuleLoader__.load({
|
|
|
2029
2029
|
};
|
|
2030
2030
|
}
|
|
2031
2031
|
//#endregion
|
|
2032
|
+
//#region src/client/rm-safe-status-text.ts
|
|
2033
|
+
/** Locale key of one rmSafe failure reason. */
|
|
2034
|
+
function rmSafeReasonKey(reason) {
|
|
2035
|
+
switch (reason) {
|
|
2036
|
+
case "snapshot-unavailable": return "rmSafeReasonSnapshotUnavailable";
|
|
2037
|
+
case "cli-missing": return "rmSafeReasonCliMissing";
|
|
2038
|
+
case "script-write-failed": return "rmSafeReasonScriptWriteFailed";
|
|
2039
|
+
case "snapshot-write-failed": return "rmSafeReasonSnapshotWriteFailed";
|
|
2040
|
+
case "runtime-not-effective": return "rmSafeReasonRuntimeNotEffective";
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
/**
|
|
2044
|
+
* Localized text of one rmSafe/status result. `disabled` maps to the empty
|
|
2045
|
+
* string — the caller decides not to surface it.
|
|
2046
|
+
*/
|
|
2047
|
+
function rmSafeStatusText(t, value) {
|
|
2048
|
+
if (value.status === "failed") return t("rmSafeStatusFailed").replace("{reason}", t(rmSafeReasonKey(value.reason)));
|
|
2049
|
+
if (value.status === "disabled") return "";
|
|
2050
|
+
return value.runtime === "function" ? t("rmSafeStatusInjected") : t("rmSafeStatusInjectedUnverified");
|
|
2051
|
+
}
|
|
2052
|
+
//#endregion
|
|
2032
2053
|
//#region src/client/ToolPlusSection.tsx
|
|
2033
2054
|
/**
|
|
2034
2055
|
* Tool Plus settings section — the plugin's own page in the Settings panel,
|
|
@@ -2272,6 +2293,27 @@ window.__ModuleLoader__.load({
|
|
|
2272
2293
|
const [openSelect, setOpenSelect] = (0, react.useState)(null);
|
|
2273
2294
|
const [actionState, setActionState] = (0, react.useState)({});
|
|
2274
2295
|
const [actionResults, setActionResults] = (0, react.useState)({});
|
|
2296
|
+
const [rmSafeToast, setRmSafeToast] = (0, react.useState)(null);
|
|
2297
|
+
const handleSave = (0, react.useCallback)(async () => {
|
|
2298
|
+
const before = Boolean(scope.getSnapshot().value?.rmSafe ?? true);
|
|
2299
|
+
await form.actions.save();
|
|
2300
|
+
const after = Boolean(scope.getSnapshot().value?.rmSafe ?? true);
|
|
2301
|
+
if (before === after || !after) return;
|
|
2302
|
+
try {
|
|
2303
|
+
const result = await createWebConnectionRpc().call(TOOL_PLUS_RPC_CHANNEL, RM_SAFE_STATUS_ENDPOINT, {});
|
|
2304
|
+
if (!result.ok) return;
|
|
2305
|
+
const value = result.value;
|
|
2306
|
+
if (value.status === "disabled") return;
|
|
2307
|
+
setRmSafeToast({
|
|
2308
|
+
text: rmSafeStatusText(t, value),
|
|
2309
|
+
ok: value.status === "injected"
|
|
2310
|
+
});
|
|
2311
|
+
} catch {}
|
|
2312
|
+
}, [
|
|
2313
|
+
scope,
|
|
2314
|
+
form,
|
|
2315
|
+
t
|
|
2316
|
+
]);
|
|
2275
2317
|
const runAction = (0, react.useCallback)((field) => {
|
|
2276
2318
|
const actionKey = field.actionKey;
|
|
2277
2319
|
if (!actionKey) return;
|
|
@@ -2520,10 +2562,17 @@ window.__ModuleLoader__.load({
|
|
|
2520
2562
|
type: "button",
|
|
2521
2563
|
className: "tps-save",
|
|
2522
2564
|
disabled: blocked,
|
|
2523
|
-
onClick: () => void
|
|
2565
|
+
onClick: () => void handleSave(),
|
|
2524
2566
|
children: form.shell.saving ? t("saving") : t("save")
|
|
2525
2567
|
})
|
|
2526
2568
|
]
|
|
2569
|
+
}),
|
|
2570
|
+
rmSafeToast !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
2571
|
+
text: rmSafeToast.text,
|
|
2572
|
+
icon: rmSafeToast.ok ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, {}),
|
|
2573
|
+
onDone: () => {
|
|
2574
|
+
setRmSafeToast(null);
|
|
2575
|
+
}
|
|
2527
2576
|
})
|
|
2528
2577
|
]
|
|
2529
2578
|
});
|
|
@@ -2663,6 +2712,16 @@ window.__ModuleLoader__.load({
|
|
|
2663
2712
|
nonInteractiveEnvHint: "对非交互使用加固环境:关闭分页器与彩色输出,不弹交互提示。",
|
|
2664
2713
|
snapshotEnabled: "Shell 快照",
|
|
2665
2714
|
snapshotEnabledHint: "把用户的 rc 文件(别名、函数、选项)快照进会话 shell。",
|
|
2715
|
+
rmSafe: "安全 rm",
|
|
2716
|
+
rmSafeHint: "在会话 shell 里重定义 rm:删除的文件移入系统回收站而非永久删除;command rm 仍走真删除。",
|
|
2717
|
+
rmSafeStatusInjected: "安全 rm 已注入并验证:rm 进入回收站",
|
|
2718
|
+
rmSafeStatusInjectedUnverified: "安全 rm 已注入(运行时验证不可用)",
|
|
2719
|
+
rmSafeStatusFailed: "安全 rm 注入失败:{reason},rm 将永久删除",
|
|
2720
|
+
rmSafeReasonSnapshotUnavailable: "快照不可用",
|
|
2721
|
+
rmSafeReasonCliMissing: "trash-cli 产物缺失",
|
|
2722
|
+
rmSafeReasonScriptWriteFailed: "脚本写入失败",
|
|
2723
|
+
rmSafeReasonSnapshotWriteFailed: "快照不可写",
|
|
2724
|
+
rmSafeReasonRuntimeNotEffective: "运行时未生效",
|
|
2666
2725
|
useShellCommandWrapper: "用 bash -c 包裹命令",
|
|
2667
2726
|
useShellCommandWrapperHint: "把每条命令用 bash -c 包裹,以获得完整的 bash 环境。",
|
|
2668
2727
|
outputTruncateStrategy: "截断按",
|
|
@@ -2834,6 +2893,16 @@ window.__ModuleLoader__.load({
|
|
|
2834
2893
|
nonInteractiveEnvHint: "Harden the environment for non-interactive use: no pagers, no prompts, no color.",
|
|
2835
2894
|
snapshotEnabled: "Shell snapshot",
|
|
2836
2895
|
snapshotEnabledHint: "Capture the user's rc file (aliases, functions, options) into the session shell.",
|
|
2896
|
+
rmSafe: "Safe rm",
|
|
2897
|
+
rmSafeHint: "Redefine rm in the session shell: deleted files move to the system trash instead of being permanently removed; `command rm` still deletes for real.",
|
|
2898
|
+
rmSafeStatusInjected: "Safe rm injected and verified: rm moves to trash",
|
|
2899
|
+
rmSafeStatusInjectedUnverified: "Safe rm injected (runtime verification unavailable)",
|
|
2900
|
+
rmSafeStatusFailed: "Safe rm injection failed: {reason}, rm deletes permanently",
|
|
2901
|
+
rmSafeReasonSnapshotUnavailable: "snapshot unavailable",
|
|
2902
|
+
rmSafeReasonCliMissing: "trash-cli binary missing",
|
|
2903
|
+
rmSafeReasonScriptWriteFailed: "script write failed",
|
|
2904
|
+
rmSafeReasonSnapshotWriteFailed: "snapshot not writable",
|
|
2905
|
+
rmSafeReasonRuntimeNotEffective: "not effective at runtime",
|
|
2837
2906
|
useShellCommandWrapper: "Wrap commands in bash -c",
|
|
2838
2907
|
useShellCommandWrapperHint: "Wrap every command in bash -c for a full bash environment.",
|
|
2839
2908
|
outputTruncateStrategy: "Truncate by",
|
package/lib/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ __installBunShim();
|
|
|
3
3
|
import { n as fs_promises_shim_exports, t as exists } from "./fs-promises-shim-Bx0fpxuk.mjs";
|
|
4
4
|
import { i as setRuntimeLogger, n as systemChromiumCandidatesForTest, r as runtimeLogger } from "./launch-DwpE70Pv.mjs";
|
|
5
5
|
import { $ as peekFile, A as formatAge, C as error, F as $envpos, I as $flag, M as formatCount, P as $env, Q as isProbablyBinary, S as debug, T as warn, U as getAutoQaDbPath, V as VERSION, X as getRemoteDir, Y as getPuppeteerDir, b as isEnotdir, c as combineSignals, d as $which, j as formatBytes$1, k as installBunShim, l as exec, p as readImageMetadata, q as getInstallId, tt as untilAborted, u as render, v as hasFsCode, y as isEnoent } from "./src-D_e1qMwi.mjs";
|
|
6
|
-
import { C as truncateToWidth$1, S as replaceTabs, _ as formatCodeFrameLine, a as fetchBinary, b as LRUCache, c as getParallelExtractContent, d as convertFileWithMarkit, f as Settings, g as capParseErrors, h as OMP_DEFAULTS, i as convertWithMarkit, l as ensureTool, m as settings, o as extractWithParallel, p as isSettingsInitialized, s as findParallelApiKey, u as CONVERTIBLE_EXTENSIONS, v as formatParseErrors, x as padding, y as shortenPath } from "./utils-
|
|
6
|
+
import { C as truncateToWidth$1, S as replaceTabs, _ as formatCodeFrameLine, a as fetchBinary, b as LRUCache, c as getParallelExtractContent, d as convertFileWithMarkit, f as Settings, g as capParseErrors, h as OMP_DEFAULTS, i as convertWithMarkit, l as ensureTool, m as settings, o as extractWithParallel, p as isSettingsInitialized, s as findParallelApiKey, u as CONVERTIBLE_EXTENSIONS, v as formatParseErrors, x as padding, y as shortenPath } from "./utils-D1p8-H8L.mjs";
|
|
7
7
|
import { t as sanitizeText } from "./sanitize-text-CfcXzw5X.mjs";
|
|
8
8
|
import { n as isRecord$2 } from "./type-guards-B9VVjRDK.mjs";
|
|
9
9
|
import { n as ToolError, r as throwIfAborted, t as ToolAbortError } from "./tool-errors-DoyxivAR.mjs";
|
|
@@ -719,7 +719,26 @@ async function getOrCreateSnapshot(shell, env, timeoutMs = SNAPSHOT_TIMEOUT_MS)
|
|
|
719
719
|
const cached = cachedSnapshotPaths.get(cacheKey);
|
|
720
720
|
if (cached && fs.existsSync(cached)) return cached;
|
|
721
721
|
if (cached) cachedSnapshotPaths.delete(cacheKey);
|
|
722
|
-
if (process.platform === "win32")
|
|
722
|
+
if (process.platform === "win32") {
|
|
723
|
+
const uid = process.getuid?.();
|
|
724
|
+
const snapshotDir = path.join(os.tmpdir(), uid === void 0 ? "omp-shell-snapshots" : `omp-shell-snapshots-${uid}`);
|
|
725
|
+
const snapshotPath = path.join(snapshotDir, `snapshot-bash-${crypto.randomUUID()}.sh`);
|
|
726
|
+
try {
|
|
727
|
+
fs.mkdirSync(snapshotDir, {
|
|
728
|
+
recursive: true,
|
|
729
|
+
mode: 448
|
|
730
|
+
});
|
|
731
|
+
fs.writeFileSync(snapshotPath, "# Shell snapshot - generated by omp agent (windows minimal)\n", { mode: 384 });
|
|
732
|
+
cachedSnapshotPaths.set(cacheKey, snapshotPath);
|
|
733
|
+
return snapshotPath;
|
|
734
|
+
} catch (err) {
|
|
735
|
+
runtimeLogger().debug("shell-snapshot: windows snapshot dir unusable", {
|
|
736
|
+
dir: snapshotDir,
|
|
737
|
+
err: String(err)
|
|
738
|
+
});
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
723
742
|
const rcFile = getShellConfigFile(shell, env);
|
|
724
743
|
const uid = process.getuid?.();
|
|
725
744
|
const snapshotDir = path.join(os.tmpdir(), uid === void 0 ? "omp-shell-snapshots" : `omp-shell-snapshots-${uid}`);
|
|
@@ -788,6 +807,85 @@ function cleanupSnapshots() {
|
|
|
788
807
|
cachedSnapshotPaths.clear();
|
|
789
808
|
}
|
|
790
809
|
//#endregion
|
|
810
|
+
//#region src/tools/bash/rm-safe.ts
|
|
811
|
+
/**
|
|
812
|
+
* rmSafe: rm 重定义脚本的生成与快照注入。
|
|
813
|
+
*
|
|
814
|
+
* 生成一个 bash 函数定义脚本(rm → `node <trash-cli.mjs> "$@"`),
|
|
815
|
+
* 通过快照文件(会话 shell 创建时 source 一次)注入:
|
|
816
|
+
* - POSIX:追加到用户环境快照末尾(幂等,带标记行);
|
|
817
|
+
* - Windows:getOrCreateSnapshot 生成"仅注入"快照文件后同样追加。
|
|
818
|
+
*
|
|
819
|
+
* rmSafe 关闭时调用方不注入,快照保持原样,系统 rm 生效。
|
|
820
|
+
* @module @xiaoso/dsh-tool-plus/bash/rm-safe
|
|
821
|
+
*/
|
|
822
|
+
/** 注入标记行:injectRmSafe 以此判断是否已注入(幂等)。 */
|
|
823
|
+
const RM_SAFE_MARKER = "# dsh-tool-plus rmSafe";
|
|
824
|
+
/** rm-safe 脚本缓存目录(tmpdir 下,跨会话复用)。 */
|
|
825
|
+
function rmSafeScriptDir() {
|
|
826
|
+
return path.join(os.tmpdir(), "dsh-bash-plus");
|
|
827
|
+
}
|
|
828
|
+
/** trash-cli 构建产物路径(lib/trash-cli.mjs,与主 bundle 同目录)。 */
|
|
829
|
+
function rmSafeCliPath() {
|
|
830
|
+
return fileURLToPath(new URL("./trash-cli.mjs", import.meta.url));
|
|
831
|
+
}
|
|
832
|
+
/** 单引号转义(bash 单引号内 `'\''` 表示一个字面单引号)。 */
|
|
833
|
+
function quote(s) {
|
|
834
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* 生成 rm 重定义脚本内容:bash 函数 `rm()` 把参数原样转交 trash-cli。
|
|
838
|
+
* 逃生口:`command rm`(bash 的 command 关键字跳过函数/alias 查找)或
|
|
839
|
+
* 绝对路径(/usr/bin/rm)走系统真删除。注意 `\rm` 反斜杠只抑制 alias
|
|
840
|
+
* 展开、不抑制函数查找,对函数重定义无效——不是逃生口。
|
|
841
|
+
*/
|
|
842
|
+
function rmSafeScript(nodePath, cliPath) {
|
|
843
|
+
return [
|
|
844
|
+
RM_SAFE_MARKER,
|
|
845
|
+
"rm() {",
|
|
846
|
+
` ${quote(nodePath)} ${quote(cliPath)} "$@"`,
|
|
847
|
+
"}",
|
|
848
|
+
""
|
|
849
|
+
].join("\n");
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* 确保 rm-safe 脚本存在(内容不变时幂等,不重写文件)。
|
|
853
|
+
* 返回脚本路径;目录/写入失败时返回 null(不抛异常,调用方降级并告警)。
|
|
854
|
+
*/
|
|
855
|
+
function ensureRmSafeScript(dir, nodePath, cliPath) {
|
|
856
|
+
const target = path.join(dir, "rm-safe.sh");
|
|
857
|
+
const content = rmSafeScript(nodePath, cliPath);
|
|
858
|
+
try {
|
|
859
|
+
if (fs.readFileSync(target, "utf8") === content) return target;
|
|
860
|
+
} catch {}
|
|
861
|
+
try {
|
|
862
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
863
|
+
fs.writeFileSync(target, content, { mode: 384 });
|
|
864
|
+
return target;
|
|
865
|
+
} catch {
|
|
866
|
+
return null;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* 向快照文件追加 `source <rm-safe.sh>`(幂等:已有标记则跳过)。
|
|
871
|
+
* 返回是否注入成功;快照文件不可读/不可写时返回 false(不抛异常)。
|
|
872
|
+
*/
|
|
873
|
+
function injectRmSafe(snapshotPath, scriptPath) {
|
|
874
|
+
let content;
|
|
875
|
+
try {
|
|
876
|
+
content = fs.readFileSync(snapshotPath, "utf8");
|
|
877
|
+
} catch {
|
|
878
|
+
return false;
|
|
879
|
+
}
|
|
880
|
+
if (content.includes("# dsh-tool-plus rmSafe")) return true;
|
|
881
|
+
try {
|
|
882
|
+
fs.appendFileSync(snapshotPath, `\n${RM_SAFE_MARKER}\nsource ${quote(scriptPath)}\n`);
|
|
883
|
+
return true;
|
|
884
|
+
} catch {
|
|
885
|
+
return false;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
//#endregion
|
|
791
889
|
//#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/path.ts
|
|
792
890
|
const WINDOWS_DRIVE_EXTENDED_PREFIX = /^\\\\[?]\\([A-Za-z]:[\\/].*)$/;
|
|
793
891
|
const WINDOWS_UNC_EXTENDED_PREFIX = /^\\\\[?]\\UNC[\\/]([^\\/]+)[\\/](.+)$/i;
|
|
@@ -2325,7 +2423,19 @@ async function executeBash(command, options) {
|
|
|
2325
2423
|
const baseShellConfig = getShellConfig();
|
|
2326
2424
|
const { shell, args, env: shellEnv, prefix } = options?.useUserShell === true ? resolveUserShellConfig(baseShellConfig) : baseShellConfig;
|
|
2327
2425
|
const bashShell = isBashShell(shell);
|
|
2328
|
-
|
|
2426
|
+
let snapshotPath = bashShell && options?.snapshotEnabled !== false ? await getOrCreateSnapshot(shell, shellEnv) : null;
|
|
2427
|
+
let rmSafeInjectionFailed = false;
|
|
2428
|
+
if (snapshotPath !== null && options?.rmSafe === true) {
|
|
2429
|
+
const scriptPath = ensureRmSafeScript(rmSafeScriptDir(), process.execPath, rmSafeCliPath());
|
|
2430
|
+
if (scriptPath === null || !injectRmSafe(snapshotPath, scriptPath)) {
|
|
2431
|
+
rmSafeInjectionFailed = true;
|
|
2432
|
+
runtimeLogger().warn("rmSafe: injection failed, rm will delete permanently", {
|
|
2433
|
+
scriptPath: scriptPath ?? null,
|
|
2434
|
+
snapshotPath
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
const injectionFailedMarker = rmSafeInjectionFailed ? { rmSafeInjectionFailed: true } : {};
|
|
2329
2439
|
const minimizerSettings = options?.minimizerSettings ?? DEFAULT_MINIMIZER_SETTINGS;
|
|
2330
2440
|
const minimizer = options?.minimizerEnabled ?? minimizerSettings.enabled ? buildMinimizerOptions(minimizerSettings) : void 0;
|
|
2331
2441
|
const commandCwd = resolveShellCwd(options?.cwd);
|
|
@@ -2354,6 +2464,7 @@ async function executeBash(command, options) {
|
|
|
2354
2464
|
if (options?.signal?.aborted) return {
|
|
2355
2465
|
exitCode: void 0,
|
|
2356
2466
|
cancelled: true,
|
|
2467
|
+
...injectionFailedMarker,
|
|
2357
2468
|
...await settle("Command cancelled")
|
|
2358
2469
|
};
|
|
2359
2470
|
const shellOptions = {
|
|
@@ -2432,6 +2543,7 @@ async function executeBash(command, options) {
|
|
|
2432
2543
|
return {
|
|
2433
2544
|
exitCode: void 0,
|
|
2434
2545
|
cancelled: true,
|
|
2546
|
+
...injectionFailedMarker,
|
|
2435
2547
|
...winner.kind === "timeout" ? { timedOut: true } : {},
|
|
2436
2548
|
...await settle(winner.kind === "timeout" && deadlineTimeoutMs !== void 0 ? `Command timed out after ${Math.round(deadlineTimeoutMs / 1e3)} seconds` : "Command cancelled")
|
|
2437
2549
|
};
|
|
@@ -2448,6 +2560,7 @@ async function executeBash(command, options) {
|
|
|
2448
2560
|
exitCode: void 0,
|
|
2449
2561
|
cancelled: true,
|
|
2450
2562
|
timedOut: true,
|
|
2563
|
+
...injectionFailedMarker,
|
|
2451
2564
|
...await settle(annotation)
|
|
2452
2565
|
};
|
|
2453
2566
|
}
|
|
@@ -2457,6 +2570,7 @@ async function executeBash(command, options) {
|
|
|
2457
2570
|
return {
|
|
2458
2571
|
exitCode: void 0,
|
|
2459
2572
|
cancelled: true,
|
|
2573
|
+
...injectionFailedMarker,
|
|
2460
2574
|
...await settle("Command cancelled")
|
|
2461
2575
|
};
|
|
2462
2576
|
}
|
|
@@ -2473,6 +2587,7 @@ async function executeBash(command, options) {
|
|
|
2473
2587
|
return {
|
|
2474
2588
|
exitCode: winner.result.exitCode,
|
|
2475
2589
|
cancelled: false,
|
|
2590
|
+
...injectionFailedMarker,
|
|
2476
2591
|
minimized: minimized && minimized.text !== minimized.originalText ? {
|
|
2477
2592
|
filter: minimized.filter,
|
|
2478
2593
|
inputBytes: minimized.inputBytes,
|
|
@@ -3166,6 +3281,7 @@ function renderBashResult(value) {
|
|
|
3166
3281
|
const truncation = formatTruncationNotice(value.output);
|
|
3167
3282
|
if (truncation !== void 0) notices.push(...truncation);
|
|
3168
3283
|
else if (value.output.spillPath !== void 0) notices.push(`[full raw stream saved: ${value.output.spillPath}]`);
|
|
3284
|
+
if (value.rmSafeInjectionFailed === true) notices.push("[Safe rm injection failed: rm deletes permanently — use `command rm` to bypass]");
|
|
3169
3285
|
const markers = [];
|
|
3170
3286
|
if (value.timedOut) markers.push(`[timed out after ${value.timeoutMs ?? "?"}ms]`);
|
|
3171
3287
|
else if (value.exitCode !== 0) markers.push(`[exit code: ${value.exitCode}]`);
|
|
@@ -3229,6 +3345,15 @@ const TOOL_PLUS_FIELDS = [
|
|
|
3229
3345
|
group: "behavior",
|
|
3230
3346
|
tool: "bash"
|
|
3231
3347
|
},
|
|
3348
|
+
{
|
|
3349
|
+
name: "rmSafe",
|
|
3350
|
+
kind: "boolean",
|
|
3351
|
+
default: true,
|
|
3352
|
+
labelKey: "rmSafe",
|
|
3353
|
+
hintKey: "rmSafeHint",
|
|
3354
|
+
group: "behavior",
|
|
3355
|
+
tool: "bash"
|
|
3356
|
+
},
|
|
3232
3357
|
{
|
|
3233
3358
|
name: "useShellCommandWrapper",
|
|
3234
3359
|
kind: "boolean",
|
|
@@ -3895,6 +4020,7 @@ const Config = z.object({
|
|
|
3895
4020
|
interceptorEnabled: z.boolean().default(fieldDefault("interceptorEnabled", true)),
|
|
3896
4021
|
nonInteractiveEnv: z.boolean().default(fieldDefault("nonInteractiveEnv", true)),
|
|
3897
4022
|
snapshotEnabled: z.boolean().default(fieldDefault("snapshotEnabled", true)),
|
|
4023
|
+
rmSafe: z.boolean().default(fieldDefault("rmSafe", true)),
|
|
3898
4024
|
useShellCommandWrapper: z.boolean().default(fieldDefault("useShellCommandWrapper", false)),
|
|
3899
4025
|
maxBackgroundJobs: z.number().default(fieldDefault("maxBackgroundJobs", 15)),
|
|
3900
4026
|
outputTruncateStrategy: z.union(["bytes", "lines"]).default(fieldDefault("outputTruncateStrategy", "bytes")),
|
|
@@ -3987,6 +4113,7 @@ function resolveConfig(config) {
|
|
|
3987
4113
|
interceptorEnabled: config.interceptorEnabled ?? fieldDefault("interceptorEnabled", true),
|
|
3988
4114
|
nonInteractiveEnv: config.nonInteractiveEnv ?? fieldDefault("nonInteractiveEnv", true),
|
|
3989
4115
|
snapshotEnabled: config.snapshotEnabled ?? fieldDefault("snapshotEnabled", true),
|
|
4116
|
+
rmSafe: config.rmSafe ?? fieldDefault("rmSafe", true),
|
|
3990
4117
|
useShellCommandWrapper: config.useShellCommandWrapper ?? fieldDefault("useShellCommandWrapper", false),
|
|
3991
4118
|
maxBackgroundJobs: config.maxBackgroundJobs ?? fieldDefault("maxBackgroundJobs", 15),
|
|
3992
4119
|
outputTruncate: {
|
|
@@ -4196,27 +4323,145 @@ function probeBrowsers(env = defaultBrowserDetectEnv()) {
|
|
|
4196
4323
|
return found;
|
|
4197
4324
|
}
|
|
4198
4325
|
//#endregion
|
|
4326
|
+
//#region src/tools/bash/rm-safe-status.ts
|
|
4327
|
+
/**
|
|
4328
|
+
* rmSafe 注入状态查询:文件层(快照标记 + 脚本 + trash-cli 产物)与
|
|
4329
|
+
* 运行时层(一次性 bash 会话 source 快照后探测 `rm` 是否被重定义)。
|
|
4330
|
+
*
|
|
4331
|
+
* 分层语义:
|
|
4332
|
+
* - 文件层成功只证明"注入已写入快照",不证明"会话里生效"——快照是会话
|
|
4333
|
+
* 创建时 source 的,注入只对之后创建的会话生效;trash-cli 产物缺失或
|
|
4334
|
+
* node 路径不可用时函数调用会直接失败。
|
|
4335
|
+
* - 运行时层用一次性 bash 会话模拟"新会话创建"(source 快照 + `type rm`),
|
|
4336
|
+
* 验证的正是注入承诺的语义:新会话里 `rm` 是函数而非系统命令。探测
|
|
4337
|
+
* 无副作用,不触碰任何真实会话。
|
|
4338
|
+
* @module @xiaoso/dsh-tool-plus/bash/rm-safe-status
|
|
4339
|
+
*/
|
|
4340
|
+
/**
|
|
4341
|
+
* 解析 `type rm` / `declare -f rm` 探测输出。
|
|
4342
|
+
* `rm is a function` → 函数重定义生效;其余(`rm is /usr/bin/rm`、
|
|
4343
|
+
* `rm is hashed (...)`、空输出)→ 系统命令。
|
|
4344
|
+
*/
|
|
4345
|
+
function parseProbeOutput(stdout) {
|
|
4346
|
+
return /rm is a function/.test(stdout) ? "function" : "system";
|
|
4347
|
+
}
|
|
4348
|
+
/**
|
|
4349
|
+
* Windows 路径转 Git Bash 形式(`C:\Users\x` → `/c/Users/x`)。
|
|
4350
|
+
* 非 Windows 原样返回;非盘符路径仅把反斜杠转正斜杠。
|
|
4351
|
+
*/
|
|
4352
|
+
function toGitBashPath(p) {
|
|
4353
|
+
if (process.platform !== "win32") return p;
|
|
4354
|
+
const drive = /^([A-Za-z]):[\\/](.*)$/.exec(p);
|
|
4355
|
+
if (drive === null) return p.replace(/\\/g, "/");
|
|
4356
|
+
return `/${drive[1].toLowerCase()}/${drive[2].replace(/\\/g, "/")}`;
|
|
4357
|
+
}
|
|
4358
|
+
/** 默认 spawn 实现:收集 stdout,超时 kill 并标记 timedOut。 */
|
|
4359
|
+
const defaultSpawnDeps = { spawnShell: (shell, args, timeoutMs) => new Promise((resolve, reject) => {
|
|
4360
|
+
const child = spawn(shell, args, { stdio: [
|
|
4361
|
+
"ignore",
|
|
4362
|
+
"pipe",
|
|
4363
|
+
"pipe"
|
|
4364
|
+
] });
|
|
4365
|
+
let stdout = "";
|
|
4366
|
+
let timedOut = false;
|
|
4367
|
+
const timer = setTimeout(() => {
|
|
4368
|
+
timedOut = true;
|
|
4369
|
+
child.kill();
|
|
4370
|
+
}, timeoutMs);
|
|
4371
|
+
child.stdout.on("data", (chunk) => {
|
|
4372
|
+
stdout += chunk.toString();
|
|
4373
|
+
});
|
|
4374
|
+
child.on("error", (error) => {
|
|
4375
|
+
clearTimeout(timer);
|
|
4376
|
+
reject(error);
|
|
4377
|
+
});
|
|
4378
|
+
child.on("close", () => {
|
|
4379
|
+
clearTimeout(timer);
|
|
4380
|
+
resolve({
|
|
4381
|
+
stdout,
|
|
4382
|
+
timedOut
|
|
4383
|
+
});
|
|
4384
|
+
});
|
|
4385
|
+
}) };
|
|
4386
|
+
/**
|
|
4387
|
+
* 运行时探测:在一次性 bash 会话里 source 快照(模拟新会话创建),
|
|
4388
|
+
* 然后 `type rm` + `declare -f rm` 看 `rm` 是否被重定义为函数。
|
|
4389
|
+
* spawn 失败或超时 → 'unknown'(探测不可用,不代表注入失败)。
|
|
4390
|
+
*/
|
|
4391
|
+
async function probeRmSafeRuntime(shell, snapshotPath, deps = defaultSpawnDeps, timeoutMs = 5e3) {
|
|
4392
|
+
const script = `source ${quote(toGitBashPath(snapshotPath))}; type rm; declare -f rm`;
|
|
4393
|
+
try {
|
|
4394
|
+
const { stdout, timedOut } = await deps.spawnShell(shell, ["-c", script], timeoutMs);
|
|
4395
|
+
if (timedOut) return "unknown";
|
|
4396
|
+
return parseProbeOutput(stdout);
|
|
4397
|
+
} catch {
|
|
4398
|
+
return "unknown";
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
/**
|
|
4402
|
+
* 查询 rmSafe 注入状态(查询即确保注入:幂等,与 executeBash 的注入
|
|
4403
|
+
* 路径一致)。rmSafe 关闭时调用方不应调用本函数(返回 disabled 由
|
|
4404
|
+
* 调用方短路);此处仍兜底返回 disabled 语义由调用方保证。
|
|
4405
|
+
*/
|
|
4406
|
+
async function queryRmSafeStatus(deps) {
|
|
4407
|
+
const snapshotPath = await deps.getOrCreateSnapshot();
|
|
4408
|
+
if (snapshotPath === null) return {
|
|
4409
|
+
status: "failed",
|
|
4410
|
+
reason: "snapshot-unavailable"
|
|
4411
|
+
};
|
|
4412
|
+
if (!deps.cliExists()) return {
|
|
4413
|
+
status: "failed",
|
|
4414
|
+
reason: "cli-missing"
|
|
4415
|
+
};
|
|
4416
|
+
const scriptPath = deps.ensureScript();
|
|
4417
|
+
if (scriptPath === null) return {
|
|
4418
|
+
status: "failed",
|
|
4419
|
+
reason: "script-write-failed"
|
|
4420
|
+
};
|
|
4421
|
+
if (!deps.inject(snapshotPath, scriptPath)) return {
|
|
4422
|
+
status: "failed",
|
|
4423
|
+
reason: "snapshot-write-failed"
|
|
4424
|
+
};
|
|
4425
|
+
const runtime = await deps.probe(snapshotPath);
|
|
4426
|
+
if (runtime === "system") return {
|
|
4427
|
+
status: "failed",
|
|
4428
|
+
reason: "runtime-not-effective"
|
|
4429
|
+
};
|
|
4430
|
+
return {
|
|
4431
|
+
status: "injected",
|
|
4432
|
+
runtime
|
|
4433
|
+
};
|
|
4434
|
+
}
|
|
4435
|
+
//#endregion
|
|
4199
4436
|
//#region src/host/browser-probe-rpc.ts
|
|
4200
4437
|
/**
|
|
4201
|
-
*
|
|
4438
|
+
* Host half of the tool-plus RPC channel: registers the `/tool-plus` logical
|
|
4439
|
+
* channel on the official Connection transport and answers
|
|
4440
|
+
* `browser/detect` (probe the machine for usable browsers) and
|
|
4441
|
+
* `rmSafe/status` (query = ensure + verify the rmSafe injection). The
|
|
4442
|
+
* settings panel (client half) calls these through
|
|
4443
|
+
* `createWebConnectionRpc().call(...)` — the documented public RPC surface
|
|
4444
|
+
* of `@deepseek-ai/dsh-client-connection`, not a private plugin hook.
|
|
4445
|
+
*
|
|
4446
|
+
* Trust policy is `loopback`: the probe only reports local filesystem paths,
|
|
4447
|
+
* so it must never be reachable from a non-loopback host. Registration is
|
|
4448
|
+
* best-effort — if `connection` is missing (non-web deployments) or the
|
|
4449
|
+
* channel is already taken, the endpoints are simply unavailable and the
|
|
4450
|
+
* panel shows "Unavailable".
|
|
4451
|
+
* @module @xiaoso/dsh-tool-plus/host/browser-probe-rpc
|
|
4452
|
+
*/
|
|
4453
|
+
/**
|
|
4454
|
+
* Install the tool-plus RPC endpoints. Returns a disposer that removes
|
|
4202
4455
|
* the registration (call it from the plugin's `ctx.effect` teardown).
|
|
4203
4456
|
* Never throws for "unavailable" — the return value tells the caller whether
|
|
4204
4457
|
* the channel is actually live.
|
|
4205
4458
|
*/
|
|
4206
|
-
function installBrowserProbeRpc(ctx) {
|
|
4459
|
+
function installBrowserProbeRpc(ctx, deps) {
|
|
4207
4460
|
let disposer;
|
|
4208
4461
|
ctx.inject(["connection"], (connectionCtx) => {
|
|
4209
4462
|
try {
|
|
4210
4463
|
const handler = async (endpoint, _payload, _signal) => {
|
|
4211
|
-
if (endpoint
|
|
4212
|
-
ok: false,
|
|
4213
|
-
error: {
|
|
4214
|
-
code: "bad-request",
|
|
4215
|
-
message: `Unknown tool-plus endpoint: ${endpoint}`,
|
|
4216
|
-
details: { issues: [] }
|
|
4217
|
-
}
|
|
4218
|
-
};
|
|
4219
|
-
try {
|
|
4464
|
+
if (endpoint === "browser/detect") try {
|
|
4220
4465
|
return {
|
|
4221
4466
|
ok: true,
|
|
4222
4467
|
value: { found: probeBrowsers() }
|
|
@@ -4231,6 +4476,42 @@ function installBrowserProbeRpc(ctx) {
|
|
|
4231
4476
|
}
|
|
4232
4477
|
};
|
|
4233
4478
|
}
|
|
4479
|
+
if (endpoint === "rmSafe/status") {
|
|
4480
|
+
if (!deps.getRmSafe()) return {
|
|
4481
|
+
ok: true,
|
|
4482
|
+
value: { status: "disabled" }
|
|
4483
|
+
};
|
|
4484
|
+
try {
|
|
4485
|
+
const { shell, env } = getShellConfig();
|
|
4486
|
+
return {
|
|
4487
|
+
ok: true,
|
|
4488
|
+
value: await queryRmSafeStatus({
|
|
4489
|
+
getOrCreateSnapshot: () => getOrCreateSnapshot(shell, env),
|
|
4490
|
+
cliExists: () => fs.existsSync(rmSafeCliPath()),
|
|
4491
|
+
ensureScript: () => ensureRmSafeScript(rmSafeScriptDir(), process.execPath, rmSafeCliPath()),
|
|
4492
|
+
inject: (snapshotPath, scriptPath) => injectRmSafe(snapshotPath, scriptPath),
|
|
4493
|
+
probe: (snapshotPath) => probeRmSafeRuntime(shell, snapshotPath)
|
|
4494
|
+
})
|
|
4495
|
+
};
|
|
4496
|
+
} catch (error) {
|
|
4497
|
+
return {
|
|
4498
|
+
ok: false,
|
|
4499
|
+
error: {
|
|
4500
|
+
code: "internal",
|
|
4501
|
+
message: error instanceof Error ? error.message : String(error),
|
|
4502
|
+
details: {}
|
|
4503
|
+
}
|
|
4504
|
+
};
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
return {
|
|
4508
|
+
ok: false,
|
|
4509
|
+
error: {
|
|
4510
|
+
code: "bad-request",
|
|
4511
|
+
message: `Unknown tool-plus endpoint: ${endpoint}`,
|
|
4512
|
+
details: { issues: [] }
|
|
4513
|
+
}
|
|
4514
|
+
};
|
|
4234
4515
|
};
|
|
4235
4516
|
disposer = connectionCtx.connection.rpc.handle(TOOL_PLUS_RPC_CHANNEL, handler, { authority: "loopback" });
|
|
4236
4517
|
} catch {
|
|
@@ -14555,7 +14836,7 @@ let specialHandlersPromise;
|
|
|
14555
14836
|
* requires a special handler, so we keep them out of the cold-startup graph.
|
|
14556
14837
|
*/
|
|
14557
14838
|
function loadSpecialHandlers() {
|
|
14558
|
-
specialHandlersPromise ??= import("./scrapers-
|
|
14839
|
+
specialHandlersPromise ??= import("./scrapers-DI8yWlu_.mjs").then((m) => m.specialHandlers);
|
|
14559
14840
|
return specialHandlersPromise;
|
|
14560
14841
|
}
|
|
14561
14842
|
/**
|
|
@@ -25704,6 +25985,7 @@ function buildForeground(result, wallTimeMs, timeoutMs) {
|
|
|
25704
25985
|
wallTimeMs,
|
|
25705
25986
|
...result.workingDir !== void 0 ? { workingDir: result.workingDir } : {},
|
|
25706
25987
|
...result.minimized !== void 0 ? { minimized: result.minimized } : {},
|
|
25988
|
+
...result.rmSafeInjectionFailed === true ? { rmSafeInjectionFailed: true } : {},
|
|
25707
25989
|
output: {
|
|
25708
25990
|
text: result.output,
|
|
25709
25991
|
truncated: result.truncated,
|
|
@@ -25772,7 +26054,7 @@ function apply(ctx, config = {}) {
|
|
|
25772
26054
|
cfg = current();
|
|
25773
26055
|
registerModeSensitive();
|
|
25774
26056
|
});
|
|
25775
|
-
const disposeBrowserProbe = installBrowserProbeRpc(ctx);
|
|
26057
|
+
const disposeBrowserProbe = installBrowserProbeRpc(ctx, { getRmSafe: () => cfg.rmSafe });
|
|
25776
26058
|
ctx.systemPrompt.section({
|
|
25777
26059
|
name: "tool:bash",
|
|
25778
26060
|
order: 105,
|
|
@@ -26028,6 +26310,7 @@ function apply(ctx, config = {}) {
|
|
|
26028
26310
|
headBytes: cfg.outputSinkHeadBytes,
|
|
26029
26311
|
useShellCommandWrapper: cfg.useShellCommandWrapper,
|
|
26030
26312
|
snapshotEnabled: cfg.snapshotEnabled,
|
|
26313
|
+
rmSafe: cfg.rmSafe,
|
|
26031
26314
|
nonInteractiveEnv: cfg.nonInteractiveEnv,
|
|
26032
26315
|
artifactPath: allocateSpillFile()
|
|
26033
26316
|
});
|
|
@@ -26085,6 +26368,7 @@ function apply(ctx, config = {}) {
|
|
|
26085
26368
|
headBytes: cfg.outputSinkHeadBytes,
|
|
26086
26369
|
useShellCommandWrapper: cfg.useShellCommandWrapper,
|
|
26087
26370
|
snapshotEnabled: cfg.snapshotEnabled,
|
|
26371
|
+
rmSafe: cfg.rmSafe,
|
|
26088
26372
|
nonInteractiveEnv: cfg.nonInteractiveEnv,
|
|
26089
26373
|
artifactPath: allocateSpillFile()
|
|
26090
26374
|
});
|
|
@@ -3,7 +3,7 @@ __installBunShim();
|
|
|
3
3
|
import { n as fs_promises_shim_exports } from "./fs-promises-shim-Bx0fpxuk.mjs";
|
|
4
4
|
import { B as USER_AGENT, G as getDocsRsCacheDir, N as formatNumber, P as $env, T as warn, c as combineSignals, j as formatBytes, l as exec, s as Snowflake, x as parseFrontmatter, y as isEnoent } from "./src-D_e1qMwi.mjs";
|
|
5
5
|
import { V as tryParseJson } from "./utils-fnV2zzp3.mjs";
|
|
6
|
-
import { a as fetchBinary, c as getParallelExtractContent, i as convertWithMarkit, l as ensureTool, m as settings, n as asRecord, o as extractWithParallel, r as asString, s as findParallelApiKey, t as asNumber } from "./utils-
|
|
6
|
+
import { a as fetchBinary, c as getParallelExtractContent, i as convertWithMarkit, l as ensureTool, m as settings, n as asRecord, o as extractWithParallel, r as asString, s as findParallelApiKey, t as asNumber } from "./utils-D1p8-H8L.mjs";
|
|
7
7
|
import { n as isRecord } from "./type-guards-B9VVjRDK.mjs";
|
|
8
8
|
import { r as throwIfAborted, t as ToolAbortError } from "./tool-errors-DoyxivAR.mjs";
|
|
9
9
|
import { c as getLocalizedText, d as looksLikeHtml, i as decodeHtmlEntities, l as htmlToBasicMarkdown, o as formatIsoDate, r as buildResult, s as formatMediaDuration, u as loadPage } from "./types-BUO0JXO-.mjs";
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
//#region src/tools/bash/trash-cli.ts
|
|
3
|
+
const SHORT_OPTIONS = {
|
|
4
|
+
r: "recursive",
|
|
5
|
+
R: "recursive",
|
|
6
|
+
f: "force",
|
|
7
|
+
i: "interactive",
|
|
8
|
+
I: "interactive",
|
|
9
|
+
v: "verbose",
|
|
10
|
+
d: "dir"
|
|
11
|
+
};
|
|
12
|
+
const LONG_OPTIONS = {
|
|
13
|
+
"--recursive": {
|
|
14
|
+
field: "recursive",
|
|
15
|
+
value: true
|
|
16
|
+
},
|
|
17
|
+
"--force": {
|
|
18
|
+
field: "force",
|
|
19
|
+
value: true
|
|
20
|
+
},
|
|
21
|
+
"--interactive": {
|
|
22
|
+
field: "interactive",
|
|
23
|
+
value: true
|
|
24
|
+
},
|
|
25
|
+
"--verbose": {
|
|
26
|
+
field: "verbose",
|
|
27
|
+
value: true
|
|
28
|
+
},
|
|
29
|
+
"--dir": {
|
|
30
|
+
field: "dir",
|
|
31
|
+
value: true
|
|
32
|
+
},
|
|
33
|
+
"--preserve-root": {
|
|
34
|
+
field: "preserveRoot",
|
|
35
|
+
value: true
|
|
36
|
+
},
|
|
37
|
+
"--no-preserve-root": {
|
|
38
|
+
field: "preserveRoot",
|
|
39
|
+
value: false
|
|
40
|
+
},
|
|
41
|
+
"--one-file-system": {
|
|
42
|
+
field: "oneFileSystem",
|
|
43
|
+
value: true
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
/** --interactive=WHEN 的合法取值(GNU rm 语义)。 */
|
|
47
|
+
const INTERACTIVE_WHEN = /* @__PURE__ */ new Set([
|
|
48
|
+
"once",
|
|
49
|
+
"always",
|
|
50
|
+
"never"
|
|
51
|
+
]);
|
|
52
|
+
/**
|
|
53
|
+
* 解析 rm 风格参数(coreutils rm 语义):
|
|
54
|
+
* - 短选项支持组合(`-rf`),`-r`/`-R`/`--recursive` 等价,`-i`/`-I` 等价;
|
|
55
|
+
* - `--` 结束符后全部视为路径(允许 `-` 开头的文件名);
|
|
56
|
+
* - `--interactive[=once|always|never]` 接受(回收站模式忽略交互);
|
|
57
|
+
* - 未知短选项 → invalid-option,未知长选项 → unrecognized-option;
|
|
58
|
+
* - 无路径 → missing-operand。
|
|
59
|
+
*/
|
|
60
|
+
function parseRmArgs(argv) {
|
|
61
|
+
const parsed = {
|
|
62
|
+
recursive: false,
|
|
63
|
+
force: false,
|
|
64
|
+
interactive: false,
|
|
65
|
+
verbose: false,
|
|
66
|
+
dir: false,
|
|
67
|
+
preserveRoot: true,
|
|
68
|
+
oneFileSystem: false,
|
|
69
|
+
paths: []
|
|
70
|
+
};
|
|
71
|
+
let afterDoubleDash = false;
|
|
72
|
+
for (const arg of argv) {
|
|
73
|
+
if (afterDoubleDash) {
|
|
74
|
+
parsed.paths.push(arg);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (arg === "--") {
|
|
78
|
+
afterDoubleDash = true;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (arg.startsWith("--")) {
|
|
82
|
+
const eq = arg.indexOf("=");
|
|
83
|
+
const name = eq >= 0 ? arg.slice(0, eq) : arg;
|
|
84
|
+
const value = eq >= 0 ? arg.slice(eq + 1) : void 0;
|
|
85
|
+
if (name === "---presume-input-tty") continue;
|
|
86
|
+
const flag = LONG_OPTIONS[name];
|
|
87
|
+
if (flag === void 0) {
|
|
88
|
+
parsed.error = {
|
|
89
|
+
kind: "unrecognized-option",
|
|
90
|
+
option: name
|
|
91
|
+
};
|
|
92
|
+
return parsed;
|
|
93
|
+
}
|
|
94
|
+
if (value !== void 0) {
|
|
95
|
+
if (name === "--interactive") {
|
|
96
|
+
if (!INTERACTIVE_WHEN.has(value)) {
|
|
97
|
+
parsed.error = {
|
|
98
|
+
kind: "invalid-argument",
|
|
99
|
+
option: name,
|
|
100
|
+
value
|
|
101
|
+
};
|
|
102
|
+
return parsed;
|
|
103
|
+
}
|
|
104
|
+
parsed.interactive = true;
|
|
105
|
+
} else if (name === "--preserve-root") {
|
|
106
|
+
if (value === "all") parsed.preserveRoot = true;
|
|
107
|
+
else {
|
|
108
|
+
parsed.error = {
|
|
109
|
+
kind: "invalid-argument",
|
|
110
|
+
option: name,
|
|
111
|
+
value
|
|
112
|
+
};
|
|
113
|
+
return parsed;
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
parsed.error = {
|
|
117
|
+
kind: "option-argument",
|
|
118
|
+
option: name
|
|
119
|
+
};
|
|
120
|
+
return parsed;
|
|
121
|
+
}
|
|
122
|
+
} else parsed[flag.field] = flag.value;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (arg.startsWith("-") && arg.length > 1) {
|
|
126
|
+
for (const ch of arg.slice(1)) {
|
|
127
|
+
const flag = SHORT_OPTIONS[ch];
|
|
128
|
+
if (flag === void 0) {
|
|
129
|
+
parsed.error = {
|
|
130
|
+
kind: "invalid-option",
|
|
131
|
+
option: ch
|
|
132
|
+
};
|
|
133
|
+
return parsed;
|
|
134
|
+
}
|
|
135
|
+
parsed[flag] = true;
|
|
136
|
+
}
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
parsed.paths.push(arg);
|
|
140
|
+
}
|
|
141
|
+
if (parsed.paths.length === 0 && parsed.error === void 0) parsed.error = { kind: "missing-operand" };
|
|
142
|
+
return parsed;
|
|
143
|
+
}
|
|
144
|
+
/** 规范化 verbose 输出的目录尾部斜杠(`a///` → `a/`,对齐 rm)。 */
|
|
145
|
+
function normalizeTrailingSlash(p) {
|
|
146
|
+
return p.replace(/[\\/]+$/, "/");
|
|
147
|
+
}
|
|
148
|
+
function errorMessage(err) {
|
|
149
|
+
return err instanceof Error ? err.message : String(err);
|
|
150
|
+
}
|
|
151
|
+
/** 路径规范化后以 `.` 或 `..` 结尾(r-4 移植:拒绝删除)。 */
|
|
152
|
+
function isDotOrDotDot(p) {
|
|
153
|
+
const last = p.replace(/[\\/]+$/, "").split(/[\\/]/).pop();
|
|
154
|
+
return last === "." || last === "..";
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 路径是 `/` 或其同义词(`//`、`///`、符号链接/junction 指向根目录)。
|
|
158
|
+
* 根判定用 dev/ino 比较(GNU preserve-root 的精确语义):字符串比较在
|
|
159
|
+
* Windows 上会漏判(junction 指向 C:\ 时 realpath 是 "C:\\" 而非 "/"),
|
|
160
|
+
* 而 dev/ino 在 Windows 与 POSIX 上都正确。空串不是根。
|
|
161
|
+
*
|
|
162
|
+
* Windows 语义说明:Node 的 `/` 解析为当前盘根(随 cwd 变化),而 Git
|
|
163
|
+
* bash 的 `/` 是 MSYS 根——两者都只保护"字面 `/` 及其 dev/ino 等价物",
|
|
164
|
+
* 与 GNU rm 在 Git bash 的行为一致(GNU 同样不保护指向盘根的链接)。
|
|
165
|
+
*/
|
|
166
|
+
async function isRootPath(p, deps) {
|
|
167
|
+
if (p.length > 0 && p.replace(/[\\/]+$/, "") === "") return true;
|
|
168
|
+
const st = await deps.stat(p);
|
|
169
|
+
if (st === null) return false;
|
|
170
|
+
const root = await deps.stat("/");
|
|
171
|
+
return root !== null && st.dev === root.dev && st.ino === root.ino;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* 执行一次 rm 语义的回收站删除。
|
|
175
|
+
*
|
|
176
|
+
* 行为对齐 coreutils rm:
|
|
177
|
+
* - 无 -r/-d 时目录报 `Is a directory` 并保留(rm4);
|
|
178
|
+
* - 不存在路径:-f 静默忽略(f-1),否则报 `No such file or directory`;
|
|
179
|
+
* - `.`/`..` 结尾路径拒绝(r-4);`/` 及同义词拒绝(r-root,preserve-root 默认开);
|
|
180
|
+
* - 符号链接按链接本身处理(lstat,dangling-symlink 移植);
|
|
181
|
+
* - trash 失败:-f 静默但退出 1,否则报 `cannot remove`;
|
|
182
|
+
* - -v 输出 `removed 'x'` / `removed directory 'x'`(r-1/d-1/v-slash);
|
|
183
|
+
* - 退出码:全部成功 0,任一失败 1。
|
|
184
|
+
*/
|
|
185
|
+
async function runTrashCli(argv, deps) {
|
|
186
|
+
const parsed = parseRmArgs(argv);
|
|
187
|
+
if (parsed.error !== void 0) {
|
|
188
|
+
if (parsed.error.kind === "missing-operand") deps.stderr("rm: missing operand");
|
|
189
|
+
else if (parsed.error.kind === "invalid-option") deps.stderr(`rm: invalid option -- '${parsed.error.option}'`);
|
|
190
|
+
else if (parsed.error.kind === "unrecognized-option") deps.stderr(`rm: unrecognized option '${parsed.error.option}'`);
|
|
191
|
+
else if (parsed.error.kind === "invalid-argument") deps.stderr(`rm: invalid argument '${parsed.error.value}' for '${parsed.error.option}'`);
|
|
192
|
+
else deps.stderr(`rm: option '${parsed.error.option}' doesn't allow an argument`);
|
|
193
|
+
deps.exit(1);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const { recursive, force, verbose, dir, preserveRoot, paths } = parsed;
|
|
197
|
+
const items = [];
|
|
198
|
+
let failed = false;
|
|
199
|
+
for (const p of paths) {
|
|
200
|
+
if (isDotOrDotDot(p)) {
|
|
201
|
+
deps.stderr(`rm: refusing to remove '.' or '..' directory: skipping '${p}'`);
|
|
202
|
+
failed = true;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (preserveRoot && await isRootPath(p, deps)) {
|
|
206
|
+
deps.stderr("rm: it is dangerous to operate recursively on '/'");
|
|
207
|
+
deps.stderr("rm: use --no-preserve-root to override this failsafe");
|
|
208
|
+
failed = true;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const st = await deps.lstat(p);
|
|
212
|
+
if (st === null) {
|
|
213
|
+
if (!force) {
|
|
214
|
+
deps.stderr(`rm: cannot remove '${p}': No such file or directory`);
|
|
215
|
+
failed = true;
|
|
216
|
+
}
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (st.isDirectory && !recursive && !dir) {
|
|
220
|
+
deps.stderr(`rm: cannot remove '${p}': Is a directory`);
|
|
221
|
+
failed = true;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
items.push({
|
|
225
|
+
path: p,
|
|
226
|
+
isDir: st.isDirectory
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
if (items.length > 0) try {
|
|
230
|
+
await deps.trash(items.map((item) => item.path));
|
|
231
|
+
} catch (err) {
|
|
232
|
+
deps.stderr(`rm: cannot remove '${items[0].path}': ${errorMessage(err)}`);
|
|
233
|
+
failed = true;
|
|
234
|
+
}
|
|
235
|
+
if (verbose && !failed) for (const { path: p, isDir } of items) deps.stdout(isDir ? `removed directory '${normalizeTrailingSlash(p)}'` : `removed '${p}'`);
|
|
236
|
+
deps.exit(failed ? 1 : 0);
|
|
237
|
+
}
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/tools/bash/trash-cli-main.ts
|
|
240
|
+
/**
|
|
241
|
+
* rmSafe CLI 入口(构建产物 lib/trash-cli.mjs,被注入的 bash `rm()` 函数调用)。
|
|
242
|
+
*
|
|
243
|
+
* trash 包在此动态加载:单测只 import trash-cli.ts(纯逻辑),
|
|
244
|
+
* 不会触发 trash 依赖解析。
|
|
245
|
+
* @module @xiaoso/dsh-tool-plus/bash/trash-cli-main
|
|
246
|
+
*/
|
|
247
|
+
async function main() {
|
|
248
|
+
const { default: trash } = await import("trash");
|
|
249
|
+
await runTrashCli(process.argv.slice(2), {
|
|
250
|
+
trash,
|
|
251
|
+
lstat: async (p) => {
|
|
252
|
+
try {
|
|
253
|
+
return { isDirectory: (await fs.promises.lstat(p)).isDirectory() };
|
|
254
|
+
} catch {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
stat: async (p) => {
|
|
259
|
+
try {
|
|
260
|
+
const st = await fs.promises.stat(p);
|
|
261
|
+
return {
|
|
262
|
+
dev: st.dev,
|
|
263
|
+
ino: st.ino
|
|
264
|
+
};
|
|
265
|
+
} catch {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
stdout: (s) => process.stdout.write(`${s}\n`),
|
|
270
|
+
stderr: (s) => process.stderr.write(`${s}\n`),
|
|
271
|
+
exit: (c) => {
|
|
272
|
+
process.exitCode = c;
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
main().catch((err) => {
|
|
277
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
278
|
+
process.exitCode = 1;
|
|
279
|
+
});
|
|
280
|
+
//#endregion
|
|
281
|
+
export {};
|
|
@@ -7202,7 +7202,7 @@ const settings = {
|
|
|
7202
7202
|
};
|
|
7203
7203
|
//#endregion
|
|
7204
7204
|
//#region package.json
|
|
7205
|
-
var version = "0.1.
|
|
7205
|
+
var version = "0.1.1";
|
|
7206
7206
|
/** `.tmp` files older than this are treated as orphaned writes and swept. */
|
|
7207
7207
|
const TMP_ORPHAN_MAX_AGE_MS = 3e5;
|
|
7208
7208
|
function markitConversionCacheKey(bytes, extension) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiaoso/dsh-tool-plus",
|
|
3
3
|
"description": "Oh My Pi bash runtime ported as a deepseek-harness tool plugin: persistent shell session, output minimizer, managed background jobs, command interception",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"deepseek-harness",
|
|
@@ -109,6 +109,7 @@
|
|
|
109
109
|
"@oh-my-pi/pi-utils": "17.3.5",
|
|
110
110
|
"json5": "^2.2.3",
|
|
111
111
|
"puppeteer-core": "^25.3.0",
|
|
112
|
+
"trash": "^10.1.1",
|
|
112
113
|
"yaml": "^2.9.0"
|
|
113
114
|
},
|
|
114
115
|
"devDependencies": {
|
package/presets/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiaoso/dsh-tool-plus-presets",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Agent-preset installer for dsh-tool-plus: installs the standard / PTC enhanced preset templates into a DeepSeek Harness home.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|