@rezti/dsh-rez-suite 0.1.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/lib/style.css ADDED
@@ -0,0 +1,309 @@
1
+ [data-pane="conversation"] {
2
+ position: relative;
3
+ }
4
+
5
+ [data-dsh-rez-view] {
6
+ z-index: 60;
7
+ background: var(--dsw-alias-bg-base);
8
+ display: none;
9
+ position: absolute;
10
+ inset: 0;
11
+ }
12
+
13
+ html[data-dsh-rez-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]) [data-dsh-rez-view] {
14
+ display: block;
15
+ }
16
+
17
+ html[data-dsh-rez-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]) [data-pane="conversation"] > :not([data-dsh-rez-view]), html[data-dsh-rez-active]:not([data-dsh-taskboard-active]):not([data-dsh-ssh-active]) [class*="centerCol"] > :not([data-dsh-rez-view]) {
18
+ display: none !important;
19
+ }
20
+
21
+ .WRGRRW_entry {
22
+ width: 100%;
23
+ height: 32px;
24
+ color: var(--dsw-alias-label-secondary);
25
+ cursor: pointer;
26
+ white-space: nowrap;
27
+ background: none;
28
+ border: none;
29
+ border-radius: 8px;
30
+ align-items: center;
31
+ gap: 8px;
32
+ padding: 0 12px;
33
+ font-size: 13px;
34
+ display: flex;
35
+ }
36
+
37
+ .WRGRRW_entry:hover {
38
+ background: var(--dsw-specific-sidebar-nav-item-hover);
39
+ color: var(--dsw-alias-label-primary);
40
+ }
41
+
42
+ .WRGRRW_entryIcon {
43
+ flex: none;
44
+ justify-content: center;
45
+ align-items: center;
46
+ display: inline-flex;
47
+ }
48
+
49
+ .WRGRRW_entryLabel {
50
+ text-overflow: ellipsis;
51
+ overflow: hidden;
52
+ }
53
+
54
+ [data-dsh-frame][data-sidebar-collapsed] .WRGRRW_entry {
55
+ justify-content: center;
56
+ width: 100%;
57
+ padding: 0;
58
+ }
59
+
60
+ [data-dsh-frame][data-sidebar-collapsed] .WRGRRW_entryLabel {
61
+ display: none;
62
+ }
63
+
64
+ .WRGRRW_view {
65
+ overflow: hidden;
66
+ }
67
+
68
+ .WRGRRW_panel {
69
+ background: var(--dsw-alias-bg-base);
70
+ min-width: 0;
71
+ height: 100%;
72
+ min-height: 0;
73
+ color: var(--dsw-alias-label-primary);
74
+ font-family: var(--dsw-font-family);
75
+ flex-direction: column;
76
+ gap: 10px;
77
+ padding: 14px 16px 16px;
78
+ display: flex;
79
+ }
80
+
81
+ .WRGRRW_panelHeader {
82
+ flex: none;
83
+ align-items: center;
84
+ gap: 10px;
85
+ display: flex;
86
+ }
87
+
88
+ .WRGRRW_panelTitle {
89
+ flex: 1;
90
+ margin: 0;
91
+ font-size: 16px;
92
+ font-weight: 700;
93
+ }
94
+
95
+ .WRGRRW_iconButton {
96
+ color: var(--dsw-alias-label-secondary);
97
+ cursor: pointer;
98
+ background: none;
99
+ border: none;
100
+ border-radius: 6px;
101
+ padding: 4px 8px;
102
+ font-size: 14px;
103
+ }
104
+
105
+ .WRGRRW_iconButton:hover {
106
+ background: var(--dsw-alias-interactive-bg-hover);
107
+ color: var(--dsw-alias-label-primary);
108
+ }
109
+
110
+ .WRGRRW_tabBar {
111
+ border-bottom: 1px solid var(--dsw-alias-border-l1);
112
+ flex: none;
113
+ gap: 2px;
114
+ display: flex;
115
+ }
116
+
117
+ .WRGRRW_tab {
118
+ color: var(--dsw-alias-label-secondary);
119
+ cursor: pointer;
120
+ background: none;
121
+ border: none;
122
+ border-bottom: 2px solid #0000;
123
+ border-radius: 6px 6px 0 0;
124
+ padding: 7px 14px;
125
+ font-size: 13px;
126
+ }
127
+
128
+ .WRGRRW_tab:hover {
129
+ color: var(--dsw-alias-label-primary);
130
+ background: var(--dsw-alias-interactive-bg-hover);
131
+ }
132
+
133
+ .WRGRRW_tab[data-active] {
134
+ color: var(--dsw-alias-label-primary);
135
+ border-bottom-color: var(--dsw-alias-state-business-primary);
136
+ font-weight: 600;
137
+ }
138
+
139
+ .WRGRRW_panelContent {
140
+ flex-direction: column;
141
+ flex: 1;
142
+ min-height: 0;
143
+ display: flex;
144
+ overflow: hidden;
145
+ }
146
+
147
+ .WRGRRW_tabBody {
148
+ flex-direction: column;
149
+ flex: 1;
150
+ gap: 12px;
151
+ min-height: 0;
152
+ display: flex;
153
+ overflow-y: auto;
154
+ }
155
+
156
+ .WRGRRW_section {
157
+ border: 1px solid var(--dsw-alias-border-l1);
158
+ background: var(--dsw-alias-bg-layer-1, var(--dsw-alias-bg-base));
159
+ border-radius: 10px;
160
+ flex-direction: column;
161
+ gap: 8px;
162
+ padding: 12px;
163
+ display: flex;
164
+ }
165
+
166
+ .WRGRRW_sectionTitle {
167
+ margin: 0;
168
+ font-size: 13px;
169
+ font-weight: 600;
170
+ }
171
+
172
+ .WRGRRW_field {
173
+ grid-template-columns: 140px 1fr;
174
+ align-items: center;
175
+ gap: 8px;
176
+ display: grid;
177
+ }
178
+
179
+ .WRGRRW_label {
180
+ color: var(--dsw-alias-label-secondary);
181
+ font-size: 12.5px;
182
+ }
183
+
184
+ .WRGRRW_input, .WRGRRW_select {
185
+ color: var(--dsw-alias-label-primary);
186
+ background: var(--dsw-specific-input-major);
187
+ border: 1px solid var(--dsw-alias-border-l2);
188
+ border-radius: 8px;
189
+ outline: none;
190
+ padding: 6px 8px;
191
+ font-size: 13px;
192
+ }
193
+
194
+ .WRGRRW_checkbox {
195
+ justify-self: start;
196
+ width: 15px;
197
+ height: 15px;
198
+ }
199
+
200
+ .WRGRRW_toolbar {
201
+ flex-wrap: wrap;
202
+ flex: none;
203
+ align-items: center;
204
+ gap: 8px;
205
+ display: flex;
206
+ }
207
+
208
+ .WRGRRW_button {
209
+ border: 1px solid var(--dsw-alias-border-l2);
210
+ background: var(--dsw-specific-input-major);
211
+ color: var(--dsw-alias-label-primary);
212
+ cursor: pointer;
213
+ border-radius: 8px;
214
+ padding: 7px 12px;
215
+ font-size: 13px;
216
+ }
217
+
218
+ .WRGRRW_button:hover {
219
+ background: var(--dsw-alias-interactive-bg-hover);
220
+ }
221
+
222
+ .WRGRRW_primaryButton {
223
+ background: var(--dsw-alias-state-business-primary);
224
+ border-color: var(--dsw-alias-state-business-primary);
225
+ color: #fff;
226
+ }
227
+
228
+ .WRGRRW_dangerButton {
229
+ background: var(--dsw-alias-state-danger, #c62828);
230
+ border-color: var(--dsw-alias-state-danger, #c62828);
231
+ color: #fff;
232
+ }
233
+
234
+ .WRGRRW_message {
235
+ color: var(--dsw-alias-label-secondary);
236
+ font-size: 12.5px;
237
+ }
238
+
239
+ .WRGRRW_error {
240
+ color: var(--dsw-alias-state-danger, #c62828);
241
+ }
242
+
243
+ .WRGRRW_tableWrap {
244
+ border: 1px solid var(--dsw-alias-border-l1);
245
+ border-radius: 10px;
246
+ flex: 1;
247
+ min-height: 0;
248
+ overflow: auto;
249
+ }
250
+
251
+ .WRGRRW_table {
252
+ border-collapse: collapse;
253
+ width: 100%;
254
+ font-size: 12.5px;
255
+ }
256
+
257
+ .WRGRRW_table th {
258
+ z-index: 1;
259
+ text-align: left;
260
+ background: var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1));
261
+ padding: 8px 10px;
262
+ position: sticky;
263
+ top: 0;
264
+ }
265
+
266
+ .WRGRRW_table td {
267
+ border-top: 1px solid var(--dsw-alias-border-l1);
268
+ vertical-align: top;
269
+ padding: 8px 10px;
270
+ }
271
+
272
+ .WRGRRW_badge {
273
+ border-radius: 999px;
274
+ padding: 2px 8px;
275
+ font-size: 11px;
276
+ display: inline-block;
277
+ }
278
+
279
+ .WRGRRW_badgeOk {
280
+ color: #229a55;
281
+ background: #229a5524;
282
+ }
283
+
284
+ .WRGRRW_badgeFail {
285
+ color: #c62828;
286
+ background: #c6282824;
287
+ }
288
+
289
+ .WRGRRW_cards {
290
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
291
+ gap: 8px;
292
+ display: grid;
293
+ }
294
+
295
+ .WRGRRW_card {
296
+ border: 1px solid var(--dsw-alias-border-l1);
297
+ border-radius: 10px;
298
+ padding: 10px;
299
+ }
300
+
301
+ .WRGRRW_cardLabel {
302
+ color: var(--dsw-alias-label-tertiary);
303
+ font-size: 11px;
304
+ }
305
+
306
+ .WRGRRW_cardValue {
307
+ font-size: 15px;
308
+ font-weight: 600;
309
+ }
package/package.json ADDED
@@ -0,0 +1,103 @@
1
+ {
2
+ "name": "@rezti/dsh-rez-suite",
3
+ "description": "ReZ-TI employee plugin for DeepSeek Harness: one install enables company MCP (Odoo / Nextcloud / WeCom / Home Assistant) and work identity.",
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": "^22.19.0 || >=24.0.0"
8
+ },
9
+ "main": "lib/index.js",
10
+ "types": "lib/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./lib/index.d.ts",
14
+ "default": "./lib/index.js"
15
+ },
16
+ "./client": {
17
+ "types": "./lib/client.d.ts",
18
+ "default": "./lib/client.js"
19
+ },
20
+ "./src/*": "./src/*",
21
+ "./package.json": "./package.json"
22
+ },
23
+ "dsh": {
24
+ "bundle": {
25
+ "patch": "./cordis.patch.yml"
26
+ },
27
+ "client": {
28
+ "inject": [
29
+ "@deepseek-ai/dsh-client-runtime",
30
+ "@deepseek-ai/dsh-client-connection",
31
+ "@deepseek-ai/dsh-client-ui-settings"
32
+ ],
33
+ "platform": "web"
34
+ }
35
+ },
36
+ "dependencies": {
37
+ "@modelcontextprotocol/sdk": "^1.30.0",
38
+ "zod": "^4.4.3",
39
+ "@rezti/dsh-rez-intent": "0.1.0",
40
+ "@rezti/dsh-rez-ha-bridge": "0.1.0",
41
+ "@rezti/dsh-rez-nextcloud": "0.1.0",
42
+ "@rezti/dsh-rez-odoo": "0.1.0",
43
+ "@rezti/dsh-rez-sso": "0.1.0",
44
+ "@rezti/dsh-rez-token-manager": "0.1.0",
45
+ "@rezti/dsh-rez-wechat": "0.1.0"
46
+ },
47
+ "peerDependencies": {
48
+ "react": "^18.2.0",
49
+ "react-dom": "^18.2.0"
50
+ },
51
+ "devDependencies": {
52
+ "@deepseek-ai/cordis": "^4.0.1",
53
+ "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6",
54
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
55
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
56
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
57
+ "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.0-rc.6",
58
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
59
+ "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
60
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
61
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
62
+ "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6",
63
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
64
+ "@deepseek-ai/schemastery": "^3.18.1",
65
+ "@tsdown/css": "0.22.2",
66
+ "@types/node": "^22.20.0",
67
+ "@types/react": "~18.3.1",
68
+ "@types/react-dom": "^18.3.5",
69
+ "react": "^18.3.1",
70
+ "react-dom": "^18.3.1",
71
+ "tsdown": "0.22.2",
72
+ "typescript": "~5.7.2",
73
+ "vitest": "^3.0.0"
74
+ },
75
+ "files": [
76
+ "lib/**/*.js",
77
+ "lib/**/*.js.map",
78
+ "lib/**/*.d.ts",
79
+ "lib/**/*.d.ts.map",
80
+ "lib/**/*.css",
81
+ "src",
82
+ "servers",
83
+ "cordis.patch.yml",
84
+ "README.md",
85
+ "README.zh.md"
86
+ ],
87
+ "license": "Apache-2.0",
88
+ "publishConfig": {
89
+ "access": "public"
90
+ },
91
+ "repository": {
92
+ "type": "git",
93
+ "url": "git+https://github.com/kelvinchen1119/deepseek-harness.git",
94
+ "directory": "packages/dsh-rez-suite"
95
+ },
96
+ "scripts": {
97
+ "build": "tsc -p tsconfig.build.json && tsdown --config-loader native && node scripts/wrap-client.mjs",
98
+ "bundle": "tsdown --config-loader native",
99
+ "watch": "tsdown --config-loader native --watch",
100
+ "typecheck": "tsc --noEmit",
101
+ "test": "vitest run"
102
+ }
103
+ }
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Bundled fs-mcp-server: minimal stdio filesystem MCP server for the suite.
4
+ * Root is taken from REZ_FS_ROOT or the first CLI arg. Every path is resolved
5
+ * inside the root; traversal outside is rejected.
6
+ */
7
+
8
+ import { promises as fs } from 'node:fs'
9
+ import { dirname, join, resolve, sep } from 'node:path'
10
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
11
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
12
+ import z from 'zod'
13
+
14
+ const root = resolve(process.env.REZ_FS_ROOT || process.argv[2] || join(process.cwd(), 'rez-fs'))
15
+ await fs.mkdir(root, { recursive: true })
16
+
17
+ function inside(input) {
18
+ const target = resolve(root, input === '' ? '.' : input)
19
+ if (target !== root && !target.startsWith(root + sep)) {
20
+ throw new Error('path escapes fs root: ' + input)
21
+ }
22
+ return target
23
+ }
24
+
25
+ const server = new McpServer({ name: 'fs-mcp-server', version: '0.1.0' })
26
+
27
+ server.registerTool('fs_read_file', {
28
+ title: 'Read local file',
29
+ description: 'Read a UTF-8 text file inside the suite filesystem root.',
30
+ inputSchema: { path: z.string().describe('Path relative to the fs root.') },
31
+ }, async ({ path }) => {
32
+ const content = await fs.readFile(inside(path), 'utf8')
33
+ return { content: [{ type: 'text', text: content }] }
34
+ })
35
+
36
+ server.registerTool('fs_write_file', {
37
+ title: 'Write local file',
38
+ description: 'Write UTF-8 text to a file inside the suite filesystem root, creating parent directories.',
39
+ inputSchema: { path: z.string(), content: z.string() },
40
+ }, async ({ path, content }) => {
41
+ const target = inside(path)
42
+ await fs.mkdir(dirname(target), { recursive: true })
43
+ await fs.writeFile(target, content, 'utf8')
44
+ return { content: [{ type: 'text', text: 'ok' }] }
45
+ })
46
+
47
+ server.registerTool('fs_list_dir', {
48
+ title: 'List directory',
49
+ description: 'List directory entries under a path inside the suite filesystem root.',
50
+ inputSchema: { path: z.string().default('.') },
51
+ }, async ({ path }) => {
52
+ const entries = []
53
+ for (const entry of await fs.readdir(inside(path), { withFileTypes: true })) {
54
+ const full = join(root, path, entry.name)
55
+ const stat = await fs.stat(full)
56
+ entries.push({ name: entry.name, type: entry.isDirectory() ? 'dir' : 'file', size: stat.size, mtimeMs: stat.mtimeMs })
57
+ }
58
+ return { content: [{ type: 'text', text: JSON.stringify(entries, null, 2) }] }
59
+ })
60
+
61
+ server.registerTool('fs_stat', {
62
+ title: 'Stat path',
63
+ description: 'Return metadata for a path inside the suite filesystem root.',
64
+ inputSchema: { path: z.string() },
65
+ }, async ({ path }) => {
66
+ const stat = await fs.stat(inside(path))
67
+ return { content: [{ type: 'text', text: JSON.stringify({ ...stat, isFile: stat.isFile(), isDirectory: stat.isDirectory() }, null, 2) }] }
68
+ })
69
+
70
+ const transport = new StdioServerTransport()
71
+ await server.connect(transport)
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Bundled sqlite-mcp-server: minimal stdio SQLite MCP server for the suite.
4
+ * Database path is taken from REZ_SQLITE_PATH or the first CLI arg. It is
5
+ * read/write; treat it as a local cache/audit store, not a production DB.
6
+ */
7
+
8
+ import { DatabaseSync } from 'node:sqlite'
9
+ import { mkdirSync } from 'node:fs'
10
+ import { dirname, resolve } from 'node:path'
11
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
12
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
13
+ import z from 'zod'
14
+
15
+ const dbPath = resolve(process.env.REZ_SQLITE_PATH || process.argv[2] || 'rez-cache.sqlite')
16
+ mkdirSync(dirname(dbPath), { recursive: true })
17
+ const db = new DatabaseSync(dbPath)
18
+ db.exec('CREATE TABLE IF NOT EXISTS _mcp_meta (k TEXT PRIMARY KEY, v TEXT)')
19
+
20
+ const server = new McpServer({ name: 'sqlite-mcp-server', version: '0.1.0' })
21
+
22
+ server.registerTool('sqlite_query', {
23
+ title: 'Query SQLite',
24
+ description: 'Run a read-only SQL SELECT and return rows as JSON.',
25
+ inputSchema: { sql: z.string().describe('Single SELECT statement.'), params: z.array(z.union([z.string(), z.number(), z.null(), z.boolean()])).optional() },
26
+ }, async ({ sql, params }) => {
27
+ const statement = db.prepare(sql)
28
+ const rows = params === undefined ? statement.all() : statement.all(...params)
29
+ return { content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }] }
30
+ })
31
+
32
+ server.registerTool('sqlite_execute', {
33
+ title: 'Execute SQLite',
34
+ description: 'Run a write statement (CREATE/INSERT/UPDATE/DELETE/DDL). Returns the number of changes.',
35
+ inputSchema: { sql: z.string().describe('A single write statement.') },
36
+ }, async ({ sql }) => {
37
+ const result = db.exec(sql)
38
+ return { content: [{ type: 'text', text: 'changes=' + result.changes }] }
39
+ })
40
+
41
+ server.registerTool('sqlite_tables', {
42
+ title: 'List SQLite tables',
43
+ description: 'List tables and views in the database.',
44
+ }, async () => {
45
+ const rows = db.prepare("SELECT type, name FROM sqlite_master WHERE type IN ('table','view') ORDER BY name").all()
46
+ return { content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }] }
47
+ })
48
+
49
+ const transport = new StdioServerTransport()
50
+ await server.connect(transport)
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Browser-side API client for /api/dsh-rez-suite. Plain same-origin fetch.
3
+ */
4
+
5
+ import {
6
+ REZ_API,
7
+ type RezAuditResponse,
8
+ type RezPublicConfig,
9
+ type RezServerStatus,
10
+ type RezStatusResponse,
11
+ type RezTestResult,
12
+ } from '../protocol.ts'
13
+
14
+ export class RezApiError extends Error {
15
+ constructor(message: string) {
16
+ super(message)
17
+ this.name = 'RezApiError'
18
+ }
19
+ }
20
+
21
+ async function readJson<T>(response: Response): Promise<T> {
22
+ let body: unknown
23
+ try {
24
+ body = await response.json()
25
+ } catch {
26
+ throw new RezApiError('HTTP ' + response.status + ': invalid JSON response')
27
+ }
28
+ if (!response.ok) {
29
+ const message = typeof body === 'object' && body !== null && typeof (body as { error?: unknown }).error === 'string'
30
+ ? (body as { error: string }).error
31
+ : 'HTTP ' + response.status
32
+ throw new RezApiError(message)
33
+ }
34
+ return body as T
35
+ }
36
+
37
+ export class RezApi {
38
+ async getConfig(): Promise<RezPublicConfig> {
39
+ const response = await fetch(REZ_API.config)
40
+ const body = await readJson<{ config: RezPublicConfig }>(response)
41
+ return body.config
42
+ }
43
+
44
+ async saveConfig(patch: unknown): Promise<RezPublicConfig> {
45
+ const response = await fetch(REZ_API.config, {
46
+ method: 'PUT',
47
+ headers: { 'content-type': 'application/json' },
48
+ body: JSON.stringify(patch),
49
+ })
50
+ const body = await readJson<{ config: RezPublicConfig }>(response)
51
+ return body.config
52
+ }
53
+
54
+ async test(): Promise<RezTestResult[]> {
55
+ const response = await fetch(REZ_API.test, { method: 'POST' })
56
+ const body = await readJson<{ results: RezTestResult[] }>(response)
57
+ return body.results
58
+ }
59
+
60
+ async status(): Promise<RezStatusResponse> {
61
+ const response = await fetch(REZ_API.status)
62
+ return readJson<RezStatusResponse>(response)
63
+ }
64
+
65
+ async audit(): Promise<RezAuditResponse> {
66
+ const response = await fetch(REZ_API.audit)
67
+ const body = await readJson<{ audit: RezAuditResponse }>(response)
68
+ return body.audit
69
+ }
70
+
71
+ async resetAudit(): Promise<void> {
72
+ const response = await fetch(REZ_API.auditReset, { method: 'POST' })
73
+ await readJson<{ ok: boolean }>(response)
74
+ }
75
+ }
@@ -0,0 +1,4 @@
1
+ declare module '*.module.css' {
2
+ const classes: Record<string, string>
3
+ export default classes
4
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Browser-half entry for dsh-rez-suite — runs inside the dsh web GUI.
3
+ * Registers locale dictionaries and mounts the Rez Suite panel (sidebar entry
4
+ * plus center-column view). DOM mounting problems are logged, never thrown.
5
+ */
6
+
7
+ import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
8
+ import type {} from '@deepseek-ai/dsh-client-locale/client'
9
+ import type {} from '@deepseek-ai/dsh-client-ui-slots'
10
+ import { RezApi } from './api.ts'
11
+ import { en, zh, type RezKey } from './locales.ts'
12
+ import { mountPanel } from './mount.tsx'
13
+ import { PanelController } from './panel/controller.ts'
14
+ import { mountSidebarEntry } from './sidebar-entry.ts'
15
+
16
+ const NS = 'dsh-rez-suite'
17
+
18
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
19
+ interface LocaleNamespaceMap {
20
+ 'dsh-rez-suite': RezKey
21
+ }
22
+ }
23
+
24
+ export const inject = ['slots', 'locale']
25
+
26
+ export type { PanelControllerSnapshot } from './panel/controller.ts'
27
+ export type { RezPanelProps } from './panel/RezPanel.tsx'
28
+ export type { RezKey } from './locales.ts'
29
+
30
+ export function apply(ctx: ClientContext): void {
31
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-rez-suite: dictionaries')
32
+
33
+ const controller = new PanelController()
34
+ const api = new RezApi()
35
+ const disposers: Array<() => void> = []
36
+ try {
37
+ disposers.push(mountSidebarEntry(controller))
38
+ disposers.push(mountPanel(controller, api))
39
+ } catch (error) {
40
+ console.warn('[dsh-rez-suite] mount failed:', error)
41
+ }
42
+ ctx.effect(() => () => {
43
+ for (const dispose of disposers.splice(0)) dispose()
44
+ }, 'dsh-rez-suite: ui mounts')
45
+ }