@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/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: panel-only. Company MCP rows live in cordis.patch.yml. */
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