dsh-slash-command 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/lib/client.js +13 -3
- package/lib/client.js.map +2 -2
- package/lib/core.js +45 -0
- package/lib/core.js.map +2 -2
- package/lib/index.js +49 -4
- package/lib/index.js.map +3 -3
- package/lib/types/client/locales.d.ts +3 -0
- package/lib/types/core.d.ts +9 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ dsh plugin --profile web add dsh-slash-command
|
|
|
33
33
|
]}
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
`name`/别名需匹配 `^[a-z][a-z0-9_-]*$`;`cwd` 为 `workspace`(当前会话目录)或绝对路径;`timeoutMs` 默认 30000,上限 600000
|
|
36
|
+
`name`/别名需匹配 `^[a-z][a-z0-9_-]*$`;`cwd` 为 `workspace`(当前会话目录)或绝对路径;`timeoutMs` 默认 30000,上限 600000;`passEnv` 见下方「安全」。
|
|
37
37
|
|
|
38
38
|
参数(命令名后面的文字):
|
|
39
39
|
|
|
@@ -44,6 +44,13 @@ dsh plugin --profile web add dsh-slash-command
|
|
|
44
44
|
|
|
45
45
|
Shell 命令以 dsh 进程权限运行,**没有审批确认**。能写配置文件或能登录 Web UI 的人就等于拥有这个权限,请只在可信环境使用。
|
|
46
46
|
|
|
47
|
+
对标 Hermes,Shell 命令额外做两层防误泄露:
|
|
48
|
+
|
|
49
|
+
- **环境变量过滤**:名字含 `KEY` / `TOKEN` / `SECRET` / `PASSWORD` / `CREDENTIAL` / `AUTH` 等片段的变量(如 `DEEPSEEK_API_KEY`)不传给命令。确实需要的,在该命令的 `passEnv` 里列出,例如 `"passEnv":["GH_TOKEN"]`。
|
|
50
|
+
- **输出遮蔽**:结果会写进会话记录,显示前先把 dsh 环境里的密钥值、常见 key 格式(`sk-…`、`ghp_…`、`AKIA…` 等)、`XXX_KEY=…` 赋值、`Authorization` 头、JWT、私钥块、URL 里的账号密码替换成 `***`。
|
|
51
|
+
|
|
52
|
+
这两层只降低误泄露概率,**不是安全边界**:命令与 dsh 同一系统用户,仍能直接读取 `~/.dsh` 等文件。
|
|
53
|
+
|
|
47
54
|
## 开发
|
|
48
55
|
|
|
49
56
|
```sh
|
package/lib/client.js
CHANGED
|
@@ -54,6 +54,7 @@ var zh = {
|
|
|
54
54
|
"field.target": "\u76EE\u6807\u547D\u4EE4",
|
|
55
55
|
"field.cwd": "\u5DE5\u4F5C\u76EE\u5F55",
|
|
56
56
|
"field.timeoutMs": "\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09",
|
|
57
|
+
"field.passEnv": "\u4FDD\u7559\u73AF\u5883\u53D8\u91CF",
|
|
57
58
|
"type.prompt": "Prompt",
|
|
58
59
|
"type.exec": "Shell",
|
|
59
60
|
"type.alias": "\u8F6C\u53D1",
|
|
@@ -61,10 +62,12 @@ var zh = {
|
|
|
61
62
|
"tip.command": "$ARGUMENTS \u4E3A\u5B8C\u6574\u53C2\u6570\uFF0C$1 $2\u2026 \u4E3A\u6309\u7A7A\u683C\u62C6\u5206\u7684\u53C2\u6570\uFF1B\u53C2\u6570\u53EA\u4F5C\u53D8\u91CF\u4F20\u5165\uFF0C\u4E0D\u4F1A\u88AB\u5F53\u4F5C\u547D\u4EE4\u6267\u884C\u3002",
|
|
62
63
|
"tip.target": "\u8F6C\u53D1\u5230\u53E6\u4E00\u6761\u659C\u6760\u547D\u4EE4\uFF0C\u53EF\u5E26\u56FA\u5B9A\u53C2\u6570\u3002$ARGUMENTS \u66FF\u6362\u4E3A\u53C2\u6570\uFF0C\u672A\u4F7F\u7528\u65F6\u53C2\u6570\u63A5\u5728\u672B\u5C3E\u3002",
|
|
63
64
|
"tip.cwd": "workspace \u4E3A\u5F53\u524D\u4F1A\u8BDD\u76EE\u5F55\uFF0C\u4E5F\u53EF\u586B\u7EDD\u5BF9\u8DEF\u5F84\u3002",
|
|
65
|
+
"tip.passEnv": "\u540D\u5B57\u542B KEY\u3001TOKEN\u3001SECRET\u3001PASSWORD \u7B49\u7684\u73AF\u5883\u53D8\u91CF\u9ED8\u8BA4\u4E0D\u4F20\u7ED9\u547D\u4EE4\uFF1B\u8FD9\u91CC\u5217\u51FA\u7684\u7167\u5E38\u4F20\u5165\u3002\u8F93\u51FA\u91CC\u7684\u5BC6\u94A5\u59CB\u7EC8\u4F1A\u88AB\u906E\u853D\u3002",
|
|
64
66
|
"execWarn": "\u4EE5 dsh \u8FDB\u7A0B\u6743\u9650\u76F4\u63A5\u8FD0\u884C\uFF0C\u4E0D\u7ECF\u5BA1\u6279\u3002",
|
|
65
67
|
"err.type": "\u7C7B\u578B\u4E0D\u5BF9",
|
|
66
68
|
"err.required": "\u5FC5\u586B",
|
|
67
69
|
"err.pattern": "\u5C0F\u5199\u5B57\u6BCD\u5F00\u5934\uFF0C\u4EC5\u542B a-z 0-9 - _",
|
|
70
|
+
"err.envName": "\u53D8\u91CF\u540D\u4EC5\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001_\uFF0C\u9017\u53F7\u5206\u9694",
|
|
68
71
|
"err.duplicate": "\u5DF2\u88AB\u5360\u7528\u6216\u6307\u5411\u81EA\u8EAB",
|
|
69
72
|
"err.enum": "\u586B workspace \u6216\u7EDD\u5BF9\u8DEF\u5F84",
|
|
70
73
|
"err.range": "1\u2013600000 \u4E4B\u95F4\u7684\u6574\u6570"
|
|
@@ -94,6 +97,7 @@ var en = {
|
|
|
94
97
|
"field.target": "Target command",
|
|
95
98
|
"field.cwd": "Working directory",
|
|
96
99
|
"field.timeoutMs": "Timeout (ms)",
|
|
100
|
+
"field.passEnv": "Pass-through env vars",
|
|
97
101
|
"type.prompt": "Prompt",
|
|
98
102
|
"type.exec": "Shell",
|
|
99
103
|
"type.alias": "Alias",
|
|
@@ -101,10 +105,12 @@ var en = {
|
|
|
101
105
|
"tip.command": "$ARGUMENTS is the full argument text; $1 $2\u2026 are the space-separated words. Arguments are passed as variables, never run as code.",
|
|
102
106
|
"tip.target": "Forwards to another slash command, optionally with fixed arguments. $ARGUMENTS is replaced with the arguments; if absent they are appended.",
|
|
103
107
|
"tip.cwd": "workspace is the current session directory; an absolute path also works.",
|
|
108
|
+
"tip.passEnv": "Variables named like KEY, TOKEN, SECRET or PASSWORD are withheld from the command by default; ones listed here are passed through. Secrets in the output are always masked.",
|
|
104
109
|
"execWarn": "Runs directly with the dsh process\u2019s privileges, with no approval.",
|
|
105
110
|
"err.type": "Wrong type",
|
|
106
111
|
"err.required": "Required",
|
|
107
112
|
"err.pattern": "Start with a lowercase letter; only a-z 0-9 - _",
|
|
113
|
+
"err.envName": "Letters, digits and _ only, comma separated",
|
|
108
114
|
"err.duplicate": "Already used, or points to itself",
|
|
109
115
|
"err.enum": "Use workspace or an absolute path",
|
|
110
116
|
"err.range": "An integer from 1 to 600000"
|
|
@@ -113,7 +119,7 @@ var en = {
|
|
|
113
119
|
// src/client/panel.tsx
|
|
114
120
|
var import_react = require("react");
|
|
115
121
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
116
|
-
var blank = () => ({ name: "", aliases: "", description: "", type: "prompt", prompt: "", target: "", command: "", cwd: "", timeoutMs: "" });
|
|
122
|
+
var blank = () => ({ name: "", aliases: "", description: "", type: "prompt", prompt: "", target: "", command: "", cwd: "", timeoutMs: "", passEnv: "" });
|
|
117
123
|
var toDraft = (c) => ({
|
|
118
124
|
name: String(c.name ?? ""),
|
|
119
125
|
aliases: Array.isArray(c.aliases) ? c.aliases.join(", ") : "",
|
|
@@ -123,7 +129,8 @@ var toDraft = (c) => ({
|
|
|
123
129
|
target: String(c.target ?? ""),
|
|
124
130
|
command: String(c.command ?? ""),
|
|
125
131
|
cwd: c.cwd === void 0 || c.cwd === "workspace" ? "" : String(c.cwd),
|
|
126
|
-
timeoutMs: c.timeoutMs === void 0 || c.timeoutMs === 3e4 ? "" : String(c.timeoutMs)
|
|
132
|
+
timeoutMs: c.timeoutMs === void 0 || c.timeoutMs === 3e4 ? "" : String(c.timeoutMs),
|
|
133
|
+
passEnv: Array.isArray(c.passEnv) ? c.passEnv.join(", ") : ""
|
|
127
134
|
});
|
|
128
135
|
function toWire(d) {
|
|
129
136
|
const out = {
|
|
@@ -138,6 +145,8 @@ function toWire(d) {
|
|
|
138
145
|
out.command = d.command;
|
|
139
146
|
if (d.cwd.trim() !== "") out.cwd = d.cwd.trim();
|
|
140
147
|
if (d.timeoutMs.trim() !== "") out.timeoutMs = Number(d.timeoutMs);
|
|
148
|
+
const passEnv = d.passEnv.split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
149
|
+
if (passEnv.length > 0) out.passEnv = passEnv;
|
|
141
150
|
}
|
|
142
151
|
return out;
|
|
143
152
|
}
|
|
@@ -343,7 +352,7 @@ function Editor({ t, draft, errors, failure, busy, onChange, onCancel, onSave })
|
|
|
343
352
|
tip && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Help, { label: t("help", { field: label }), text: t(tip) })
|
|
344
353
|
] }),
|
|
345
354
|
control({ id, ...err ? { "aria-invalid": true, "aria-describedby": `${id}-err` } : {} }),
|
|
346
|
-
err && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { id: `${id}-err`, className: "dsc-error", children: t(`err.${err.code}`) })
|
|
355
|
+
err && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { id: `${id}-err`, className: "dsc-error", children: t(key === "passEnv" && err.code === "pattern" ? "err.envName" : `err.${err.code}`) })
|
|
347
356
|
] });
|
|
348
357
|
};
|
|
349
358
|
const text = (key, extra = {}) => (p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -405,6 +414,7 @@ function Editor({ t, draft, errors, failure, busy, onChange, onCancel, onSave })
|
|
|
405
414
|
field("cwd", text("cwd", { placeholder: "workspace", mono: true }), "tip.cwd"),
|
|
406
415
|
field("timeoutMs", text("timeoutMs", { placeholder: "30000", inputMode: "numeric" }))
|
|
407
416
|
] }),
|
|
417
|
+
field("passEnv", text("passEnv", { placeholder: "GH_TOKEN", mono: true }), "tip.passEnv"),
|
|
408
418
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsc-warn", children: t("execWarn") })
|
|
409
419
|
] }),
|
|
410
420
|
failure !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsc-error", role: "alert", children: failure }),
|
package/lib/client.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/client/index.tsx", "../src/client/locales.ts", "../src/client/panel.tsx"],
|
|
4
|
-
"sourcesContent": ["/** dsh-slash-command client \u534A\u8FB9\uFF1A\u8BBE\u7F6E\u5F39\u7A97\u91CC\u7684\u300C\u81EA\u5B9A\u4E49\u547D\u4EE4\u300D\u5206\u533A\uFF08settings.section\uFF09\u3002 */\nimport type { Context } from '@deepseek-ai/cordis'\nimport { en, NS, zh, type LocaleKey } from './locales.ts'\nimport { SlashCommandSection } from './panel.tsx'\n\nexport const name = 'dsh-slash-command'\nexport const inject = ['slots', 'locale']\n\ntype Translate = (key: LocaleKey, params?: Record<string, unknown>) => string\n\ninterface ClientContext extends Context {\n slots: {\n inject(key: string, callback: () => unknown): () => void\n register(options: { name: string; id: string; order: number; locale: string; label: () => string }, component: unknown): () => void\n }\n locale: {\n register(ns: string, dictionaries: { zh: Record<string, string>; en: Record<string, string> }): () => void\n bind(ns: string): Translate\n }\n}\n\n// \u53EA\u7528\u539F\u751F --dsw-* token\uFF0C\u53D6\u503C\u5BF9\u9F50 ui-settings-models / ui-primitives \u7684\u8BBE\u7F6E\u9875\u6837\u5F0F\u3002\nconst css = `\n.dsc-section{display:flex;flex-direction:column;gap:12px;max-width:720px;color:var(--dsw-alias-label-primary)}\n.dsc-title{margin:0;font-size:16px;line-height:24px;font-weight:500}\n.dsc-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}\n.dsc-body{display:flex;flex-direction:column;gap:8px;margin-top:12px}\n.dsc-rows{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}\n.dsc-body>.dsc-editor{margin-top:0}\n.dsc-card{border:.5px solid var(--dsw-alias-settings-card-stroke);background:var(--dsw-alias-settings-card-fill);border-radius:var(--dsw-radius-xl);padding:12px 14px;display:flex;flex-direction:column;gap:6px;}\n.dsc-head{display:flex;align-items:center;gap:10px;min-height:28px}\n.dsc-identity{display:inline-flex;align-items:center;flex-wrap:wrap;gap:6px;min-width:0}\n.dsc-name{font-family:var(--ds-font-family-code);font-size:14px;line-height:22px;font-weight:500}\n.dsc-tag{flex:none;padding:1px 6px;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-xs);font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary)}\ncode.dsc-tag{font-family:var(--ds-font-family-code)}\n.dsc-desc{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}\n.dsc-actions{display:inline-flex;align-items:center;gap:4px;margin-left:auto}\n.dsc-btn{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:6px;height:36px;padding:0 14px;border:none;border-radius:var(--dsw-radius-md);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;cursor:pointer}\n.dsc-btn:disabled{opacity:.4;cursor:default}\n.dsc-btn:focus-visible,.dsc-help-btn:focus-visible,.dsc-segment:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary))}\n.dsc-compact{height:28px;padding:0 10px;border-radius:var(--dsw-radius-sm);font-size:12px;line-height:18px}\n.dsc-primary{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}\n.dsc-secondary{border:.5px solid var(--dsw-alias-border-l3)}\n.dsc-danger{color:var(--dsw-alias-state-error-primary)}\n.dsc-danger-fill{background:var(--dsw-alias-state-error-primary);color:#fff}\n.dsc-add{width:100%;height:44px;border:1px dashed var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-lg)}\n@media (hover:hover){\n.dsc-primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}\n.dsc-secondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}\n.dsc-add:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}\n.dsc-danger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}\n.dsc-danger-fill:hover:not(:disabled){filter:brightness(.92)}\n.dsc-segment:hover{color:var(--dsw-alias-label-primary)}\n.dsc-help:hover .dsc-tip{display:block}\n.dsc-help-btn:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-tertiary)}\n}\n.dsc-confirm{display:flex;align-items:center;flex-wrap:wrap;gap:8px 12px;margin-top:4px;padding:10px 12px;border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-bg-module-platform);font-size:13px;line-height:20px}\n.dsc-editor{display:flex;flex-direction:column;gap:14px;margin-top:6px;padding:14px 16px;border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-bg-module-platform)}\n.dsc-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px}\n.dsc-field{display:flex;flex-direction:column;gap:6px;min-width:0}\n.dsc-label{display:inline-flex;align-items:center;gap:8px;font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-secondary)}\n.dsc-input,.dsc-affix{box-sizing:border-box;width:100%;min-height:32px;padding:4px 8px;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;outline:none}\n.dsc-input:focus,.dsc-affix:focus-within{border-color:var(--dsw-alias-state-business-primary)}\n.dsc-input[aria-invalid],.dsc-affix:has([aria-invalid]){border-color:var(--dsw-alias-state-error-primary)}\n.dsc-input::placeholder{color:var(--dsw-alias-label-dimmed)}\n.dsc-area{resize:vertical;min-height:72px}\n.dsc-mono{font-family:var(--ds-font-family-code);font-size:13px}\n.dsc-affix{display:flex;align-items:center;gap:2px;font-family:var(--ds-font-family-code);color:var(--dsw-alias-label-tertiary)}\n.dsc-bare{flex:1;min-width:0;padding:0;border:none;outline:none;background:transparent;color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}\n.dsc-segmented{position:relative;display:inline-grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:2px;align-self:flex-start;padding:4px;border-radius:var(--dsw-radius-md);background:var(--dsw-alias-interactive-bg-hover)}\n.dsc-indicator{position:absolute;top:4px;left:4px;width:calc((100% - 8px - 2px * (var(--dsc-count) - 1)) / var(--dsc-count));height:calc(100% - 8px);border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-elevation-soft);transform:translateX(calc(var(--dsc-index) * (100% + 2px)));transition:transform 160ms ease;pointer-events:none}\n.dsc-segment{position:relative;z-index:1;box-sizing:border-box;height:28px;padding:0 16px;border:0;border-radius:var(--dsw-radius-sm);background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;line-height:20px;font-weight:500;cursor:pointer}\n.dsc-segment[aria-pressed=true]{color:var(--dsw-alias-label-primary)}\n.dsc-help{position:relative;display:inline-flex}\n.dsc-help-btn{box-sizing:border-box;width:16px;height:16px;padding:0;border:.5px solid var(--dsw-alias-border-l3);border-radius:50%;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:11px;line-height:15px;text-align:center;cursor:help}\n.dsc-tip{display:none;position:absolute;z-index:10;left:-4px;top:calc(100% + 6px);width:max-content;max-width:280px;padding:3px 7px;border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-tooltip-bg);color:var(--dsw-static-neutral-bluish-00,#fff);font-size:13px;line-height:20px;font-weight:400;pointer-events:none}\n.dsc-help-btn:focus-visible+.dsc-tip{display:block}\n.dsc-warn{margin:-4px 0 0;font-size:12px;line-height:18px;color:var(--dsw-alias-state-warn-label)}\n.dsc-error{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-state-error-primary)}\n.dsc-notice{display:flex;align-items:center;gap:12px;font-size:13px;line-height:20px;color:var(--dsw-alias-state-error-primary)}\n.dsc-editor-actions{display:flex;justify-content:flex-end;gap:8px}\n@media (prefers-reduced-motion:reduce){.dsc-indicator{transition:none}}\n`\n\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-slash-command: dictionaries')\n const t = ctx.locale.bind(NS)\n if (typeof document !== 'undefined') {\n document.querySelectorAll('style[data-plugin=\"dsh-slash-command\"]').forEach(node => node.remove())\n const tag = document.createElement('style')\n tag.dataset.plugin = 'dsh-slash-command'\n tag.textContent = css\n document.head.appendChild(tag)\n }\n\n ctx.slots.inject('settings.section', () => ctx.slots.register(\n { name: 'settings.section', id: 'slash-commands', order: 25, locale: NS, label: () => t('title') },\n SlashCommandSection,\n ))\n}\n", "/** \u8BBE\u7F6E\u9875\u300C\u81EA\u5B9A\u4E49\u547D\u4EE4\u300D\u6587\u6848\u5B57\u5178\uFF08zh/en\uFF09\uFF1B`/` \u83DC\u5355\u6587\u6848\u6765\u81EA\u914D\u7F6E\u6587\u4EF6\u672C\u8EAB\uFF0C\u8FD9\u91CC\u4E0D\u7BA1\u3002 */\nexport const NS = 'dsh-slash-command'\n\nexport const zh = {\n 'title': '\u81EA\u5B9A\u4E49\u547D\u4EE4',\n 'add': '\u65B0\u589E\u547D\u4EE4',\n 'edit': '\u7F16\u8F91',\n 'editOf': '\u7F16\u8F91 /{name}',\n 'delete': '\u5220\u9664',\n 'deleteOf': '\u5220\u9664 /{name}',\n 'deleteConfirm': '\u5220\u9664 /{name}\uFF1F\u7ACB\u5373\u751F\u6548\uFF0C\u65E0\u6CD5\u64A4\u9500\u3002',\n 'cancel': '\u53D6\u6D88',\n 'save': '\u4FDD\u5B58',\n 'saving': '\u4FDD\u5B58\u4E2D\u2026',\n 'saved': '\u5DF2\u4FDD\u5B58',\n 'retry': '\u91CD\u8BD5',\n 'loadFailed': '\u8BFB\u53D6\u5931\u8D25\uFF1A{detail}',\n 'saveFailed': '\u4FDD\u5B58\u5931\u8D25\uFF1A{detail}',\n 'help': '{field}\u8BF4\u660E',\n 'field.type': '\u7C7B\u578B',\n 'field.name': '\u540D\u79F0',\n 'field.aliases': '\u522B\u540D',\n 'field.description': '\u63CF\u8FF0',\n 'field.prompt': 'Prompt',\n 'field.command': '\u547D\u4EE4',\n 'field.target': '\u76EE\u6807\u547D\u4EE4',\n 'field.cwd': '\u5DE5\u4F5C\u76EE\u5F55',\n 'field.timeoutMs': '\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09',\n 'type.prompt': 'Prompt',\n 'type.exec': 'Shell',\n 'type.alias': '\u8F6C\u53D1',\n 'tip.prompt': '$ARGUMENTS \u66FF\u6362\u4E3A\u547D\u4EE4\u540E\u7684\u53C2\u6570\uFF1B\u672A\u4F7F\u7528\u65F6\u53C2\u6570\u8FFD\u52A0\u5230\u672B\u5C3E\u3002',\n 'tip.command': '$ARGUMENTS \u4E3A\u5B8C\u6574\u53C2\u6570\uFF0C$1 $2\u2026 \u4E3A\u6309\u7A7A\u683C\u62C6\u5206\u7684\u53C2\u6570\uFF1B\u53C2\u6570\u53EA\u4F5C\u53D8\u91CF\u4F20\u5165\uFF0C\u4E0D\u4F1A\u88AB\u5F53\u4F5C\u547D\u4EE4\u6267\u884C\u3002',\n 'tip.target': '\u8F6C\u53D1\u5230\u53E6\u4E00\u6761\u659C\u6760\u547D\u4EE4\uFF0C\u53EF\u5E26\u56FA\u5B9A\u53C2\u6570\u3002$ARGUMENTS \u66FF\u6362\u4E3A\u53C2\u6570\uFF0C\u672A\u4F7F\u7528\u65F6\u53C2\u6570\u63A5\u5728\u672B\u5C3E\u3002',\n 'tip.cwd': 'workspace \u4E3A\u5F53\u524D\u4F1A\u8BDD\u76EE\u5F55\uFF0C\u4E5F\u53EF\u586B\u7EDD\u5BF9\u8DEF\u5F84\u3002',\n 'execWarn': '\u4EE5 dsh \u8FDB\u7A0B\u6743\u9650\u76F4\u63A5\u8FD0\u884C\uFF0C\u4E0D\u7ECF\u5BA1\u6279\u3002',\n 'err.type': '\u7C7B\u578B\u4E0D\u5BF9',\n 'err.required': '\u5FC5\u586B',\n 'err.pattern': '\u5C0F\u5199\u5B57\u6BCD\u5F00\u5934\uFF0C\u4EC5\u542B a-z 0-9 - _',\n 'err.duplicate': '\u5DF2\u88AB\u5360\u7528\u6216\u6307\u5411\u81EA\u8EAB',\n 'err.enum': '\u586B workspace \u6216\u7EDD\u5BF9\u8DEF\u5F84',\n 'err.range': '1\u2013600000 \u4E4B\u95F4\u7684\u6574\u6570',\n} as const\n\nexport type LocaleKey = keyof typeof zh\n\nexport const en: Record<LocaleKey, string> = {\n 'title': 'Custom commands',\n 'add': 'Add command',\n 'edit': 'Edit',\n 'editOf': 'Edit /{name}',\n 'delete': 'Delete',\n 'deleteOf': 'Delete /{name}',\n 'deleteConfirm': 'Delete /{name}? Takes effect immediately and cannot be undone.',\n 'cancel': 'Cancel',\n 'save': 'Save',\n 'saving': 'Saving\u2026',\n 'saved': 'Saved',\n 'retry': 'Retry',\n 'loadFailed': 'Load failed: {detail}',\n 'saveFailed': 'Save failed: {detail}',\n 'help': 'About {field}',\n 'field.type': 'Type',\n 'field.name': 'Name',\n 'field.aliases': 'Aliases',\n 'field.description': 'Description',\n 'field.prompt': 'Prompt',\n 'field.command': 'Command',\n 'field.target': 'Target command',\n 'field.cwd': 'Working directory',\n 'field.timeoutMs': 'Timeout (ms)',\n 'type.prompt': 'Prompt',\n 'type.exec': 'Shell',\n 'type.alias': 'Alias',\n 'tip.prompt': '$ARGUMENTS is replaced with the text after the command; if absent, that text is appended.',\n 'tip.command': '$ARGUMENTS is the full argument text; $1 $2\u2026 are the space-separated words. Arguments are passed as variables, never run as code.',\n 'tip.target': 'Forwards to another slash command, optionally with fixed arguments. $ARGUMENTS is replaced with the arguments; if absent they are appended.',\n 'tip.cwd': 'workspace is the current session directory; an absolute path also works.',\n 'execWarn': 'Runs directly with the dsh process\u2019s privileges, with no approval.',\n 'err.type': 'Wrong type',\n 'err.required': 'Required',\n 'err.pattern': 'Start with a lowercase letter; only a-z 0-9 - _',\n 'err.duplicate': 'Already used, or points to itself',\n 'err.enum': 'Use workspace or an absolute path',\n 'err.range': 'An integer from 1 to 600000',\n}\n", "/** \u8BBE\u7F6E\u9875\u300C\u81EA\u5B9A\u4E49\u547D\u4EE4\u300D\u5206\u533A\uFF1A\u547D\u4EE4\u5361\u7247\u5217\u8868 + \u5355\u5361\u884C\u5185\u7F16\u8F91\uFF1B\u6BCF\u6B21\u4FDD\u5B58/\u5220\u9664\u90FD\u6574\u8868\u5199\u56DE\u670D\u52A1\u7AEF\u3002 */\nimport { useEffect, useId, useState, type CSSProperties, type ReactElement, type ReactNode } from 'react'\nimport type { LocaleKey } from './locales.ts'\n\ntype T = (key: LocaleKey, params?: Record<string, unknown>) => string\n\ninterface Draft {\n name: string\n aliases: string\n description: string\n type: 'prompt' | 'exec' | 'alias'\n prompt: string\n target: string\n command: string\n cwd: string\n timeoutMs: string\n}\n\ninterface ApiError { index?: number; field?: string; code: string; message: string }\n\nconst blank = (): Draft => ({ name: '', aliases: '', description: '', type: 'prompt', prompt: '', target: '', command: '', cwd: '', timeoutMs: '' })\n\nconst toDraft = (c: Record<string, unknown>): Draft => ({\n name: String(c.name ?? ''),\n aliases: Array.isArray(c.aliases) ? c.aliases.join(', ') : '',\n description: String(c.description ?? ''),\n type: c.type === 'exec' || c.type === 'alias' ? c.type : 'prompt',\n prompt: String(c.prompt ?? ''),\n target: String(c.target ?? ''),\n command: String(c.command ?? ''),\n cwd: c.cwd === undefined || c.cwd === 'workspace' ? '' : String(c.cwd),\n timeoutMs: c.timeoutMs === undefined || c.timeoutMs === 30000 ? '' : String(c.timeoutMs),\n})\n\n/** \u7A7A\u5B57\u7B26\u4E32\u7701\u7565\uFF0C\u4EA4\u7ED9\u670D\u52A1\u7AEF\u8865\u9ED8\u8BA4\u503C\uFF1BtimeoutMs \u975E\u6570\u5B57\u539F\u6837\u53D1\u51FA\uFF0C\u7531\u670D\u52A1\u7AEF\u62A5 range\u3002 */\nfunction toWire(d: Draft): Record<string, unknown> {\n const out: Record<string, unknown> = {\n name: d.name.trim(),\n aliases: d.aliases.split(',').map(s => s.trim()).filter(s => s !== ''),\n description: d.description,\n type: d.type,\n }\n if (d.type === 'prompt') out.prompt = d.prompt\n else if (d.type === 'alias') out.target = d.target.trim()\n else {\n out.command = d.command\n if (d.cwd.trim() !== '') out.cwd = d.cwd.trim()\n if (d.timeoutMs.trim() !== '') out.timeoutMs = Number(d.timeoutMs)\n }\n return out\n}\n\nconst aliasList = (d: Draft): string[] => d.aliases.split(',').map(s => s.trim()).filter(s => s !== '')\n\ntype SaveResult = { ok: true } | { ok: false; errors: ApiError[]; text?: string }\n\nasync function saveAll(commands: Draft[], t: T): Promise<SaveResult> {\n try {\n const res = await fetch('/api/dsh-slash-command/save', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ commands: commands.map(toWire) }),\n })\n const body = await res.json() as { ok: boolean; errors?: ApiError[] }\n if (res.ok && body.ok) return { ok: true }\n if (res.status === 400) return { ok: false, errors: body.errors ?? [] }\n return { ok: false, errors: [], text: t('saveFailed', { detail: `HTTP ${res.status}` }) }\n } catch (e) {\n return { ok: false, errors: [], text: t('saveFailed', { detail: e instanceof Error ? e.message : String(e) }) }\n }\n}\n\nconst TYPES = ['prompt', 'exec', 'alias'] as const\n\n/** \u6253\u5F00\u4E2D\u7684\u7F16\u8F91\u5361\uFF1A`index` \u4E3A undefined \u8868\u793A\u65B0\u589E\u3002 */\ninterface Editing { index: number | undefined; draft: Draft }\n\nexport function SlashCommandSection({ t }: { t: T }): ReactElement {\n const [commands, setCommands] = useState<Draft[] | null>(null)\n const [loadError, setLoadError] = useState<string | undefined>()\n const [editing, setEditing] = useState<Editing | undefined>()\n const [deleting, setDeleting] = useState<number | undefined>()\n const [busy, setBusy] = useState(false)\n const [errors, setErrors] = useState<ApiError[]>([])\n const [failure, setFailure] = useState<string | undefined>()\n const [announce, setAnnounce] = useState('')\n\n const load = (): void => {\n setLoadError(undefined)\n fetch('/api/dsh-slash-command/read', { headers: { accept: 'application/json' }, cache: 'no-store' })\n .then(async (res) => {\n const body = await res.json() as { ok: boolean; commands?: Record<string, unknown>[]; errors?: ApiError[] }\n if (!res.ok || !body.ok) throw new Error(body.errors?.[0]?.message ?? `HTTP ${res.status}`)\n setCommands((body.commands ?? []).map(toDraft))\n })\n .catch((e: unknown) => { setLoadError(t('loadFailed', { detail: e instanceof Error ? e.message : String(e) })) })\n }\n useEffect(load, [])\n\n const reset = (): void => { setErrors([]); setFailure(undefined); setAnnounce('') }\n const openEditor = (next: Editing | undefined): void => { reset(); setDeleting(undefined); setEditing(next) }\n\n const commit = async (next: Draft[], target: number): Promise<boolean> => {\n setBusy(true)\n reset()\n const result = await saveAll(next, t)\n setBusy(false)\n if (result.ok) {\n setCommands(next)\n setAnnounce(t('saved'))\n return true\n }\n // \u53EA\u6709\u5F53\u524D\u5361\u7247\u7684\u5B57\u6BB5\u9519\u8BEF\u80FD\u5C31\u5730\u663E\u793A\uFF1B\u5176\u4F59\uFF08\u7406\u8BBA\u4E0A\u4E0D\u4F1A\u51FA\u73B0\uFF09\u5E76\u5165\u5361\u7247\u9876\u90E8\u7684\u5931\u8D25\u884C\u3002\n setErrors(result.errors.filter(e => e.index === target && e.field !== undefined))\n const stray = result.errors.filter(e => e.index !== target || e.field === undefined)\n setFailure(result.text ?? (stray.length > 0 ? t('saveFailed', { detail: stray.map(e => e.message).join('; ') }) : undefined))\n return false\n }\n\n const saveEditing = async (): Promise<void> => {\n if (commands === null || editing === undefined) return\n const index = editing.index ?? commands.length\n const next = editing.index === undefined\n ? [...commands, editing.draft]\n : commands.map((c, i) => (i === editing.index ? editing.draft : c))\n if (await commit(next, index)) setEditing(undefined)\n }\n\n const confirmDelete = async (index: number): Promise<void> => {\n if (commands === null) return\n if (await commit(commands.filter((_, i) => i !== index), -1)) setDeleting(undefined)\n }\n\n const editor = (key: string): ReactNode => editing && (\n <Editor\n key={key}\n t={t}\n draft={editing.draft}\n errors={errors}\n failure={failure}\n busy={busy}\n onChange={p => { setEditing({ ...editing, draft: { ...editing.draft, ...p } }); setErrors(es => es.filter(e => !(e.field! in p))) }}\n onCancel={() => openEditor(undefined)}\n onSave={() => { void saveEditing() }}\n />\n )\n\n return (\n <div className=\"dsc-section\">\n <h2 className=\"dsc-title\">{t('title')}</h2>\n <span className=\"dsc-sr\" role=\"status\">{announce}</span>\n {loadError !== undefined && (\n <div className=\"dsc-notice\" role=\"alert\">\n <span>{loadError}</span>\n <button type=\"button\" className=\"dsc-btn dsc-secondary dsc-compact\" onClick={load}>{t('retry')}</button>\n </div>\n )}\n {commands !== null && (\n <div className=\"dsc-body\">\n {commands.length > 0 && (\n <ul className=\"dsc-rows\">\n {commands.map((c, i) => {\n const open = editing?.index === i\n return (\n <li key={i} className=\"dsc-card\">\n <div className=\"dsc-head\">\n <span className=\"dsc-identity\">\n <code className=\"dsc-name\">/{c.name}</code>\n {aliasList(c).map(a => <code key={a} className=\"dsc-tag\">/{a}</code>)}\n {c.type === 'exec' && <span className=\"dsc-tag\">{t('type.exec')}</span>}\n {c.type === 'alias' && <code className=\"dsc-tag\">\u2192 {c.target.startsWith('/') ? c.target : `/${c.target}`}</code>}\n </span>\n {!open && deleting !== i && (\n <span className=\"dsc-actions\">\n <button type=\"button\" className=\"dsc-btn dsc-secondary dsc-compact\" aria-label={t('editOf', { name: c.name })}\n disabled={busy} onClick={() => openEditor({ index: i, draft: c })}>{t('edit')}</button>\n <button type=\"button\" className=\"dsc-btn dsc-danger dsc-compact\" aria-label={t('deleteOf', { name: c.name })}\n disabled={busy} onClick={() => { openEditor(undefined); setDeleting(i) }}>{t('delete')}</button>\n </span>\n )}\n </div>\n {!open && <p className=\"dsc-desc\">{c.description}</p>}\n {deleting === i && (\n <div className=\"dsc-confirm\" role=\"alertdialog\" aria-label={t('deleteOf', { name: c.name })}>\n <span>{t('deleteConfirm', { name: c.name })}</span>\n {failure !== undefined && <span className=\"dsc-error\" role=\"alert\">{failure}</span>}\n <span className=\"dsc-actions\">\n <button type=\"button\" className=\"dsc-btn dsc-secondary dsc-compact\" disabled={busy} autoFocus\n onClick={() => { setDeleting(undefined); reset() }}>{t('cancel')}</button>\n <button type=\"button\" className=\"dsc-btn dsc-danger-fill dsc-compact\" disabled={busy}\n onClick={() => { void confirmDelete(i) }}>{busy ? t('saving') : t('delete')}</button>\n </span>\n </div>\n )}\n {open && editor(`edit-${i}`)}\n </li>\n )\n })}\n </ul>\n )}\n {editing?.index === undefined && editing !== undefined\n ? editor('add')\n : (\n <button type=\"button\" className=\"dsc-btn dsc-add\" disabled={busy} onClick={() => openEditor({ index: undefined, draft: blank() })}>\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" aria-hidden=\"true\"><path d=\"M7 2v10M2 7h10\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" /></svg>\n {t('add')}\n </button>\n )}\n </div>\n )}\n </div>\n )\n}\n\ninterface EditorProps {\n t: T\n draft: Draft\n errors: ApiError[]\n failure: string | undefined\n busy: boolean\n onChange: (p: Partial<Draft>) => void\n onCancel: () => void\n onSave: () => void\n}\n\nfunction Editor({ t, draft, errors, failure, busy, onChange, onCancel, onSave }: EditorProps): ReactElement {\n const base = useId()\n const errorOf = (field: keyof Draft): ApiError | undefined => errors.find(e => e.field === field)\n const field = (key: keyof Draft, control: (props: { id: string; 'aria-invalid'?: true; 'aria-describedby'?: string }) => ReactNode, tip?: LocaleKey): ReactElement => {\n const id = `${base}-${key}`\n const err = errorOf(key)\n const label = t(`field.${key}` as LocaleKey)\n return (\n <div className=\"dsc-field\">\n <span className=\"dsc-label\">\n <label htmlFor={id}>{label}</label>\n {tip && <Help label={t('help', { field: label })} text={t(tip)} />}\n </span>\n {control({ id, ...err ? { 'aria-invalid': true, 'aria-describedby': `${id}-err` } : {} })}\n {err && <span id={`${id}-err`} className=\"dsc-error\">{t(`err.${err.code}` as LocaleKey)}</span>}\n </div>\n )\n }\n const text = (key: keyof Draft, extra: { placeholder?: string; inputMode?: 'numeric'; mono?: boolean } = {}) =>\n (p: { id: string }): ReactNode => (\n <input {...p} className={`dsc-input${extra.mono ? ' dsc-mono' : ''}`} value={draft[key]} placeholder={extra.placeholder}\n inputMode={extra.inputMode} spellCheck={false} autoComplete=\"off\" onChange={e => onChange({ [key]: e.target.value })} />\n )\n const area = (key: 'prompt' | 'command', rows: number) => (p: { id: string }): ReactNode => (\n <textarea {...p} className={`dsc-input dsc-area${key === 'command' ? ' dsc-mono' : ''}`} rows={rows} value={draft[key]}\n spellCheck={false} onChange={e => onChange({ [key]: e.target.value })} />\n )\n\n return (\n <form className=\"dsc-editor\" onSubmit={(e) => { e.preventDefault(); onSave() }}>\n <div className=\"dsc-segmented\" role=\"group\" aria-label={t('field.type')} style={{ '--dsc-index': TYPES.indexOf(draft.type), '--dsc-count': TYPES.length } as CSSProperties}>\n <span className=\"dsc-indicator\" aria-hidden=\"true\" />\n {TYPES.map(v => (\n <button key={v} type=\"button\" aria-pressed={draft.type === v} className=\"dsc-segment\" onClick={() => onChange({ type: v })}>\n {t(`type.${v}`)}\n </button>\n ))}\n </div>\n <div className=\"dsc-grid\">\n {field('name', p => (\n <span className=\"dsc-affix\">\n <span aria-hidden=\"true\">/</span>\n <input {...p} className=\"dsc-bare dsc-mono\" value={draft.name} spellCheck={false} autoComplete=\"off\" autoFocus\n onChange={e => onChange({ name: e.target.value })} />\n </span>\n ))}\n {field('aliases', text('aliases', { placeholder: 'rv, r', mono: true }))}\n </div>\n {field('description', text('description'))}\n {draft.type === 'prompt' && field('prompt', area('prompt', 5), 'tip.prompt')}\n {draft.type === 'alias' && field('target', text('target', { placeholder: '/compact', mono: true }), 'tip.target')}\n {draft.type === 'exec' && (\n <>\n {field('command', area('command', 3), 'tip.command')}\n <div className=\"dsc-grid\">\n {field('cwd', text('cwd', { placeholder: 'workspace', mono: true }), 'tip.cwd')}\n {field('timeoutMs', text('timeoutMs', { placeholder: '30000', inputMode: 'numeric' }))}\n </div>\n <p className=\"dsc-warn\">{t('execWarn')}</p>\n </>\n )}\n {failure !== undefined && <p className=\"dsc-error\" role=\"alert\">{failure}</p>}\n <div className=\"dsc-editor-actions\">\n <button type=\"button\" className=\"dsc-btn dsc-secondary\" disabled={busy} onClick={onCancel}>{t('cancel')}</button>\n <button type=\"submit\" className=\"dsc-btn dsc-primary\" disabled={busy || draft.name.trim() === ''}>{busy ? t('saving') : t('save')}</button>\n </div>\n </form>\n )\n}\n\n/** \u539F\u751F\u8BBE\u7F6E\u9875\u540C\u6B3E\u300C?\u300D\u8BF4\u660E\u94AE\uFF1A\u60AC\u505C\u6216\u952E\u76D8\u805A\u7126\u65F6\u663E\u793A\u6C14\u6CE1\uFF0C\u8BFB\u5C4F\u7ECF aria-describedby \u8BFB\u51FA\u3002 */\nfunction Help({ label, text }: { label: string; text: string }): ReactElement {\n const id = useId()\n return (\n <span className=\"dsc-help\">\n <button type=\"button\" className=\"dsc-help-btn\" aria-label={label} aria-describedby={id}>?</button>\n <span id={id} role=\"tooltip\" className=\"dsc-tip\">{text}</span>\n </span>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/** dsh-slash-command client \u534A\u8FB9\uFF1A\u8BBE\u7F6E\u5F39\u7A97\u91CC\u7684\u300C\u81EA\u5B9A\u4E49\u547D\u4EE4\u300D\u5206\u533A\uFF08settings.section\uFF09\u3002 */\nimport type { Context } from '@deepseek-ai/cordis'\nimport { en, NS, zh, type LocaleKey } from './locales.ts'\nimport { SlashCommandSection } from './panel.tsx'\n\nexport const name = 'dsh-slash-command'\nexport const inject = ['slots', 'locale']\n\ntype Translate = (key: LocaleKey, params?: Record<string, unknown>) => string\n\ninterface ClientContext extends Context {\n slots: {\n inject(key: string, callback: () => unknown): () => void\n register(options: { name: string; id: string; order: number; locale: string; label: () => string }, component: unknown): () => void\n }\n locale: {\n register(ns: string, dictionaries: { zh: Record<string, string>; en: Record<string, string> }): () => void\n bind(ns: string): Translate\n }\n}\n\n// \u53EA\u7528\u539F\u751F --dsw-* token\uFF0C\u53D6\u503C\u5BF9\u9F50 ui-settings-models / ui-primitives \u7684\u8BBE\u7F6E\u9875\u6837\u5F0F\u3002\nconst css = `\n.dsc-section{display:flex;flex-direction:column;gap:12px;max-width:720px;color:var(--dsw-alias-label-primary)}\n.dsc-title{margin:0;font-size:16px;line-height:24px;font-weight:500}\n.dsc-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}\n.dsc-body{display:flex;flex-direction:column;gap:8px;margin-top:12px}\n.dsc-rows{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:8px}\n.dsc-body>.dsc-editor{margin-top:0}\n.dsc-card{border:.5px solid var(--dsw-alias-settings-card-stroke);background:var(--dsw-alias-settings-card-fill);border-radius:var(--dsw-radius-xl);padding:12px 14px;display:flex;flex-direction:column;gap:6px;}\n.dsc-head{display:flex;align-items:center;gap:10px;min-height:28px}\n.dsc-identity{display:inline-flex;align-items:center;flex-wrap:wrap;gap:6px;min-width:0}\n.dsc-name{font-family:var(--ds-font-family-code);font-size:14px;line-height:22px;font-weight:500}\n.dsc-tag{flex:none;padding:1px 6px;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-xs);font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary)}\ncode.dsc-tag{font-family:var(--ds-font-family-code)}\n.dsc-desc{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}\n.dsc-actions{display:inline-flex;align-items:center;gap:4px;margin-left:auto}\n.dsc-btn{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:6px;height:36px;padding:0 14px;border:none;border-radius:var(--dsw-radius-md);background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;cursor:pointer}\n.dsc-btn:disabled{opacity:.4;cursor:default}\n.dsc-btn:focus-visible,.dsc-help-btn:focus-visible,.dsc-segment:focus-visible{outline:none;box-shadow:0 0 0 2px var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary))}\n.dsc-compact{height:28px;padding:0 10px;border-radius:var(--dsw-radius-sm);font-size:12px;line-height:18px}\n.dsc-primary{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}\n.dsc-secondary{border:.5px solid var(--dsw-alias-border-l3)}\n.dsc-danger{color:var(--dsw-alias-state-error-primary)}\n.dsc-danger-fill{background:var(--dsw-alias-state-error-primary);color:#fff}\n.dsc-add{width:100%;height:44px;border:1px dashed var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-lg)}\n@media (hover:hover){\n.dsc-primary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}\n.dsc-secondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}\n.dsc-add:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}\n.dsc-danger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}\n.dsc-danger-fill:hover:not(:disabled){filter:brightness(.92)}\n.dsc-segment:hover{color:var(--dsw-alias-label-primary)}\n.dsc-help:hover .dsc-tip{display:block}\n.dsc-help-btn:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-tertiary)}\n}\n.dsc-confirm{display:flex;align-items:center;flex-wrap:wrap;gap:8px 12px;margin-top:4px;padding:10px 12px;border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-bg-module-platform);font-size:13px;line-height:20px}\n.dsc-editor{display:flex;flex-direction:column;gap:14px;margin-top:6px;padding:14px 16px;border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-bg-module-platform)}\n.dsc-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px}\n.dsc-field{display:flex;flex-direction:column;gap:6px;min-width:0}\n.dsc-label{display:inline-flex;align-items:center;gap:8px;font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-secondary)}\n.dsc-input,.dsc-affix{box-sizing:border-box;width:100%;min-height:32px;padding:4px 8px;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:14px;line-height:22px;outline:none}\n.dsc-input:focus,.dsc-affix:focus-within{border-color:var(--dsw-alias-state-business-primary)}\n.dsc-input[aria-invalid],.dsc-affix:has([aria-invalid]){border-color:var(--dsw-alias-state-error-primary)}\n.dsc-input::placeholder{color:var(--dsw-alias-label-dimmed)}\n.dsc-area{resize:vertical;min-height:72px}\n.dsc-mono{font-family:var(--ds-font-family-code);font-size:13px}\n.dsc-affix{display:flex;align-items:center;gap:2px;font-family:var(--ds-font-family-code);color:var(--dsw-alias-label-tertiary)}\n.dsc-bare{flex:1;min-width:0;padding:0;border:none;outline:none;background:transparent;color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}\n.dsc-segmented{position:relative;display:inline-grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:2px;align-self:flex-start;padding:4px;border-radius:var(--dsw-radius-md);background:var(--dsw-alias-interactive-bg-hover)}\n.dsc-indicator{position:absolute;top:4px;left:4px;width:calc((100% - 8px - 2px * (var(--dsc-count) - 1)) / var(--dsc-count));height:calc(100% - 8px);border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-elevation-soft);transform:translateX(calc(var(--dsc-index) * (100% + 2px)));transition:transform 160ms ease;pointer-events:none}\n.dsc-segment{position:relative;z-index:1;box-sizing:border-box;height:28px;padding:0 16px;border:0;border-radius:var(--dsw-radius-sm);background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:13px;line-height:20px;font-weight:500;cursor:pointer}\n.dsc-segment[aria-pressed=true]{color:var(--dsw-alias-label-primary)}\n.dsc-help{position:relative;display:inline-flex}\n.dsc-help-btn{box-sizing:border-box;width:16px;height:16px;padding:0;border:.5px solid var(--dsw-alias-border-l3);border-radius:50%;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:11px;line-height:15px;text-align:center;cursor:help}\n.dsc-tip{display:none;position:absolute;z-index:10;left:-4px;top:calc(100% + 6px);width:max-content;max-width:280px;padding:3px 7px;border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-tooltip-bg);color:var(--dsw-static-neutral-bluish-00,#fff);font-size:13px;line-height:20px;font-weight:400;pointer-events:none}\n.dsc-help-btn:focus-visible+.dsc-tip{display:block}\n.dsc-warn{margin:-4px 0 0;font-size:12px;line-height:18px;color:var(--dsw-alias-state-warn-label)}\n.dsc-error{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-state-error-primary)}\n.dsc-notice{display:flex;align-items:center;gap:12px;font-size:13px;line-height:20px;color:var(--dsw-alias-state-error-primary)}\n.dsc-editor-actions{display:flex;justify-content:flex-end;gap:8px}\n@media (prefers-reduced-motion:reduce){.dsc-indicator{transition:none}}\n`\n\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-slash-command: dictionaries')\n const t = ctx.locale.bind(NS)\n if (typeof document !== 'undefined') {\n document.querySelectorAll('style[data-plugin=\"dsh-slash-command\"]').forEach(node => node.remove())\n const tag = document.createElement('style')\n tag.dataset.plugin = 'dsh-slash-command'\n tag.textContent = css\n document.head.appendChild(tag)\n }\n\n ctx.slots.inject('settings.section', () => ctx.slots.register(\n { name: 'settings.section', id: 'slash-commands', order: 25, locale: NS, label: () => t('title') },\n SlashCommandSection,\n ))\n}\n", "/** \u8BBE\u7F6E\u9875\u300C\u81EA\u5B9A\u4E49\u547D\u4EE4\u300D\u6587\u6848\u5B57\u5178\uFF08zh/en\uFF09\uFF1B`/` \u83DC\u5355\u6587\u6848\u6765\u81EA\u914D\u7F6E\u6587\u4EF6\u672C\u8EAB\uFF0C\u8FD9\u91CC\u4E0D\u7BA1\u3002 */\nexport const NS = 'dsh-slash-command'\n\nexport const zh = {\n 'title': '\u81EA\u5B9A\u4E49\u547D\u4EE4',\n 'add': '\u65B0\u589E\u547D\u4EE4',\n 'edit': '\u7F16\u8F91',\n 'editOf': '\u7F16\u8F91 /{name}',\n 'delete': '\u5220\u9664',\n 'deleteOf': '\u5220\u9664 /{name}',\n 'deleteConfirm': '\u5220\u9664 /{name}\uFF1F\u7ACB\u5373\u751F\u6548\uFF0C\u65E0\u6CD5\u64A4\u9500\u3002',\n 'cancel': '\u53D6\u6D88',\n 'save': '\u4FDD\u5B58',\n 'saving': '\u4FDD\u5B58\u4E2D\u2026',\n 'saved': '\u5DF2\u4FDD\u5B58',\n 'retry': '\u91CD\u8BD5',\n 'loadFailed': '\u8BFB\u53D6\u5931\u8D25\uFF1A{detail}',\n 'saveFailed': '\u4FDD\u5B58\u5931\u8D25\uFF1A{detail}',\n 'help': '{field}\u8BF4\u660E',\n 'field.type': '\u7C7B\u578B',\n 'field.name': '\u540D\u79F0',\n 'field.aliases': '\u522B\u540D',\n 'field.description': '\u63CF\u8FF0',\n 'field.prompt': 'Prompt',\n 'field.command': '\u547D\u4EE4',\n 'field.target': '\u76EE\u6807\u547D\u4EE4',\n 'field.cwd': '\u5DE5\u4F5C\u76EE\u5F55',\n 'field.timeoutMs': '\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09',\n 'field.passEnv': '\u4FDD\u7559\u73AF\u5883\u53D8\u91CF',\n 'type.prompt': 'Prompt',\n 'type.exec': 'Shell',\n 'type.alias': '\u8F6C\u53D1',\n 'tip.prompt': '$ARGUMENTS \u66FF\u6362\u4E3A\u547D\u4EE4\u540E\u7684\u53C2\u6570\uFF1B\u672A\u4F7F\u7528\u65F6\u53C2\u6570\u8FFD\u52A0\u5230\u672B\u5C3E\u3002',\n 'tip.command': '$ARGUMENTS \u4E3A\u5B8C\u6574\u53C2\u6570\uFF0C$1 $2\u2026 \u4E3A\u6309\u7A7A\u683C\u62C6\u5206\u7684\u53C2\u6570\uFF1B\u53C2\u6570\u53EA\u4F5C\u53D8\u91CF\u4F20\u5165\uFF0C\u4E0D\u4F1A\u88AB\u5F53\u4F5C\u547D\u4EE4\u6267\u884C\u3002',\n 'tip.target': '\u8F6C\u53D1\u5230\u53E6\u4E00\u6761\u659C\u6760\u547D\u4EE4\uFF0C\u53EF\u5E26\u56FA\u5B9A\u53C2\u6570\u3002$ARGUMENTS \u66FF\u6362\u4E3A\u53C2\u6570\uFF0C\u672A\u4F7F\u7528\u65F6\u53C2\u6570\u63A5\u5728\u672B\u5C3E\u3002',\n 'tip.cwd': 'workspace \u4E3A\u5F53\u524D\u4F1A\u8BDD\u76EE\u5F55\uFF0C\u4E5F\u53EF\u586B\u7EDD\u5BF9\u8DEF\u5F84\u3002',\n 'tip.passEnv': '\u540D\u5B57\u542B KEY\u3001TOKEN\u3001SECRET\u3001PASSWORD \u7B49\u7684\u73AF\u5883\u53D8\u91CF\u9ED8\u8BA4\u4E0D\u4F20\u7ED9\u547D\u4EE4\uFF1B\u8FD9\u91CC\u5217\u51FA\u7684\u7167\u5E38\u4F20\u5165\u3002\u8F93\u51FA\u91CC\u7684\u5BC6\u94A5\u59CB\u7EC8\u4F1A\u88AB\u906E\u853D\u3002',\n 'execWarn': '\u4EE5 dsh \u8FDB\u7A0B\u6743\u9650\u76F4\u63A5\u8FD0\u884C\uFF0C\u4E0D\u7ECF\u5BA1\u6279\u3002',\n 'err.type': '\u7C7B\u578B\u4E0D\u5BF9',\n 'err.required': '\u5FC5\u586B',\n 'err.pattern': '\u5C0F\u5199\u5B57\u6BCD\u5F00\u5934\uFF0C\u4EC5\u542B a-z 0-9 - _',\n 'err.envName': '\u53D8\u91CF\u540D\u4EC5\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001_\uFF0C\u9017\u53F7\u5206\u9694',\n 'err.duplicate': '\u5DF2\u88AB\u5360\u7528\u6216\u6307\u5411\u81EA\u8EAB',\n 'err.enum': '\u586B workspace \u6216\u7EDD\u5BF9\u8DEF\u5F84',\n 'err.range': '1\u2013600000 \u4E4B\u95F4\u7684\u6574\u6570',\n} as const\n\nexport type LocaleKey = keyof typeof zh\n\nexport const en: Record<LocaleKey, string> = {\n 'title': 'Custom commands',\n 'add': 'Add command',\n 'edit': 'Edit',\n 'editOf': 'Edit /{name}',\n 'delete': 'Delete',\n 'deleteOf': 'Delete /{name}',\n 'deleteConfirm': 'Delete /{name}? Takes effect immediately and cannot be undone.',\n 'cancel': 'Cancel',\n 'save': 'Save',\n 'saving': 'Saving\u2026',\n 'saved': 'Saved',\n 'retry': 'Retry',\n 'loadFailed': 'Load failed: {detail}',\n 'saveFailed': 'Save failed: {detail}',\n 'help': 'About {field}',\n 'field.type': 'Type',\n 'field.name': 'Name',\n 'field.aliases': 'Aliases',\n 'field.description': 'Description',\n 'field.prompt': 'Prompt',\n 'field.command': 'Command',\n 'field.target': 'Target command',\n 'field.cwd': 'Working directory',\n 'field.timeoutMs': 'Timeout (ms)',\n 'field.passEnv': 'Pass-through env vars',\n 'type.prompt': 'Prompt',\n 'type.exec': 'Shell',\n 'type.alias': 'Alias',\n 'tip.prompt': '$ARGUMENTS is replaced with the text after the command; if absent, that text is appended.',\n 'tip.command': '$ARGUMENTS is the full argument text; $1 $2\u2026 are the space-separated words. Arguments are passed as variables, never run as code.',\n 'tip.target': 'Forwards to another slash command, optionally with fixed arguments. $ARGUMENTS is replaced with the arguments; if absent they are appended.',\n 'tip.cwd': 'workspace is the current session directory; an absolute path also works.',\n 'tip.passEnv': 'Variables named like KEY, TOKEN, SECRET or PASSWORD are withheld from the command by default; ones listed here are passed through. Secrets in the output are always masked.',\n 'execWarn': 'Runs directly with the dsh process\u2019s privileges, with no approval.',\n 'err.type': 'Wrong type',\n 'err.required': 'Required',\n 'err.pattern': 'Start with a lowercase letter; only a-z 0-9 - _',\n 'err.envName': 'Letters, digits and _ only, comma separated',\n 'err.duplicate': 'Already used, or points to itself',\n 'err.enum': 'Use workspace or an absolute path',\n 'err.range': 'An integer from 1 to 600000',\n}\n", "/** \u8BBE\u7F6E\u9875\u300C\u81EA\u5B9A\u4E49\u547D\u4EE4\u300D\u5206\u533A\uFF1A\u547D\u4EE4\u5361\u7247\u5217\u8868 + \u5355\u5361\u884C\u5185\u7F16\u8F91\uFF1B\u6BCF\u6B21\u4FDD\u5B58/\u5220\u9664\u90FD\u6574\u8868\u5199\u56DE\u670D\u52A1\u7AEF\u3002 */\nimport { useEffect, useId, useState, type CSSProperties, type ReactElement, type ReactNode } from 'react'\nimport type { LocaleKey } from './locales.ts'\n\ntype T = (key: LocaleKey, params?: Record<string, unknown>) => string\n\ninterface Draft {\n name: string\n aliases: string\n description: string\n type: 'prompt' | 'exec' | 'alias'\n prompt: string\n target: string\n command: string\n cwd: string\n timeoutMs: string\n passEnv: string\n}\n\ninterface ApiError { index?: number; field?: string; code: string; message: string }\n\nconst blank = (): Draft => ({ name: '', aliases: '', description: '', type: 'prompt', prompt: '', target: '', command: '', cwd: '', timeoutMs: '', passEnv: '' })\n\nconst toDraft = (c: Record<string, unknown>): Draft => ({\n name: String(c.name ?? ''),\n aliases: Array.isArray(c.aliases) ? c.aliases.join(', ') : '',\n description: String(c.description ?? ''),\n type: c.type === 'exec' || c.type === 'alias' ? c.type : 'prompt',\n prompt: String(c.prompt ?? ''),\n target: String(c.target ?? ''),\n command: String(c.command ?? ''),\n cwd: c.cwd === undefined || c.cwd === 'workspace' ? '' : String(c.cwd),\n timeoutMs: c.timeoutMs === undefined || c.timeoutMs === 30000 ? '' : String(c.timeoutMs),\n passEnv: Array.isArray(c.passEnv) ? c.passEnv.join(', ') : '',\n})\n\n/** \u7A7A\u5B57\u7B26\u4E32\u7701\u7565\uFF0C\u4EA4\u7ED9\u670D\u52A1\u7AEF\u8865\u9ED8\u8BA4\u503C\uFF1BtimeoutMs \u975E\u6570\u5B57\u539F\u6837\u53D1\u51FA\uFF0C\u7531\u670D\u52A1\u7AEF\u62A5 range\u3002 */\nfunction toWire(d: Draft): Record<string, unknown> {\n const out: Record<string, unknown> = {\n name: d.name.trim(),\n aliases: d.aliases.split(',').map(s => s.trim()).filter(s => s !== ''),\n description: d.description,\n type: d.type,\n }\n if (d.type === 'prompt') out.prompt = d.prompt\n else if (d.type === 'alias') out.target = d.target.trim()\n else {\n out.command = d.command\n if (d.cwd.trim() !== '') out.cwd = d.cwd.trim()\n if (d.timeoutMs.trim() !== '') out.timeoutMs = Number(d.timeoutMs)\n const passEnv = d.passEnv.split(',').map(s => s.trim()).filter(s => s !== '')\n if (passEnv.length > 0) out.passEnv = passEnv\n }\n return out\n}\n\nconst aliasList = (d: Draft): string[] => d.aliases.split(',').map(s => s.trim()).filter(s => s !== '')\n\ntype SaveResult = { ok: true } | { ok: false; errors: ApiError[]; text?: string }\n\nasync function saveAll(commands: Draft[], t: T): Promise<SaveResult> {\n try {\n const res = await fetch('/api/dsh-slash-command/save', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ commands: commands.map(toWire) }),\n })\n const body = await res.json() as { ok: boolean; errors?: ApiError[] }\n if (res.ok && body.ok) return { ok: true }\n if (res.status === 400) return { ok: false, errors: body.errors ?? [] }\n return { ok: false, errors: [], text: t('saveFailed', { detail: `HTTP ${res.status}` }) }\n } catch (e) {\n return { ok: false, errors: [], text: t('saveFailed', { detail: e instanceof Error ? e.message : String(e) }) }\n }\n}\n\nconst TYPES = ['prompt', 'exec', 'alias'] as const\n\n/** \u6253\u5F00\u4E2D\u7684\u7F16\u8F91\u5361\uFF1A`index` \u4E3A undefined \u8868\u793A\u65B0\u589E\u3002 */\ninterface Editing { index: number | undefined; draft: Draft }\n\nexport function SlashCommandSection({ t }: { t: T }): ReactElement {\n const [commands, setCommands] = useState<Draft[] | null>(null)\n const [loadError, setLoadError] = useState<string | undefined>()\n const [editing, setEditing] = useState<Editing | undefined>()\n const [deleting, setDeleting] = useState<number | undefined>()\n const [busy, setBusy] = useState(false)\n const [errors, setErrors] = useState<ApiError[]>([])\n const [failure, setFailure] = useState<string | undefined>()\n const [announce, setAnnounce] = useState('')\n\n const load = (): void => {\n setLoadError(undefined)\n fetch('/api/dsh-slash-command/read', { headers: { accept: 'application/json' }, cache: 'no-store' })\n .then(async (res) => {\n const body = await res.json() as { ok: boolean; commands?: Record<string, unknown>[]; errors?: ApiError[] }\n if (!res.ok || !body.ok) throw new Error(body.errors?.[0]?.message ?? `HTTP ${res.status}`)\n setCommands((body.commands ?? []).map(toDraft))\n })\n .catch((e: unknown) => { setLoadError(t('loadFailed', { detail: e instanceof Error ? e.message : String(e) })) })\n }\n useEffect(load, [])\n\n const reset = (): void => { setErrors([]); setFailure(undefined); setAnnounce('') }\n const openEditor = (next: Editing | undefined): void => { reset(); setDeleting(undefined); setEditing(next) }\n\n const commit = async (next: Draft[], target: number): Promise<boolean> => {\n setBusy(true)\n reset()\n const result = await saveAll(next, t)\n setBusy(false)\n if (result.ok) {\n setCommands(next)\n setAnnounce(t('saved'))\n return true\n }\n // \u53EA\u6709\u5F53\u524D\u5361\u7247\u7684\u5B57\u6BB5\u9519\u8BEF\u80FD\u5C31\u5730\u663E\u793A\uFF1B\u5176\u4F59\uFF08\u7406\u8BBA\u4E0A\u4E0D\u4F1A\u51FA\u73B0\uFF09\u5E76\u5165\u5361\u7247\u9876\u90E8\u7684\u5931\u8D25\u884C\u3002\n setErrors(result.errors.filter(e => e.index === target && e.field !== undefined))\n const stray = result.errors.filter(e => e.index !== target || e.field === undefined)\n setFailure(result.text ?? (stray.length > 0 ? t('saveFailed', { detail: stray.map(e => e.message).join('; ') }) : undefined))\n return false\n }\n\n const saveEditing = async (): Promise<void> => {\n if (commands === null || editing === undefined) return\n const index = editing.index ?? commands.length\n const next = editing.index === undefined\n ? [...commands, editing.draft]\n : commands.map((c, i) => (i === editing.index ? editing.draft : c))\n if (await commit(next, index)) setEditing(undefined)\n }\n\n const confirmDelete = async (index: number): Promise<void> => {\n if (commands === null) return\n if (await commit(commands.filter((_, i) => i !== index), -1)) setDeleting(undefined)\n }\n\n const editor = (key: string): ReactNode => editing && (\n <Editor\n key={key}\n t={t}\n draft={editing.draft}\n errors={errors}\n failure={failure}\n busy={busy}\n onChange={p => { setEditing({ ...editing, draft: { ...editing.draft, ...p } }); setErrors(es => es.filter(e => !(e.field! in p))) }}\n onCancel={() => openEditor(undefined)}\n onSave={() => { void saveEditing() }}\n />\n )\n\n return (\n <div className=\"dsc-section\">\n <h2 className=\"dsc-title\">{t('title')}</h2>\n <span className=\"dsc-sr\" role=\"status\">{announce}</span>\n {loadError !== undefined && (\n <div className=\"dsc-notice\" role=\"alert\">\n <span>{loadError}</span>\n <button type=\"button\" className=\"dsc-btn dsc-secondary dsc-compact\" onClick={load}>{t('retry')}</button>\n </div>\n )}\n {commands !== null && (\n <div className=\"dsc-body\">\n {commands.length > 0 && (\n <ul className=\"dsc-rows\">\n {commands.map((c, i) => {\n const open = editing?.index === i\n return (\n <li key={i} className=\"dsc-card\">\n <div className=\"dsc-head\">\n <span className=\"dsc-identity\">\n <code className=\"dsc-name\">/{c.name}</code>\n {aliasList(c).map(a => <code key={a} className=\"dsc-tag\">/{a}</code>)}\n {c.type === 'exec' && <span className=\"dsc-tag\">{t('type.exec')}</span>}\n {c.type === 'alias' && <code className=\"dsc-tag\">\u2192 {c.target.startsWith('/') ? c.target : `/${c.target}`}</code>}\n </span>\n {!open && deleting !== i && (\n <span className=\"dsc-actions\">\n <button type=\"button\" className=\"dsc-btn dsc-secondary dsc-compact\" aria-label={t('editOf', { name: c.name })}\n disabled={busy} onClick={() => openEditor({ index: i, draft: c })}>{t('edit')}</button>\n <button type=\"button\" className=\"dsc-btn dsc-danger dsc-compact\" aria-label={t('deleteOf', { name: c.name })}\n disabled={busy} onClick={() => { openEditor(undefined); setDeleting(i) }}>{t('delete')}</button>\n </span>\n )}\n </div>\n {!open && <p className=\"dsc-desc\">{c.description}</p>}\n {deleting === i && (\n <div className=\"dsc-confirm\" role=\"alertdialog\" aria-label={t('deleteOf', { name: c.name })}>\n <span>{t('deleteConfirm', { name: c.name })}</span>\n {failure !== undefined && <span className=\"dsc-error\" role=\"alert\">{failure}</span>}\n <span className=\"dsc-actions\">\n <button type=\"button\" className=\"dsc-btn dsc-secondary dsc-compact\" disabled={busy} autoFocus\n onClick={() => { setDeleting(undefined); reset() }}>{t('cancel')}</button>\n <button type=\"button\" className=\"dsc-btn dsc-danger-fill dsc-compact\" disabled={busy}\n onClick={() => { void confirmDelete(i) }}>{busy ? t('saving') : t('delete')}</button>\n </span>\n </div>\n )}\n {open && editor(`edit-${i}`)}\n </li>\n )\n })}\n </ul>\n )}\n {editing?.index === undefined && editing !== undefined\n ? editor('add')\n : (\n <button type=\"button\" className=\"dsc-btn dsc-add\" disabled={busy} onClick={() => openEditor({ index: undefined, draft: blank() })}>\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" aria-hidden=\"true\"><path d=\"M7 2v10M2 7h10\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" /></svg>\n {t('add')}\n </button>\n )}\n </div>\n )}\n </div>\n )\n}\n\ninterface EditorProps {\n t: T\n draft: Draft\n errors: ApiError[]\n failure: string | undefined\n busy: boolean\n onChange: (p: Partial<Draft>) => void\n onCancel: () => void\n onSave: () => void\n}\n\nfunction Editor({ t, draft, errors, failure, busy, onChange, onCancel, onSave }: EditorProps): ReactElement {\n const base = useId()\n const errorOf = (field: keyof Draft): ApiError | undefined => errors.find(e => e.field === field)\n const field = (key: keyof Draft, control: (props: { id: string; 'aria-invalid'?: true; 'aria-describedby'?: string }) => ReactNode, tip?: LocaleKey): ReactElement => {\n const id = `${base}-${key}`\n const err = errorOf(key)\n const label = t(`field.${key}` as LocaleKey)\n return (\n <div className=\"dsc-field\">\n <span className=\"dsc-label\">\n <label htmlFor={id}>{label}</label>\n {tip && <Help label={t('help', { field: label })} text={t(tip)} />}\n </span>\n {control({ id, ...err ? { 'aria-invalid': true, 'aria-describedby': `${id}-err` } : {} })}\n {err && <span id={`${id}-err`} className=\"dsc-error\">{t(key === 'passEnv' && err.code === 'pattern' ? 'err.envName' : `err.${err.code}` as LocaleKey)}</span>}\n </div>\n )\n }\n const text = (key: keyof Draft, extra: { placeholder?: string; inputMode?: 'numeric'; mono?: boolean } = {}) =>\n (p: { id: string }): ReactNode => (\n <input {...p} className={`dsc-input${extra.mono ? ' dsc-mono' : ''}`} value={draft[key]} placeholder={extra.placeholder}\n inputMode={extra.inputMode} spellCheck={false} autoComplete=\"off\" onChange={e => onChange({ [key]: e.target.value })} />\n )\n const area = (key: 'prompt' | 'command', rows: number) => (p: { id: string }): ReactNode => (\n <textarea {...p} className={`dsc-input dsc-area${key === 'command' ? ' dsc-mono' : ''}`} rows={rows} value={draft[key]}\n spellCheck={false} onChange={e => onChange({ [key]: e.target.value })} />\n )\n\n return (\n <form className=\"dsc-editor\" onSubmit={(e) => { e.preventDefault(); onSave() }}>\n <div className=\"dsc-segmented\" role=\"group\" aria-label={t('field.type')} style={{ '--dsc-index': TYPES.indexOf(draft.type), '--dsc-count': TYPES.length } as CSSProperties}>\n <span className=\"dsc-indicator\" aria-hidden=\"true\" />\n {TYPES.map(v => (\n <button key={v} type=\"button\" aria-pressed={draft.type === v} className=\"dsc-segment\" onClick={() => onChange({ type: v })}>\n {t(`type.${v}`)}\n </button>\n ))}\n </div>\n <div className=\"dsc-grid\">\n {field('name', p => (\n <span className=\"dsc-affix\">\n <span aria-hidden=\"true\">/</span>\n <input {...p} className=\"dsc-bare dsc-mono\" value={draft.name} spellCheck={false} autoComplete=\"off\" autoFocus\n onChange={e => onChange({ name: e.target.value })} />\n </span>\n ))}\n {field('aliases', text('aliases', { placeholder: 'rv, r', mono: true }))}\n </div>\n {field('description', text('description'))}\n {draft.type === 'prompt' && field('prompt', area('prompt', 5), 'tip.prompt')}\n {draft.type === 'alias' && field('target', text('target', { placeholder: '/compact', mono: true }), 'tip.target')}\n {draft.type === 'exec' && (\n <>\n {field('command', area('command', 3), 'tip.command')}\n <div className=\"dsc-grid\">\n {field('cwd', text('cwd', { placeholder: 'workspace', mono: true }), 'tip.cwd')}\n {field('timeoutMs', text('timeoutMs', { placeholder: '30000', inputMode: 'numeric' }))}\n </div>\n {field('passEnv', text('passEnv', { placeholder: 'GH_TOKEN', mono: true }), 'tip.passEnv')}\n <p className=\"dsc-warn\">{t('execWarn')}</p>\n </>\n )}\n {failure !== undefined && <p className=\"dsc-error\" role=\"alert\">{failure}</p>}\n <div className=\"dsc-editor-actions\">\n <button type=\"button\" className=\"dsc-btn dsc-secondary\" disabled={busy} onClick={onCancel}>{t('cancel')}</button>\n <button type=\"submit\" className=\"dsc-btn dsc-primary\" disabled={busy || draft.name.trim() === ''}>{busy ? t('saving') : t('save')}</button>\n </div>\n </form>\n )\n}\n\n/** \u539F\u751F\u8BBE\u7F6E\u9875\u540C\u6B3E\u300C?\u300D\u8BF4\u660E\u94AE\uFF1A\u60AC\u505C\u6216\u952E\u76D8\u805A\u7126\u65F6\u663E\u793A\u6C14\u6CE1\uFF0C\u8BFB\u5C4F\u7ECF aria-describedby \u8BFB\u51FA\u3002 */\nfunction Help({ label, text }: { label: string; text: string }): ReactElement {\n const id = useId()\n return (\n <span className=\"dsc-help\">\n <button type=\"button\" className=\"dsc-help-btn\" aria-label={label} aria-describedby={id}>?</button>\n <span id={id} role=\"tooltip\" className=\"dsc-tip\">{text}</span>\n </span>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["field"]
|
|
7
7
|
}
|
package/lib/core.js
CHANGED
|
@@ -4,6 +4,7 @@ var DEFAULT_TIMEOUT_MS = 3e4;
|
|
|
4
4
|
var MAX_TIMEOUT_MS = 6e5;
|
|
5
5
|
var OUTPUT_LIMIT = 16 * 1024;
|
|
6
6
|
var TARGET_RE = /^\/?([a-z][a-z0-9_-]*)(?:\s[\s\S]*)?$/;
|
|
7
|
+
var ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
7
8
|
var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
8
9
|
var nonEmpty = (v) => typeof v === "string" && v.trim() !== "";
|
|
9
10
|
function validateConfig(raw) {
|
|
@@ -53,6 +54,9 @@ function validateConfig(raw) {
|
|
|
53
54
|
if (item.cwd !== void 0 && !(typeof item.cwd === "string" && (item.cwd === "workspace" || item.cwd.startsWith("/")))) {
|
|
54
55
|
fail("cwd", "enum", 'cwd must be "workspace" or an absolute path');
|
|
55
56
|
}
|
|
57
|
+
if (item.passEnv !== void 0 && !(Array.isArray(item.passEnv) && item.passEnv.every((v) => typeof v === "string" && ENV_NAME_RE.test(v)))) {
|
|
58
|
+
fail("passEnv", "pattern", "passEnv must be an array of environment variable names");
|
|
59
|
+
}
|
|
56
60
|
if (item.timeoutMs !== void 0 && !(typeof item.timeoutMs === "number" && Number.isInteger(item.timeoutMs) && item.timeoutMs >= 1 && item.timeoutMs <= MAX_TIMEOUT_MS)) {
|
|
57
61
|
fail("timeoutMs", "range", `timeoutMs must be an integer in 1..${MAX_TIMEOUT_MS}`);
|
|
58
62
|
}
|
|
@@ -73,6 +77,7 @@ function validateConfig(raw) {
|
|
|
73
77
|
cmd.command = item.command;
|
|
74
78
|
cmd.cwd = item.cwd ?? "workspace";
|
|
75
79
|
cmd.timeoutMs = item.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
80
|
+
if (Array.isArray(item.passEnv) && item.passEnv.length > 0) cmd.passEnv = item.passEnv;
|
|
76
81
|
}
|
|
77
82
|
commands.push(cmd);
|
|
78
83
|
});
|
|
@@ -99,6 +104,42 @@ function execArgs(rawInput) {
|
|
|
99
104
|
const args = rawInput.trim();
|
|
100
105
|
return { env: { ARGUMENTS: args }, argv: args === "" ? [] : args.split(/\s+/) };
|
|
101
106
|
}
|
|
107
|
+
var SECRET_ENV_RE = /(?:^|_)(?:API_?KEY|KEY|KEYS|TOKEN|TOKENS|SECRET|SECRETS|PASSWORD|PASSWD|PASS|CREDENTIALS?|AUTH|COOKIE)(?:_|$)/i;
|
|
108
|
+
var ENV_KEEP = /* @__PURE__ */ new Set(["SSH_AUTH_SOCK", "PWD", "OLDPWD"]);
|
|
109
|
+
var isSecretEnvName = (name) => !ENV_KEEP.has(name) && SECRET_ENV_RE.test(name);
|
|
110
|
+
function scrubEnv(env, passEnv = []) {
|
|
111
|
+
const pass = new Set(passEnv);
|
|
112
|
+
const out = {};
|
|
113
|
+
for (const [k, v] of Object.entries(env)) {
|
|
114
|
+
if (v !== void 0 && (pass.has(k) || !isSecretEnvName(k))) out[k] = v;
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
function secretValues(env) {
|
|
119
|
+
return Object.entries(env).filter(([k, v]) => v !== void 0 && v.length >= 8 && isSecretEnvName(k)).map(([, v]) => v).sort((a, b) => b.length - a.length);
|
|
120
|
+
}
|
|
121
|
+
var MASK = "***";
|
|
122
|
+
var SECRET_NAME = "[A-Za-z0-9_.-]*(?:api[_-]?key|secret|token|passw(?:or)?d|credential|private[_-]?key)[A-Za-z0-9_.-]*";
|
|
123
|
+
var REDACTIONS = [
|
|
124
|
+
// 私钥块
|
|
125
|
+
[/-----BEGIN[A-Z ]*PRIVATE KEY-----[\s\S]*?-----END[A-Z ]*PRIVATE KEY-----/g, `-----BEGIN PRIVATE KEY-----${MASK}-----END PRIVATE KEY-----`],
|
|
126
|
+
// 常见厂商前缀:保留前缀便于辨认
|
|
127
|
+
[/\b((?:sk|pk|rk)-(?:[a-z]+-)?|gh[pousr]_|github_pat_|glpat-|xox[abprs]-|AKIA|ASIA|AIza|hf_|npm_)[A-Za-z0-9_\-]{12,}/g, `$1${MASK}`],
|
|
128
|
+
// Authorization / Proxy-Authorization 头
|
|
129
|
+
[/((?:Proxy-)?Authorization\s*[:=]\s*)(?:(Bearer|Basic|Token)\s+)?[^\s"',]+/gi, `$1$2 ${MASK}`],
|
|
130
|
+
// JWT
|
|
131
|
+
[/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{4,}(?:\.[A-Za-z0-9_-]+)?/g, MASK],
|
|
132
|
+
// URL 里的账号密码
|
|
133
|
+
[/\b([a-z][a-z0-9+.-]*:\/\/)[^\s/:@]+:[^\s/@]+@/gi, `$1${MASK}@`],
|
|
134
|
+
// NAME=value / NAME: value / "name": "value",name 像密钥
|
|
135
|
+
[new RegExp(`(["']?${SECRET_NAME}["']?\\s*[:=]\\s*)(["']?)[^\\s"',]{4,}\\2`, "gi"), `$1$2${MASK}$2`]
|
|
136
|
+
];
|
|
137
|
+
function redactSecrets(text, known = []) {
|
|
138
|
+
let out = text;
|
|
139
|
+
for (const v of known) out = out.split(v).join(MASK);
|
|
140
|
+
for (const [re, to] of REDACTIONS) out = out.replace(re, to);
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
102
143
|
function clip(text, limit) {
|
|
103
144
|
const buf = Buffer.from(text, "utf8");
|
|
104
145
|
if (buf.length <= limit) return text;
|
|
@@ -121,7 +162,11 @@ export {
|
|
|
121
162
|
execArgs,
|
|
122
163
|
expandNames,
|
|
123
164
|
formatExecResult,
|
|
165
|
+
isSecretEnvName,
|
|
124
166
|
normalizeTarget,
|
|
167
|
+
redactSecrets,
|
|
168
|
+
scrubEnv,
|
|
169
|
+
secretValues,
|
|
125
170
|
validateConfig
|
|
126
171
|
};
|
|
127
172
|
//# sourceMappingURL=core.js.map
|
package/lib/core.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/core.ts"],
|
|
4
|
-
"sourcesContent": ["/** \u914D\u7F6E\u89E3\u6790/\u6821\u9A8C\u4E0E\u6587\u672C\u62FC\u88C5\uFF1A\u7EAF\u51FD\u6570\uFF0C\u65E0 host \u4F9D\u8D56\uFF0C\u6D4B\u8BD5\u76F4\u63A5\u5F15\u7528\u3002 */\n\nexport const NAME_RE = /^[a-z][a-z0-9_-]*$/\nexport const DEFAULT_TIMEOUT_MS = 30_000\nexport const MAX_TIMEOUT_MS = 600_000\nexport const OUTPUT_LIMIT = 16 * 1024\n\nexport interface SlashCommand {\n name: string\n aliases: string[]\n description: string\n type: 'prompt' | 'exec' | 'alias'\n prompt?: string\n command?: string\n /** alias \u578B\uFF1A\u53E6\u4E00\u6761\u659C\u6760\u547D\u4EE4\uFF0C\u5982 `/compact` \u6216 `/review \u53EA\u770B src`\u3002 */\n target?: string\n cwd?: string\n timeoutMs?: number\n}\n\n/** `index` \u6307\u5411 commands \u6570\u7EC4\u4E0B\u6807\uFF1B\u7F3A\u7701\u8868\u793A\u9876\u5C42\u9519\u8BEF\u3002 */\nexport interface FieldError {\n index?: number\n field?: string\n code: 'type' | 'required' | 'pattern' | 'duplicate' | 'enum' | 'range'\n message: string\n}\n\n/** alias \u76EE\u6807\uFF1A`/name` \u540E\u53EF\u8DDF\u56FA\u5B9A\u53C2\u6570\uFF1B\u5F00\u5934\u7684 `/` \u53EF\u7701\u7565\u3002 */\nconst TARGET_RE = /^\\/?([a-z][a-z0-9_-]*)(?:\\s[\\s\\S]*)?$/\n\nconst isObj = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v)\nconst nonEmpty = (v: unknown): v is string => typeof v === 'string' && v.trim() !== ''\n\n/**\n * \u6821\u9A8C\u5E76\u89C4\u8303\u5316\u914D\u7F6E\u3002\u975E\u6CD5\u6761\u76EE\u4E0D\u4F1A\u51FA\u73B0\u5728 `commands` \u91CC\uFF08\u6574\u6761\u4E22\u5F03\uFF09\uFF0C\u539F\u56E0\u5199\u5165 `errors`\u3002\n * \u540D\u5B57\u4E0E\u522B\u540D\u5171\u7528\u540C\u4E00\u547D\u540D\u7A7A\u95F4\uFF0C\u5148\u51FA\u73B0\u8005\u80DC\uFF0C\u540E\u8005\u62A5 duplicate\u3002\n */\nexport function validateConfig(raw: unknown): { commands: SlashCommand[]; errors: FieldError[] } {\n const commands: SlashCommand[] = []\n const errors: FieldError[] = []\n if (!isObj(raw) || !Array.isArray(raw.commands)) {\n return { commands, errors: [{ code: 'type', field: 'commands', message: 'commands must be an array' }] }\n }\n const seen = new Set<string>()\n raw.commands.forEach((item: unknown, index) => {\n const bad: FieldError[] = []\n const fail = (field: string, code: FieldError['code'], message: string): void => {\n bad.push({ index, field, code, message })\n }\n if (!isObj(item)) {\n errors.push({ index, code: 'type', message: 'command must be an object' })\n return\n }\n const names: string[] = []\n if (typeof item.name !== 'string' || item.name === '') fail('name', 'required', 'name is required')\n else if (!NAME_RE.test(item.name)) fail('name', 'pattern', `name \"${item.name}\" must match ${String(NAME_RE)}`)\n else names.push(item.name)\n const aliases: string[] = []\n if (item.aliases !== undefined) {\n if (!Array.isArray(item.aliases)) fail('aliases', 'type', 'aliases must be an array of strings')\n else {\n for (const a of item.aliases as unknown[]) {\n if (typeof a !== 'string' || !NAME_RE.test(a)) fail('aliases', 'pattern', `alias \"${String(a)}\" must match ${String(NAME_RE)}`)\n else aliases.push(a)\n }\n }\n }\n const own = [...names, ...aliases]\n own.forEach((n, i) => {\n if (seen.has(n) || own.indexOf(n) !== i) fail(n === item.name ? 'name' : 'aliases', 'duplicate', `\"${n}\" is already used`)\n })\n if (!nonEmpty(item.description)) fail('description', 'required', 'description is required')\n if (item.type !== 'prompt' && item.type !== 'exec' && item.type !== 'alias') fail('type', 'enum', 'type must be \"prompt\", \"exec\" or \"alias\"')\n else if (item.type === 'prompt' && !nonEmpty(item.prompt)) fail('prompt', 'required', 'prompt is required for prompt commands')\n else if (item.type === 'exec' && !nonEmpty(item.command)) fail('command', 'required', 'command is required for exec commands')\n else if (item.type === 'alias') {\n const m = typeof item.target === 'string' ? TARGET_RE.exec(item.target.trim()) : null\n if (!nonEmpty(item.target)) fail('target', 'required', 'target is required for alias commands')\n else if (m === null) fail('target', 'pattern', `target \"${String(item.target)}\" must be a slash command like /compact`)\n else if (own.includes(m[1]!)) fail('target', 'duplicate', `alias /${m[1]} cannot target itself`)\n }\n if (item.cwd !== undefined && !(typeof item.cwd === 'string' && (item.cwd === 'workspace' || item.cwd.startsWith('/')))) {\n fail('cwd', 'enum', 'cwd must be \"workspace\" or an absolute path')\n }\n if (item.timeoutMs !== undefined\n && !(typeof item.timeoutMs === 'number' && Number.isInteger(item.timeoutMs) && item.timeoutMs >= 1 && item.timeoutMs <= MAX_TIMEOUT_MS)) {\n fail('timeoutMs', 'range', `timeoutMs must be an integer in 1..${MAX_TIMEOUT_MS}`)\n }\n if (bad.length > 0) {\n errors.push(...bad)\n return\n }\n for (const n of own) seen.add(n)\n const cmd: SlashCommand = {\n name: item.name as string,\n aliases,\n description: (item.description as string).trim(),\n type: item.type as 'prompt' | 'exec',\n }\n if (cmd.type === 'prompt') cmd.prompt = item.prompt as string\n else if (cmd.type === 'alias') cmd.target = normalizeTarget(item.target as string)\n else {\n cmd.command = item.command as string\n cmd.cwd = (item.cwd as string | undefined) ?? 'workspace'\n cmd.timeoutMs = (item.timeoutMs as number | undefined) ?? DEFAULT_TIMEOUT_MS\n }\n commands.push(cmd)\n })\n return { commands, errors }\n}\n\n/** \u540D\u5B57 + \u522B\u540D\u5C55\u5F00\u6210\u8981\u6CE8\u518C\u7684\u5168\u90E8\u5165\u53E3\u540D\u3002 */\nexport const expandNames = (cmd: SlashCommand): string[] => [cmd.name, ...cmd.aliases]\n\n/** `$ARGUMENTS` \u539F\u4F4D\u66FF\u6362\uFF1B\u6CA1\u6709\u5360\u4F4D\u7B26\u4E14\u6709\u53C2\u6570\u65F6\u4EE5\u7A7A\u884C\u62FC\u5230\u672B\u5C3E\u3002 */\nexport function composePrompt(prompt: string, rawInput: string): string {\n const args = rawInput.trim()\n if (prompt.includes('$ARGUMENTS')) return prompt.replaceAll('$ARGUMENTS', () => args)\n return args === '' ? prompt : `${prompt}\\n\\n${args}`\n}\n\n/** \u7EDF\u4E00\u6210 `/name args` \u5F62\u5F0F\u3002 */\nexport const normalizeTarget = (target: string): string => {\n const t = target.trim()\n return t.startsWith('/') ? t : `/${t}`\n}\n\n/** alias \u5C55\u5F00\uFF1A`$ARGUMENTS` \u539F\u4F4D\u66FF\u6362\uFF1B\u5426\u5219\u53C2\u6570\u4EE5\u7A7A\u683C\u63A5\u5728\u76EE\u6807\u547D\u4EE4\u540E\u3002 */\nexport function composeAlias(target: string, rawInput: string): string {\n const args = rawInput.trim()\n if (target.includes('$ARGUMENTS')) return target.replaceAll('$ARGUMENTS', () => args).trim()\n return args === '' ? target : `${target} ${args}`\n}\n\n/**\n * exec \u53C2\u6570\u4EE5\u53D8\u91CF\u5F62\u5F0F\u4F20\u5165\u800C\u975E\u62FC\u8FDB\u811A\u672C\uFF08\u4E0D\u4F1A\u88AB\u5F53\u6210\u4EE3\u7801\u6267\u884C\uFF09\uFF1A\n * `$ARGUMENTS` = \u5B8C\u6574\u53C2\u6570\uFF0C`$1 $2 \u2026` / `\"$@\"` = \u6309\u7A7A\u767D\u62C6\u5206\u7684\u53C2\u6570\u3002\n */\nexport function execArgs(rawInput: string): { env: Record<string, string>; argv: string[] } {\n const args = rawInput.trim()\n return { env: { ARGUMENTS: args }, argv: args === '' ? [] : args.split(/\\s+/) }\n}\n\n/** \u6309 UTF-8 \u5B57\u8282\u622A\u65AD\uFF08\u4E0D\u62C6\u5B57\u7B26\uFF09\u3002 */\nfunction clip(text: string, limit: number): string {\n const buf = Buffer.from(text, 'utf8')\n if (buf.length <= limit) return text\n return `${buf.subarray(0, limit).toString('utf8').replace(/\uFFFD$/, '')}\\n\u2026[truncated]`\n}\n\nexport interface ExecOutcome {\n exit: string\n stdout: string\n stderr: string\n}\n\n/**\n * exec \u7ED3\u679C\u76F4\u63A5\u56DE\u663E\u5728\u547D\u4EE4\u5361\u7247\u91CC\uFF08\u4E0D\u8FDB\u6A21\u578B\uFF09\uFF1Astdout + stderr \u5408\u8BA1\u622A\u65AD\u5230 16KB\uFF1B\n * \u975E\u96F6\u9000\u51FA/\u8D85\u65F6\u5728\u672B\u5C3E\u9644 `exit=N`\uFF0C\u65E0\u8F93\u51FA\u65F6\u53EA\u7ED9 exit\u3002\n */\nexport function formatExecResult(o: ExecOutcome): string {\n const output = clip(`${o.stdout}${o.stderr}`.replace(/\\s+$/, ''), OUTPUT_LIMIT)\n if (o.exit === '0') return output === '' ? 'exit=0' : output\n return output === '' ? `exit=${o.exit}` : `${output}\\nexit=${o.exit}`\n}\n"],
|
|
5
|
-
"mappings": ";AAEO,IAAM,UAAU;AAChB,IAAM,qBAAqB;AAC3B,IAAM,iBAAiB;AACvB,IAAM,eAAe,KAAK;
|
|
4
|
+
"sourcesContent": ["/** \u914D\u7F6E\u89E3\u6790/\u6821\u9A8C\u4E0E\u6587\u672C\u62FC\u88C5\uFF1A\u7EAF\u51FD\u6570\uFF0C\u65E0 host \u4F9D\u8D56\uFF0C\u6D4B\u8BD5\u76F4\u63A5\u5F15\u7528\u3002 */\n\nexport const NAME_RE = /^[a-z][a-z0-9_-]*$/\nexport const DEFAULT_TIMEOUT_MS = 30_000\nexport const MAX_TIMEOUT_MS = 600_000\nexport const OUTPUT_LIMIT = 16 * 1024\n\nexport interface SlashCommand {\n name: string\n aliases: string[]\n description: string\n type: 'prompt' | 'exec' | 'alias'\n prompt?: string\n command?: string\n /** alias \u578B\uFF1A\u53E6\u4E00\u6761\u659C\u6760\u547D\u4EE4\uFF0C\u5982 `/compact` \u6216 `/review \u53EA\u770B src`\u3002 */\n target?: string\n cwd?: string\n timeoutMs?: number\n /** exec \u578B\uFF1A\u7167\u5E38\u4F20\u5165\u5B50\u8FDB\u7A0B\u7684\u5BC6\u94A5\u7C7B\u73AF\u5883\u53D8\u91CF\uFF08\u9ED8\u8BA4\u4F1A\u88AB\u53BB\u6389\uFF09\u3002 */\n passEnv?: string[]\n}\n\n/** `index` \u6307\u5411 commands \u6570\u7EC4\u4E0B\u6807\uFF1B\u7F3A\u7701\u8868\u793A\u9876\u5C42\u9519\u8BEF\u3002 */\nexport interface FieldError {\n index?: number\n field?: string\n code: 'type' | 'required' | 'pattern' | 'duplicate' | 'enum' | 'range'\n message: string\n}\n\n/** alias \u76EE\u6807\uFF1A`/name` \u540E\u53EF\u8DDF\u56FA\u5B9A\u53C2\u6570\uFF1B\u5F00\u5934\u7684 `/` \u53EF\u7701\u7565\u3002 */\nconst TARGET_RE = /^\\/?([a-z][a-z0-9_-]*)(?:\\s[\\s\\S]*)?$/\n\nconst ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/\n\nconst isObj = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v)\nconst nonEmpty = (v: unknown): v is string => typeof v === 'string' && v.trim() !== ''\n\n/**\n * \u6821\u9A8C\u5E76\u89C4\u8303\u5316\u914D\u7F6E\u3002\u975E\u6CD5\u6761\u76EE\u4E0D\u4F1A\u51FA\u73B0\u5728 `commands` \u91CC\uFF08\u6574\u6761\u4E22\u5F03\uFF09\uFF0C\u539F\u56E0\u5199\u5165 `errors`\u3002\n * \u540D\u5B57\u4E0E\u522B\u540D\u5171\u7528\u540C\u4E00\u547D\u540D\u7A7A\u95F4\uFF0C\u5148\u51FA\u73B0\u8005\u80DC\uFF0C\u540E\u8005\u62A5 duplicate\u3002\n */\nexport function validateConfig(raw: unknown): { commands: SlashCommand[]; errors: FieldError[] } {\n const commands: SlashCommand[] = []\n const errors: FieldError[] = []\n if (!isObj(raw) || !Array.isArray(raw.commands)) {\n return { commands, errors: [{ code: 'type', field: 'commands', message: 'commands must be an array' }] }\n }\n const seen = new Set<string>()\n raw.commands.forEach((item: unknown, index) => {\n const bad: FieldError[] = []\n const fail = (field: string, code: FieldError['code'], message: string): void => {\n bad.push({ index, field, code, message })\n }\n if (!isObj(item)) {\n errors.push({ index, code: 'type', message: 'command must be an object' })\n return\n }\n const names: string[] = []\n if (typeof item.name !== 'string' || item.name === '') fail('name', 'required', 'name is required')\n else if (!NAME_RE.test(item.name)) fail('name', 'pattern', `name \"${item.name}\" must match ${String(NAME_RE)}`)\n else names.push(item.name)\n const aliases: string[] = []\n if (item.aliases !== undefined) {\n if (!Array.isArray(item.aliases)) fail('aliases', 'type', 'aliases must be an array of strings')\n else {\n for (const a of item.aliases as unknown[]) {\n if (typeof a !== 'string' || !NAME_RE.test(a)) fail('aliases', 'pattern', `alias \"${String(a)}\" must match ${String(NAME_RE)}`)\n else aliases.push(a)\n }\n }\n }\n const own = [...names, ...aliases]\n own.forEach((n, i) => {\n if (seen.has(n) || own.indexOf(n) !== i) fail(n === item.name ? 'name' : 'aliases', 'duplicate', `\"${n}\" is already used`)\n })\n if (!nonEmpty(item.description)) fail('description', 'required', 'description is required')\n if (item.type !== 'prompt' && item.type !== 'exec' && item.type !== 'alias') fail('type', 'enum', 'type must be \"prompt\", \"exec\" or \"alias\"')\n else if (item.type === 'prompt' && !nonEmpty(item.prompt)) fail('prompt', 'required', 'prompt is required for prompt commands')\n else if (item.type === 'exec' && !nonEmpty(item.command)) fail('command', 'required', 'command is required for exec commands')\n else if (item.type === 'alias') {\n const m = typeof item.target === 'string' ? TARGET_RE.exec(item.target.trim()) : null\n if (!nonEmpty(item.target)) fail('target', 'required', 'target is required for alias commands')\n else if (m === null) fail('target', 'pattern', `target \"${String(item.target)}\" must be a slash command like /compact`)\n else if (own.includes(m[1]!)) fail('target', 'duplicate', `alias /${m[1]} cannot target itself`)\n }\n if (item.cwd !== undefined && !(typeof item.cwd === 'string' && (item.cwd === 'workspace' || item.cwd.startsWith('/')))) {\n fail('cwd', 'enum', 'cwd must be \"workspace\" or an absolute path')\n }\n if (item.passEnv !== undefined\n && !(Array.isArray(item.passEnv) && item.passEnv.every(v => typeof v === 'string' && ENV_NAME_RE.test(v)))) {\n fail('passEnv', 'pattern', 'passEnv must be an array of environment variable names')\n }\n if (item.timeoutMs !== undefined\n && !(typeof item.timeoutMs === 'number' && Number.isInteger(item.timeoutMs) && item.timeoutMs >= 1 && item.timeoutMs <= MAX_TIMEOUT_MS)) {\n fail('timeoutMs', 'range', `timeoutMs must be an integer in 1..${MAX_TIMEOUT_MS}`)\n }\n if (bad.length > 0) {\n errors.push(...bad)\n return\n }\n for (const n of own) seen.add(n)\n const cmd: SlashCommand = {\n name: item.name as string,\n aliases,\n description: (item.description as string).trim(),\n type: item.type as 'prompt' | 'exec',\n }\n if (cmd.type === 'prompt') cmd.prompt = item.prompt as string\n else if (cmd.type === 'alias') cmd.target = normalizeTarget(item.target as string)\n else {\n cmd.command = item.command as string\n cmd.cwd = (item.cwd as string | undefined) ?? 'workspace'\n cmd.timeoutMs = (item.timeoutMs as number | undefined) ?? DEFAULT_TIMEOUT_MS\n if (Array.isArray(item.passEnv) && item.passEnv.length > 0) cmd.passEnv = item.passEnv as string[]\n }\n commands.push(cmd)\n })\n return { commands, errors }\n}\n\n/** \u540D\u5B57 + \u522B\u540D\u5C55\u5F00\u6210\u8981\u6CE8\u518C\u7684\u5168\u90E8\u5165\u53E3\u540D\u3002 */\nexport const expandNames = (cmd: SlashCommand): string[] => [cmd.name, ...cmd.aliases]\n\n/** `$ARGUMENTS` \u539F\u4F4D\u66FF\u6362\uFF1B\u6CA1\u6709\u5360\u4F4D\u7B26\u4E14\u6709\u53C2\u6570\u65F6\u4EE5\u7A7A\u884C\u62FC\u5230\u672B\u5C3E\u3002 */\nexport function composePrompt(prompt: string, rawInput: string): string {\n const args = rawInput.trim()\n if (prompt.includes('$ARGUMENTS')) return prompt.replaceAll('$ARGUMENTS', () => args)\n return args === '' ? prompt : `${prompt}\\n\\n${args}`\n}\n\n/** \u7EDF\u4E00\u6210 `/name args` \u5F62\u5F0F\u3002 */\nexport const normalizeTarget = (target: string): string => {\n const t = target.trim()\n return t.startsWith('/') ? t : `/${t}`\n}\n\n/** alias \u5C55\u5F00\uFF1A`$ARGUMENTS` \u539F\u4F4D\u66FF\u6362\uFF1B\u5426\u5219\u53C2\u6570\u4EE5\u7A7A\u683C\u63A5\u5728\u76EE\u6807\u547D\u4EE4\u540E\u3002 */\nexport function composeAlias(target: string, rawInput: string): string {\n const args = rawInput.trim()\n if (target.includes('$ARGUMENTS')) return target.replaceAll('$ARGUMENTS', () => args).trim()\n return args === '' ? target : `${target} ${args}`\n}\n\n/**\n * exec \u53C2\u6570\u4EE5\u53D8\u91CF\u5F62\u5F0F\u4F20\u5165\u800C\u975E\u62FC\u8FDB\u811A\u672C\uFF08\u4E0D\u4F1A\u88AB\u5F53\u6210\u4EE3\u7801\u6267\u884C\uFF09\uFF1A\n * `$ARGUMENTS` = \u5B8C\u6574\u53C2\u6570\uFF0C`$1 $2 \u2026` / `\"$@\"` = \u6309\u7A7A\u767D\u62C6\u5206\u7684\u53C2\u6570\u3002\n */\nexport function execArgs(rawInput: string): { env: Record<string, string>; argv: string[] } {\n const args = rawInput.trim()\n return { env: { ARGUMENTS: args }, argv: args === '' ? [] : args.split(/\\s+/) }\n}\n\n/**\n * \u540D\u5B57\u50CF\u5BC6\u94A5\u7684\u73AF\u5883\u53D8\u91CF\uFF08\u5BF9\u6807 Hermes \u7684\u5B50\u8FDB\u7A0B env \u8FC7\u6EE4\uFF09\u3002\u6309 `_` \u5206\u6BB5\u5339\u914D\uFF0C\n * \u6240\u4EE5 `KEYBOARD`\u3001`SSH_AUTH_SOCK` \u8FD9\u7C7B\u4E0D\u53D7\u5F71\u54CD\u3002\n */\nconst SECRET_ENV_RE = /(?:^|_)(?:API_?KEY|KEY|KEYS|TOKEN|TOKENS|SECRET|SECRETS|PASSWORD|PASSWD|PASS|CREDENTIALS?|AUTH|COOKIE)(?:_|$)/i\nconst ENV_KEEP = new Set(['SSH_AUTH_SOCK', 'PWD', 'OLDPWD'])\n\nexport const isSecretEnvName = (name: string): boolean => !ENV_KEEP.has(name) && SECRET_ENV_RE.test(name)\n\n/** \u53BB\u6389\u5BC6\u94A5\u7C7B\u53D8\u91CF\uFF0C`passEnv` \u91CC\u5217\u51FA\u7684\u7167\u5E38\u4FDD\u7559\u3002 */\nexport function scrubEnv(env: NodeJS.ProcessEnv, passEnv: readonly string[] = []): Record<string, string> {\n const pass = new Set(passEnv)\n const out: Record<string, string> = {}\n for (const [k, v] of Object.entries(env)) {\n if (v !== undefined && (pass.has(k) || !isSecretEnvName(k))) out[k] = v\n }\n return out\n}\n\n/** \u5BBF\u4E3B\u73AF\u5883\u91CC\u6240\u6709\u5BC6\u94A5\u7C7B\u53D8\u91CF\u7684\u503C\uFF08\u2265 8 \u5B57\u7B26\uFF09\uFF0C\u8F93\u51FA\u4E2D\u539F\u6837\u51FA\u73B0\u5373\u906E\u853D\u3002 */\nexport function secretValues(env: NodeJS.ProcessEnv): string[] {\n return Object.entries(env)\n .filter(([k, v]) => v !== undefined && v.length >= 8 && isSecretEnvName(k))\n .map(([, v]) => v!)\n .sort((a, b) => b.length - a.length)\n}\n\nconst MASK = '***'\nconst SECRET_NAME = '[A-Za-z0-9_.-]*(?:api[_-]?key|secret|token|passw(?:or)?d|credential|private[_-]?key)[A-Za-z0-9_.-]*'\nconst REDACTIONS: [RegExp, string][] = [\n // \u79C1\u94A5\u5757\n [/-----BEGIN[A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END[A-Z ]*PRIVATE KEY-----/g, `-----BEGIN PRIVATE KEY-----${MASK}-----END PRIVATE KEY-----`],\n // \u5E38\u89C1\u5382\u5546\u524D\u7F00\uFF1A\u4FDD\u7559\u524D\u7F00\u4FBF\u4E8E\u8FA8\u8BA4\n [/\\b((?:sk|pk|rk)-(?:[a-z]+-)?|gh[pousr]_|github_pat_|glpat-|xox[abprs]-|AKIA|ASIA|AIza|hf_|npm_)[A-Za-z0-9_\\-]{12,}/g, `$1${MASK}`],\n // Authorization / Proxy-Authorization \u5934\n [/((?:Proxy-)?Authorization\\s*[:=]\\s*)(?:(Bearer|Basic|Token)\\s+)?[^\\s\"',]+/gi, `$1$2 ${MASK}`],\n // JWT\n [/\\beyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{4,}(?:\\.[A-Za-z0-9_-]+)?/g, MASK],\n // URL \u91CC\u7684\u8D26\u53F7\u5BC6\u7801\n [/\\b([a-z][a-z0-9+.-]*:\\/\\/)[^\\s/:@]+:[^\\s/@]+@/gi, `$1${MASK}@`],\n // NAME=value / NAME: value / \"name\": \"value\"\uFF0Cname \u50CF\u5BC6\u94A5\n [new RegExp(`([\"']?${SECRET_NAME}[\"']?\\\\s*[:=]\\\\s*)([\"']?)[^\\\\s\"',]{4,}\\\\2`, 'gi'), `$1$2${MASK}$2`],\n]\n\n/** \u906E\u853D\u8F93\u51FA\u4E2D\u7684\u5BC6\u94A5\uFF1A\u5148\u6309\u5DF2\u77E5\u5BC6\u94A5\u503C\u7CBE\u786E\u66FF\u6362\uFF0C\u518D\u6309\u5E38\u89C1\u683C\u5F0F\u515C\u5E95\u3002 */\nexport function redactSecrets(text: string, known: readonly string[] = []): string {\n let out = text\n for (const v of known) out = out.split(v).join(MASK)\n for (const [re, to] of REDACTIONS) out = out.replace(re, to)\n return out\n}\n\n/** \u6309 UTF-8 \u5B57\u8282\u622A\u65AD\uFF08\u4E0D\u62C6\u5B57\u7B26\uFF09\u3002 */\nfunction clip(text: string, limit: number): string {\n const buf = Buffer.from(text, 'utf8')\n if (buf.length <= limit) return text\n return `${buf.subarray(0, limit).toString('utf8').replace(/\uFFFD$/, '')}\\n\u2026[truncated]`\n}\n\nexport interface ExecOutcome {\n exit: string\n stdout: string\n stderr: string\n}\n\n/**\n * exec \u7ED3\u679C\u76F4\u63A5\u56DE\u663E\u5728\u547D\u4EE4\u5361\u7247\u91CC\uFF08\u4E0D\u8FDB\u6A21\u578B\uFF09\uFF1Astdout + stderr \u5408\u8BA1\u622A\u65AD\u5230 16KB\uFF1B\n * \u975E\u96F6\u9000\u51FA/\u8D85\u65F6\u5728\u672B\u5C3E\u9644 `exit=N`\uFF0C\u65E0\u8F93\u51FA\u65F6\u53EA\u7ED9 exit\u3002\n */\nexport function formatExecResult(o: ExecOutcome): string {\n const output = clip(`${o.stdout}${o.stderr}`.replace(/\\s+$/, ''), OUTPUT_LIMIT)\n if (o.exit === '0') return output === '' ? 'exit=0' : output\n return output === '' ? `exit=${o.exit}` : `${output}\\nexit=${o.exit}`\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/lib/index.js
CHANGED
|
@@ -12,6 +12,7 @@ var DEFAULT_TIMEOUT_MS = 3e4;
|
|
|
12
12
|
var MAX_TIMEOUT_MS = 6e5;
|
|
13
13
|
var OUTPUT_LIMIT = 16 * 1024;
|
|
14
14
|
var TARGET_RE = /^\/?([a-z][a-z0-9_-]*)(?:\s[\s\S]*)?$/;
|
|
15
|
+
var ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
15
16
|
var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
16
17
|
var nonEmpty = (v) => typeof v === "string" && v.trim() !== "";
|
|
17
18
|
function validateConfig(raw) {
|
|
@@ -61,6 +62,9 @@ function validateConfig(raw) {
|
|
|
61
62
|
if (item.cwd !== void 0 && !(typeof item.cwd === "string" && (item.cwd === "workspace" || item.cwd.startsWith("/")))) {
|
|
62
63
|
fail("cwd", "enum", 'cwd must be "workspace" or an absolute path');
|
|
63
64
|
}
|
|
65
|
+
if (item.passEnv !== void 0 && !(Array.isArray(item.passEnv) && item.passEnv.every((v) => typeof v === "string" && ENV_NAME_RE.test(v)))) {
|
|
66
|
+
fail("passEnv", "pattern", "passEnv must be an array of environment variable names");
|
|
67
|
+
}
|
|
64
68
|
if (item.timeoutMs !== void 0 && !(typeof item.timeoutMs === "number" && Number.isInteger(item.timeoutMs) && item.timeoutMs >= 1 && item.timeoutMs <= MAX_TIMEOUT_MS)) {
|
|
65
69
|
fail("timeoutMs", "range", `timeoutMs must be an integer in 1..${MAX_TIMEOUT_MS}`);
|
|
66
70
|
}
|
|
@@ -81,6 +85,7 @@ function validateConfig(raw) {
|
|
|
81
85
|
cmd.command = item.command;
|
|
82
86
|
cmd.cwd = item.cwd ?? "workspace";
|
|
83
87
|
cmd.timeoutMs = item.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
88
|
+
if (Array.isArray(item.passEnv) && item.passEnv.length > 0) cmd.passEnv = item.passEnv;
|
|
84
89
|
}
|
|
85
90
|
commands.push(cmd);
|
|
86
91
|
});
|
|
@@ -107,6 +112,42 @@ function execArgs(rawInput) {
|
|
|
107
112
|
const args = rawInput.trim();
|
|
108
113
|
return { env: { ARGUMENTS: args }, argv: args === "" ? [] : args.split(/\s+/) };
|
|
109
114
|
}
|
|
115
|
+
var SECRET_ENV_RE = /(?:^|_)(?:API_?KEY|KEY|KEYS|TOKEN|TOKENS|SECRET|SECRETS|PASSWORD|PASSWD|PASS|CREDENTIALS?|AUTH|COOKIE)(?:_|$)/i;
|
|
116
|
+
var ENV_KEEP = /* @__PURE__ */ new Set(["SSH_AUTH_SOCK", "PWD", "OLDPWD"]);
|
|
117
|
+
var isSecretEnvName = (name2) => !ENV_KEEP.has(name2) && SECRET_ENV_RE.test(name2);
|
|
118
|
+
function scrubEnv(env, passEnv = []) {
|
|
119
|
+
const pass = new Set(passEnv);
|
|
120
|
+
const out = {};
|
|
121
|
+
for (const [k, v] of Object.entries(env)) {
|
|
122
|
+
if (v !== void 0 && (pass.has(k) || !isSecretEnvName(k))) out[k] = v;
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
function secretValues(env) {
|
|
127
|
+
return Object.entries(env).filter(([k, v]) => v !== void 0 && v.length >= 8 && isSecretEnvName(k)).map(([, v]) => v).sort((a, b) => b.length - a.length);
|
|
128
|
+
}
|
|
129
|
+
var MASK = "***";
|
|
130
|
+
var SECRET_NAME = "[A-Za-z0-9_.-]*(?:api[_-]?key|secret|token|passw(?:or)?d|credential|private[_-]?key)[A-Za-z0-9_.-]*";
|
|
131
|
+
var REDACTIONS = [
|
|
132
|
+
// 私钥块
|
|
133
|
+
[/-----BEGIN[A-Z ]*PRIVATE KEY-----[\s\S]*?-----END[A-Z ]*PRIVATE KEY-----/g, `-----BEGIN PRIVATE KEY-----${MASK}-----END PRIVATE KEY-----`],
|
|
134
|
+
// 常见厂商前缀:保留前缀便于辨认
|
|
135
|
+
[/\b((?:sk|pk|rk)-(?:[a-z]+-)?|gh[pousr]_|github_pat_|glpat-|xox[abprs]-|AKIA|ASIA|AIza|hf_|npm_)[A-Za-z0-9_\-]{12,}/g, `$1${MASK}`],
|
|
136
|
+
// Authorization / Proxy-Authorization 头
|
|
137
|
+
[/((?:Proxy-)?Authorization\s*[:=]\s*)(?:(Bearer|Basic|Token)\s+)?[^\s"',]+/gi, `$1$2 ${MASK}`],
|
|
138
|
+
// JWT
|
|
139
|
+
[/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{4,}(?:\.[A-Za-z0-9_-]+)?/g, MASK],
|
|
140
|
+
// URL 里的账号密码
|
|
141
|
+
[/\b([a-z][a-z0-9+.-]*:\/\/)[^\s/:@]+:[^\s/@]+@/gi, `$1${MASK}@`],
|
|
142
|
+
// NAME=value / NAME: value / "name": "value",name 像密钥
|
|
143
|
+
[new RegExp(`(["']?${SECRET_NAME}["']?\\s*[:=]\\s*)(["']?)[^\\s"',]{4,}\\2`, "gi"), `$1$2${MASK}$2`]
|
|
144
|
+
];
|
|
145
|
+
function redactSecrets(text, known = []) {
|
|
146
|
+
let out = text;
|
|
147
|
+
for (const v of known) out = out.split(v).join(MASK);
|
|
148
|
+
for (const [re, to] of REDACTIONS) out = out.replace(re, to);
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
110
151
|
function clip(text, limit) {
|
|
111
152
|
const buf = Buffer.from(text, "utf8");
|
|
112
153
|
if (buf.length <= limit) return text;
|
|
@@ -142,15 +183,18 @@ async function workspaceOf(ctx, agent, signal) {
|
|
|
142
183
|
}
|
|
143
184
|
return process.cwd();
|
|
144
185
|
}
|
|
145
|
-
function runExec(
|
|
186
|
+
function runExec(cmd, rawInput, cwd, signal) {
|
|
146
187
|
const { env, argv } = execArgs(rawInput);
|
|
188
|
+
const command = cmd.command;
|
|
189
|
+
const timeout = cmd.timeoutMs;
|
|
147
190
|
return new Promise((resolve) => {
|
|
148
191
|
execFile("/bin/bash", ["-lc", command, "dsh-slash-command", ...argv], {
|
|
192
|
+
// 对标 Hermes:dsh 进程环境里有各家 API key,子进程默认拿不到
|
|
149
193
|
cwd,
|
|
150
194
|
timeout,
|
|
151
195
|
maxBuffer: MAX_BUFFER,
|
|
152
196
|
signal,
|
|
153
|
-
env: { ...process.env, ...env }
|
|
197
|
+
env: { ...scrubEnv(process.env, cmd.passEnv), ...env }
|
|
154
198
|
}, (err, stdout, stderr) => {
|
|
155
199
|
if (err === null) return resolve({ exit: "0", stdout, stderr });
|
|
156
200
|
const e = err;
|
|
@@ -180,8 +224,9 @@ function makeHandler(ctx, commands, cmd) {
|
|
|
180
224
|
}
|
|
181
225
|
return async (inv) => {
|
|
182
226
|
const cwd = cmd.cwd === void 0 || cmd.cwd === "workspace" ? await workspaceOf(ctx, inv.agent, inv.signal) : cmd.cwd;
|
|
183
|
-
const outcome = await runExec(cmd
|
|
184
|
-
const
|
|
227
|
+
const outcome = await runExec(cmd, inv.rawInput, cwd, inv.signal);
|
|
228
|
+
const known = secretValues(process.env);
|
|
229
|
+
const text = formatExecResult({ ...outcome, stdout: redactSecrets(outcome.stdout, known), stderr: redactSecrets(outcome.stderr, known) });
|
|
185
230
|
return outcome.exit === "0" ? { kind: "success", text } : { kind: "error", text };
|
|
186
231
|
};
|
|
187
232
|
}
|
package/lib/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts", "../src/core.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * dsh-slash-command host \u534A\u8FB9\uFF1A\u8BFB `$DSH_HOME/slash-commands.json`\uFF0C\u4E3A\u6BCF\u4E2A name/alias \u6CE8\u518C\u659C\u6760\u547D\u4EE4\uFF0C\n * \u76D1\u542C\u6587\u4EF6\u70ED\u91CD\u8F7D\uFF0C\u5E76\u901A\u8FC7 `connection.fetch` \u66B4\u9732 UI \u7528\u7684 read/save \u63A5\u53E3\uFF08\u6CBF\u7528\u5BBF\u4E3B\u8BA4\u8BC1\u6805\u680F\uFF0C\u4EC5 owner \u53EF\u8FBE\uFF09\u3002\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport { createUserMessage } from '@deepseek-ai/dsh-llm'\nimport { execFile } from 'node:child_process'\nimport { watch } from 'node:fs'\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { basename, dirname, join } from 'node:path'\nimport {\n composeAlias, composePrompt, execArgs, expandNames, formatExecResult, validateConfig,\n type ExecOutcome, type SlashCommand,\n} from './core.ts'\n\nexport const name = 'dsh-slash-command'\nexport const inject = ['commands', 'connection']\n\nconst READ_PATH = '/api/dsh-slash-command/read'\nconst SAVE_PATH = '/api/dsh-slash-command/save'\nconst MAX_BUFFER = 4 * 1024 * 1024\nconst MAX_BODY = 1024 * 1024\n/** alias \u94FE\u6700\u5927\u6DF1\u5EA6\uFF0C\u9632 a \u2192 b \u2192 a \u8FD9\u7C7B\u5FAA\u73AF\u3002 */\nconst MAX_ALIAS_DEPTH = 8\nconst ALIAS_DEPTH = Symbol('dsh-slash-command.aliasDepth')\n\n// \u5BBF\u4E3B\u547D\u4EE4 / agent / connection \u7684\u6700\u5C0F\u7ED3\u6784\u9762\uFF0C\u907F\u514D\u4F9D\u8D56\u989D\u5916\u7C7B\u578B\u5305\u3002\ntype Result = { kind: 'success'; text?: string; sourceEventSeq?: number } | { kind: 'error'; text: string }\ninterface AgentFace { steer(message: unknown): void; ctx?: unknown }\ninterface Invocation { agent: AgentFace; rawInput: string; signal: AbortSignal; [ALIAS_DEPTH]?: number }\ninterface CommandDef {\n name: string\n description: string\n input: { hint: string }\n handler: (inv: Invocation) => Result | Promise<Result>\n}\ninterface CommandsFace {\n register(def: CommandDef): () => void\n find(agent: AgentFace, name: string): CommandDef | undefined\n}\ninterface ConnectionFace {\n fetch: {\n register(route: {\n path: string\n methods: ('GET' | 'POST')[]\n requestBody: 'buffered'\n fetch: (request: Request) => Promise<Response>\n }): () => Promise<void>\n }\n}\ninterface WorkingDirectoryFace { ensure(agent: unknown, signal?: AbortSignal): Promise<string> }\n\nconst configPath = (): string => join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'slash-commands.json')\n\nconst json = (status: number, body: unknown): Response =>\n new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' } })\n\nfunction steerText(agent: AgentFace, text: string): void {\n agent.steer(createUserMessage({ content: [{ type: 'text', text }], source: { kind: 'user' } }))\n}\n\n/** \u4F1A\u8BDD\u5DE5\u4F5C\u533A\uFF1B\u53D6\u4E0D\u5230\u56DE\u843D process.cwd()\u3002 */\nasync function workspaceOf(ctx: Context, agent: AgentFace, signal: AbortSignal): Promise<string> {\n try {\n const wd = Reflect.get(ctx, 'workingDirectory') as WorkingDirectoryFace | undefined\n if (wd !== undefined) return await wd.ensure(agent, signal)\n } catch { /* \u56DE\u843D */ }\n return process.cwd()\n}\n\nfunction runExec(command: string, rawInput: string, cwd: string, timeout: number, signal: AbortSignal): Promise<ExecOutcome> {\n const { env, argv } = execArgs(rawInput)\n return new Promise((resolve) => {\n // bash -c script name args\u2026\uFF1Aargv \u6210\u4E3A $1\u2026\uFF0C\u811A\u672C\u6587\u672C\u91CC\u4E0D\u62FC\u4EFB\u4F55\u7528\u6237\u8F93\u5165\n execFile('/bin/bash', ['-lc', command, 'dsh-slash-command', ...argv], {\n cwd, timeout, maxBuffer: MAX_BUFFER, signal, env: { ...process.env, ...env },\n }, (err, stdout, stderr) => {\n if (err === null) return resolve({ exit: '0', stdout, stderr })\n const e = err as NodeJS.ErrnoException & { killed?: boolean; signal?: string }\n if (typeof e.code === 'number') return resolve({ exit: String(e.code), stdout, stderr })\n // \u8D85\u65F6/\u4FE1\u53F7/spawn \u5931\u8D25\uFF1A\u6CA1\u6709\u6570\u5B57\u9000\u51FA\u7801\n const exit = e.killed === true ? `timeout(${e.signal ?? 'killed'})` : String(e.code ?? e.signal ?? 'error')\n resolve({ exit, stdout, stderr: stderr + (stderr === '' || stderr.endsWith('\\n') ? '' : '\\n') + e.message })\n })\n })\n}\n\nfunction makeHandler(ctx: Context, commands: CommandsFace, cmd: SlashCommand): (inv: Invocation) => Promise<Result> {\n if (cmd.type === 'alias') {\n // \u76F4\u63A5\u8C03\u7528\u76EE\u6807\u547D\u4EE4\u7684 handler\uFF1A\u7ED3\u679C\u843D\u5728\u672C\u6761\u547D\u4EE4\u5361\u7247\u91CC\uFF0C\u4E0D\u518D\u591A\u51FA\u4E00\u5F20\u5361\n return async (inv) => {\n const depth = (inv[ALIAS_DEPTH] ?? 0) + 1\n if (depth > MAX_ALIAS_DEPTH) return { kind: 'error', text: `/${cmd.name}: alias chain too deep` }\n const line = composeAlias(cmd.target!, inv.rawInput)\n const m = /^\\/([a-z][a-z0-9_-]*)/.exec(line)\n const target = m === null ? undefined : commands.find(inv.agent, m[1]!)\n if (target === undefined) return { kind: 'error', text: `${line.split(/\\s/)[0]}: unknown command` }\n return target.handler({ ...inv, rawInput: line.slice(m![0].length), [ALIAS_DEPTH]: depth })\n }\n }\n if (cmd.type === 'prompt') {\n return (inv) => {\n steerText(inv.agent, composePrompt(cmd.prompt!, inv.rawInput))\n return Promise.resolve({ kind: 'success', text: '\u5DF2\u53D1\u9001' })\n }\n }\n return async (inv) => {\n const cwd = cmd.cwd === undefined || cmd.cwd === 'workspace' ? await workspaceOf(ctx, inv.agent, inv.signal) : cmd.cwd\n // \u5BF9\u6807 Hermes quick_commands\uFF1A\u53EA\u5728\u672C\u673A\u6267\u884C\u5E76\u56DE\u663E\uFF0C\u4E0D steer\u3001\u4E0D\u89E6\u53D1\u6A21\u578B\u56DE\u5408\u3002\n const outcome = await runExec(cmd.command!, inv.rawInput, cwd, cmd.timeoutMs!, inv.signal)\n const text = formatExecResult(outcome)\n return outcome.exit === '0' ? { kind: 'success', text } : { kind: 'error', text }\n }\n}\n\nexport function apply(ctx: Context): void {\n const commands = Reflect.get(ctx, 'commands') as CommandsFace\n const connection = Reflect.get(ctx, 'connection') as ConnectionFace\n const file = configPath()\n let disposers: (() => void)[] = []\n /** \u4E0A\u4E00\u4EFD\u5DF2\u751F\u6548\u7684\u539F\u59CB\u6587\u672C\uFF1B\u76F8\u540C\u5219\u4E0D\u91CD\u590D\u91CD\u6CE8\u518C\u3002 */\n let applied: string | undefined\n\n const unregisterAll = (): void => {\n for (const d of disposers) d()\n disposers = []\n }\n\n /** \u5168\u91CF\u53CD\u6CE8\u518C\u518D\u91CD\u6CE8\u518C\u3002\u89E3\u6790\u5931\u8D25\u65F6\u4FDD\u7559\u4E0A\u4E00\u4EFD\u3002 */\n async function reload(): Promise<void> {\n let text: string\n try {\n text = await readFile(file, 'utf8')\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'ENOENT') {\n ctx.logger.warn(`dsh-slash-command: cannot read ${file}: ${String(e)}`)\n return\n }\n text = '{\"commands\":[]}'\n }\n if (text === applied) return\n let raw: unknown\n try {\n raw = JSON.parse(text)\n } catch (e) {\n ctx.logger.warn(`dsh-slash-command: ${file} is not valid JSON, keeping previous config: ${String(e)}`)\n return\n }\n const { commands: valid, errors } = validateConfig(raw)\n for (const err of errors) {\n ctx.logger.warn(`dsh-slash-command: skipped commands[${err.index ?? '?'}]${err.field ? `.${err.field}` : ''}: ${err.message}`)\n }\n unregisterAll()\n applied = text\n for (const cmd of valid) {\n const handler = makeHandler(ctx, commands, cmd)\n for (const n of expandNames(cmd)) {\n try {\n disposers.push(commands.register({ name: n, description: cmd.description, input: { hint: '[args]' }, handler }))\n } catch (e) {\n ctx.logger.warn(`dsh-slash-command: cannot register /${n}: ${e instanceof Error ? e.message : String(e)}`)\n }\n }\n }\n }\n\n // watch \u76EE\u5F55\u800C\u975E\u6587\u4EF6\uFF1A\u7F16\u8F91\u5668/\u539F\u5B50 rename \u4F1A\u66FF\u6362 inode\u3002\n ctx.effect(() => {\n let timer: NodeJS.Timeout | undefined\n const trigger = (): void => {\n clearTimeout(timer)\n timer = setTimeout(() => { void reload() }, 300)\n }\n let watcher: ReturnType<typeof watch> | undefined\n void mkdir(dirname(file), { recursive: true }).then(() => {\n watcher = watch(dirname(file), (_event, filename) => { if (filename === basename(file)) trigger() })\n watcher.on('error', e => ctx.logger.warn(`dsh-slash-command: watcher error: ${String(e)}`))\n }).catch(e => ctx.logger.warn(`dsh-slash-command: cannot watch ${file}: ${String(e)}`))\n void reload()\n return () => {\n clearTimeout(timer)\n watcher?.close()\n unregisterAll()\n }\n }, 'dsh-slash-command: config watcher')\n\n connection.fetch.register({\n path: READ_PATH,\n methods: ['GET'],\n requestBody: 'buffered',\n fetch: async () => {\n try {\n const raw: unknown = JSON.parse(await readFile(file, 'utf8'))\n const commands = (raw as { commands?: unknown }).commands\n return json(200, { ok: true, commands: Array.isArray(commands) ? commands : [] })\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return json(200, { ok: true, commands: [] })\n return json(500, { ok: false, errors: [{ code: 'type', message: `cannot read config: ${String(e)}` }] })\n }\n },\n })\n\n connection.fetch.register({\n path: SAVE_PATH,\n methods: ['POST'],\n requestBody: 'buffered',\n fetch: async (request) => {\n // \u4FDD\u5B58\u4F1A\u5199\u5165\u53EF\u6267\u884C\u547D\u4EE4\uFF1A\u62D2\u7EDD\u8DE8\u7AD9\u4E0E\u975E JSON \u8BF7\u6C42\uFF08\u975E\u7B80\u5355 content-type \u4F1A\u5F3A\u5236 CORS \u9884\u68C0\uFF0C\u5BBF\u4E3B\u4E0D\u4F1A\u653E\u884C\uFF09\u3002\n if (!(request.headers.get('content-type') ?? '').toLowerCase().startsWith('application/json')) {\n return json(415, { ok: false, errors: [{ code: 'type', message: 'content-type must be application/json' }] })\n }\n const origin = request.headers.get('origin')\n if (origin !== null && origin !== new URL(request.url).origin) {\n return json(403, { ok: false, errors: [{ code: 'type', message: 'cross-origin request rejected' }] })\n }\n const body = await request.text()\n if (body.length > MAX_BODY) return json(413, { ok: false, errors: [{ code: 'range', message: 'body too large' }] })\n let raw: unknown\n try { raw = JSON.parse(body) } catch { return json(400, { ok: false, errors: [{ code: 'type', message: 'body is not valid JSON' }] }) }\n const { commands: valid, errors } = validateConfig(raw)\n if (errors.length > 0) return json(400, { ok: false, errors })\n const text = `${JSON.stringify({ commands: valid }, null, 2)}\\n`\n await mkdir(dirname(file), { recursive: true })\n const tmp = `${file}.${process.pid}.tmp`\n await writeFile(tmp, text)\n await rename(tmp, file)\n await reload() // \u7ACB\u5373\u751F\u6548\uFF0C\u4E0D\u7B49 watcher \u7684 debounce\n return json(200, { ok: true, commands: valid })\n },\n })\n}\n", "/** \u914D\u7F6E\u89E3\u6790/\u6821\u9A8C\u4E0E\u6587\u672C\u62FC\u88C5\uFF1A\u7EAF\u51FD\u6570\uFF0C\u65E0 host \u4F9D\u8D56\uFF0C\u6D4B\u8BD5\u76F4\u63A5\u5F15\u7528\u3002 */\n\nexport const NAME_RE = /^[a-z][a-z0-9_-]*$/\nexport const DEFAULT_TIMEOUT_MS = 30_000\nexport const MAX_TIMEOUT_MS = 600_000\nexport const OUTPUT_LIMIT = 16 * 1024\n\nexport interface SlashCommand {\n name: string\n aliases: string[]\n description: string\n type: 'prompt' | 'exec' | 'alias'\n prompt?: string\n command?: string\n /** alias \u578B\uFF1A\u53E6\u4E00\u6761\u659C\u6760\u547D\u4EE4\uFF0C\u5982 `/compact` \u6216 `/review \u53EA\u770B src`\u3002 */\n target?: string\n cwd?: string\n timeoutMs?: number\n}\n\n/** `index` \u6307\u5411 commands \u6570\u7EC4\u4E0B\u6807\uFF1B\u7F3A\u7701\u8868\u793A\u9876\u5C42\u9519\u8BEF\u3002 */\nexport interface FieldError {\n index?: number\n field?: string\n code: 'type' | 'required' | 'pattern' | 'duplicate' | 'enum' | 'range'\n message: string\n}\n\n/** alias \u76EE\u6807\uFF1A`/name` \u540E\u53EF\u8DDF\u56FA\u5B9A\u53C2\u6570\uFF1B\u5F00\u5934\u7684 `/` \u53EF\u7701\u7565\u3002 */\nconst TARGET_RE = /^\\/?([a-z][a-z0-9_-]*)(?:\\s[\\s\\S]*)?$/\n\nconst isObj = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v)\nconst nonEmpty = (v: unknown): v is string => typeof v === 'string' && v.trim() !== ''\n\n/**\n * \u6821\u9A8C\u5E76\u89C4\u8303\u5316\u914D\u7F6E\u3002\u975E\u6CD5\u6761\u76EE\u4E0D\u4F1A\u51FA\u73B0\u5728 `commands` \u91CC\uFF08\u6574\u6761\u4E22\u5F03\uFF09\uFF0C\u539F\u56E0\u5199\u5165 `errors`\u3002\n * \u540D\u5B57\u4E0E\u522B\u540D\u5171\u7528\u540C\u4E00\u547D\u540D\u7A7A\u95F4\uFF0C\u5148\u51FA\u73B0\u8005\u80DC\uFF0C\u540E\u8005\u62A5 duplicate\u3002\n */\nexport function validateConfig(raw: unknown): { commands: SlashCommand[]; errors: FieldError[] } {\n const commands: SlashCommand[] = []\n const errors: FieldError[] = []\n if (!isObj(raw) || !Array.isArray(raw.commands)) {\n return { commands, errors: [{ code: 'type', field: 'commands', message: 'commands must be an array' }] }\n }\n const seen = new Set<string>()\n raw.commands.forEach((item: unknown, index) => {\n const bad: FieldError[] = []\n const fail = (field: string, code: FieldError['code'], message: string): void => {\n bad.push({ index, field, code, message })\n }\n if (!isObj(item)) {\n errors.push({ index, code: 'type', message: 'command must be an object' })\n return\n }\n const names: string[] = []\n if (typeof item.name !== 'string' || item.name === '') fail('name', 'required', 'name is required')\n else if (!NAME_RE.test(item.name)) fail('name', 'pattern', `name \"${item.name}\" must match ${String(NAME_RE)}`)\n else names.push(item.name)\n const aliases: string[] = []\n if (item.aliases !== undefined) {\n if (!Array.isArray(item.aliases)) fail('aliases', 'type', 'aliases must be an array of strings')\n else {\n for (const a of item.aliases as unknown[]) {\n if (typeof a !== 'string' || !NAME_RE.test(a)) fail('aliases', 'pattern', `alias \"${String(a)}\" must match ${String(NAME_RE)}`)\n else aliases.push(a)\n }\n }\n }\n const own = [...names, ...aliases]\n own.forEach((n, i) => {\n if (seen.has(n) || own.indexOf(n) !== i) fail(n === item.name ? 'name' : 'aliases', 'duplicate', `\"${n}\" is already used`)\n })\n if (!nonEmpty(item.description)) fail('description', 'required', 'description is required')\n if (item.type !== 'prompt' && item.type !== 'exec' && item.type !== 'alias') fail('type', 'enum', 'type must be \"prompt\", \"exec\" or \"alias\"')\n else if (item.type === 'prompt' && !nonEmpty(item.prompt)) fail('prompt', 'required', 'prompt is required for prompt commands')\n else if (item.type === 'exec' && !nonEmpty(item.command)) fail('command', 'required', 'command is required for exec commands')\n else if (item.type === 'alias') {\n const m = typeof item.target === 'string' ? TARGET_RE.exec(item.target.trim()) : null\n if (!nonEmpty(item.target)) fail('target', 'required', 'target is required for alias commands')\n else if (m === null) fail('target', 'pattern', `target \"${String(item.target)}\" must be a slash command like /compact`)\n else if (own.includes(m[1]!)) fail('target', 'duplicate', `alias /${m[1]} cannot target itself`)\n }\n if (item.cwd !== undefined && !(typeof item.cwd === 'string' && (item.cwd === 'workspace' || item.cwd.startsWith('/')))) {\n fail('cwd', 'enum', 'cwd must be \"workspace\" or an absolute path')\n }\n if (item.timeoutMs !== undefined\n && !(typeof item.timeoutMs === 'number' && Number.isInteger(item.timeoutMs) && item.timeoutMs >= 1 && item.timeoutMs <= MAX_TIMEOUT_MS)) {\n fail('timeoutMs', 'range', `timeoutMs must be an integer in 1..${MAX_TIMEOUT_MS}`)\n }\n if (bad.length > 0) {\n errors.push(...bad)\n return\n }\n for (const n of own) seen.add(n)\n const cmd: SlashCommand = {\n name: item.name as string,\n aliases,\n description: (item.description as string).trim(),\n type: item.type as 'prompt' | 'exec',\n }\n if (cmd.type === 'prompt') cmd.prompt = item.prompt as string\n else if (cmd.type === 'alias') cmd.target = normalizeTarget(item.target as string)\n else {\n cmd.command = item.command as string\n cmd.cwd = (item.cwd as string | undefined) ?? 'workspace'\n cmd.timeoutMs = (item.timeoutMs as number | undefined) ?? DEFAULT_TIMEOUT_MS\n }\n commands.push(cmd)\n })\n return { commands, errors }\n}\n\n/** \u540D\u5B57 + \u522B\u540D\u5C55\u5F00\u6210\u8981\u6CE8\u518C\u7684\u5168\u90E8\u5165\u53E3\u540D\u3002 */\nexport const expandNames = (cmd: SlashCommand): string[] => [cmd.name, ...cmd.aliases]\n\n/** `$ARGUMENTS` \u539F\u4F4D\u66FF\u6362\uFF1B\u6CA1\u6709\u5360\u4F4D\u7B26\u4E14\u6709\u53C2\u6570\u65F6\u4EE5\u7A7A\u884C\u62FC\u5230\u672B\u5C3E\u3002 */\nexport function composePrompt(prompt: string, rawInput: string): string {\n const args = rawInput.trim()\n if (prompt.includes('$ARGUMENTS')) return prompt.replaceAll('$ARGUMENTS', () => args)\n return args === '' ? prompt : `${prompt}\\n\\n${args}`\n}\n\n/** \u7EDF\u4E00\u6210 `/name args` \u5F62\u5F0F\u3002 */\nexport const normalizeTarget = (target: string): string => {\n const t = target.trim()\n return t.startsWith('/') ? t : `/${t}`\n}\n\n/** alias \u5C55\u5F00\uFF1A`$ARGUMENTS` \u539F\u4F4D\u66FF\u6362\uFF1B\u5426\u5219\u53C2\u6570\u4EE5\u7A7A\u683C\u63A5\u5728\u76EE\u6807\u547D\u4EE4\u540E\u3002 */\nexport function composeAlias(target: string, rawInput: string): string {\n const args = rawInput.trim()\n if (target.includes('$ARGUMENTS')) return target.replaceAll('$ARGUMENTS', () => args).trim()\n return args === '' ? target : `${target} ${args}`\n}\n\n/**\n * exec \u53C2\u6570\u4EE5\u53D8\u91CF\u5F62\u5F0F\u4F20\u5165\u800C\u975E\u62FC\u8FDB\u811A\u672C\uFF08\u4E0D\u4F1A\u88AB\u5F53\u6210\u4EE3\u7801\u6267\u884C\uFF09\uFF1A\n * `$ARGUMENTS` = \u5B8C\u6574\u53C2\u6570\uFF0C`$1 $2 \u2026` / `\"$@\"` = \u6309\u7A7A\u767D\u62C6\u5206\u7684\u53C2\u6570\u3002\n */\nexport function execArgs(rawInput: string): { env: Record<string, string>; argv: string[] } {\n const args = rawInput.trim()\n return { env: { ARGUMENTS: args }, argv: args === '' ? [] : args.split(/\\s+/) }\n}\n\n/** \u6309 UTF-8 \u5B57\u8282\u622A\u65AD\uFF08\u4E0D\u62C6\u5B57\u7B26\uFF09\u3002 */\nfunction clip(text: string, limit: number): string {\n const buf = Buffer.from(text, 'utf8')\n if (buf.length <= limit) return text\n return `${buf.subarray(0, limit).toString('utf8').replace(/\uFFFD$/, '')}\\n\u2026[truncated]`\n}\n\nexport interface ExecOutcome {\n exit: string\n stdout: string\n stderr: string\n}\n\n/**\n * exec \u7ED3\u679C\u76F4\u63A5\u56DE\u663E\u5728\u547D\u4EE4\u5361\u7247\u91CC\uFF08\u4E0D\u8FDB\u6A21\u578B\uFF09\uFF1Astdout + stderr \u5408\u8BA1\u622A\u65AD\u5230 16KB\uFF1B\n * \u975E\u96F6\u9000\u51FA/\u8D85\u65F6\u5728\u672B\u5C3E\u9644 `exit=N`\uFF0C\u65E0\u8F93\u51FA\u65F6\u53EA\u7ED9 exit\u3002\n */\nexport function formatExecResult(o: ExecOutcome): string {\n const output = clip(`${o.stdout}${o.stderr}`.replace(/\\s+$/, ''), OUTPUT_LIMIT)\n if (o.exit === '0') return output === '' ? 'exit=0' : output\n return output === '' ? `exit=${o.exit}` : `${output}\\nexit=${o.exit}`\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["commands"]
|
|
4
|
+
"sourcesContent": ["/**\n * dsh-slash-command host \u534A\u8FB9\uFF1A\u8BFB `$DSH_HOME/slash-commands.json`\uFF0C\u4E3A\u6BCF\u4E2A name/alias \u6CE8\u518C\u659C\u6760\u547D\u4EE4\uFF0C\n * \u76D1\u542C\u6587\u4EF6\u70ED\u91CD\u8F7D\uFF0C\u5E76\u901A\u8FC7 `connection.fetch` \u66B4\u9732 UI \u7528\u7684 read/save \u63A5\u53E3\uFF08\u6CBF\u7528\u5BBF\u4E3B\u8BA4\u8BC1\u6805\u680F\uFF0C\u4EC5 owner \u53EF\u8FBE\uFF09\u3002\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport { createUserMessage } from '@deepseek-ai/dsh-llm'\nimport { execFile } from 'node:child_process'\nimport { watch } from 'node:fs'\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { basename, dirname, join } from 'node:path'\nimport {\n composeAlias, composePrompt, execArgs, expandNames, formatExecResult, redactSecrets, scrubEnv, secretValues, validateConfig,\n type ExecOutcome, type SlashCommand,\n} from './core.ts'\n\nexport const name = 'dsh-slash-command'\nexport const inject = ['commands', 'connection']\n\nconst READ_PATH = '/api/dsh-slash-command/read'\nconst SAVE_PATH = '/api/dsh-slash-command/save'\nconst MAX_BUFFER = 4 * 1024 * 1024\nconst MAX_BODY = 1024 * 1024\n/** alias \u94FE\u6700\u5927\u6DF1\u5EA6\uFF0C\u9632 a \u2192 b \u2192 a \u8FD9\u7C7B\u5FAA\u73AF\u3002 */\nconst MAX_ALIAS_DEPTH = 8\nconst ALIAS_DEPTH = Symbol('dsh-slash-command.aliasDepth')\n\n// \u5BBF\u4E3B\u547D\u4EE4 / agent / connection \u7684\u6700\u5C0F\u7ED3\u6784\u9762\uFF0C\u907F\u514D\u4F9D\u8D56\u989D\u5916\u7C7B\u578B\u5305\u3002\ntype Result = { kind: 'success'; text?: string; sourceEventSeq?: number } | { kind: 'error'; text: string }\ninterface AgentFace { steer(message: unknown): void; ctx?: unknown }\ninterface Invocation { agent: AgentFace; rawInput: string; signal: AbortSignal; [ALIAS_DEPTH]?: number }\ninterface CommandDef {\n name: string\n description: string\n input: { hint: string }\n handler: (inv: Invocation) => Result | Promise<Result>\n}\ninterface CommandsFace {\n register(def: CommandDef): () => void\n find(agent: AgentFace, name: string): CommandDef | undefined\n}\ninterface ConnectionFace {\n fetch: {\n register(route: {\n path: string\n methods: ('GET' | 'POST')[]\n requestBody: 'buffered'\n fetch: (request: Request) => Promise<Response>\n }): () => Promise<void>\n }\n}\ninterface WorkingDirectoryFace { ensure(agent: unknown, signal?: AbortSignal): Promise<string> }\n\nconst configPath = (): string => join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'slash-commands.json')\n\nconst json = (status: number, body: unknown): Response =>\n new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' } })\n\nfunction steerText(agent: AgentFace, text: string): void {\n agent.steer(createUserMessage({ content: [{ type: 'text', text }], source: { kind: 'user' } }))\n}\n\n/** \u4F1A\u8BDD\u5DE5\u4F5C\u533A\uFF1B\u53D6\u4E0D\u5230\u56DE\u843D process.cwd()\u3002 */\nasync function workspaceOf(ctx: Context, agent: AgentFace, signal: AbortSignal): Promise<string> {\n try {\n const wd = Reflect.get(ctx, 'workingDirectory') as WorkingDirectoryFace | undefined\n if (wd !== undefined) return await wd.ensure(agent, signal)\n } catch { /* \u56DE\u843D */ }\n return process.cwd()\n}\n\nfunction runExec(cmd: SlashCommand, rawInput: string, cwd: string, signal: AbortSignal): Promise<ExecOutcome> {\n const { env, argv } = execArgs(rawInput)\n const command = cmd.command!\n const timeout = cmd.timeoutMs!\n return new Promise((resolve) => {\n // bash -c script name args\u2026\uFF1Aargv \u6210\u4E3A $1\u2026\uFF0C\u811A\u672C\u6587\u672C\u91CC\u4E0D\u62FC\u4EFB\u4F55\u7528\u6237\u8F93\u5165\n execFile('/bin/bash', ['-lc', command, 'dsh-slash-command', ...argv], {\n // \u5BF9\u6807 Hermes\uFF1Adsh \u8FDB\u7A0B\u73AF\u5883\u91CC\u6709\u5404\u5BB6 API key\uFF0C\u5B50\u8FDB\u7A0B\u9ED8\u8BA4\u62FF\u4E0D\u5230\n cwd, timeout, maxBuffer: MAX_BUFFER, signal, env: { ...scrubEnv(process.env, cmd.passEnv), ...env },\n }, (err, stdout, stderr) => {\n if (err === null) return resolve({ exit: '0', stdout, stderr })\n const e = err as NodeJS.ErrnoException & { killed?: boolean; signal?: string }\n if (typeof e.code === 'number') return resolve({ exit: String(e.code), stdout, stderr })\n // \u8D85\u65F6/\u4FE1\u53F7/spawn \u5931\u8D25\uFF1A\u6CA1\u6709\u6570\u5B57\u9000\u51FA\u7801\n const exit = e.killed === true ? `timeout(${e.signal ?? 'killed'})` : String(e.code ?? e.signal ?? 'error')\n resolve({ exit, stdout, stderr: stderr + (stderr === '' || stderr.endsWith('\\n') ? '' : '\\n') + e.message })\n })\n })\n}\n\nfunction makeHandler(ctx: Context, commands: CommandsFace, cmd: SlashCommand): (inv: Invocation) => Promise<Result> {\n if (cmd.type === 'alias') {\n // \u76F4\u63A5\u8C03\u7528\u76EE\u6807\u547D\u4EE4\u7684 handler\uFF1A\u7ED3\u679C\u843D\u5728\u672C\u6761\u547D\u4EE4\u5361\u7247\u91CC\uFF0C\u4E0D\u518D\u591A\u51FA\u4E00\u5F20\u5361\n return async (inv) => {\n const depth = (inv[ALIAS_DEPTH] ?? 0) + 1\n if (depth > MAX_ALIAS_DEPTH) return { kind: 'error', text: `/${cmd.name}: alias chain too deep` }\n const line = composeAlias(cmd.target!, inv.rawInput)\n const m = /^\\/([a-z][a-z0-9_-]*)/.exec(line)\n const target = m === null ? undefined : commands.find(inv.agent, m[1]!)\n if (target === undefined) return { kind: 'error', text: `${line.split(/\\s/)[0]}: unknown command` }\n return target.handler({ ...inv, rawInput: line.slice(m![0].length), [ALIAS_DEPTH]: depth })\n }\n }\n if (cmd.type === 'prompt') {\n return (inv) => {\n steerText(inv.agent, composePrompt(cmd.prompt!, inv.rawInput))\n return Promise.resolve({ kind: 'success', text: '\u5DF2\u53D1\u9001' })\n }\n }\n return async (inv) => {\n const cwd = cmd.cwd === undefined || cmd.cwd === 'workspace' ? await workspaceOf(ctx, inv.agent, inv.signal) : cmd.cwd\n // \u5BF9\u6807 Hermes quick_commands\uFF1A\u53EA\u5728\u672C\u673A\u6267\u884C\u5E76\u56DE\u663E\uFF0C\u4E0D steer\u3001\u4E0D\u89E6\u53D1\u6A21\u578B\u56DE\u5408\u3002\n const outcome = await runExec(cmd, inv.rawInput, cwd, inv.signal)\n // \u7ED3\u679C\u4F1A\u5199\u8FDB\u4F1A\u8BDD\u8BB0\u5F55\uFF0C\u5148\u906E\u853D\u518D\u663E\u793A\n const known = secretValues(process.env)\n const text = formatExecResult({ ...outcome, stdout: redactSecrets(outcome.stdout, known), stderr: redactSecrets(outcome.stderr, known) })\n return outcome.exit === '0' ? { kind: 'success', text } : { kind: 'error', text }\n }\n}\n\nexport function apply(ctx: Context): void {\n const commands = Reflect.get(ctx, 'commands') as CommandsFace\n const connection = Reflect.get(ctx, 'connection') as ConnectionFace\n const file = configPath()\n let disposers: (() => void)[] = []\n /** \u4E0A\u4E00\u4EFD\u5DF2\u751F\u6548\u7684\u539F\u59CB\u6587\u672C\uFF1B\u76F8\u540C\u5219\u4E0D\u91CD\u590D\u91CD\u6CE8\u518C\u3002 */\n let applied: string | undefined\n\n const unregisterAll = (): void => {\n for (const d of disposers) d()\n disposers = []\n }\n\n /** \u5168\u91CF\u53CD\u6CE8\u518C\u518D\u91CD\u6CE8\u518C\u3002\u89E3\u6790\u5931\u8D25\u65F6\u4FDD\u7559\u4E0A\u4E00\u4EFD\u3002 */\n async function reload(): Promise<void> {\n let text: string\n try {\n text = await readFile(file, 'utf8')\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'ENOENT') {\n ctx.logger.warn(`dsh-slash-command: cannot read ${file}: ${String(e)}`)\n return\n }\n text = '{\"commands\":[]}'\n }\n if (text === applied) return\n let raw: unknown\n try {\n raw = JSON.parse(text)\n } catch (e) {\n ctx.logger.warn(`dsh-slash-command: ${file} is not valid JSON, keeping previous config: ${String(e)}`)\n return\n }\n const { commands: valid, errors } = validateConfig(raw)\n for (const err of errors) {\n ctx.logger.warn(`dsh-slash-command: skipped commands[${err.index ?? '?'}]${err.field ? `.${err.field}` : ''}: ${err.message}`)\n }\n unregisterAll()\n applied = text\n for (const cmd of valid) {\n const handler = makeHandler(ctx, commands, cmd)\n for (const n of expandNames(cmd)) {\n try {\n disposers.push(commands.register({ name: n, description: cmd.description, input: { hint: '[args]' }, handler }))\n } catch (e) {\n ctx.logger.warn(`dsh-slash-command: cannot register /${n}: ${e instanceof Error ? e.message : String(e)}`)\n }\n }\n }\n }\n\n // watch \u76EE\u5F55\u800C\u975E\u6587\u4EF6\uFF1A\u7F16\u8F91\u5668/\u539F\u5B50 rename \u4F1A\u66FF\u6362 inode\u3002\n ctx.effect(() => {\n let timer: NodeJS.Timeout | undefined\n const trigger = (): void => {\n clearTimeout(timer)\n timer = setTimeout(() => { void reload() }, 300)\n }\n let watcher: ReturnType<typeof watch> | undefined\n void mkdir(dirname(file), { recursive: true }).then(() => {\n watcher = watch(dirname(file), (_event, filename) => { if (filename === basename(file)) trigger() })\n watcher.on('error', e => ctx.logger.warn(`dsh-slash-command: watcher error: ${String(e)}`))\n }).catch(e => ctx.logger.warn(`dsh-slash-command: cannot watch ${file}: ${String(e)}`))\n void reload()\n return () => {\n clearTimeout(timer)\n watcher?.close()\n unregisterAll()\n }\n }, 'dsh-slash-command: config watcher')\n\n connection.fetch.register({\n path: READ_PATH,\n methods: ['GET'],\n requestBody: 'buffered',\n fetch: async () => {\n try {\n const raw: unknown = JSON.parse(await readFile(file, 'utf8'))\n const commands = (raw as { commands?: unknown }).commands\n return json(200, { ok: true, commands: Array.isArray(commands) ? commands : [] })\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return json(200, { ok: true, commands: [] })\n return json(500, { ok: false, errors: [{ code: 'type', message: `cannot read config: ${String(e)}` }] })\n }\n },\n })\n\n connection.fetch.register({\n path: SAVE_PATH,\n methods: ['POST'],\n requestBody: 'buffered',\n fetch: async (request) => {\n // \u4FDD\u5B58\u4F1A\u5199\u5165\u53EF\u6267\u884C\u547D\u4EE4\uFF1A\u62D2\u7EDD\u8DE8\u7AD9\u4E0E\u975E JSON \u8BF7\u6C42\uFF08\u975E\u7B80\u5355 content-type \u4F1A\u5F3A\u5236 CORS \u9884\u68C0\uFF0C\u5BBF\u4E3B\u4E0D\u4F1A\u653E\u884C\uFF09\u3002\n if (!(request.headers.get('content-type') ?? '').toLowerCase().startsWith('application/json')) {\n return json(415, { ok: false, errors: [{ code: 'type', message: 'content-type must be application/json' }] })\n }\n const origin = request.headers.get('origin')\n if (origin !== null && origin !== new URL(request.url).origin) {\n return json(403, { ok: false, errors: [{ code: 'type', message: 'cross-origin request rejected' }] })\n }\n const body = await request.text()\n if (body.length > MAX_BODY) return json(413, { ok: false, errors: [{ code: 'range', message: 'body too large' }] })\n let raw: unknown\n try { raw = JSON.parse(body) } catch { return json(400, { ok: false, errors: [{ code: 'type', message: 'body is not valid JSON' }] }) }\n const { commands: valid, errors } = validateConfig(raw)\n if (errors.length > 0) return json(400, { ok: false, errors })\n const text = `${JSON.stringify({ commands: valid }, null, 2)}\\n`\n await mkdir(dirname(file), { recursive: true })\n const tmp = `${file}.${process.pid}.tmp`\n await writeFile(tmp, text)\n await rename(tmp, file)\n await reload() // \u7ACB\u5373\u751F\u6548\uFF0C\u4E0D\u7B49 watcher \u7684 debounce\n return json(200, { ok: true, commands: valid })\n },\n })\n}\n", "/** \u914D\u7F6E\u89E3\u6790/\u6821\u9A8C\u4E0E\u6587\u672C\u62FC\u88C5\uFF1A\u7EAF\u51FD\u6570\uFF0C\u65E0 host \u4F9D\u8D56\uFF0C\u6D4B\u8BD5\u76F4\u63A5\u5F15\u7528\u3002 */\n\nexport const NAME_RE = /^[a-z][a-z0-9_-]*$/\nexport const DEFAULT_TIMEOUT_MS = 30_000\nexport const MAX_TIMEOUT_MS = 600_000\nexport const OUTPUT_LIMIT = 16 * 1024\n\nexport interface SlashCommand {\n name: string\n aliases: string[]\n description: string\n type: 'prompt' | 'exec' | 'alias'\n prompt?: string\n command?: string\n /** alias \u578B\uFF1A\u53E6\u4E00\u6761\u659C\u6760\u547D\u4EE4\uFF0C\u5982 `/compact` \u6216 `/review \u53EA\u770B src`\u3002 */\n target?: string\n cwd?: string\n timeoutMs?: number\n /** exec \u578B\uFF1A\u7167\u5E38\u4F20\u5165\u5B50\u8FDB\u7A0B\u7684\u5BC6\u94A5\u7C7B\u73AF\u5883\u53D8\u91CF\uFF08\u9ED8\u8BA4\u4F1A\u88AB\u53BB\u6389\uFF09\u3002 */\n passEnv?: string[]\n}\n\n/** `index` \u6307\u5411 commands \u6570\u7EC4\u4E0B\u6807\uFF1B\u7F3A\u7701\u8868\u793A\u9876\u5C42\u9519\u8BEF\u3002 */\nexport interface FieldError {\n index?: number\n field?: string\n code: 'type' | 'required' | 'pattern' | 'duplicate' | 'enum' | 'range'\n message: string\n}\n\n/** alias \u76EE\u6807\uFF1A`/name` \u540E\u53EF\u8DDF\u56FA\u5B9A\u53C2\u6570\uFF1B\u5F00\u5934\u7684 `/` \u53EF\u7701\u7565\u3002 */\nconst TARGET_RE = /^\\/?([a-z][a-z0-9_-]*)(?:\\s[\\s\\S]*)?$/\n\nconst ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/\n\nconst isObj = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v)\nconst nonEmpty = (v: unknown): v is string => typeof v === 'string' && v.trim() !== ''\n\n/**\n * \u6821\u9A8C\u5E76\u89C4\u8303\u5316\u914D\u7F6E\u3002\u975E\u6CD5\u6761\u76EE\u4E0D\u4F1A\u51FA\u73B0\u5728 `commands` \u91CC\uFF08\u6574\u6761\u4E22\u5F03\uFF09\uFF0C\u539F\u56E0\u5199\u5165 `errors`\u3002\n * \u540D\u5B57\u4E0E\u522B\u540D\u5171\u7528\u540C\u4E00\u547D\u540D\u7A7A\u95F4\uFF0C\u5148\u51FA\u73B0\u8005\u80DC\uFF0C\u540E\u8005\u62A5 duplicate\u3002\n */\nexport function validateConfig(raw: unknown): { commands: SlashCommand[]; errors: FieldError[] } {\n const commands: SlashCommand[] = []\n const errors: FieldError[] = []\n if (!isObj(raw) || !Array.isArray(raw.commands)) {\n return { commands, errors: [{ code: 'type', field: 'commands', message: 'commands must be an array' }] }\n }\n const seen = new Set<string>()\n raw.commands.forEach((item: unknown, index) => {\n const bad: FieldError[] = []\n const fail = (field: string, code: FieldError['code'], message: string): void => {\n bad.push({ index, field, code, message })\n }\n if (!isObj(item)) {\n errors.push({ index, code: 'type', message: 'command must be an object' })\n return\n }\n const names: string[] = []\n if (typeof item.name !== 'string' || item.name === '') fail('name', 'required', 'name is required')\n else if (!NAME_RE.test(item.name)) fail('name', 'pattern', `name \"${item.name}\" must match ${String(NAME_RE)}`)\n else names.push(item.name)\n const aliases: string[] = []\n if (item.aliases !== undefined) {\n if (!Array.isArray(item.aliases)) fail('aliases', 'type', 'aliases must be an array of strings')\n else {\n for (const a of item.aliases as unknown[]) {\n if (typeof a !== 'string' || !NAME_RE.test(a)) fail('aliases', 'pattern', `alias \"${String(a)}\" must match ${String(NAME_RE)}`)\n else aliases.push(a)\n }\n }\n }\n const own = [...names, ...aliases]\n own.forEach((n, i) => {\n if (seen.has(n) || own.indexOf(n) !== i) fail(n === item.name ? 'name' : 'aliases', 'duplicate', `\"${n}\" is already used`)\n })\n if (!nonEmpty(item.description)) fail('description', 'required', 'description is required')\n if (item.type !== 'prompt' && item.type !== 'exec' && item.type !== 'alias') fail('type', 'enum', 'type must be \"prompt\", \"exec\" or \"alias\"')\n else if (item.type === 'prompt' && !nonEmpty(item.prompt)) fail('prompt', 'required', 'prompt is required for prompt commands')\n else if (item.type === 'exec' && !nonEmpty(item.command)) fail('command', 'required', 'command is required for exec commands')\n else if (item.type === 'alias') {\n const m = typeof item.target === 'string' ? TARGET_RE.exec(item.target.trim()) : null\n if (!nonEmpty(item.target)) fail('target', 'required', 'target is required for alias commands')\n else if (m === null) fail('target', 'pattern', `target \"${String(item.target)}\" must be a slash command like /compact`)\n else if (own.includes(m[1]!)) fail('target', 'duplicate', `alias /${m[1]} cannot target itself`)\n }\n if (item.cwd !== undefined && !(typeof item.cwd === 'string' && (item.cwd === 'workspace' || item.cwd.startsWith('/')))) {\n fail('cwd', 'enum', 'cwd must be \"workspace\" or an absolute path')\n }\n if (item.passEnv !== undefined\n && !(Array.isArray(item.passEnv) && item.passEnv.every(v => typeof v === 'string' && ENV_NAME_RE.test(v)))) {\n fail('passEnv', 'pattern', 'passEnv must be an array of environment variable names')\n }\n if (item.timeoutMs !== undefined\n && !(typeof item.timeoutMs === 'number' && Number.isInteger(item.timeoutMs) && item.timeoutMs >= 1 && item.timeoutMs <= MAX_TIMEOUT_MS)) {\n fail('timeoutMs', 'range', `timeoutMs must be an integer in 1..${MAX_TIMEOUT_MS}`)\n }\n if (bad.length > 0) {\n errors.push(...bad)\n return\n }\n for (const n of own) seen.add(n)\n const cmd: SlashCommand = {\n name: item.name as string,\n aliases,\n description: (item.description as string).trim(),\n type: item.type as 'prompt' | 'exec',\n }\n if (cmd.type === 'prompt') cmd.prompt = item.prompt as string\n else if (cmd.type === 'alias') cmd.target = normalizeTarget(item.target as string)\n else {\n cmd.command = item.command as string\n cmd.cwd = (item.cwd as string | undefined) ?? 'workspace'\n cmd.timeoutMs = (item.timeoutMs as number | undefined) ?? DEFAULT_TIMEOUT_MS\n if (Array.isArray(item.passEnv) && item.passEnv.length > 0) cmd.passEnv = item.passEnv as string[]\n }\n commands.push(cmd)\n })\n return { commands, errors }\n}\n\n/** \u540D\u5B57 + \u522B\u540D\u5C55\u5F00\u6210\u8981\u6CE8\u518C\u7684\u5168\u90E8\u5165\u53E3\u540D\u3002 */\nexport const expandNames = (cmd: SlashCommand): string[] => [cmd.name, ...cmd.aliases]\n\n/** `$ARGUMENTS` \u539F\u4F4D\u66FF\u6362\uFF1B\u6CA1\u6709\u5360\u4F4D\u7B26\u4E14\u6709\u53C2\u6570\u65F6\u4EE5\u7A7A\u884C\u62FC\u5230\u672B\u5C3E\u3002 */\nexport function composePrompt(prompt: string, rawInput: string): string {\n const args = rawInput.trim()\n if (prompt.includes('$ARGUMENTS')) return prompt.replaceAll('$ARGUMENTS', () => args)\n return args === '' ? prompt : `${prompt}\\n\\n${args}`\n}\n\n/** \u7EDF\u4E00\u6210 `/name args` \u5F62\u5F0F\u3002 */\nexport const normalizeTarget = (target: string): string => {\n const t = target.trim()\n return t.startsWith('/') ? t : `/${t}`\n}\n\n/** alias \u5C55\u5F00\uFF1A`$ARGUMENTS` \u539F\u4F4D\u66FF\u6362\uFF1B\u5426\u5219\u53C2\u6570\u4EE5\u7A7A\u683C\u63A5\u5728\u76EE\u6807\u547D\u4EE4\u540E\u3002 */\nexport function composeAlias(target: string, rawInput: string): string {\n const args = rawInput.trim()\n if (target.includes('$ARGUMENTS')) return target.replaceAll('$ARGUMENTS', () => args).trim()\n return args === '' ? target : `${target} ${args}`\n}\n\n/**\n * exec \u53C2\u6570\u4EE5\u53D8\u91CF\u5F62\u5F0F\u4F20\u5165\u800C\u975E\u62FC\u8FDB\u811A\u672C\uFF08\u4E0D\u4F1A\u88AB\u5F53\u6210\u4EE3\u7801\u6267\u884C\uFF09\uFF1A\n * `$ARGUMENTS` = \u5B8C\u6574\u53C2\u6570\uFF0C`$1 $2 \u2026` / `\"$@\"` = \u6309\u7A7A\u767D\u62C6\u5206\u7684\u53C2\u6570\u3002\n */\nexport function execArgs(rawInput: string): { env: Record<string, string>; argv: string[] } {\n const args = rawInput.trim()\n return { env: { ARGUMENTS: args }, argv: args === '' ? [] : args.split(/\\s+/) }\n}\n\n/**\n * \u540D\u5B57\u50CF\u5BC6\u94A5\u7684\u73AF\u5883\u53D8\u91CF\uFF08\u5BF9\u6807 Hermes \u7684\u5B50\u8FDB\u7A0B env \u8FC7\u6EE4\uFF09\u3002\u6309 `_` \u5206\u6BB5\u5339\u914D\uFF0C\n * \u6240\u4EE5 `KEYBOARD`\u3001`SSH_AUTH_SOCK` \u8FD9\u7C7B\u4E0D\u53D7\u5F71\u54CD\u3002\n */\nconst SECRET_ENV_RE = /(?:^|_)(?:API_?KEY|KEY|KEYS|TOKEN|TOKENS|SECRET|SECRETS|PASSWORD|PASSWD|PASS|CREDENTIALS?|AUTH|COOKIE)(?:_|$)/i\nconst ENV_KEEP = new Set(['SSH_AUTH_SOCK', 'PWD', 'OLDPWD'])\n\nexport const isSecretEnvName = (name: string): boolean => !ENV_KEEP.has(name) && SECRET_ENV_RE.test(name)\n\n/** \u53BB\u6389\u5BC6\u94A5\u7C7B\u53D8\u91CF\uFF0C`passEnv` \u91CC\u5217\u51FA\u7684\u7167\u5E38\u4FDD\u7559\u3002 */\nexport function scrubEnv(env: NodeJS.ProcessEnv, passEnv: readonly string[] = []): Record<string, string> {\n const pass = new Set(passEnv)\n const out: Record<string, string> = {}\n for (const [k, v] of Object.entries(env)) {\n if (v !== undefined && (pass.has(k) || !isSecretEnvName(k))) out[k] = v\n }\n return out\n}\n\n/** \u5BBF\u4E3B\u73AF\u5883\u91CC\u6240\u6709\u5BC6\u94A5\u7C7B\u53D8\u91CF\u7684\u503C\uFF08\u2265 8 \u5B57\u7B26\uFF09\uFF0C\u8F93\u51FA\u4E2D\u539F\u6837\u51FA\u73B0\u5373\u906E\u853D\u3002 */\nexport function secretValues(env: NodeJS.ProcessEnv): string[] {\n return Object.entries(env)\n .filter(([k, v]) => v !== undefined && v.length >= 8 && isSecretEnvName(k))\n .map(([, v]) => v!)\n .sort((a, b) => b.length - a.length)\n}\n\nconst MASK = '***'\nconst SECRET_NAME = '[A-Za-z0-9_.-]*(?:api[_-]?key|secret|token|passw(?:or)?d|credential|private[_-]?key)[A-Za-z0-9_.-]*'\nconst REDACTIONS: [RegExp, string][] = [\n // \u79C1\u94A5\u5757\n [/-----BEGIN[A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END[A-Z ]*PRIVATE KEY-----/g, `-----BEGIN PRIVATE KEY-----${MASK}-----END PRIVATE KEY-----`],\n // \u5E38\u89C1\u5382\u5546\u524D\u7F00\uFF1A\u4FDD\u7559\u524D\u7F00\u4FBF\u4E8E\u8FA8\u8BA4\n [/\\b((?:sk|pk|rk)-(?:[a-z]+-)?|gh[pousr]_|github_pat_|glpat-|xox[abprs]-|AKIA|ASIA|AIza|hf_|npm_)[A-Za-z0-9_\\-]{12,}/g, `$1${MASK}`],\n // Authorization / Proxy-Authorization \u5934\n [/((?:Proxy-)?Authorization\\s*[:=]\\s*)(?:(Bearer|Basic|Token)\\s+)?[^\\s\"',]+/gi, `$1$2 ${MASK}`],\n // JWT\n [/\\beyJ[A-Za-z0-9_-]{10,}\\.[A-Za-z0-9_-]{4,}(?:\\.[A-Za-z0-9_-]+)?/g, MASK],\n // URL \u91CC\u7684\u8D26\u53F7\u5BC6\u7801\n [/\\b([a-z][a-z0-9+.-]*:\\/\\/)[^\\s/:@]+:[^\\s/@]+@/gi, `$1${MASK}@`],\n // NAME=value / NAME: value / \"name\": \"value\"\uFF0Cname \u50CF\u5BC6\u94A5\n [new RegExp(`([\"']?${SECRET_NAME}[\"']?\\\\s*[:=]\\\\s*)([\"']?)[^\\\\s\"',]{4,}\\\\2`, 'gi'), `$1$2${MASK}$2`],\n]\n\n/** \u906E\u853D\u8F93\u51FA\u4E2D\u7684\u5BC6\u94A5\uFF1A\u5148\u6309\u5DF2\u77E5\u5BC6\u94A5\u503C\u7CBE\u786E\u66FF\u6362\uFF0C\u518D\u6309\u5E38\u89C1\u683C\u5F0F\u515C\u5E95\u3002 */\nexport function redactSecrets(text: string, known: readonly string[] = []): string {\n let out = text\n for (const v of known) out = out.split(v).join(MASK)\n for (const [re, to] of REDACTIONS) out = out.replace(re, to)\n return out\n}\n\n/** \u6309 UTF-8 \u5B57\u8282\u622A\u65AD\uFF08\u4E0D\u62C6\u5B57\u7B26\uFF09\u3002 */\nfunction clip(text: string, limit: number): string {\n const buf = Buffer.from(text, 'utf8')\n if (buf.length <= limit) return text\n return `${buf.subarray(0, limit).toString('utf8').replace(/\uFFFD$/, '')}\\n\u2026[truncated]`\n}\n\nexport interface ExecOutcome {\n exit: string\n stdout: string\n stderr: string\n}\n\n/**\n * exec \u7ED3\u679C\u76F4\u63A5\u56DE\u663E\u5728\u547D\u4EE4\u5361\u7247\u91CC\uFF08\u4E0D\u8FDB\u6A21\u578B\uFF09\uFF1Astdout + stderr \u5408\u8BA1\u622A\u65AD\u5230 16KB\uFF1B\n * \u975E\u96F6\u9000\u51FA/\u8D85\u65F6\u5728\u672B\u5C3E\u9644 `exit=N`\uFF0C\u65E0\u8F93\u51FA\u65F6\u53EA\u7ED9 exit\u3002\n */\nexport function formatExecResult(o: ExecOutcome): string {\n const output = clip(`${o.stdout}${o.stderr}`.replace(/\\s+$/, ''), OUTPUT_LIMIT)\n if (o.exit === '0') return output === '' ? 'exit=0' : output\n return output === '' ? `exit=${o.exit}` : `${output}\\nexit=${o.exit}`\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["name", "commands"]
|
|
7
7
|
}
|
|
@@ -25,6 +25,7 @@ export declare const zh: {
|
|
|
25
25
|
readonly 'field.target': "目标命令";
|
|
26
26
|
readonly 'field.cwd': "工作目录";
|
|
27
27
|
readonly 'field.timeoutMs': "超时(毫秒)";
|
|
28
|
+
readonly 'field.passEnv': "保留环境变量";
|
|
28
29
|
readonly 'type.prompt': "Prompt";
|
|
29
30
|
readonly 'type.exec': "Shell";
|
|
30
31
|
readonly 'type.alias': "转发";
|
|
@@ -32,10 +33,12 @@ export declare const zh: {
|
|
|
32
33
|
readonly 'tip.command': "$ARGUMENTS 为完整参数,$1 $2… 为按空格拆分的参数;参数只作变量传入,不会被当作命令执行。";
|
|
33
34
|
readonly 'tip.target': "转发到另一条斜杠命令,可带固定参数。$ARGUMENTS 替换为参数,未使用时参数接在末尾。";
|
|
34
35
|
readonly 'tip.cwd': "workspace 为当前会话目录,也可填绝对路径。";
|
|
36
|
+
readonly 'tip.passEnv': "名字含 KEY、TOKEN、SECRET、PASSWORD 等的环境变量默认不传给命令;这里列出的照常传入。输出里的密钥始终会被遮蔽。";
|
|
35
37
|
readonly execWarn: "以 dsh 进程权限直接运行,不经审批。";
|
|
36
38
|
readonly 'err.type': "类型不对";
|
|
37
39
|
readonly 'err.required': "必填";
|
|
38
40
|
readonly 'err.pattern': "小写字母开头,仅含 a-z 0-9 - _";
|
|
41
|
+
readonly 'err.envName': "变量名仅含字母、数字、_,逗号分隔";
|
|
39
42
|
readonly 'err.duplicate': "已被占用或指向自身";
|
|
40
43
|
readonly 'err.enum': "填 workspace 或绝对路径";
|
|
41
44
|
readonly 'err.range': "1–600000 之间的整数";
|
package/lib/types/core.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export interface SlashCommand {
|
|
|
14
14
|
target?: string;
|
|
15
15
|
cwd?: string;
|
|
16
16
|
timeoutMs?: number;
|
|
17
|
+
/** exec 型:照常传入子进程的密钥类环境变量(默认会被去掉)。 */
|
|
18
|
+
passEnv?: string[];
|
|
17
19
|
}
|
|
18
20
|
/** `index` 指向 commands 数组下标;缺省表示顶层错误。 */
|
|
19
21
|
export interface FieldError {
|
|
@@ -46,6 +48,13 @@ export declare function execArgs(rawInput: string): {
|
|
|
46
48
|
env: Record<string, string>;
|
|
47
49
|
argv: string[];
|
|
48
50
|
};
|
|
51
|
+
export declare const isSecretEnvName: (name: string) => boolean;
|
|
52
|
+
/** 去掉密钥类变量,`passEnv` 里列出的照常保留。 */
|
|
53
|
+
export declare function scrubEnv(env: NodeJS.ProcessEnv, passEnv?: readonly string[]): Record<string, string>;
|
|
54
|
+
/** 宿主环境里所有密钥类变量的值(≥ 8 字符),输出中原样出现即遮蔽。 */
|
|
55
|
+
export declare function secretValues(env: NodeJS.ProcessEnv): string[];
|
|
56
|
+
/** 遮蔽输出中的密钥:先按已知密钥值精确替换,再按常见格式兜底。 */
|
|
57
|
+
export declare function redactSecrets(text: string, known?: readonly string[]): string;
|
|
49
58
|
export interface ExecOutcome {
|
|
50
59
|
exit: string;
|
|
51
60
|
stdout: string;
|