@rezti/dsh-rez-suite 0.1.5 → 0.1.6
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 +1 -1
- package/README.zh.md +1 -1
- package/lib/client.d.ts +10 -3
- package/lib/client.js +209 -161
- package/lib/index.d.ts +20 -0
- package/lib/index.js +91 -12
- package/package.json +7 -7
- package/src/client/locales.ts +21 -7
- package/src/client/panel/ConfigTab.tsx +141 -86
- package/src/index.ts +33 -7
- package/src/protocol.ts +23 -0
- package/src/routes.ts +32 -4
- package/src/store.ts +37 -1
package/src/protocol.ts
CHANGED
|
@@ -35,12 +35,29 @@ export interface RezMcpServerSpec {
|
|
|
35
35
|
toolCallTimeoutMs?: number
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/** Company MCP endpoints persisted under ~/.dsh/dsh-rez-suite.json. Secrets are not stored here. */
|
|
39
|
+
export interface RezConnections {
|
|
40
|
+
odoo: { enabled: boolean; url: string }
|
|
41
|
+
nextcloud: { enabled: boolean; url: string; username: string }
|
|
42
|
+
wechat: { enabled: boolean }
|
|
43
|
+
homeassistant: { enabled: boolean; url: string }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface RezPublicConnection {
|
|
47
|
+
enabled: boolean
|
|
48
|
+
url?: string
|
|
49
|
+
username?: string
|
|
50
|
+
secretConfigured: boolean
|
|
51
|
+
secretRef: string
|
|
52
|
+
}
|
|
53
|
+
|
|
38
54
|
/** Full plugin configuration persisted under ~/.dsh/dsh-rez-suite.json. */
|
|
39
55
|
export interface RezConfig {
|
|
40
56
|
enabled: boolean
|
|
41
57
|
announceToAgent: boolean
|
|
42
58
|
role: RezRoleId
|
|
43
59
|
servers: Record<string, RezMcpServerSpec>
|
|
60
|
+
connections: RezConnections
|
|
44
61
|
billing: {
|
|
45
62
|
inputCostPer1k: number
|
|
46
63
|
outputCostPer1k: number
|
|
@@ -56,6 +73,12 @@ export interface RezPublicConfig {
|
|
|
56
73
|
enabled: boolean
|
|
57
74
|
role: RezRoleId
|
|
58
75
|
servers: Record<string, RezPublicServer>
|
|
76
|
+
connections: {
|
|
77
|
+
odoo: RezPublicConnection
|
|
78
|
+
nextcloud: RezPublicConnection
|
|
79
|
+
wechat: RezPublicConnection
|
|
80
|
+
homeassistant: RezPublicConnection
|
|
81
|
+
}
|
|
59
82
|
billing: { inputCostPer1k: number; outputCostPer1k: number; monthlyBudget: number }
|
|
60
83
|
}
|
|
61
84
|
|
package/src/routes.ts
CHANGED
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
8
8
|
import type { WebRoute } from '@deepseek-ai/dsh-host-webserver'
|
|
9
|
+
import {
|
|
10
|
+
REZ_CREDENTIAL_REFS,
|
|
11
|
+
REZ_SYSTEMS,
|
|
12
|
+
mergeConnections,
|
|
13
|
+
writeManagedCredential,
|
|
14
|
+
type RezSystem,
|
|
15
|
+
} from '@rezti/dsh-rez-sso'
|
|
9
16
|
import type { RezConfig, RezMcpServerSpec, RezRoleId } from './protocol.ts'
|
|
10
17
|
import { REZ_API } from './protocol.ts'
|
|
11
18
|
import type { McpHost } from './mcp-host.ts'
|
|
@@ -74,11 +81,25 @@ function mergeMaskedRecord(base: Record<string, string> | undefined, incoming: u
|
|
|
74
81
|
}
|
|
75
82
|
|
|
76
83
|
/** Apply a browser-safe public patch without overwriting masked secrets. */
|
|
77
|
-
function applyPublicPatch(current: RezConfig, patch: Record<string, unknown>): RezConfig {
|
|
84
|
+
function applyPublicPatch(current: RezConfig, patch: Record<string, unknown>): { next: RezConfig; secrets: Array<{ system: RezSystem; value: string }> } {
|
|
78
85
|
const next: RezConfig = JSON.parse(JSON.stringify(current)) as RezConfig
|
|
86
|
+
const secrets: Array<{ system: RezSystem; value: string }> = []
|
|
79
87
|
if (typeof patch.enabled === 'boolean') next.enabled = patch.enabled
|
|
80
88
|
if (patch.role === 'engineer' || patch.role === 'sales' || patch.role === 'operations' || patch.role === 'all') next.role = patch.role as RezRoleId
|
|
81
89
|
|
|
90
|
+
if (typeof patch.connections === 'object' && patch.connections !== null) {
|
|
91
|
+
const incoming = patch.connections as Record<string, unknown>
|
|
92
|
+
next.connections = mergeConnections({ ...next.connections, ...incoming })
|
|
93
|
+
for (const system of Object.keys(REZ_CREDENTIAL_REFS) as RezSystem[]) {
|
|
94
|
+
const row = incoming[system]
|
|
95
|
+
if (typeof row !== 'object' || row === null) continue
|
|
96
|
+
const secret = (row as { secret?: unknown }).secret
|
|
97
|
+
if (typeof secret === 'string' && secret.length > 0 && secret !== SECRET_MASK) {
|
|
98
|
+
secrets.push({ system, value: secret })
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
82
103
|
if (typeof patch.servers === 'object' && patch.servers !== null) {
|
|
83
104
|
for (const [id, raw] of Object.entries(patch.servers as Record<string, unknown>)) {
|
|
84
105
|
if (typeof raw !== 'object' || raw === null) continue
|
|
@@ -104,7 +125,7 @@ function applyPublicPatch(current: RezConfig, patch: Record<string, unknown>): R
|
|
|
104
125
|
if (typeof value.outputCostPer1k === 'number') next.billing.outputCostPer1k = value.outputCostPer1k
|
|
105
126
|
if (typeof value.monthlyBudget === 'number') next.billing.monthlyBudget = value.monthlyBudget
|
|
106
127
|
}
|
|
107
|
-
return next
|
|
128
|
+
return { next, secrets }
|
|
108
129
|
}
|
|
109
130
|
|
|
110
131
|
export interface RezRoutesDeps {
|
|
@@ -113,11 +134,12 @@ export interface RezRoutesDeps {
|
|
|
113
134
|
updateConfig: (next: RezConfig) => Promise<RezConfig>
|
|
114
135
|
host: McpHost
|
|
115
136
|
tokens: TokenManager
|
|
137
|
+
onCredentialWritten?: (ref: string) => void
|
|
116
138
|
}
|
|
117
139
|
|
|
118
140
|
/** Build every /api/dsh-rez-suite route (exact paths). */
|
|
119
141
|
export function makeRoutes(deps: RezRoutesDeps): WebRoute[] {
|
|
120
|
-
const { getConfig, updateConfig, host, tokens } = deps
|
|
142
|
+
const { getConfig, updateConfig, host, tokens, onCredentialWritten } = deps
|
|
121
143
|
|
|
122
144
|
const guard = (req: IncomingMessage, res: ServerResponse, method: string): boolean => {
|
|
123
145
|
if (!isLoopbackRequest(req)) {
|
|
@@ -146,8 +168,14 @@ export function makeRoutes(deps: RezRoutesDeps): WebRoute[] {
|
|
|
146
168
|
writeJson(res, 400, { error: 'invalid JSON body' })
|
|
147
169
|
return
|
|
148
170
|
}
|
|
149
|
-
const next = applyPublicPatch(getConfig(), body)
|
|
171
|
+
const { next, secrets } = applyPublicPatch(getConfig(), body)
|
|
150
172
|
const saved = await updateConfig(next)
|
|
173
|
+
for (const item of secrets) {
|
|
174
|
+
writeManagedCredential(REZ_CREDENTIAL_REFS[item.system], item.value)
|
|
175
|
+
}
|
|
176
|
+
for (const system of REZ_SYSTEMS) {
|
|
177
|
+
onCredentialWritten?.(REZ_CREDENTIAL_REFS[system])
|
|
178
|
+
}
|
|
151
179
|
writeJson(res, 200, { config: publicConfig(saved) })
|
|
152
180
|
},
|
|
153
181
|
},
|
package/src/store.ts
CHANGED
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
|
|
9
9
|
import { homedir } from 'node:os'
|
|
10
10
|
import { dirname, join, resolve } from 'node:path'
|
|
11
|
+
import {
|
|
12
|
+
REZ_CREDENTIAL_REFS,
|
|
13
|
+
REZ_DEFAULT_CONNECTIONS,
|
|
14
|
+
mergeConnections,
|
|
15
|
+
secretConfigured,
|
|
16
|
+
} from '@rezti/dsh-rez-sso'
|
|
11
17
|
import type { RezConfig, RezMcpServerSpec, RezPublicConfig } from './protocol.ts'
|
|
12
18
|
|
|
13
19
|
/** File format version. */
|
|
@@ -36,13 +42,14 @@ export function expandHome(path: string): string {
|
|
|
36
42
|
return resolve(path)
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
/** Defaults:
|
|
45
|
+
/** Defaults: company MCP endpoints. Secrets live in credentials.yaml / env. */
|
|
40
46
|
export function defaultConfig(): RezConfig {
|
|
41
47
|
return {
|
|
42
48
|
enabled: true,
|
|
43
49
|
announceToAgent: true,
|
|
44
50
|
role: 'all',
|
|
45
51
|
servers: {},
|
|
52
|
+
connections: JSON.parse(JSON.stringify(REZ_DEFAULT_CONNECTIONS)) as RezConfig['connections'],
|
|
46
53
|
billing: {
|
|
47
54
|
inputCostPer1k: 0.001,
|
|
48
55
|
outputCostPer1k: 0.002,
|
|
@@ -74,10 +81,37 @@ export function publicConfig(config: RezConfig): RezPublicConfig {
|
|
|
74
81
|
for (const [id, server] of Object.entries(config.servers)) {
|
|
75
82
|
servers[id] = maskServer(server)
|
|
76
83
|
}
|
|
84
|
+
const connections = mergeConnections(config.connections)
|
|
77
85
|
return {
|
|
78
86
|
enabled: config.enabled,
|
|
79
87
|
role: config.role,
|
|
80
88
|
servers,
|
|
89
|
+
connections: {
|
|
90
|
+
odoo: {
|
|
91
|
+
enabled: connections.odoo.enabled,
|
|
92
|
+
url: connections.odoo.url,
|
|
93
|
+
secretConfigured: secretConfigured('odoo'),
|
|
94
|
+
secretRef: REZ_CREDENTIAL_REFS.odoo,
|
|
95
|
+
},
|
|
96
|
+
nextcloud: {
|
|
97
|
+
enabled: connections.nextcloud.enabled,
|
|
98
|
+
url: connections.nextcloud.url,
|
|
99
|
+
username: connections.nextcloud.username,
|
|
100
|
+
secretConfigured: secretConfigured('nextcloud'),
|
|
101
|
+
secretRef: REZ_CREDENTIAL_REFS.nextcloud,
|
|
102
|
+
},
|
|
103
|
+
wechat: {
|
|
104
|
+
enabled: connections.wechat.enabled,
|
|
105
|
+
secretConfigured: secretConfigured('wechat'),
|
|
106
|
+
secretRef: REZ_CREDENTIAL_REFS.wechat,
|
|
107
|
+
},
|
|
108
|
+
homeassistant: {
|
|
109
|
+
enabled: connections.homeassistant.enabled,
|
|
110
|
+
url: connections.homeassistant.url,
|
|
111
|
+
secretConfigured: secretConfigured('homeassistant'),
|
|
112
|
+
secretRef: REZ_CREDENTIAL_REFS.homeassistant,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
81
115
|
billing: { ...config.billing },
|
|
82
116
|
}
|
|
83
117
|
}
|
|
@@ -96,6 +130,8 @@ export function mergeConfig(base: RezConfig, override: unknown): RezConfig {
|
|
|
96
130
|
if (typeof value.announceToAgent === 'boolean') result.announceToAgent = value.announceToAgent
|
|
97
131
|
if (value.role === 'engineer' || value.role === 'sales' || value.role === 'operations' || value.role === 'all') result.role = value.role
|
|
98
132
|
|
|
133
|
+
result.connections = mergeConnections(value.connections ?? result.connections)
|
|
134
|
+
|
|
99
135
|
if (typeof value.servers === 'object' && value.servers !== null) {
|
|
100
136
|
for (const [id, raw] of Object.entries(value.servers as Record<string, unknown>)) {
|
|
101
137
|
if (typeof raw !== 'object' || raw === null) continue
|