dsh-sidebar-suite 0.1.3 → 0.2.5
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/CHANGELOG.md +55 -0
- package/DETAIL.md +137 -64
- package/README.md +213 -90
- package/assets/icon-180.png +0 -0
- package/assets/screenshots/screenshot-1.png +0 -0
- package/assets/screenshots/screenshot-2.png +0 -0
- package/assets/screenshots/screenshot-3.png +0 -0
- package/lib/client.js +2285 -1276
- package/lib/index.js +215 -158
- package/lib/temp-session-host.mjs +842 -0
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +72 -60
- package/screenshots.json +5 -0
- package/temp-session-config.txt +53 -0
- package//344/275/277/347/224/250/350/257/264/346/230/216.md +418 -0
package/lib/index.js
CHANGED
|
@@ -1,158 +1,215 @@
|
|
|
1
|
-
// dsh-sidebar-suite — Host half.
|
|
2
|
-
//
|
|
3
|
-
// Ships only the option schema. Every layout rule lives in the Client half, so
|
|
4
|
-
// this module has no runtime dependencies of its own: `@deepseek-ai/schemastery`
|
|
5
|
-
// is resolved the way the host resolves it (bare specifier first, then the
|
|
6
|
-
// profile's own node_modules), and when neither works the plugin still loads
|
|
7
|
-
// without a schema instead of failing the whole plugin tree.
|
|
8
|
-
import { existsSync, readdirSync } from 'node:fs'
|
|
9
|
-
import { createRequire } from 'node:module'
|
|
10
|
-
import { dirname, join } from 'node:path'
|
|
11
|
-
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
climb(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
.
|
|
139
|
-
.
|
|
140
|
-
|
|
141
|
-
.
|
|
142
|
-
|
|
143
|
-
.
|
|
144
|
-
.
|
|
145
|
-
|
|
146
|
-
.
|
|
147
|
-
|
|
148
|
-
.
|
|
149
|
-
.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
1
|
+
// dsh-sidebar-suite — Host half.
|
|
2
|
+
//
|
|
3
|
+
// Ships only the option schema. Every layout rule lives in the Client half, so
|
|
4
|
+
// this module has no runtime dependencies of its own: `@deepseek-ai/schemastery`
|
|
5
|
+
// is resolved the way the host resolves it (bare specifier first, then the
|
|
6
|
+
// profile's own node_modules), and when neither works the plugin still loads
|
|
7
|
+
// without a schema instead of failing the whole plugin tree.
|
|
8
|
+
import { existsSync, readdirSync } from 'node:fs'
|
|
9
|
+
import { createRequire } from 'node:module'
|
|
10
|
+
import { dirname, join } from 'node:path'
|
|
11
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
12
|
+
|
|
13
|
+
// Load the temporary-session host lazily. If that one file ever fails to parse
|
|
14
|
+
// (a broken edit, a partial install), the three sidebar layouts must still
|
|
15
|
+
// load: a static import would take down the entire Host half with it.
|
|
16
|
+
let createTempSessionHost = null
|
|
17
|
+
let tempImportError = null
|
|
18
|
+
try {
|
|
19
|
+
const tempModule = await import('./temp-session-host.mjs')
|
|
20
|
+
createTempSessionHost = tempModule.createTempSessionHost || null
|
|
21
|
+
} catch (error) {
|
|
22
|
+
tempImportError = String(error && (error.message || error))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Values the Client half falls back to when a field is absent. */
|
|
26
|
+
const DEFAULTS = {
|
|
27
|
+
mode: 'b',
|
|
28
|
+
showQuickSwitch: true,
|
|
29
|
+
tab: 'ext',
|
|
30
|
+
foldExt: false,
|
|
31
|
+
foldWs: false,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the schema library without making the module import depend on it.
|
|
36
|
+
*
|
|
37
|
+
* A plugin installed from a local directory is linked into the profile, so the
|
|
38
|
+
* host usually resolves a bare specifier through the profile's node_modules. If
|
|
39
|
+
* the loader realpaths that link, a bare specifier fails; the profile directory
|
|
40
|
+
* itself is then the second attempt, and the directories around this file and
|
|
41
|
+
* the running process are the last resort. When nothing resolves, the plugin
|
|
42
|
+
* still loads, just without a settings schema.
|
|
43
|
+
*/
|
|
44
|
+
function looksLikeSchema(value) {
|
|
45
|
+
return typeof value === 'function' || typeof value?.object === 'function'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function tryBare() {
|
|
49
|
+
try {
|
|
50
|
+
const mod = await import('@deepseek-ai/schemastery')
|
|
51
|
+
const schema = mod && (mod.default || mod)
|
|
52
|
+
return looksLikeSchema(schema) ? schema : null
|
|
53
|
+
} catch (error) {
|
|
54
|
+
void error
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Directories whose `node_modules` may hold the profile's schema copy. */
|
|
60
|
+
function candidateBases() {
|
|
61
|
+
const bases = []
|
|
62
|
+
const push = (dir) => {
|
|
63
|
+
if (typeof dir === 'string' && dir.length > 0 && !bases.includes(dir)) bases.push(dir)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const climb = (start, levels) => {
|
|
67
|
+
let dir = start
|
|
68
|
+
for (let i = 0; i < levels && dir; i += 1) {
|
|
69
|
+
push(dir)
|
|
70
|
+
const parent = dirname(dir)
|
|
71
|
+
if (parent === dir) break
|
|
72
|
+
dir = parent
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
push(process.env.DSH_PROFILE_DIR)
|
|
77
|
+
climb(dirname(fileURLToPath(import.meta.url)), 8)
|
|
78
|
+
climb(process.cwd(), 8)
|
|
79
|
+
|
|
80
|
+
const homes = [process.env.DSH_HOME, process.env.USERPROFILE, process.env.HOME].filter(Boolean)
|
|
81
|
+
for (const home of homes) {
|
|
82
|
+
const profiles = join(home, '.dsh', 'profiles')
|
|
83
|
+
try {
|
|
84
|
+
for (const name of readdirSync(profiles)) push(join(profiles, name))
|
|
85
|
+
} catch (error) {
|
|
86
|
+
void error
|
|
87
|
+
}
|
|
88
|
+
const direct = join(home, 'profiles')
|
|
89
|
+
try {
|
|
90
|
+
for (const name of readdirSync(direct)) push(join(direct, name))
|
|
91
|
+
} catch (error) {
|
|
92
|
+
void error
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return bases
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function loadSchema() {
|
|
99
|
+
const bare = await tryBare()
|
|
100
|
+
if (bare) return bare
|
|
101
|
+
|
|
102
|
+
for (const base of candidateBases()) {
|
|
103
|
+
const packageDir = join(base, 'node_modules', '@deepseek-ai', 'schemastery')
|
|
104
|
+
const entry = join(packageDir, 'lib', 'index.mjs')
|
|
105
|
+
if (!existsSync(entry)) continue
|
|
106
|
+
|
|
107
|
+
// require() follows the package's own `exports` map, so this keeps working
|
|
108
|
+
// when the library layout changes between versions.
|
|
109
|
+
try {
|
|
110
|
+
const require = createRequire(join(base, 'package.json'))
|
|
111
|
+
const schema = require('@deepseek-ai/schemastery')
|
|
112
|
+
if (looksLikeSchema(schema)) return schema
|
|
113
|
+
} catch (error) {
|
|
114
|
+
void error
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const mod = await import(pathToFileURL(entry).href)
|
|
118
|
+
const schema = mod && (mod.default || mod)
|
|
119
|
+
if (looksLikeSchema(schema)) return schema
|
|
120
|
+
} catch (error) {
|
|
121
|
+
void error
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const schema = await loadSchema()
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Volatile fields are the ones the settings page can change without restarting
|
|
131
|
+
* this plugin: the host re-applies them in place and the Client half reads the
|
|
132
|
+
* new values through its config form.
|
|
133
|
+
*/
|
|
134
|
+
export const Config = schema === null
|
|
135
|
+
? undefined
|
|
136
|
+
: schema.object({
|
|
137
|
+
mode: schema
|
|
138
|
+
.union(['a', 'b', 'c'])
|
|
139
|
+
.default(DEFAULTS.mode)
|
|
140
|
+
.description('Sidebar layout: a folds each group, b switches the two groups with tabs, c keeps the workspaces and opens the panels as an overlay.')
|
|
141
|
+
.volatile(),
|
|
142
|
+
showQuickSwitch: schema
|
|
143
|
+
.boolean()
|
|
144
|
+
.default(DEFAULTS.showQuickSwitch)
|
|
145
|
+
.description('Show the button that cycles through the three layouts in the sidebar control bar.')
|
|
146
|
+
.volatile(),
|
|
147
|
+
tab: schema
|
|
148
|
+
.union(['ext', 'ws'])
|
|
149
|
+
.default(DEFAULTS.tab)
|
|
150
|
+
.description('Which group the tabbed layout shows first.')
|
|
151
|
+
.volatile(),
|
|
152
|
+
foldExt: schema
|
|
153
|
+
.boolean()
|
|
154
|
+
.default(DEFAULTS.foldExt)
|
|
155
|
+
.description('Folded layout: whether the panels group starts folded.')
|
|
156
|
+
.volatile(),
|
|
157
|
+
foldWs: schema
|
|
158
|
+
.boolean()
|
|
159
|
+
.default(DEFAULTS.foldWs)
|
|
160
|
+
.description('Folded layout: whether the workspaces group starts folded.')
|
|
161
|
+
.volatile(),
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
let tempHost = null
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* `connection` is the one service this bundle cannot work without: it owns the
|
|
168
|
+
* `/api` Fetch registry that serves the temporary-session routes.
|
|
169
|
+
*
|
|
170
|
+
* This MUST be a static declaration. Verified against the host's own
|
|
171
|
+
* `@deepseek-ai/cordis` 4.1.0: a plugin that registers no `inject` export still
|
|
172
|
+
* receives a ctx where reading `ctx.connection` throws
|
|
173
|
+
* "cannot get property \\"connection\\" without inject", and calling
|
|
174
|
+
* `ctx.inject(['connection'], cb)` from inside `apply` never activates — so the
|
|
175
|
+
* routes were never registered and the browser got
|
|
176
|
+
* `404 {"error":"not found"}` from Connection.createSharedFetchHandler
|
|
177
|
+
* (dsh-client-connection/lib/index.js). Every official bundle that publishes
|
|
178
|
+
* /api routes declares it statically (dsh-session-log-export:
|
|
179
|
+
* `inject = ["commands", "connection"]`).
|
|
180
|
+
*
|
|
181
|
+
* Since 0.2.0 the model is no longer read here at all: a temporary session is
|
|
182
|
+
* a real DSH session, so it inherits whatever model the user picked. That
|
|
183
|
+
* removes the whole class of "no model" failures 0.1.5/0.1.6 had to patch.
|
|
184
|
+
*/
|
|
185
|
+
export const inject = ['connection']
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Register the /api routes that track temporary sessions.
|
|
189
|
+
*
|
|
190
|
+
* `register()` is idempotent per route and treats "already registered" as
|
|
191
|
+
* success, so repeated calls (volatile config re-applies `apply`) are safe.
|
|
192
|
+
*/
|
|
193
|
+
function startTempHost(ctx) {
|
|
194
|
+
if (!createTempSessionHost) {
|
|
195
|
+
try { ctx.logger?.warn?.(`[dsh-sidebar-suite] temp-session host file failed to load: ${tempImportError}`) } catch (error) { void error }
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
if (!tempHost) tempHost = createTempSessionHost(ctx)
|
|
200
|
+
else tempHost.setContext(ctx)
|
|
201
|
+
tempHost.register(ctx)
|
|
202
|
+
try { ctx.logger?.debug?.(`[dsh-sidebar-suite] temp routes=${tempHost.state.routes.registered}`) } catch (error) { void error }
|
|
203
|
+
} catch (error) {
|
|
204
|
+
try { ctx.logger?.debug?.(`[dsh-sidebar-suite] temp session unavailable: ${error && error.message}`) } catch (inner) { void inner }
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function apply(ctx) {
|
|
209
|
+
try {
|
|
210
|
+
ctx.logger?.debug?.('[dsh-sidebar-suite] host half ready')
|
|
211
|
+
} catch (error) {
|
|
212
|
+
void error
|
|
213
|
+
}
|
|
214
|
+
startTempHost(ctx)
|
|
215
|
+
}
|