dsh-winrm 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -113,6 +113,28 @@ export declare const zh: {
113
113
  readonly 'transfer.mib': "MB";
114
114
  readonly 'transfer.dir': "目录";
115
115
  readonly 'transfer.file': "文件";
116
+ readonly 'tool.read': "读取远程文件";
117
+ readonly 'tool.write': "写入远程文件";
118
+ readonly 'tool.edit': "编辑远程文件";
119
+ readonly 'tool.running': "执行中…";
120
+ readonly 'tool.noDetail': "无卡片数据";
121
+ readonly 'tool.created': "新建 {bytes} 字节";
122
+ readonly 'tool.updated': "覆盖 {bytes} 字节";
123
+ readonly 'tool.replacements': "替换 {count} 处";
124
+ readonly 'tool.beyondBasis': "文件超过 diff 体积上限,仅显示路径";
125
+ readonly 'tool.truncatedByBytes': "已按字节上限截断";
126
+ readonly 'tool.truncatedLines': "{count} 行过长已截断";
127
+ readonly 'tool.codeLabel': "代码";
128
+ readonly 'tool.wrapLabel': "自动换行";
129
+ readonly 'tool.unwrapLabel': "取消自动换行";
130
+ readonly 'tool.window': "第 {shown} / {total} 行";
131
+ readonly 'tool.lines': "共 {count} 行";
132
+ readonly 'tool.copy': "复制";
133
+ readonly 'tool.copied': "已复制";
134
+ readonly 'tool.collapseAria': "收起";
135
+ readonly 'tool.expandAria': "展开剩余 {hidden} 行";
136
+ readonly 'tool.collapse': "收起";
137
+ readonly 'tool.expand': "展开其余 {hidden} 行";
116
138
  };
117
139
  export declare const en: {
118
140
  readonly 'entry.label': "Windows";
@@ -225,6 +247,28 @@ export declare const en: {
225
247
  readonly 'transfer.mib': "MB";
226
248
  readonly 'transfer.dir': "dir";
227
249
  readonly 'transfer.file': "file";
250
+ readonly 'tool.read': "Read remote file";
251
+ readonly 'tool.write': "Write remote file";
252
+ readonly 'tool.edit': "Edit remote file";
253
+ readonly 'tool.running': "Working…";
254
+ readonly 'tool.noDetail': "No card data";
255
+ readonly 'tool.created': "Created {bytes} bytes";
256
+ readonly 'tool.updated': "Overwrote {bytes} bytes";
257
+ readonly 'tool.replacements': "{count} replacements";
258
+ readonly 'tool.beyondBasis': "File is past the diff size cap; showing the path only";
259
+ readonly 'tool.truncatedByBytes': "Cut at the byte budget";
260
+ readonly 'tool.truncatedLines': "{count} long lines truncated";
261
+ readonly 'tool.codeLabel': "Code";
262
+ readonly 'tool.wrapLabel': "Wrap lines";
263
+ readonly 'tool.unwrapLabel': "Do not wrap";
264
+ readonly 'tool.window': "{shown} of {total} lines";
265
+ readonly 'tool.lines': "{count} lines";
266
+ readonly 'tool.copy': "Copy";
267
+ readonly 'tool.copied': "Copied";
268
+ readonly 'tool.collapseAria': "Collapse";
269
+ readonly 'tool.expandAria': "Expand the remaining {hidden} lines";
270
+ readonly 'tool.collapse': "Collapse";
271
+ readonly 'tool.expand': "Show the remaining {hidden} lines";
228
272
  };
229
273
  export type WinKey = keyof typeof zh;
230
274
  /** Translate a key in one dictionary with optional {name} params. */
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Card models for the three remote file tools.
3
+ *
4
+ * The host projects a small JSON payload through `output.presentationMeta` and
5
+ * the session log persists it; these functions narrow that unknown payload into
6
+ * something the card components can hand to the harness's `ReadBlock` and
7
+ * `DiffBlock` primitives. Kept apart from the components so the narrowing can be
8
+ * unit tested without a DOM.
9
+ * @module dsh-winrm/src/client/tool/card-model
10
+ */
11
+ /** One line of a read window. */
12
+ export interface ReadCardLine {
13
+ number: number;
14
+ text: string;
15
+ }
16
+ /** A completed `winrm_read`: a line-numbered window of a remote file. */
17
+ export interface ReadCard {
18
+ kind: 'read';
19
+ path: string;
20
+ offset: number;
21
+ lines: ReadCardLine[];
22
+ totalLines: number;
23
+ encoding: string;
24
+ truncatedByBytes: boolean;
25
+ truncatedLines: number;
26
+ }
27
+ /** One hunk of a remote write or edit, in the primitive's own shape. */
28
+ export interface DiffHunk {
29
+ /** Prior fragment including shared context; `''` when the hunk had no prior content. */
30
+ oldText: string;
31
+ /** Resulting fragment including the same shared context. */
32
+ newText: string;
33
+ }
34
+ /** A completed `winrm_write` or `winrm_edit`: the changes it applied. */
35
+ export interface DiffCard {
36
+ kind: 'diff';
37
+ /** Which tool produced it, so the header can name the operation. */
38
+ tool: 'write' | 'edit';
39
+ path: string;
40
+ operation: 'create' | 'update';
41
+ bytes: number;
42
+ size: number;
43
+ replacements: number;
44
+ encoding: string;
45
+ hunks: DiffHunk[];
46
+ /** True when the previous text was past the basis cap, so no hunks exist. */
47
+ beyondBasis: boolean;
48
+ }
49
+ /** A failed call: keeps the path and the message, drops card-specific detail. */
50
+ export interface ErrorCard {
51
+ kind: 'error';
52
+ path: string;
53
+ message: string;
54
+ }
55
+ /** Any card these tools render. */
56
+ export type FileCard = ReadCard | DiffCard | ErrorCard;
57
+ /**
58
+ * Narrow a `winrm_read` metadata payload into a card.
59
+ * @param meta - the persisted payload, of unknown shape.
60
+ * @param fallbackPath - the authored path, used when the payload omits one.
61
+ * @returns the card, or `undefined` when the payload is not a read card.
62
+ */
63
+ export declare function readCard(meta: unknown, fallbackPath: string): FileCard | undefined;
64
+ /**
65
+ * Narrow a `winrm_write` / `winrm_edit` metadata payload into a card.
66
+ * @param meta - the persisted payload, of unknown shape.
67
+ * @param fallbackPath - the authored path, used when the payload omits one.
68
+ * @param tool - which tool produced the payload.
69
+ * @returns the card, or `undefined` when the payload belongs to another card.
70
+ */
71
+ export declare function diffCard(meta: unknown, fallbackPath: string, tool: 'write' | 'edit'): FileCard | undefined;
@@ -0,0 +1,9 @@
1
+ import type { ToolCallViewProps } from '@deepseek-ai/dsh-client-ui-tool/client';
2
+ /** The Tool view props these cards read. */
3
+ export type WinrmFileCardProps = ToolCallViewProps;
4
+ /** Card for `winrm_read`. */
5
+ export declare function WinrmReadCard(props: WinrmFileCardProps): import("react").JSX.Element;
6
+ /** Card for `winrm_write`. */
7
+ export declare function WinrmWriteCard(props: WinrmFileCardProps): import("react").JSX.Element;
8
+ /** Card for `winrm_edit`. */
9
+ export declare function WinrmEditCard(props: WinrmFileCardProps): import("react").JSX.Element;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Contextual diff derivation for the write/edit presentation metadata.
3
+ *
4
+ * The GUI renders a diff card from `tool/result` metadata rather than from the
5
+ * model-facing text, so the hunks are projected here, at result time, and
6
+ * persisted with the session log. This mirrors the harness's own file tools:
7
+ * one entry per applied change with a few surrounding context lines.
8
+ *
9
+ * The alignment is deliberately cheap rather than a full LCS: common leading
10
+ * and trailing lines are trimmed, and when the remaining halves have the same
11
+ * length each differing run becomes its own hunk. That covers the cases these
12
+ * two tools actually produce — an exact-string edit, and a rewrite that keeps
13
+ * most lines — without a diff dependency in a plugin whose point is a small
14
+ * footprint. A rewrite that changes the line count collapses to one hunk over
15
+ * the changed span, which is still a truthful summary.
16
+ * @module dsh-winrm/src/diff
17
+ */
18
+ /** Context lines shown on each side of an applied hunk. */
19
+ export declare const DIFF_CONTEXT = 3;
20
+ /**
21
+ * Largest file whose text may serve as a diff basis. Above this the card shows
22
+ * the path and operation without hunks: fetching and projecting a bigger basis
23
+ * would cost another transfer and a large metadata payload for a summary the
24
+ * user reads at a glance.
25
+ */
26
+ export declare const DIFF_BASIS_MAX_BYTES: number;
27
+ /**
28
+ * Largest serialized hunk list kept on one result. A pathological edit (a
29
+ * whole-file rewrite of a large file) can produce thousands of hunks; past
30
+ * this bound the card degrades to path-only instead of bloating the log.
31
+ */
32
+ export declare const DIFF_META_MAX_BYTES: number;
33
+ /**
34
+ * One applied hunk: the fragment before the change and after it.
35
+ *
36
+ * Declared as a type alias rather than an interface so it satisfies the
37
+ * `JsonValue` index signature the tool-output metadata is typed against.
38
+ */
39
+ export type RemoteFileDiff = {
40
+ /** Remote path the hunk belongs to (the authored argument). */
41
+ path: string;
42
+ /** Prior content including context lines; empty for a pure insertion. */
43
+ oldText: string;
44
+ /** Content after the change, including the shared context lines. */
45
+ newText: string;
46
+ };
47
+ /**
48
+ * Compute the hunks between two texts.
49
+ * @param path - remote path stamped on every hunk.
50
+ * @param before - file text before the change.
51
+ * @param after - file text after the change.
52
+ * @returns one hunk per contiguous change in file order; empty when equal.
53
+ */
54
+ export declare function computeHunkDiffs(path: string, before: string, after: string): RemoteFileDiff[];
55
+ /**
56
+ * Compute hunks and drop them when the serialized payload would exceed the cap.
57
+ * @param path - remote path stamped on every hunk.
58
+ * @param before - prior text.
59
+ * @param after - text after the change.
60
+ * @returns the kept hunks; empty when nothing changed or the payload is too big.
61
+ */
62
+ export declare function boundedHunkDiffs(path: string, before: string, after: string): RemoteFileDiff[];
@@ -53,6 +53,32 @@ export declare const TRANSFER_CHUNK: number;
53
53
  export declare function remoteFileSize(conn: WinRMParams, remotePath: string, timeoutMs?: number): Promise<number | null>;
54
54
  export declare function downloadChunks(conn: WinRMParams, remotePath: string, onChunk: (b64: string, bytes: number) => void, onProgress?: (transferred: number, total: number) => void, timeoutMs?: number): Promise<number>;
55
55
  export declare function uploadBuffer(conn: WinRMParams, remotePath: string, data: Buffer, onProgress?: (transferred: number, total: number) => void, timeoutMs?: number): Promise<number>;
56
+ /**
57
+ * How long a streaming transfer may deliver nothing before it is treated as
58
+ * stalled.
59
+ *
60
+ * A wall-clock cap on the whole transfer cannot work here: this transport is
61
+ * latency-bound and measured at ~0.73 MiB/s against a real target, so a 700 MiB
62
+ * file legitimately needs about sixteen minutes and any smaller fixed cap fails
63
+ * a transfer that is making steady progress. What a transfer actually needs
64
+ * bounding against is a *stall* — a range that has stopped producing bytes —
65
+ * which is what this idle window measures.
66
+ */
67
+ export declare const STREAM_IDLE_TIMEOUT_MS = 180000;
68
+ /**
69
+ * Budget for one streaming transfer of `bytes` bytes.
70
+ *
71
+ * The idle window is the effective limit; the hard window exists so a wedged
72
+ * transfer cannot stay alive forever, and it scales with the payload because
73
+ * the payload is what sets the legitimate duration.
74
+ * @param bytes - total bytes the transfer must move.
75
+ * @param idleMs - how long the transfer may go without delivering anything.
76
+ * @returns the idle window and the absolute backstop, both in milliseconds.
77
+ */
78
+ export declare function transferBudget(bytes: number, idleMs?: number): {
79
+ idleMs: number;
80
+ hardMs: number;
81
+ };
56
82
  /**
57
83
  * Upload a buffer over a single WinRS stdin stream.
58
84
  *
@@ -68,10 +94,10 @@ export declare function uploadBuffer(conn: WinRMParams, remotePath: string, data
68
94
  * @param remotePath - destination path on the target.
69
95
  * @param data - the file contents to send.
70
96
  * @param onProgress - called with cumulative bytes after each frame.
71
- * @param timeoutMs - whole-transfer budget.
97
+ * @param idleMs - how long the transfer may go without moving a slice.
72
98
  * @returns bytes written, or null when the streaming path is unavailable.
73
99
  */
74
- export declare function uploadStream(conn: WinRMParams, remotePath: string, data: Buffer, onProgress?: (transferred: number, total: number) => void, timeoutMs?: number): Promise<number | null>;
100
+ export declare function uploadStream(conn: WinRMParams, remotePath: string, data: Buffer, onProgress?: (transferred: number, total: number) => void, idleMs?: number): Promise<number | null>;
75
101
  /**
76
102
  * Stream a remote file to a consumer, in parallel byte ranges.
77
103
  *
@@ -90,7 +116,7 @@ export declare function uploadStream(conn: WinRMParams, remotePath: string, data
90
116
  * @param remotePath - file to read.
91
117
  * @param onData - receives each decompressed piece with its absolute file offset.
92
118
  * @param onProgress - receives cumulative bytes delivered and the total size.
93
- * @param timeoutMs - budget for the whole download.
119
+ * @param idleMs - how long a worker may go without delivering bytes.
94
120
  * @returns the number of bytes delivered, or null when streaming is unavailable.
95
121
  */
96
- export declare function downloadStream(conn: WinRMParams, remotePath: string, onData: (offset: number, data: Buffer) => void, onProgress?: (transferred: number, total: number) => void, timeoutMs?: number): Promise<number | null>;
122
+ export declare function downloadStream(conn: WinRMParams, remotePath: string, onData: (offset: number, data: Buffer) => void, onProgress?: (transferred: number, total: number) => void, idleMs?: number): Promise<number | null>;
@@ -5,10 +5,18 @@
5
5
  * (client, console); this class composes them behind one WinRmEngine instance
6
6
  * per plugin apply.
7
7
  */
8
- import type { ClusterResult, ExecResult, ProcessInfo, RemoteDirEntry, ServiceInfo, TestResult, TransferProgress, WinHostSummary } from './protocol.ts';
8
+ import type { ClusterResult, ExecResult, ProcessInfo, RemoteDirEntry, RemoteEditOutcome, RemoteTextEncoding, RemoteTextWindow, RemoteWriteOutcome, ServiceInfo, TestResult, TransferProgress, WinHostSummary } from './protocol.ts';
9
9
  import type { HostStore } from './store.ts';
10
10
  import { type TransferChannel } from './smb.ts';
11
11
  import { ConsoleSession } from './engine/console.ts';
12
+ /** Longest single line the reader returns before cutting it (characters). */
13
+ export declare const READ_MAX_LINE_LENGTH = 2000;
14
+ /** Lines the reader returns when the caller does not choose a page size. */
15
+ export declare const READ_DEFAULT_LIMIT = 2000;
16
+ /** Most lines the reader will return in one call. */
17
+ export declare const READ_MAX_LIMIT = 5000;
18
+ /** Byte budget for one read window; keeps the envelope well under the transport limit. */
19
+ export declare const READ_MAX_BYTES: number;
12
20
  /** The engine. One instance per plugin apply; dispose() clears nothing pooled (WinRM is per-call HTTP). */
13
21
  export declare class WinRmEngine {
14
22
  readonly store: HostStore;
@@ -39,6 +47,48 @@ export declare class WinRmEngine {
39
47
  killProcess(alias: string, id: number): Promise<void>;
40
48
  /** List a remote directory. */
41
49
  ls(alias: string, dir: string): Promise<RemoteDirEntry[]>;
50
+ /**
51
+ * Read a bounded, line-numbered window of a remote text file.
52
+ *
53
+ * One PowerShell round trip: the remote side decodes the file, counts every
54
+ * line, and returns only the requested window, so a large log costs one
55
+ * transfer of the window rather than of the file.
56
+ * @param alias - configured host.
57
+ * @param remotePath - path on the target.
58
+ * @param offset - 1-based first line to return.
59
+ * @param limit - maximum number of lines to return.
60
+ * @returns the window plus the file's total line count and encoding.
61
+ */
62
+ readFile(alias: string, remotePath: string, offset: number, limit: number): Promise<RemoteTextWindow>;
63
+ /**
64
+ * Write a whole remote text file, creating parent directories as needed.
65
+ *
66
+ * Reuses the transfer transport (single streamed stdin when available, the
67
+ * 48 KiB chunk path otherwise) so a large write does not pay one shell cycle
68
+ * per chunk.
69
+ * @param alias - configured host.
70
+ * @param remotePath - path on the target; overwritten when it exists.
71
+ * @param content - UTF-8 text to write.
72
+ * @param encoding - byte-order mark to emit (`utf8`, `utf8bom`, `utf16le`).
73
+ * @returns the operation kind, byte count, encoding written, and both texts
74
+ * the diff card compares.
75
+ */
76
+ writeFile(alias: string, remotePath: string, content: string, encoding: RemoteTextEncoding): Promise<RemoteWriteOutcome>;
77
+ /**
78
+ * Replace `oldString` with `newString` inside a remote text file.
79
+ *
80
+ * The replacement is literal, never a pattern, and the file's existing
81
+ * encoding is preserved. Without `replaceAll` the edit is refused unless the
82
+ * needle is unique, so a stale needle cannot rewrite the wrong region.
83
+ * @param alias - configured host.
84
+ * @param remotePath - path on the target.
85
+ * @param oldString - exact text to find.
86
+ * @param newString - text to substitute.
87
+ * @param replaceAll - replace every occurrence instead of requiring one.
88
+ * @returns the replacement count, the file's new size, and both texts the
89
+ * diff card compares.
90
+ */
91
+ editFile(alias: string, remotePath: string, oldString: string, newString: string, replaceAll: boolean): Promise<RemoteEditOutcome>;
42
92
  /** Upload one local file to a remote path. Defaults to SMB with WinRM fallback. */
43
93
  upload(alias: string, localPath: string, remotePath: string, onProgress?: (progress: TransferProgress) => void, channel?: TransferChannel): Promise<{
44
94
  bytes: number;
@@ -3,8 +3,9 @@
3
3
  * execution with a UTF-8 envelope, streaming console, service/process
4
4
  * management, streamed file transfer, cluster), the /api/dsh-winrm
5
5
  * route family plus the console WebSocket upgrade, the agent tools
6
- * (winrm_list, winrm_exec, winrm_service, winrm_process, winrm_upload,
7
- * winrm_download, winrm_cluster), and a system-prompt announcement. The
6
+ * (winrm_list, winrm_exec, winrm_service, winrm_process, winrm_read,
7
+ * winrm_write, winrm_edit, winrm_upload, winrm_download, winrm_cluster), and
8
+ * a system-prompt announcement. The
8
9
  * browser half (./client) renders the host manager and operations panel.
9
10
  * Everything rides official NPM SDK packages — no dsh source changes.
10
11
  *
@@ -34,7 +35,7 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
34
35
  enabled: z<boolean, boolean, "volatile-defined">;
35
36
  }>>, "plain">;
36
37
  /** Model-facing announcement: plugin presence, capabilities, and limits. */
37
- export declare const WINRM_GUIDANCE = "\u672C\u673A\u5DF2\u5B89\u88C5 dsh-winrm \u63D2\u4EF6\uFF08DSH \u8FDC\u7A0B Windows \u8FD0\u7EF4\uFF0CWinRM/PowerShell Remoting\uFF09\uFF1A\u4FA7\u8FB9\u680F\u300CWindows\u300D\u5165\u53E3\uFF1B\u4EFF\u7167 dsh-ssh \u63D2\u4EF6\u5F00\u53D1\u3002\u80FD\u529B\uFF1A\u4E3B\u673A\u914D\u7F6E\u5B58 ~/.dsh/dsh-winrm.json\uFF08GUI \u914D\u7F6E\u540E agent \u65B9\u53EF\u4F7F\u7528\uFF09\uFF1Bwinrm_list \u5217\u51FA\u4E3B\u673A\u3001winrm_exec \u6267\u884C PowerShell \u547D\u4EE4\uFF08UTF-8 \u4FE1\u5C01\uFF0C\u4E2D\u6587\u4E0D\u4E71\u7801\uFF09\u3001winrm_service \u670D\u52A1\u7BA1\u7406\uFF08start/stop/restart/\u542F\u52A8\u7C7B\u578B\uFF09\u3001winrm_process \u8FDB\u7A0B\u7BA1\u7406\uFF08list/kill\uFF09\u3001winrm_upload/winrm_download \u6587\u4EF6\u4F20\u8F93\uFF08SMB \u7BA1\u7406\u5171\u4EAB\u53EF\u8FBE\u65F6\u4F18\u5148\uFF0C\u5426\u5219\u8D70 WinRM \u6D41\u5F0F\u4F20\u8F93\uFF0C\u65E0 SMB \u4F9D\u8D56\uFF0C\u5927\u6587\u4EF6\u7ECF SHA256 \u6821\u9A8C\uFF09\u3001winrm_cluster \u96C6\u7FA4\u5E76\u53D1\u6267\u884C\uFF1BWeb \u63A7\u5236\u53F0\u8D70 WebSocket\u3002\u8BA4\u8BC1\uFF1A\u901A\u8FC7\u539F\u751F Node.js WinRM \u5BA2\u6237\u7AEF\uFF08winrm-client\uFF09\u7684 NTLM/Negotiate \u4E0E Basic \u5B9E\u73B0\uFF0C\u517C\u5BB9\u672C\u5730\u8D26\u6237\u4E0E\u57DF\u8D26\u6237\uFF1B\u4F20\u8F93\u652F\u6301 http(5985)/https(5986)\u3002\u524D\u63D0\uFF1A\u76EE\u6807\u673A\u9700\u542F\u7528 WinRM\uFF08scripts/enable-winrm.ps1\uFF09\uFF0C\u672C\u673A\u65E0\u9700 Python \u2014\u2014 WinRM \u8D70\u7EAF Node \u4F9D\u8D56\u3002HTTP \u4EC5\u5EFA\u8BAE\u53D7\u4FE1\u5185\u7F51\u4F7F\u7528\uFF1B\u5BC6\u7801\u4EE5\u660E\u6587\u5B58\u5728\u7528\u6237\u4E3B\u76EE\u5F55\u79C1\u6709\u6587\u4EF6\uFF08\u6743\u9650 0600\uFF09\uFF1B\u547D\u4EE4\u8F93\u51FA\u53EF\u80FD\u542B\u654F\u611F\u4FE1\u606F\uFF1B\u4F20\u8F93/\u6267\u884C\u6D88\u8017\u771F\u5B9E\u8FDC\u7A0B\u8D44\u6E90\uFF0C\u5148\u786E\u8BA4\u518D\u64CD\u4F5C\u3002\u7528\u6237\u63D0\u5230\u300CWindows \u670D\u52A1\u5668 / WinRM / PowerShell \u8FDC\u7A0B / \u670D\u52A1\u7BA1\u7406 / \u8FDB\u7A0B\u7BA1\u7406\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\uFF0C\u8BF7\u636E\u6B64\u534F\u4F5C\u3002";
38
+ export declare const WINRM_GUIDANCE = "\u672C\u673A\u5DF2\u5B89\u88C5 dsh-winrm \u63D2\u4EF6\uFF08DSH \u8FDC\u7A0B Windows \u8FD0\u7EF4\uFF0CWinRM/PowerShell Remoting\uFF09\uFF1A\u4FA7\u8FB9\u680F\u300CWindows\u300D\u5165\u53E3\uFF1B\u4EFF\u7167 dsh-ssh \u63D2\u4EF6\u5F00\u53D1\u3002\u80FD\u529B\uFF1A\u4E3B\u673A\u914D\u7F6E\u5B58 ~/.dsh/dsh-winrm.json\uFF08GUI \u914D\u7F6E\u540E agent \u65B9\u53EF\u4F7F\u7528\uFF09\uFF1Bwinrm_list \u5217\u51FA\u4E3B\u673A\u3001winrm_exec \u6267\u884C PowerShell \u547D\u4EE4\uFF08UTF-8 \u4FE1\u5C01\uFF0C\u4E2D\u6587\u4E0D\u4E71\u7801\uFF09\u3001winrm_service \u670D\u52A1\u7BA1\u7406\uFF08start/stop/restart/\u542F\u52A8\u7C7B\u578B\uFF09\u3001winrm_process \u8FDB\u7A0B\u7BA1\u7406\uFF08list/kill\uFF09\u3001winrm_read/winrm_write/winrm_edit \u8FDC\u7A0B\u6587\u672C\u6587\u4EF6\u8BFB\u5199\uFF08\u6309\u884C\u5206\u9875\u3001\u7F16\u7801\u611F\u77E5\u3001\u7CBE\u786E\u4E32\u66FF\u6362\uFF0C\u4F5C\u7528\u4E8E\u8FDC\u7AEF\u4E3B\u673A\u800C\u975E\u672C\u673A\uFF0C\u672C\u673A\u6587\u4EF6\u4ECD\u7528 read/write/edit\uFF09\u3001winrm_upload/winrm_download \u6587\u4EF6\u4F20\u8F93\uFF08channel=auto \u65F6\u82E5\u76EE\u6807 445 \u53EF\u8FBE\u8D70 SMB \u7BA1\u7406\u5171\u4EAB\uFF0C\u5426\u5219\u8D70 WinRM \u6D41\u5F0F\u4F20\u8F93\uFF1Bchannel=smb \u5728 445 \u5173\u95ED\u65F6\u76F4\u63A5\u62A5\u9519\u3001\u9700\u6539\u7528 winrm\uFF1B\u5927\u6587\u4EF6\u53EF\u4E0B\u8F7D\uFF0C\u6D41\u5F0F\u901A\u9053\u6309\u5B57\u8282\u6301\u7EED\u63A8\u8FDB\u3001\u4EC5\u5728\u5B57\u8282\u65AD\u6D41\u65F6\u624D\u5224\u8D85\u65F6\u3001\u5B8C\u6210\u540E\u7ECF SHA256 \u6821\u9A8C\uFF09\u3001winrm_cluster \u96C6\u7FA4\u5E76\u53D1\u6267\u884C\uFF1BWeb \u63A7\u5236\u53F0\u8D70 WebSocket\u3002\u8BA4\u8BC1\uFF1A\u901A\u8FC7\u539F\u751F Node.js WinRM \u5BA2\u6237\u7AEF\uFF08winrm-client\uFF09\u7684 NTLM/Negotiate \u4E0E Basic \u5B9E\u73B0\uFF0C\u517C\u5BB9\u672C\u5730\u8D26\u6237\u4E0E\u57DF\u8D26\u6237\uFF1B\u4F20\u8F93\u652F\u6301 http(5985)/https(5986)\u3002\u524D\u63D0\uFF1A\u76EE\u6807\u673A\u9700\u542F\u7528 WinRM\uFF08scripts/enable-winrm.ps1\uFF09\uFF0C\u672C\u673A\u65E0\u9700 Python \u2014\u2014 WinRM \u8D70\u7EAF Node \u4F9D\u8D56\u3002HTTP \u4EC5\u5EFA\u8BAE\u53D7\u4FE1\u5185\u7F51\u4F7F\u7528\uFF1B\u5BC6\u7801\u4EE5\u660E\u6587\u5B58\u5728\u7528\u6237\u4E3B\u76EE\u5F55\u79C1\u6709\u6587\u4EF6\uFF08\u6743\u9650 0600\uFF09\uFF1B\u547D\u4EE4\u8F93\u51FA\u53EF\u80FD\u542B\u654F\u611F\u4FE1\u606F\uFF1B\u4F20\u8F93/\u6267\u884C\u6D88\u8017\u771F\u5B9E\u8FDC\u7A0B\u8D44\u6E90\uFF0C\u5148\u786E\u8BA4\u518D\u64CD\u4F5C\u3002\u7528\u6237\u63D0\u5230\u300CWindows \u670D\u52A1\u5668 / WinRM / PowerShell \u8FDC\u7A0B / \u670D\u52A1\u7BA1\u7406 / \u8FDB\u7A0B\u7BA1\u7406\u300D\u65F6\u5373\u6307\u672C\u63D2\u4EF6\uFF0C\u8BF7\u636E\u6B64\u534F\u4F5C\u3002";
38
39
  /**
39
40
  * Mount the WinRM engine, routes, tools, and announcement.
40
41
  * @param ctx - host plugin context carrying webServer/tools/systemPrompt.
@@ -108,6 +108,70 @@ export declare function psReadChunk(p: string, offset: number, count: number): s
108
108
  * prints the number of bytes written.
109
109
  */
110
110
  export declare function psWriteChunk(p: string, b64: string, append: boolean): string;
111
+ /**
112
+ * JSON: one bounded, line-numbered window of a remote TEXT file.
113
+ *
114
+ * Decodes UTF-8 (with or without BOM), UTF-16LE and UTF-16BE by BOM, and
115
+ * rejects invalid UTF-8 instead of returning mojibake — a binary file fails
116
+ * loudly rather than as unreadable text. Lines are numbered from 1 over the
117
+ * whole file, `offset` selects the first returned line, and the window stops
118
+ * early once the joined text would exceed `maxBytes` (reported as
119
+ * `truncated`, so the caller knows more lines remain). A line longer than
120
+ * `maxLineLength` is cut to `maxLineLength + 1` code units here; the Node half
121
+ * appends the truncation note.
122
+ *
123
+ * A missing path or a directory emits `{"exists":false}`; anything the reader
124
+ * rejects emits `{"error":…}`. Both keep the exit code at 0 so the caller
125
+ * always receives parseable JSON rather than a PowerShell error record.
126
+ * @param p - remote path.
127
+ * @param offset - 1-based first line to return.
128
+ * @param limit - maximum number of lines to return.
129
+ * @param maxLineLength - maximum characters kept per line.
130
+ * @param maxBytes - maximum UTF-8 bytes of joined line text.
131
+ * @returns the PowerShell fragment.
132
+ */
133
+ export declare function psReadTextWindow(p: string, offset: number, limit: number, maxLineLength: number, maxBytes: number): string;
134
+ /**
135
+ * JSON: replace `oldB64` with `newB64` (both UTF-8, base64-carried) in a remote
136
+ * text file, preserving its encoding and writing through the same file.
137
+ *
138
+ * The search is ordinal and never pattern-based, so a replacement cannot be
139
+ * reinterpreted as a regular expression. Without `replaceAll` the edit is
140
+ * refused unless the needle appears exactly once, which is what keeps a stale
141
+ * or too-short needle from rewriting the wrong region. The file is read whole,
142
+ * so this is meant for source files and configuration, not for multi-hundred-
143
+ * megabyte logs.
144
+ *
145
+ * Failures (missing file, absent needle, ambiguous needle, undecodable bytes)
146
+ * are reported as `{"error":…}` with exit code 0 so the caller always receives
147
+ * parseable JSON.
148
+ * @param p - remote path.
149
+ * @param oldB64 - base64 of the UTF-8 text to find.
150
+ * @param newB64 - base64 of the UTF-8 text to substitute.
151
+ * @param replaceAll - replace every occurrence instead of requiring exactly one.
152
+ * @param basisMaxBytes - include the before/after text when the original file is
153
+ * at most this large; `0` omits the basis entirely.
154
+ * @returns the PowerShell fragment.
155
+ */
156
+ export declare function psEditText(p: string, oldB64: string, newB64: string, replaceAll: boolean, basisMaxBytes?: number): string;
157
+ /**
158
+ * JSON: the whole remote text file as base64, or a refusal when it is missing
159
+ * or larger than `maxBytes`.
160
+ *
161
+ * The write tool needs the previous text to project a diff card, and the line
162
+ * window reader cannot serve that: it numbers lines and caps each one, so a
163
+ * long line would silently truncate inside the diff basis. The BOM detection
164
+ * repeats {@link psEditText}'s on purpose — both must decode identically, and a
165
+ * shared emitter would hide that requirement behind a helper neither could be
166
+ * reviewed against.
167
+ *
168
+ * Failures are reported as `{"error":…}` with exit code 0, so the caller always
169
+ * receives parseable JSON.
170
+ * @param p - remote path.
171
+ * @param maxBytes - largest file whose text is returned.
172
+ * @returns the PowerShell fragment.
173
+ */
174
+ export declare function psReadTextBasis(p: string, maxBytes: number): string;
111
175
  /** Magic stamped into the frame header that precedes every stream chunk. */
112
176
  export declare const STREAM_FRAME_MAGIC = "DSHZ";
113
177
  /** Frame header size: 4 magic bytes + 4 little-endian length bytes + 1 mode byte + 1 reserved. */
@@ -88,6 +88,60 @@ export interface RemoteDirEntry {
88
88
  size: number;
89
89
  mtimeMs: number;
90
90
  }
91
+ /** One numbered line of a remote text file. */
92
+ export interface RemoteTextLine {
93
+ /** 1-based line number within the remote file. */
94
+ number: number;
95
+ /** Line text without its terminator; over-long lines are cut and annotated. */
96
+ text: string;
97
+ }
98
+ /** Result of reading a bounded window of a remote text file. */
99
+ export interface RemoteTextWindow {
100
+ path: string;
101
+ /** 1-based line number of the first returned line. */
102
+ offset: number;
103
+ lines: RemoteTextLine[];
104
+ /** Lines in the whole file, so the caller can page. */
105
+ totalLines: number;
106
+ /** Detected encoding: `utf8`, `utf8bom`, `utf16le` or `utf16be`. */
107
+ encoding: string;
108
+ /** True when the window stopped at the byte budget instead of at `limit`. */
109
+ truncatedByBytes: boolean;
110
+ /** Lines whose text was cut at the per-line cap and annotated in `lines`. */
111
+ truncatedLines: number;
112
+ }
113
+ /** Text encodings the writer can emit. */
114
+ export type RemoteTextEncoding = 'utf8' | 'utf8bom' | 'utf16le';
115
+ /** Result of writing a remote text file. */
116
+ export interface RemoteWriteOutcome {
117
+ path: string;
118
+ /** `create` when the path was absent, `update` when it was overwritten. */
119
+ operation: 'create' | 'update';
120
+ bytes: number;
121
+ encoding: RemoteTextEncoding;
122
+ /**
123
+ * Previous file text, or `null` for a create and for a file too large to
124
+ * serve as a diff basis. Only the presentation layer reads it.
125
+ */
126
+ before: string | null;
127
+ /** Text after the write, on the same basis as {@link RemoteWriteOutcome.before}. */
128
+ after: string;
129
+ }
130
+ /** Result of an in-place remote text edit. */
131
+ export interface RemoteEditOutcome {
132
+ path: string;
133
+ /** Encoding the file already had, preserved across the edit. */
134
+ encoding: string;
135
+ replacements: number;
136
+ size: number;
137
+ /**
138
+ * File text before the edit, or `null` when the file was too large to serve
139
+ * as a diff basis. Only the presentation layer reads it.
140
+ */
141
+ before: string | null;
142
+ /** Text after the edit, on the same basis as {@link RemoteEditOutcome.before}. */
143
+ after: string | null;
144
+ }
91
145
  /** One server entry in a cluster run. */
92
146
  export interface ClusterResult {
93
147
  alias: string;
@@ -3,7 +3,7 @@
3
3
  * the same engine the web UI uses, so a host configured in the GUI is
4
4
  * immediately operable by any agent, and vice versa.
5
5
  */
6
- import type { WinRmEngine } from './engine.ts';
6
+ import { type WinRmEngine } from './engine.ts';
7
7
  /** The host-list tool. */
8
8
  export declare function winrmListTool(engine: WinRmEngine): import("@deepseek-ai/dsh-tools").ToolDefinition;
9
9
  /** The command-execution tool (PowerShell). */
@@ -18,3 +18,9 @@ export declare function winrmUploadTool(engine: WinRmEngine): import("@deepseek-
18
18
  export declare function winrmDownloadTool(engine: WinRmEngine): import("@deepseek-ai/dsh-tools").ToolDefinition;
19
19
  /** The cluster tool. */
20
20
  export declare function winrmClusterTool(engine: WinRmEngine): import("@deepseek-ai/dsh-tools").ToolDefinition;
21
+ /** The remote read tool: a line-numbered window of a text file on the target. */
22
+ export declare function winrmReadTool(engine: WinRmEngine): import("@deepseek-ai/dsh-tools").ToolDefinition;
23
+ /** The remote write tool: replace a whole text file on the target. */
24
+ export declare function winrmWriteTool(engine: WinRmEngine): import("@deepseek-ai/dsh-tools").ToolDefinition;
25
+ /** The remote edit tool: replace an exact string inside a file on the target. */
26
+ export declare function winrmEditTool(engine: WinRmEngine): import("@deepseek-ai/dsh-tools").ToolDefinition;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-winrm",
3
- "description": "Remote Windows administration for the dsh web GUI: WinRM/PowerShell Remoting host config store (~/.dsh/dsh-winrm.json), PowerShell exec / streaming console / service & process management / streamed file transfer / cluster execution, plus agent tools (winrm_list, winrm_exec, winrm_service, winrm_process, winrm_upload, winrm_download, winrm_cluster). Runs entirely on a native Node.js WinRM client (winrm-client) — no Python/pywinrm required. Standalone Cordis plugin — mounted via cordis.patch.yml + a profile node_modules link, no dsh source changes.",
4
- "version": "0.3.1",
3
+ "description": "Remote Windows administration for the dsh web GUI: WinRM/PowerShell Remoting host config store (~/.dsh/dsh-winrm.json), PowerShell exec / streaming console / service & process management / remote text read-write-edit / streamed file transfer / cluster execution, plus agent tools (winrm_list, winrm_exec, winrm_service, winrm_process, winrm_read, winrm_write, winrm_edit, winrm_upload, winrm_download, winrm_cluster). Runs entirely on a native Node.js WinRM client (winrm-client) — no Python/pywinrm required. Standalone Cordis plugin — mounted via cordis.patch.yml + a profile node_modules link, no dsh source changes.",
4
+ "version": "0.4.1",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.7.0",
7
7
  "engines": {
@@ -34,7 +34,8 @@
34
34
  "@deepseek-ai/dsh-client-ui-renderer",
35
35
  "@deepseek-ai/dsh-client-locale",
36
36
  "@deepseek-ai/dsh-client-ui-layout",
37
- "@deepseek-ai/dsh-client-ui-sidebar"
37
+ "@deepseek-ai/dsh-client-ui-sidebar",
38
+ "@deepseek-ai/dsh-client-ui-tool"
38
39
  ],
39
40
  "platform": "web"
40
41
  }
@@ -56,9 +57,11 @@
56
57
  "@deepseek-ai/cordis-plugin-loader": "^1.0.5",
57
58
  "@deepseek-ai/dsh-client-locale": "^0.2.0-rc.2",
58
59
  "@deepseek-ai/dsh-client-ui-layout": "^0.2.0-rc.2",
60
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.2",
59
61
  "@deepseek-ai/dsh-client-ui-renderer": "^0.2.0-rc.2",
60
62
  "@deepseek-ai/dsh-client-ui-sidebar": "^0.2.0-rc.2",
61
63
  "@deepseek-ai/dsh-client-ui-slots": "^0.2.0-rc.2",
64
+ "@deepseek-ai/dsh-client-ui-tool": "^0.2.0-rc.2",
62
65
  "@deepseek-ai/dsh-host-webserver": "^0.2.0-rc.2",
63
66
  "@deepseek-ai/dsh-llm": "^0.2.0-rc.2",
64
67
  "@deepseek-ai/dsh-system-prompt": "^0.2.0-rc.2",
@@ -79,8 +82,9 @@
79
82
  "lib/**/*.d.ts",
80
83
  "src",
81
84
  "cordis.patch.yml",
85
+ "docs/screenshots/*.png",
82
86
  "README.md",
83
- "README.zh.md",
87
+ "README.en.md",
84
88
  "RELEASING.md",
85
89
  "scripts"
86
90
  ],