@civilization/dsh-workspace-files 0.3.1 → 0.3.3

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/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { exec, spawn } from "node:child_process";
2
- import { isAbsolute, join, relative, resolve } from "node:path";
1
+ import { spawn } from "node:child_process";
2
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
3
3
  import Schema from "@deepseek-ai/schemastery";
4
4
  import { promises } from "node:fs";
5
5
  //#region src/files.ts
@@ -32,7 +32,21 @@ const DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set([
32
32
  "tmp",
33
33
  "temp",
34
34
  "logs",
35
- ".dsh"
35
+ ".dsh",
36
+ ".yarn",
37
+ ".vite",
38
+ ".parcel-cache",
39
+ ".rollup.cache",
40
+ ".tsdown",
41
+ ".svelte-kit",
42
+ ".astro",
43
+ ".mypy_cache",
44
+ ".pytest_cache",
45
+ ".ruff_cache",
46
+ "bin",
47
+ "obj",
48
+ ".dart_tool",
49
+ ".gemini"
36
50
  ]);
37
51
  function isWithin(parent, child) {
38
52
  const rel = relative(parent, child);
@@ -181,6 +195,30 @@ function writeJson(res, status, body) {
181
195
  });
182
196
  res.end(data);
183
197
  }
198
+ /**
199
+ * 拒绝工作区之外的目标:files.open 的 path 来自请求体,必须是工作区内的相对/绝对路径,
200
+ * 否则模型或页面脚本可以借宿主进程去打开任意本地路径。
201
+ */
202
+ function assertInsideWorkspace(cwd, fullPath) {
203
+ const root = resolve(cwd);
204
+ const target = resolve(fullPath);
205
+ const rel = relative(root, target);
206
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) throw new Error("Path must stay inside the workspace.");
207
+ }
208
+ /**
209
+ * 启动系统文件管理器/默认应用。spawn 在命令不存在时会 emit 'error',
210
+ * 没有监听器就会变成未捕获异常拖垮宿主进程,因此这里必须兜住。
211
+ */
212
+ function launch(command, args) {
213
+ const child = spawn(command, args, {
214
+ detached: true,
215
+ stdio: "ignore"
216
+ });
217
+ child.on("error", (err) => {
218
+ console.warn(`[dsh-workspace-files] failed to launch ${command}:`, err?.message || String(err));
219
+ });
220
+ child.unref();
221
+ }
184
222
  function resolveCwd(ctx, body) {
185
223
  if (body.cwd?.trim()) return body.cwd.trim();
186
224
  if (body.root?.trim()) return body.root.trim();
@@ -198,20 +236,6 @@ function apply(ctx, config = {}) {
198
236
  maxDepth: typeof config?.maxDepth === "number" && config.maxDepth > 0 ? config.maxDepth : 16,
199
237
  showHidden: Boolean(config?.showHidden)
200
238
  };
201
- if (ctx.inject) ctx.inject(["settings"], (scopedCtx) => {
202
- try {
203
- const scope = scopedCtx.settings?.register(SETTINGS_NS, Config, { base: liveConfig });
204
- const applyChange = () => {
205
- const current = scope?.get?.();
206
- if (typeof current?.maxDepth === "number") liveConfig.maxDepth = current.maxDepth;
207
- if (typeof current?.showHidden === "boolean") liveConfig.showHidden = current.showHidden;
208
- };
209
- applyChange();
210
- scope?.watch?.(applyChange);
211
- } catch (err) {
212
- console.warn("[dsh-workspace-files] failed to register settings scope:", err);
213
- }
214
- });
215
239
  const webServer = ctx.webServer;
216
240
  if (!webServer) return;
217
241
  const handleRpc = async (req, res, prefix) => {
@@ -261,37 +285,17 @@ function apply(ctx, config = {}) {
261
285
  return;
262
286
  }
263
287
  const fullPath = isAbsolute(rawPath) ? rawPath : resolve(cwd, rawPath);
288
+ assertInsideWorkspace(cwd, fullPath);
264
289
  const action = body.action || "reveal";
265
290
  const isDirectory = Boolean(body.isDirectory);
266
291
  if (process.platform === "win32") {
267
292
  const winPath = fullPath.replace(/\//g, "\\");
268
- if (action === "reveal") {
269
- if (isDirectory) spawn("explorer.exe", [winPath], {
270
- detached: true,
271
- stdio: "ignore"
272
- }).unref();
273
- else spawn("explorer.exe", [`/select,${winPath}`], {
274
- detached: true,
275
- stdio: "ignore"
276
- }).unref();
277
- } else exec(`start "" "${winPath.replace(/"/g, "\\\"")}"`, { windowsHide: true });
293
+ launch("explorer.exe", action === "reveal" && !isDirectory ? [`/select,${winPath}`] : [winPath]);
278
294
  } else if (process.platform === "darwin") {
279
- if (action === "reveal") spawn("open", [isDirectory ? fullPath : "-R", fullPath], {
280
- detached: true,
281
- stdio: "ignore"
282
- }).unref();
283
- else spawn("open", [fullPath], {
284
- detached: true,
285
- stdio: "ignore"
286
- }).unref();
287
- } else if (action === "reveal") spawn("xdg-open", [isDirectory ? fullPath : resolve(fullPath, "..")], {
288
- detached: true,
289
- stdio: "ignore"
290
- }).unref();
291
- else spawn("xdg-open", [fullPath], {
292
- detached: true,
293
- stdio: "ignore"
294
- }).unref();
295
+ if (action === "reveal") launch("open", [isDirectory ? fullPath : "-R", fullPath]);
296
+ else launch("open", [fullPath]);
297
+ } else if (action === "reveal") launch("xdg-open", [isDirectory ? fullPath : resolve(fullPath, "..")]);
298
+ else launch("xdg-open", [fullPath]);
295
299
  writeJson(res, 200, { ok: true });
296
300
  } catch (err) {
297
301
  writeJson(res, 500, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@civilization/dsh-workspace-files",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Enhanced workspace file tree explorer with fuzzy search and @-mention for DeepSeek Harness.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -120,6 +120,30 @@ export function FileGlyphIcon({ filename, size = 14 }: { filename: string; size?
120
120
  } else if (ext === 'yml' || ext === 'yaml') {
121
121
  color = '#cb171e'
122
122
  badge = 'Y'
123
+ } else if (ext === 'rs') {
124
+ color = '#dea584'
125
+ badge = 'RS'
126
+ } else if (ext === 'go') {
127
+ color = '#00add8'
128
+ badge = 'GO'
129
+ } else if (ext === 'vue') {
130
+ color = '#41b883'
131
+ badge = 'V'
132
+ } else if (ext === 'svelte') {
133
+ color = '#ff3e00'
134
+ badge = 'S'
135
+ } else if (ext === 'toml') {
136
+ color = '#9c4121'
137
+ badge = 'T'
138
+ } else if (ext === 'sql') {
139
+ color = '#e38c00'
140
+ badge = 'SQL'
141
+ } else if (ext === 'sh' || ext === 'bash' || ext === 'zsh' || ext === 'ps1' || ext === 'bat' || ext === 'cmd') {
142
+ color = '#4eaa25'
143
+ badge = '>_'
144
+ } else if (ext === 'lock') {
145
+ color = 'var(--dsw-alias-label-tertiary, #8c9ba5)'
146
+ badge = '🔒'
123
147
  }
124
148
 
125
149
  return (
@@ -159,6 +183,21 @@ export function IconTree({ size = 13 }: { size?: number }): JSX.Element {
159
183
  )
160
184
  }
161
185
 
186
+ export function IconChevronRight({ size = 11, className, style }: { size?: number; className?: string; style?: React.CSSProperties }): JSX.Element {
187
+ return (
188
+ <svg
189
+ width={size}
190
+ height={size}
191
+ viewBox="0 0 16 16"
192
+ fill="currentColor"
193
+ className={className}
194
+ style={{ display: 'inline-block', verticalAlign: 'middle', flexShrink: 0, ...style }}
195
+ >
196
+ <path fillRule="evenodd" d="M6.22 3.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L9.94 8 6.22 4.28a.75.75 0 0 1 0-1.06z" />
197
+ </svg>
198
+ )
199
+ }
200
+
162
201
  export function IconChevronDown({ size = 14 }: { size?: number }): JSX.Element {
163
202
  return (
164
203
  <svg width={size} height={size} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ display: 'inline-block', verticalAlign: 'middle', flexShrink: 0 }}>