@benz-ai-x/dsh-md-preview 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 +9 -5
- package/lib/client.js +73 -5103
- package/lib/index.js +80 -1
- package/lib/typert/remote-client.js +73 -1
- package/lib/types/client/PreviewOverlay.d.ts +5 -3
- package/lib/types/client/editor.d.ts +16 -0
- package/lib/types/client/locale.d.ts +9 -0
- package/lib/types/protocol.d.ts +13 -2
- package/lib/types/remote.d.ts +14 -1
- package/lib/types/typert/remote-client.d.ts +7 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -52,6 +52,18 @@ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializ
|
|
|
52
52
|
function failure(code, message) {
|
|
53
53
|
return new RemoteError(code, message, {});
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* `FsErrorCode` of a caught fs-layer error, duck-typed off the `code` property
|
|
57
|
+
* so the Host bundle keeps zero runtime imports beyond its declared peers.
|
|
58
|
+
* @param error - anything thrown by `ctx.fs`.
|
|
59
|
+
* @returns the stable fs code, or undefined for a foreign error shape.
|
|
60
|
+
*/
|
|
61
|
+
function fsErrorCode(error) {
|
|
62
|
+
if (error !== null && typeof error === "object" && "code" in error) {
|
|
63
|
+
const code = error.code;
|
|
64
|
+
if (typeof code === "string") return code;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
55
67
|
/** Lowercased dot-prefixed extension of a path, or the empty string. */
|
|
56
68
|
function extensionOf(path) {
|
|
57
69
|
const name = path.slice(Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) + 1);
|
|
@@ -70,10 +82,12 @@ let MdPreviewService = (() => {
|
|
|
70
82
|
let _classSuper = TypertRemoteService;
|
|
71
83
|
let _instanceExtraInitializers = [];
|
|
72
84
|
let _read_decorators;
|
|
85
|
+
let _write_decorators;
|
|
73
86
|
return class MdPreviewService extends _classSuper {
|
|
74
87
|
static {
|
|
75
88
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
76
89
|
_read_decorators = [Remote];
|
|
90
|
+
_write_decorators = [Remote];
|
|
77
91
|
__esDecorate(this, null, _read_decorators, {
|
|
78
92
|
kind: "method",
|
|
79
93
|
name: "read",
|
|
@@ -85,6 +99,17 @@ let MdPreviewService = (() => {
|
|
|
85
99
|
},
|
|
86
100
|
metadata: _metadata
|
|
87
101
|
}, null, _instanceExtraInitializers);
|
|
102
|
+
__esDecorate(this, null, _write_decorators, {
|
|
103
|
+
kind: "method",
|
|
104
|
+
name: "write",
|
|
105
|
+
static: false,
|
|
106
|
+
private: false,
|
|
107
|
+
access: {
|
|
108
|
+
has: (obj) => "write" in obj,
|
|
109
|
+
get: (obj) => obj.write
|
|
110
|
+
},
|
|
111
|
+
metadata: _metadata
|
|
112
|
+
}, null, _instanceExtraInitializers);
|
|
88
113
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, {
|
|
89
114
|
enumerable: true,
|
|
90
115
|
configurable: true,
|
|
@@ -150,7 +175,60 @@ let MdPreviewService = (() => {
|
|
|
150
175
|
if (content.length > this.config.maxBytes) throw failure("md-preview/too-large", `mdPreview/read refuses "${path}" above the configured byte cap`);
|
|
151
176
|
return {
|
|
152
177
|
path,
|
|
153
|
-
content
|
|
178
|
+
content,
|
|
179
|
+
fingerprint: info.version
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Write one previewable file back into the session's workspace.
|
|
184
|
+
* @param sessionId - owning session; its header cwd roots the write.
|
|
185
|
+
* @param path - path as it appeared in the conversation.
|
|
186
|
+
* @param content - the complete new file content.
|
|
187
|
+
* @param fingerprint - freshness token from the backing read; required
|
|
188
|
+
* unless `force` opts into an unconditional overwrite.
|
|
189
|
+
* @param force - skip the freshness guard (the conflict prompt's override).
|
|
190
|
+
* @param signal - caller cancellation carried through every fs call.
|
|
191
|
+
* @returns the saved path with its new freshness token.
|
|
192
|
+
* @throws RemoteError with a stable MdPreview failure code.
|
|
193
|
+
*/
|
|
194
|
+
async write(sessionId, path, content, fingerprint, force, signal) {
|
|
195
|
+
if (path.trim().length === 0) throw failure("md-preview/bad-request", "mdPreview/write requires a non-empty path");
|
|
196
|
+
if (!isAllowedExtension(path, this.config.allowedExtensions)) throw failure("md-preview/unsupported-extension", `mdPreview/write refuses non-previewable path "${path}"`);
|
|
197
|
+
const session = this.ctx.sessions.get(sessionId);
|
|
198
|
+
if (session === void 0) throw failure("md-preview/unknown-session", `mdPreview/write cannot resolve session "${sessionId}"`);
|
|
199
|
+
const cwd = session.header.cwd;
|
|
200
|
+
if (cwd === void 0) throw failure("md-preview/no-workspace", `mdPreview/write session "${sessionId}" has no working directory`);
|
|
201
|
+
const root = await this.resolveWorkspacePath(cwd, signal);
|
|
202
|
+
let target;
|
|
203
|
+
try {
|
|
204
|
+
target = await this.resolveWorkspacePath(path, signal, cwd);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (signal.aborted) throw error;
|
|
207
|
+
throw failure("md-preview/not-found", `mdPreview/write cannot resolve path "${path}"`);
|
|
208
|
+
}
|
|
209
|
+
if (!this.ctx.fs.contains(root, target)) throw failure("md-preview/forbidden", "mdPreview/write refuses paths outside the session workspace");
|
|
210
|
+
const info = await this.ctx.fs.stat(target, signal);
|
|
211
|
+
if (info === void 0) throw failure("md-preview/not-found", `mdPreview/write cannot find "${path}"`);
|
|
212
|
+
if (info.type !== "file") throw failure("md-preview/unsupported-extension", `mdPreview/write target "${path}" is not a regular file`);
|
|
213
|
+
if (content.length > this.config.maxBytes) throw failure("md-preview/too-large", `mdPreview/write refuses "${path}" above the configured byte cap`);
|
|
214
|
+
if (fingerprint === void 0 && !force) throw failure("md-preview/bad-request", "mdPreview/write requires a fingerprint or force");
|
|
215
|
+
const expected = fingerprint === void 0 ? void 0 : {
|
|
216
|
+
kind: "replaceIfVersion",
|
|
217
|
+
version: fingerprint
|
|
218
|
+
};
|
|
219
|
+
let outcome;
|
|
220
|
+
try {
|
|
221
|
+
outcome = await this.ctx.fs.writeText(target, content, expected, signal);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if (signal.aborted) throw error;
|
|
224
|
+
const code = fsErrorCode(error);
|
|
225
|
+
if (code === "FS_STALE_VERSION") throw failure("md-preview/conflict", `mdPreview/write refuses "${path}": the file changed since read`);
|
|
226
|
+
if (code === "FS_NOT_FOUND") throw failure("md-preview/not-found", `mdPreview/write cannot find "${path}"`);
|
|
227
|
+
throw failure("md-preview/unavailable", `mdPreview/write failed for "${path}": ${error instanceof Error ? error.message : String(error)}`);
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
path,
|
|
231
|
+
fingerprint: outcome.version
|
|
154
232
|
};
|
|
155
233
|
}
|
|
156
234
|
/** Resolve one path against the workspace, with a plain not-found mapping. */
|
|
@@ -181,6 +259,7 @@ const MD_PREVIEW_FAILURE_CODES = [
|
|
|
181
259
|
"md-preview/forbidden",
|
|
182
260
|
"md-preview/not-found",
|
|
183
261
|
"md-preview/too-large",
|
|
262
|
+
"md-preview/conflict",
|
|
184
263
|
"md-preview/unavailable"
|
|
185
264
|
];
|
|
186
265
|
//#endregion
|
|
@@ -14,7 +14,15 @@ const readSessionId$schema = z.string();
|
|
|
14
14
|
const readPath$schema = z.string().min(1);
|
|
15
15
|
const readResult$schema = z.object({
|
|
16
16
|
path: z.string(),
|
|
17
|
-
content: z.string()
|
|
17
|
+
content: z.string(),
|
|
18
|
+
fingerprint: z.string()
|
|
19
|
+
});
|
|
20
|
+
const writeContent$schema = z.string();
|
|
21
|
+
const writeFingerprint$schema = z.string().optional();
|
|
22
|
+
const writeForce$schema = z.boolean().optional();
|
|
23
|
+
const writeResult$schema = z.object({
|
|
24
|
+
path: z.string(),
|
|
25
|
+
fingerprint: z.string()
|
|
18
26
|
});
|
|
19
27
|
/** The MdPreview contribution mounted by this package's browser entry. */
|
|
20
28
|
const TYPERT_REMOTE = {
|
|
@@ -50,6 +58,70 @@ const TYPERT_REMOTE = {
|
|
|
50
58
|
typeSymbol: "@benz-ai-x/dsh-md-preview#mdPreview/read:result",
|
|
51
59
|
schema: readResult$schema
|
|
52
60
|
}
|
|
61
|
+
}, {
|
|
62
|
+
id: "@benz-ai-x/dsh-md-preview#mdPreview/write",
|
|
63
|
+
service: "mdPreview",
|
|
64
|
+
namespace: "mdPreview",
|
|
65
|
+
method: "write",
|
|
66
|
+
invocation: { kind: "direct" },
|
|
67
|
+
parameters: [
|
|
68
|
+
{
|
|
69
|
+
name: "sessionId",
|
|
70
|
+
wire: "sessionId",
|
|
71
|
+
source: "json",
|
|
72
|
+
codec: {
|
|
73
|
+
mode: "strict",
|
|
74
|
+
typeSymbol: "@deepseek-ai/dsh-session/types#SessionId",
|
|
75
|
+
schema: readSessionId$schema
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "path",
|
|
80
|
+
wire: "path",
|
|
81
|
+
source: "json",
|
|
82
|
+
codec: {
|
|
83
|
+
mode: "strict",
|
|
84
|
+
typeSymbol: "@benz-ai-x/dsh-md-preview#mdPreview/write:path",
|
|
85
|
+
schema: readPath$schema
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "content",
|
|
90
|
+
wire: "content",
|
|
91
|
+
source: "json",
|
|
92
|
+
codec: {
|
|
93
|
+
mode: "strict",
|
|
94
|
+
typeSymbol: "@benz-ai-x/dsh-md-preview#mdPreview/write:content",
|
|
95
|
+
schema: writeContent$schema
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: "fingerprint",
|
|
100
|
+
wire: "fingerprint",
|
|
101
|
+
source: "json",
|
|
102
|
+
codec: {
|
|
103
|
+
mode: "strict",
|
|
104
|
+
typeSymbol: "@benz-ai-x/dsh-md-preview#mdPreview/write:fingerprint",
|
|
105
|
+
schema: writeFingerprint$schema
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "force",
|
|
110
|
+
wire: "force",
|
|
111
|
+
source: "json",
|
|
112
|
+
codec: {
|
|
113
|
+
mode: "strict",
|
|
114
|
+
typeSymbol: "@benz-ai-x/dsh-md-preview#mdPreview/write:force",
|
|
115
|
+
schema: writeForce$schema
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
],
|
|
119
|
+
cancellation: { parameter: "signal" },
|
|
120
|
+
result: {
|
|
121
|
+
mode: "strict",
|
|
122
|
+
typeSymbol: "@benz-ai-x/dsh-md-preview#mdPreview/write:result",
|
|
123
|
+
schema: writeResult$schema
|
|
124
|
+
}
|
|
53
125
|
}]
|
|
54
126
|
};
|
|
55
127
|
//#endregion
|
|
@@ -2,9 +2,9 @@ import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
|
2
2
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
3
|
import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
|
|
4
4
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
5
|
-
import type { MdPreviewFile } from '../protocol.ts';
|
|
5
|
+
import type { MdPreviewFile, MdPreviewWriteResult } from '../protocol.ts';
|
|
6
6
|
import type { MdPreviewState } from './preview-state.ts';
|
|
7
|
-
/** Read
|
|
7
|
+
/** Read/write RPCs and panel dismissal, created in the plugin's apply world. */
|
|
8
8
|
export interface PreviewOverlayInjected {
|
|
9
9
|
hooks: {
|
|
10
10
|
/** Current preview target; null while the panel is closed. */
|
|
@@ -14,6 +14,8 @@ export interface PreviewOverlayInjected {
|
|
|
14
14
|
close(): void;
|
|
15
15
|
/** One bounded read; the transport carries the AbortSignal. */
|
|
16
16
|
read(sessionId: SessionId, path: string, signal: AbortSignal): Promise<RemoteResult<MdPreviewFile>>;
|
|
17
|
+
/** One guarded write; `fingerprint` comes from the backing read. */
|
|
18
|
+
write(sessionId: SessionId, path: string, content: string, fingerprint: string | undefined, force: boolean, signal: AbortSignal): Promise<RemoteResult<MdPreviewWriteResult>>;
|
|
17
19
|
}
|
|
18
20
|
/** Full composed panel props. */
|
|
19
21
|
export type PreviewOverlayProps = PropsRuntime<'shell.overlay'> & InjectFace<PreviewOverlayInjected> & PropsLocale<'md-preview'>;
|
|
@@ -22,5 +24,5 @@ export type PreviewOverlayProps = PropsRuntime<'shell.overlay'> & InjectFace<Pre
|
|
|
22
24
|
* @param props - target hook, read RPC, dismissal, and the locale seat.
|
|
23
25
|
* @returns the docked panel, or null while closed.
|
|
24
26
|
*/
|
|
25
|
-
export declare function PreviewOverlay({ usePreviewTarget, close, read, t }: PreviewOverlayProps): import("react").JSX.Element | null;
|
|
27
|
+
export declare function PreviewOverlay({ usePreviewTarget, close, read, write, t }: PreviewOverlayProps): import("react").JSX.Element | null;
|
|
26
28
|
//# sourceMappingURL=PreviewOverlay.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Props the panel hands to the editor. */
|
|
2
|
+
export interface MarkdownEditorProps {
|
|
3
|
+
/** Document text at edit-session start; a changed value remounts the editor. */
|
|
4
|
+
initialValue: string;
|
|
5
|
+
/** Reports every document change (the panel's draft). */
|
|
6
|
+
onChange: (value: string) => void;
|
|
7
|
+
/** Cmd/Ctrl-S from the editor's keymap. */
|
|
8
|
+
onSave: () => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Render one CodeMirror editor bound to the panel's edit session.
|
|
12
|
+
* @param props - initial document plus change and save callbacks.
|
|
13
|
+
* @returns the editor host element.
|
|
14
|
+
*/
|
|
15
|
+
export declare function MarkdownEditor({ initialValue, onChange, onSave }: MarkdownEditorProps): import("react").JSX.Element;
|
|
16
|
+
//# sourceMappingURL=editor.d.ts.map
|
|
@@ -9,6 +9,15 @@ export declare const zh: {
|
|
|
9
9
|
'panel.empty': string;
|
|
10
10
|
'panel.error': string;
|
|
11
11
|
'panel.retry': string;
|
|
12
|
+
'panel.edit': string;
|
|
13
|
+
'panel.save': string;
|
|
14
|
+
'panel.cancel': string;
|
|
15
|
+
'panel.conflict.title': string;
|
|
16
|
+
'panel.conflict.reload': string;
|
|
17
|
+
'panel.conflict.force': string;
|
|
18
|
+
'panel.unsaved.title': string;
|
|
19
|
+
'panel.unsaved.discard': string;
|
|
20
|
+
'panel.unsaved.keep': string;
|
|
12
21
|
'chip.preview': string;
|
|
13
22
|
'chip.open': string;
|
|
14
23
|
'action.label': string;
|
package/lib/types/protocol.d.ts
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
/** Wire-level vocabulary shared by the Host service and the browser face. */
|
|
2
2
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
3
|
-
/** One rendered-preview payload: the requested path
|
|
3
|
+
/** One rendered-preview payload: the requested path, its full text, and freshness. */
|
|
4
4
|
export interface MdPreviewFile {
|
|
5
5
|
/** The path exactly as requested by the caller (tool-produced spelling). */
|
|
6
6
|
readonly path: string;
|
|
7
7
|
/** The complete file content, capped by the configured byte limit. */
|
|
8
8
|
readonly content: string;
|
|
9
|
+
/** Opaque fs freshness token; pass it back to `write` to guard the save. */
|
|
10
|
+
readonly fingerprint: string;
|
|
11
|
+
}
|
|
12
|
+
/** One persisted write: the saved path and its new freshness token. */
|
|
13
|
+
export interface MdPreviewWriteResult {
|
|
14
|
+
/** The path exactly as requested by the caller. */
|
|
15
|
+
readonly path: string;
|
|
16
|
+
/** Freshness token of the file after the write; guards the next save. */
|
|
17
|
+
readonly fingerprint: string;
|
|
9
18
|
}
|
|
10
19
|
/** Stable failure codes carried by `RemoteError` across the wire. */
|
|
11
|
-
export declare const MD_PREVIEW_FAILURE_CODES: readonly ["md-preview/bad-request", "md-preview/unknown-session", "md-preview/no-workspace", "md-preview/unsupported-extension", "md-preview/forbidden", "md-preview/not-found", "md-preview/too-large", "md-preview/unavailable"];
|
|
20
|
+
export declare const MD_PREVIEW_FAILURE_CODES: readonly ["md-preview/bad-request", "md-preview/unknown-session", "md-preview/no-workspace", "md-preview/unsupported-extension", "md-preview/forbidden", "md-preview/not-found", "md-preview/too-large", "md-preview/conflict", "md-preview/unavailable"];
|
|
12
21
|
/** One stable MdPreview failure code. */
|
|
13
22
|
export type MdPreviewFailureCode = (typeof MD_PREVIEW_FAILURE_CODES)[number];
|
|
14
23
|
declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
@@ -27,6 +36,8 @@ declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
|
27
36
|
'md-preview/not-found': {};
|
|
28
37
|
/** The file exceeds the configured byte cap. */
|
|
29
38
|
'md-preview/too-large': {};
|
|
39
|
+
/** The file changed since the read backing this write's fingerprint. */
|
|
40
|
+
'md-preview/conflict': {};
|
|
30
41
|
/** The read itself failed. */
|
|
31
42
|
'md-preview/unavailable': {};
|
|
32
43
|
}
|
package/lib/types/remote.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
8
8
|
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
9
9
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
10
10
|
import type { Config } from './config.ts';
|
|
11
|
-
import type { MdPreviewFile } from './protocol.ts';
|
|
11
|
+
import type { MdPreviewFile, MdPreviewWriteResult } from './protocol.ts';
|
|
12
12
|
/** Whether a candidate extension passes the configured allowlist. */
|
|
13
13
|
export declare function isAllowedExtension(path: string, allowedExtensions: readonly string[]): boolean;
|
|
14
14
|
/**
|
|
@@ -37,6 +37,19 @@ export declare class MdPreviewService extends TypertRemoteService {
|
|
|
37
37
|
* @throws RemoteError with a stable MdPreview failure code.
|
|
38
38
|
*/
|
|
39
39
|
read(sessionId: SessionId, path: string, signal: AbortSignal): Promise<MdPreviewFile>;
|
|
40
|
+
/**
|
|
41
|
+
* Write one previewable file back into the session's workspace.
|
|
42
|
+
* @param sessionId - owning session; its header cwd roots the write.
|
|
43
|
+
* @param path - path as it appeared in the conversation.
|
|
44
|
+
* @param content - the complete new file content.
|
|
45
|
+
* @param fingerprint - freshness token from the backing read; required
|
|
46
|
+
* unless `force` opts into an unconditional overwrite.
|
|
47
|
+
* @param force - skip the freshness guard (the conflict prompt's override).
|
|
48
|
+
* @param signal - caller cancellation carried through every fs call.
|
|
49
|
+
* @returns the saved path with its new freshness token.
|
|
50
|
+
* @throws RemoteError with a stable MdPreview failure code.
|
|
51
|
+
*/
|
|
52
|
+
write(sessionId: SessionId, path: string, content: string, fingerprint: string | undefined, force: boolean, signal: AbortSignal): Promise<MdPreviewWriteResult>;
|
|
40
53
|
/** Resolve one path against the workspace, with a plain not-found mapping. */
|
|
41
54
|
private resolveWorkspacePath;
|
|
42
55
|
}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { RemoteResult, TypertRemoteContribution } from '@deepseek-ai/dsh-typert-protocol';
|
|
2
2
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
3
|
-
import type { MdPreviewFile } from '../protocol.ts';
|
|
3
|
+
import type { MdPreviewFile, MdPreviewWriteResult } from '../protocol.ts';
|
|
4
4
|
/** Browser face of the mounted `mdPreview` Remote namespace. */
|
|
5
5
|
export interface MdPreviewRemote {
|
|
6
6
|
/** Read one previewable document of a session's workspace. */
|
|
7
7
|
read(sessionId: SessionId, path: string, signal?: AbortSignal): Promise<RemoteResult<MdPreviewFile>>;
|
|
8
|
+
/**
|
|
9
|
+
* Write one previewable document back into a session's workspace. Pass the
|
|
10
|
+
* `fingerprint` from the backing read to guard the save, or `force` to
|
|
11
|
+
* overwrite unconditionally (the conflict prompt's override).
|
|
12
|
+
*/
|
|
13
|
+
write(sessionId: SessionId, path: string, content: string, fingerprint?: string, force?: boolean, signal?: AbortSignal): Promise<RemoteResult<MdPreviewWriteResult>>;
|
|
8
14
|
}
|
|
9
15
|
declare module '@deepseek-ai/dsh-typert-protocol' {
|
|
10
16
|
interface TypertRemoteNamespaceMap {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@benz-ai-x/dsh-md-preview",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "DSH markdown preview: clicking a markdown document in the conversation opens a rendered preview panel docked to the right of the chat.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|