@sparkelf/dsh-plus 0.2.0-rc.13 → 0.2.0-rc.15

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/cordis.patch.yml CHANGED
@@ -6,12 +6,19 @@
6
6
  trustedHosts: !!js ctx.webRuntime.trustedHosts
7
7
  browserAuthentication: disabled
8
8
 
9
+ # The derived index behind session history search. `first-search` builds it when a
10
+ # search first asks for it rather than delaying every start.
9
11
  - id: session-query-sqlite
10
12
  config:
11
13
  path: !!js dshHomePath('storages/session-query.sqlite')
12
14
  openAt: first-search
13
15
 
16
+ # The model-facing tools over that index: search, trace, and event read. The
17
+ # backend above answers only once something mounts the tools that call it, so a
18
+ # deployment carrying the backend alone has an index nothing asks for.
14
19
  - insert:
20
+ - id: plus-session-query-tool
21
+ name: '@deepseek-ai/dsh-tool-session-query'
15
22
  # The sidebar skill center. It reads the loaded skills through `ctx.skills`
16
23
  # and registers its row and page into the shell's own slots, so the panel
17
24
  # matches the official panels beside it.
@@ -57,3 +64,10 @@
57
64
  provider: fork
58
65
  enabled: false
59
66
  maxDepth: 0
67
+
68
+ # The first content search builds the whole index in one transaction, so it needs far
69
+ # more than a steady-state query. This row follows the insert above because a patch
70
+ # only reaches rows an earlier patch created.
71
+ - id: plus-session-query-tool
72
+ config:
73
+ searchTimeoutMs: 900000
package/lib/bin.js CHANGED
@@ -75,6 +75,296 @@ function newerVersion(name, installed, registry = "https://registry.npmjs.org")
75
75
  return publishedVersions(name, registry).filter((entry) => semver.gt(entry.version, installed)).at(-1);
76
76
  }
77
77
  //#endregion
78
+ //#region lib/types/standalone-capabilities.js
79
+ /** Capabilities offered at install time, in interview order. */
80
+ const CAPABILITIES = [
81
+ {
82
+ id: "exa",
83
+ title: "Web search (Exa)",
84
+ detail: "Search the web through Exa instead of the built-in provider; needs an API key",
85
+ needsEndpoint: false
86
+ },
87
+ {
88
+ id: "mineru",
89
+ title: "PDF parsing (MinerU)",
90
+ detail: "Parse uploaded PDFs locally; the service is installed and started here",
91
+ needsEndpoint: true
92
+ },
93
+ {
94
+ id: "officecli",
95
+ title: "Office documents (OfficeCLI)",
96
+ detail: "Create and open DOCX, XLSX, and PPTX deliverables",
97
+ needsEndpoint: false
98
+ },
99
+ {
100
+ id: "computer-use",
101
+ title: "Windows desktop control (computer-use)",
102
+ detail: "Drive the Windows desktop from WSL through the cua driver",
103
+ needsEndpoint: false
104
+ }
105
+ ];
106
+ /** Where this command starts MinerU when the capability is enabled. */
107
+ const DEFAULT_MINERU_ENDPOINT = "http://127.0.0.1:8000/file_parse";
108
+ /** File under the deployment home recording which capabilities were enabled. */
109
+ const CAPABILITY_RECORD = "capabilities.json";
110
+ /** Deployment env file the launcher reads as its `user-env` layer. */
111
+ const CAPABILITY_ENV_FILE = ".env";
112
+ /** One prompt on the terminal, resolving the trimmed line the user typed. */
113
+ function ask(question) {
114
+ return new Promise((resolveAnswer) => {
115
+ process.stdout.write(question);
116
+ process.stdin.once("data", (chunk) => {
117
+ resolveAnswer(String(chunk).trim());
118
+ });
119
+ });
120
+ }
121
+ /** Ask one yes/no question, defaulting to yes. */
122
+ async function confirmDefaultYes(question) {
123
+ const answer = (await ask(question + " [Y/n] ")).toLowerCase();
124
+ return answer === "" || answer === "y" || answer === "yes";
125
+ }
126
+ /**
127
+ * Interview the user about the optional capabilities.
128
+ *
129
+ * Each is offered on its own line and defaults to enabled, so pressing Enter
130
+ * through the interview produces a deployment whose features work. The answers are
131
+ * returned rather than applied, so a caller can report what it will do before it
132
+ * touches the profile.
133
+ *
134
+ * @param interactive - when false, every capability is enabled without asking.
135
+ * @returns the ids to enable and the values the enabled ones need.
136
+ */
137
+ async function interviewCapabilities(interactive) {
138
+ if (!interactive) return { enabled: CAPABILITIES.map((capability) => capability.id) };
139
+ console.log("");
140
+ console.log("Optional capabilities. Press Enter to accept the default (all enabled),");
141
+ console.log("or answer n for any you do not want.");
142
+ console.log("");
143
+ const enabled = [];
144
+ for (const capability of CAPABILITIES) if (await confirmDefaultYes(" Enable " + capability.title + "? (" + capability.detail + ")")) enabled.push(capability.id);
145
+ let exaApiKey;
146
+ if (enabled.includes("exa")) {
147
+ console.log("");
148
+ console.log(" Exa needs an API key. Create one at https://dashboard.exa.ai/api-keys");
149
+ const typed = await ask(" Exa API key (leave empty to add it later): ");
150
+ if (typed !== "") exaApiKey = typed;
151
+ }
152
+ let mineruEndpoint;
153
+ if (enabled.includes("mineru")) {
154
+ const typed = await ask(" MinerU endpoint [http://127.0.0.1:8000/file_parse]: ");
155
+ mineruEndpoint = typed === "" ? DEFAULT_MINERU_ENDPOINT : typed;
156
+ }
157
+ return {
158
+ enabled,
159
+ ...exaApiKey === void 0 ? {} : { exaApiKey },
160
+ ...mineruEndpoint === void 0 ? {} : { mineruEndpoint }
161
+ };
162
+ }
163
+ /** Names this module owns in the deployment env file. */
164
+ const CAPABILITY_ENV_NAMES = ["DSH_MINERU_ENDPOINT", "EXA_API_KEY"];
165
+ /**
166
+ * The deployment env file, with one assignment per enabled capability that reaches
167
+ * its service through the launch environment.
168
+ *
169
+ * MinerU is switched on by the presence of its endpoint and Exa reads its key from
170
+ * the launch environment, so a capability the user selected is enabled here as well
171
+ * as in the profile layer. Only the names this interview owns are rewritten: a
172
+ * deployment that keeps its own assignments in the same file keeps them, and
173
+ * deselecting a capability removes the assignment that turned it on.
174
+ *
175
+ * @param answers - the interview's answers.
176
+ * @param existing - the file's current text, when it already exists.
177
+ * @returns the environment file's text.
178
+ */
179
+ function capabilityEnvironment(answers, existing = "") {
180
+ const enabled = new Set(answers.enabled);
181
+ const owned = new Set(CAPABILITY_ENV_NAMES);
182
+ const kept = existing.split("\n").filter((line) => {
183
+ const name = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(line)?.[1];
184
+ return line.trim() !== "" && !line.trimStart().startsWith("#") && (name === void 0 || !owned.has(name));
185
+ });
186
+ const written = ["# Written by dsh-plus start from the capability interview. Enabling or", "# disabling a capability rewrites the lines below; other lines are kept."];
187
+ if (enabled.has("mineru")) written.push("DSH_MINERU_ENDPOINT=" + (answers.mineruEndpoint ?? "http://127.0.0.1:8000/file_parse"));
188
+ if (enabled.has("exa") && answers.exaApiKey !== void 0) written.push("EXA_API_KEY=" + answers.exaApiKey);
189
+ return [...written, ...kept].join("\n") + "\n";
190
+ }
191
+ /**
192
+ * The profile patch layer that turns the selected capabilities on.
193
+ *
194
+ * The file is data the profile's loader merges, so enabling a capability writes a
195
+ * row rather than editing the deployment. An empty selection still writes the file,
196
+ * because a layer that exists and mounts nothing is how a previously enabled
197
+ * capability is turned back off.
198
+ *
199
+ * The profile's own layer is a separate file with its own name: a deployment that
200
+ * configured something by hand keeps it, and the capability rows are rewritten
201
+ * whole on every start.
202
+ *
203
+ * @param answers - the interview's answers.
204
+ * @returns the patch layer's YAML text.
205
+ */
206
+ function capabilityPatchLayer(answers) {
207
+ const enabled = new Set(answers.enabled);
208
+ const rows = [];
209
+ if (enabled.has("exa")) rows.push(" - id: web-search-exa", " name: '@deepseek-ai/dsh-web-search-exa'", " config:", " searchType: auto", " numResults: 8");
210
+ if (enabled.has("computer-use")) rows.push(" - id: computer-use", " name: '@deepseek-ai/dsh-computer-use'", " - id: computer-use-cua-driver-mcp", " name: '@deepseek-ai/dsh-experimental-computer-use-cua-driver-mcp'");
211
+ const lines = ["# Written by dsh-plus start from the capability interview. Enabling or disabling a capability", "# rewrites this file; edits here are replaced."];
212
+ if (rows.length > 0) lines.push("- insert:", ...rows);
213
+ if (enabled.has("exa")) lines.push("", "# Route web_search through Exa rather than the built-in provider.", "- id: web", " config:", " searchProvider: exa");
214
+ return lines.join("\n") + "\n";
215
+ }
216
+ /** Run one command, reporting a failure rather than throwing. */
217
+ function runStep(command, args) {
218
+ return spawnSync(command, [...args], { stdio: "inherit" }).status === 0;
219
+ }
220
+ /**
221
+ * Install and start the backing services the selected capabilities need.
222
+ *
223
+ * Enabling a capability means the deployment expects its service to answer, so the
224
+ * command that offers the choice is also the command that provides it. Each step
225
+ * reports what it is doing and, on failure, the command a user can run by hand —
226
+ * a missing runtime is a fact about the host, not a reason to abandon the install.
227
+ *
228
+ * @param answers - the interview's answers.
229
+ * @param home - the deployment home, where generated service units are recorded.
230
+ * @returns the ids whose service is ready.
231
+ */
232
+ async function installCapabilityServices(answers, home) {
233
+ const enabled = new Set(answers.enabled);
234
+ const ready = [];
235
+ mkdirSync(home, { recursive: true });
236
+ writeFileSync(join(home, CAPABILITY_RECORD), JSON.stringify({
237
+ enabled: [...answers.enabled],
238
+ ...answers.mineruEndpoint === void 0 ? {} : { mineruEndpoint: answers.mineruEndpoint }
239
+ }, null, 2) + "\n");
240
+ if (enabled.has("mineru")) {
241
+ console.log("");
242
+ console.log(" MinerU: installing the parser and starting its service...");
243
+ if (installMineru(home) && startMineru(home)) {
244
+ ready.push("mineru");
245
+ console.log(" MinerU: ready at " + (answers.mineruEndpoint ?? "http://127.0.0.1:8000/file_parse"));
246
+ } else {
247
+ console.log(" MinerU: not installed. Install it later with:");
248
+ console.log(" pip install -U \"mineru[core]\"");
249
+ console.log(" mineru-api --host 127.0.0.1 --port 8000");
250
+ }
251
+ }
252
+ if (enabled.has("officecli")) ready.push("officecli");
253
+ if (enabled.has("computer-use")) if (desktopDriverAvailable()) ready.push("computer-use");
254
+ else {
255
+ console.log("");
256
+ console.log(" computer-use: the cua driver was not found on PATH. The plugin is");
257
+ console.log(" still mounted, so it starts once the driver is installed:");
258
+ console.log(" install the cua-driver release for this architecture, then restart");
259
+ }
260
+ return ready;
261
+ }
262
+ /**
263
+ * Install MinerU when it is absent and start its API service.
264
+ *
265
+ * MinerU is a Python package whose API server answers on a local port; the profile
266
+ * points at that port. The parser is installed into a virtual environment under the
267
+ * deployment home and an existing one is upgraded rather than skipped, so enabling
268
+ * the capability keeps the parser current without touching the system interpreter.
269
+ *
270
+ * @param home - the deployment home that owns the venv.
271
+ * @returns whether the package is installed; not whether it answered.
272
+ */
273
+ function installMineru(home) {
274
+ const python = pythonInterpreter();
275
+ if (python === void 0) {
276
+ console.log(" MinerU: no Python interpreter found on PATH.");
277
+ return false;
278
+ }
279
+ const venv = join(home, ".mineru-venv");
280
+ if (!runStep(python, [
281
+ "-m",
282
+ "venv",
283
+ venv
284
+ ])) return false;
285
+ return runStep(join(venv, "bin", "python"), [
286
+ "-m",
287
+ "pip",
288
+ "install",
289
+ "-U",
290
+ "mineru[all]"
291
+ ]);
292
+ }
293
+ /**
294
+ * Start the MinerU API server as a background service when none answers yet.
295
+ *
296
+ * The service has to outlive the install command, so it is registered with the
297
+ * host's service manager when one is available and otherwise started detached.
298
+ *
299
+ * @returns whether the server is running after this call.
300
+ */
301
+ function startMineru(home) {
302
+ const api = mineruApiBinary(home);
303
+ if (api === void 0) return false;
304
+ if (mineruAnswers()) return true;
305
+ if (spawnSync("systemctl", ["--version"], { stdio: "ignore" }).status === 0) {
306
+ writeFileSync("/etc/systemd/system/mineru-api.service", [
307
+ "[Unit]",
308
+ "Description=MinerU document parsing API for DeepSeek Harness Plus",
309
+ "After=network-online.target",
310
+ "Wants=network-online.target",
311
+ "",
312
+ "[Service]",
313
+ "Type=simple",
314
+ "User=root",
315
+ "ExecStart=" + api + " --host 127.0.0.1 --port 8000",
316
+ "Restart=on-failure",
317
+ "RestartSec=2",
318
+ "TimeoutStopSec=15",
319
+ "",
320
+ "[Install]",
321
+ "WantedBy=multi-user.target",
322
+ ""
323
+ ].join("\n"));
324
+ spawnSync("systemctl", ["daemon-reload"], { stdio: "ignore" });
325
+ return spawnSync("systemctl", [
326
+ "enable",
327
+ "--now",
328
+ "mineru-api.service"
329
+ ], { stdio: "ignore" }).status === 0;
330
+ }
331
+ console.log(" MinerU: start the API server with:");
332
+ console.log(" " + api + " --host 127.0.0.1 --port 8000");
333
+ return false;
334
+ }
335
+ /** The Python interpreter to install MinerU with, preferring python3. */
336
+ function pythonInterpreter() {
337
+ return ["python3", "python"].find((candidate) => spawnSync(candidate, ["--version"], { stdio: "ignore" }).status === 0);
338
+ }
339
+ /**
340
+ * The MinerU API entry point, wherever the install placed it.
341
+ *
342
+ * The venv this command creates comes first: it is the interpreter the parser was
343
+ * installed into, so its `mineru-api` is the one that can import MinerU. A binary on
344
+ * PATH is a fallback for a deployment that installed MinerU some other way.
345
+ *
346
+ * @param home - the deployment home that owns the venv.
347
+ * @returns the command to run, or `undefined` when none answers.
348
+ */
349
+ function mineruApiBinary(home) {
350
+ return [join(home, ".mineru-venv", "bin", "mineru-api"), "mineru-api"].find((candidate) => spawnSync(candidate, ["--help"], { stdio: "ignore" }).status === 0);
351
+ }
352
+ /** Whether a MinerU API server already answers on the default port. */
353
+ function mineruAnswers() {
354
+ return spawnSync("curl", [
355
+ "-sf",
356
+ "-o",
357
+ "/dev/null",
358
+ "--max-time",
359
+ "3",
360
+ "http://127.0.0.1:8000/docs"
361
+ ], { stdio: "ignore" }).status === 0;
362
+ }
363
+ /** Whether the Windows-side desktop driver answers from this shell. */
364
+ function desktopDriverAvailable() {
365
+ return spawnSync("cua-driver", ["--version"], { stdio: "ignore" }).status === 0;
366
+ }
367
+ //#endregion
78
368
  //#region lib/types/standalone-server.js
79
369
  /**
80
370
  * Process lifecycle for one standalone Plus server.
@@ -681,6 +971,8 @@ function resolveInstalledPackage(from, packageName) {
681
971
  */
682
972
  /** Milliseconds a start waits for the server to answer before reporting failure. */
683
973
  const READY_TIMEOUT_MILLISECONDS = 9e4;
974
+ /** Profile patch file the capability interview rewrites (the profile's user layer). */
975
+ const CAPABILITY_PATCH_FILE = "cordis.patch.yml";
684
976
  function parseStartOptions(argv) {
685
977
  let port = DEFAULT_PORT;
686
978
  let host = "127.0.0.1";
@@ -801,6 +1093,15 @@ async function start(argv) {
801
1093
  const created = ensureProfile(paths, installationRoot());
802
1094
  console.log(created ? "Created the plus profile at " + paths.profileDirectory : "Using the existing plus profile");
803
1095
  for (const label of applyProfileNpmPatches(paths.distributionDirectory, paths.profileDirectory)) console.log("Applied the reviewed patch " + label);
1096
+ const answers = created || !existsSync(join(paths.home, "capabilities.json")) ? await interviewCapabilities(true) : void 0;
1097
+ if (answers !== void 0) {
1098
+ const ready = await installCapabilityServices(answers, paths.home);
1099
+ writeCapabilityPatch(paths.profileDirectory, answers);
1100
+ writeCapabilityEnvironment(paths.home, answers);
1101
+ console.log(" Enabled: " + (answers.enabled.length === 0 ? "(none)" : answers.enabled.join(", ")));
1102
+ if (answers.enabled.length > 0) console.log(" Services ready: " + (ready.length === 0 ? "(none)" : ready.join(", ")));
1103
+ if (answers.enabled.includes("exa") && answers.exaApiKey === void 0) console.log(" Exa: add EXA_API_KEY to " + join(paths.home, CAPABILITY_ENV_FILE) + " when you have a key.");
1104
+ }
804
1105
  const entry = launcherEntry(anchor);
805
1106
  if (options.foreground) return runForeground(entry, options.port, options.host, options.open);
806
1107
  const existing = readState(paths.home);
@@ -942,6 +1243,40 @@ async function update(argv) {
942
1243
  if (readState(paths.home) !== void 0) console.log("The running server still serves " + installed + "; run dsh-plus restart to load the new release.");
943
1244
  return 0;
944
1245
  }
1246
+ /**
1247
+ * Write the profile's capability patch layer.
1248
+ *
1249
+ * The profile's loader merges this file, so enabling a capability is a data change
1250
+ * rather than an edit to the deployment. Writing it on every configured run also
1251
+ * turns a capability back off when the interview no longer selects it.
1252
+ *
1253
+ * @param profileDirectory - the profile whose layer is replaced.
1254
+ * @param answers - the interview's answers.
1255
+ */
1256
+ function writeCapabilityPatch(profileDirectory, answers) {
1257
+ const path = join(profileDirectory, CAPABILITY_PATCH_FILE);
1258
+ const existing = existsSync(path) ? readFileSync(path, "utf8") : void 0;
1259
+ if (existing !== void 0 && !existing.includes("Written by dsh-plus start from the capability interview.")) {
1260
+ writeFileSync(path + ".before-capabilities", existing);
1261
+ console.log(" Kept the existing profile layer at cordis.patch.yml.before-capabilities");
1262
+ }
1263
+ writeFileSync(path, capabilityPatchLayer(answers));
1264
+ }
1265
+ /**
1266
+ * Write the deployment's capability environment file.
1267
+ *
1268
+ * MinerU is switched on by the presence of its endpoint and Exa reads its key from
1269
+ * the launch environment, so the answers reach those two through `$DSH_HOME/.env`
1270
+ * rather than through the profile layer. The file is replaced whole on every
1271
+ * configured start, which is also how a capability the user dropped stops applying.
1272
+ *
1273
+ * @param home - the deployment home whose env file the launcher reads.
1274
+ * @param answers - the interview's answers.
1275
+ */
1276
+ function writeCapabilityEnvironment(home, answers) {
1277
+ const path = join(home, CAPABILITY_ENV_FILE);
1278
+ writeFileSync(path, capabilityEnvironment(answers, existsSync(path) ? readFileSync(path, "utf8") : ""));
1279
+ }
945
1280
  /** Ask one yes/no question on the terminal. */
946
1281
  function confirm(question) {
947
1282
  return new Promise((resolveAnswer) => {
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The optional capabilities a Plus installation can enable, and the interview that
3
+ * decides which ones to set up.
4
+ *
5
+ * A standalone installation works without any of them: the agent runs, the shell
6
+ * runs, and files are read and written. Each entry here adds a capability whose
7
+ * backing service lives outside npm — a search provider that needs a key, a PDF
8
+ * parser that needs a local server, an Office toolchain that needs a runtime — so
9
+ * the choice is offered once, at install time, rather than left to a user who has
10
+ * no way to know what the deployment is missing.
11
+ *
12
+ * Every capability is selected by default. A user who wants the plain harness
13
+ * deselects them; a user who accepts the defaults ends up with a deployment whose
14
+ * features work, which is the state the interactive question exists to reach.
15
+ *
16
+ * Enabling a capability has to mean the feature works, so each entry carries both
17
+ * halves of that: the profile row that mounts it and the service it needs. A
18
+ * capability that only asked a question and then dropped the answer would report
19
+ * success while the deployment stayed exactly as it was.
20
+ *
21
+ * @module @sparkelf/dsh-plus/standalone-capabilities
22
+ */
23
+ /** One optional capability a deployment can enable. */
24
+ export interface Capability {
25
+ /** Stable key used on the command line and in the profile. */
26
+ readonly id: string;
27
+ /** One-line name shown in the interview. */
28
+ readonly title: string;
29
+ /** What the deployment gains, shown beside the name. */
30
+ readonly detail: string;
31
+ /** Whether the backing service needs an address the user must supply. */
32
+ readonly needsEndpoint: boolean;
33
+ }
34
+ /** Capabilities offered at install time, in interview order. */
35
+ export declare const CAPABILITIES: readonly Capability[];
36
+ /** The answers one interview produced. */
37
+ export interface CapabilityAnswers {
38
+ /** Capability ids the user kept selected. */
39
+ readonly enabled: readonly string[];
40
+ /** Exa API key when web search was enabled and the user supplied one. */
41
+ readonly exaApiKey?: string;
42
+ /** MinerU endpoint to write; defaults to the local service this command starts. */
43
+ readonly mineruEndpoint?: string;
44
+ }
45
+ /** Where this command starts MinerU when the capability is enabled. */
46
+ export declare const DEFAULT_MINERU_ENDPOINT = "http://127.0.0.1:8000/file_parse";
47
+ /** File under the deployment home recording which capabilities were enabled. */
48
+ export declare const CAPABILITY_RECORD = "capabilities.json";
49
+ /** Deployment env file the launcher reads as its `user-env` layer. */
50
+ export declare const CAPABILITY_ENV_FILE = ".env";
51
+ /** Comment that identifies a profile layer this interview wrote. */
52
+ export declare const CAPABILITY_MARKER = "Written by dsh-plus start from the capability interview.";
53
+ /**
54
+ * Interview the user about the optional capabilities.
55
+ *
56
+ * Each is offered on its own line and defaults to enabled, so pressing Enter
57
+ * through the interview produces a deployment whose features work. The answers are
58
+ * returned rather than applied, so a caller can report what it will do before it
59
+ * touches the profile.
60
+ *
61
+ * @param interactive - when false, every capability is enabled without asking.
62
+ * @returns the ids to enable and the values the enabled ones need.
63
+ */
64
+ export declare function interviewCapabilities(interactive: boolean): Promise<CapabilityAnswers>;
65
+ /**
66
+ * The deployment env file, with one assignment per enabled capability that reaches
67
+ * its service through the launch environment.
68
+ *
69
+ * MinerU is switched on by the presence of its endpoint and Exa reads its key from
70
+ * the launch environment, so a capability the user selected is enabled here as well
71
+ * as in the profile layer. Only the names this interview owns are rewritten: a
72
+ * deployment that keeps its own assignments in the same file keeps them, and
73
+ * deselecting a capability removes the assignment that turned it on.
74
+ *
75
+ * @param answers - the interview's answers.
76
+ * @param existing - the file's current text, when it already exists.
77
+ * @returns the environment file's text.
78
+ */
79
+ export declare function capabilityEnvironment(answers: CapabilityAnswers, existing?: string): string;
80
+ /**
81
+ * The profile patch layer that turns the selected capabilities on.
82
+ *
83
+ * The file is data the profile's loader merges, so enabling a capability writes a
84
+ * row rather than editing the deployment. An empty selection still writes the file,
85
+ * because a layer that exists and mounts nothing is how a previously enabled
86
+ * capability is turned back off.
87
+ *
88
+ * The profile's own layer is a separate file with its own name: a deployment that
89
+ * configured something by hand keeps it, and the capability rows are rewritten
90
+ * whole on every start.
91
+ *
92
+ * @param answers - the interview's answers.
93
+ * @returns the patch layer's YAML text.
94
+ */
95
+ export declare function capabilityPatchLayer(answers: CapabilityAnswers): string;
96
+ /**
97
+ * Install and start the backing services the selected capabilities need.
98
+ *
99
+ * Enabling a capability means the deployment expects its service to answer, so the
100
+ * command that offers the choice is also the command that provides it. Each step
101
+ * reports what it is doing and, on failure, the command a user can run by hand —
102
+ * a missing runtime is a fact about the host, not a reason to abandon the install.
103
+ *
104
+ * @param answers - the interview's answers.
105
+ * @param home - the deployment home, where generated service units are recorded.
106
+ * @returns the ids whose service is ready.
107
+ */
108
+ export declare function installCapabilityServices(answers: CapabilityAnswers, home: string): Promise<readonly string[]>;
109
+ //# sourceMappingURL=standalone-capabilities.d.ts.map
@@ -0,0 +1,324 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdirSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ /** Capabilities offered at install time, in interview order. */
5
+ export const CAPABILITIES = [
6
+ {
7
+ id: 'exa',
8
+ title: 'Web search (Exa)',
9
+ detail: 'Search the web through Exa instead of the built-in provider; needs an API key',
10
+ needsEndpoint: false,
11
+ },
12
+ {
13
+ id: 'mineru',
14
+ title: 'PDF parsing (MinerU)',
15
+ detail: 'Parse uploaded PDFs locally; the service is installed and started here',
16
+ needsEndpoint: true,
17
+ },
18
+ {
19
+ id: 'officecli',
20
+ title: 'Office documents (OfficeCLI)',
21
+ detail: 'Create and open DOCX, XLSX, and PPTX deliverables',
22
+ needsEndpoint: false,
23
+ },
24
+ {
25
+ id: 'computer-use',
26
+ title: 'Windows desktop control (computer-use)',
27
+ detail: 'Drive the Windows desktop from WSL through the cua driver',
28
+ needsEndpoint: false,
29
+ },
30
+ ];
31
+ /** Where this command starts MinerU when the capability is enabled. */
32
+ export const DEFAULT_MINERU_ENDPOINT = 'http://127.0.0.1:8000/file_parse';
33
+ /** File under the deployment home recording which capabilities were enabled. */
34
+ export const CAPABILITY_RECORD = 'capabilities.json';
35
+ /** Deployment env file the launcher reads as its `user-env` layer. */
36
+ export const CAPABILITY_ENV_FILE = '.env';
37
+ /** Comment that identifies a profile layer this interview wrote. */
38
+ export const CAPABILITY_MARKER = 'Written by dsh-plus start from the capability interview.';
39
+ /** One prompt on the terminal, resolving the trimmed line the user typed. */
40
+ function ask(question) {
41
+ return new Promise((resolveAnswer) => {
42
+ process.stdout.write(question);
43
+ process.stdin.once('data', (chunk) => {
44
+ resolveAnswer(String(chunk).trim());
45
+ });
46
+ });
47
+ }
48
+ /** Ask one yes/no question, defaulting to yes. */
49
+ async function confirmDefaultYes(question) {
50
+ const answer = (await ask(question + ' [Y/n] ')).toLowerCase();
51
+ return answer === '' || answer === 'y' || answer === 'yes';
52
+ }
53
+ /**
54
+ * Interview the user about the optional capabilities.
55
+ *
56
+ * Each is offered on its own line and defaults to enabled, so pressing Enter
57
+ * through the interview produces a deployment whose features work. The answers are
58
+ * returned rather than applied, so a caller can report what it will do before it
59
+ * touches the profile.
60
+ *
61
+ * @param interactive - when false, every capability is enabled without asking.
62
+ * @returns the ids to enable and the values the enabled ones need.
63
+ */
64
+ export async function interviewCapabilities(interactive) {
65
+ if (!interactive)
66
+ return { enabled: CAPABILITIES.map(capability => capability.id) };
67
+ console.log('');
68
+ console.log('Optional capabilities. Press Enter to accept the default (all enabled),');
69
+ console.log('or answer n for any you do not want.');
70
+ console.log('');
71
+ const enabled = [];
72
+ for (const capability of CAPABILITIES) {
73
+ if (await confirmDefaultYes(' Enable ' + capability.title + '? (' + capability.detail + ')')) {
74
+ enabled.push(capability.id);
75
+ }
76
+ }
77
+ let exaApiKey;
78
+ if (enabled.includes('exa')) {
79
+ console.log('');
80
+ console.log(' Exa needs an API key. Create one at https://dashboard.exa.ai/api-keys');
81
+ const typed = await ask(' Exa API key (leave empty to add it later): ');
82
+ if (typed !== '')
83
+ exaApiKey = typed;
84
+ }
85
+ let mineruEndpoint;
86
+ if (enabled.includes('mineru')) {
87
+ // The service is installed and started by this command on the local port, so the
88
+ // default is the address it will answer on; a user running MinerU elsewhere
89
+ // overrides it here rather than editing the profile afterwards.
90
+ const typed = await ask(' MinerU endpoint [' + DEFAULT_MINERU_ENDPOINT + ']: ');
91
+ mineruEndpoint = typed === '' ? DEFAULT_MINERU_ENDPOINT : typed;
92
+ }
93
+ return {
94
+ enabled,
95
+ ...exaApiKey === undefined ? {} : { exaApiKey },
96
+ ...mineruEndpoint === undefined ? {} : { mineruEndpoint },
97
+ };
98
+ }
99
+ /** Names this module owns in the deployment env file. */
100
+ const CAPABILITY_ENV_NAMES = ['DSH_MINERU_ENDPOINT', 'EXA_API_KEY'];
101
+ /**
102
+ * The deployment env file, with one assignment per enabled capability that reaches
103
+ * its service through the launch environment.
104
+ *
105
+ * MinerU is switched on by the presence of its endpoint and Exa reads its key from
106
+ * the launch environment, so a capability the user selected is enabled here as well
107
+ * as in the profile layer. Only the names this interview owns are rewritten: a
108
+ * deployment that keeps its own assignments in the same file keeps them, and
109
+ * deselecting a capability removes the assignment that turned it on.
110
+ *
111
+ * @param answers - the interview's answers.
112
+ * @param existing - the file's current text, when it already exists.
113
+ * @returns the environment file's text.
114
+ */
115
+ export function capabilityEnvironment(answers, existing = '') {
116
+ const enabled = new Set(answers.enabled);
117
+ const owned = new Set(CAPABILITY_ENV_NAMES);
118
+ const kept = existing.split('\n').filter((line) => {
119
+ const name = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(line)?.[1];
120
+ return line.trim() !== '' && !line.trimStart().startsWith('#') && (name === undefined || !owned.has(name));
121
+ });
122
+ const written = [
123
+ '# Written by dsh-plus start from the capability interview. Enabling or',
124
+ '# disabling a capability rewrites the lines below; other lines are kept.',
125
+ ];
126
+ if (enabled.has('mineru')) {
127
+ written.push('DSH_MINERU_ENDPOINT=' + (answers.mineruEndpoint ?? DEFAULT_MINERU_ENDPOINT));
128
+ }
129
+ if (enabled.has('exa') && answers.exaApiKey !== undefined) {
130
+ written.push('EXA_API_KEY=' + answers.exaApiKey);
131
+ }
132
+ return [...written, ...kept].join('\n') + '\n';
133
+ }
134
+ /**
135
+ * The profile patch layer that turns the selected capabilities on.
136
+ *
137
+ * The file is data the profile's loader merges, so enabling a capability writes a
138
+ * row rather than editing the deployment. An empty selection still writes the file,
139
+ * because a layer that exists and mounts nothing is how a previously enabled
140
+ * capability is turned back off.
141
+ *
142
+ * The profile's own layer is a separate file with its own name: a deployment that
143
+ * configured something by hand keeps it, and the capability rows are rewritten
144
+ * whole on every start.
145
+ *
146
+ * @param answers - the interview's answers.
147
+ * @returns the patch layer's YAML text.
148
+ */
149
+ export function capabilityPatchLayer(answers) {
150
+ const enabled = new Set(answers.enabled);
151
+ const rows = [];
152
+ if (enabled.has('exa')) {
153
+ rows.push(' - id: web-search-exa', " name: '@deepseek-ai/dsh-web-search-exa'", ' config:', ' searchType: auto', ' numResults: 8');
154
+ }
155
+ if (enabled.has('computer-use')) {
156
+ rows.push(' - id: computer-use', " name: '@deepseek-ai/dsh-computer-use'", ' - id: computer-use-cua-driver-mcp', " name: '@deepseek-ai/dsh-experimental-computer-use-cua-driver-mcp'");
157
+ }
158
+ const lines = [
159
+ '# ' + CAPABILITY_MARKER + ' Enabling or disabling a capability',
160
+ '# rewrites this file; edits here are replaced.',
161
+ ];
162
+ if (rows.length > 0)
163
+ lines.push('- insert:', ...rows);
164
+ if (enabled.has('exa')) {
165
+ lines.push('', '# Route web_search through Exa rather than the built-in provider.', '- id: web', ' config:', ' searchProvider: exa');
166
+ }
167
+ return lines.join('\n') + '\n';
168
+ }
169
+ /** Run one command, reporting a failure rather than throwing. */
170
+ function runStep(command, args) {
171
+ const result = spawnSync(command, [...args], { stdio: 'inherit' });
172
+ return result.status === 0;
173
+ }
174
+ /**
175
+ * Install and start the backing services the selected capabilities need.
176
+ *
177
+ * Enabling a capability means the deployment expects its service to answer, so the
178
+ * command that offers the choice is also the command that provides it. Each step
179
+ * reports what it is doing and, on failure, the command a user can run by hand —
180
+ * a missing runtime is a fact about the host, not a reason to abandon the install.
181
+ *
182
+ * @param answers - the interview's answers.
183
+ * @param home - the deployment home, where generated service units are recorded.
184
+ * @returns the ids whose service is ready.
185
+ */
186
+ export async function installCapabilityServices(answers, home) {
187
+ const enabled = new Set(answers.enabled);
188
+ const ready = [];
189
+ // The record lets a later run name the capabilities this deployment enabled
190
+ // instead of interviewing again, and gives doctor something to check against.
191
+ mkdirSync(home, { recursive: true });
192
+ writeFileSync(join(home, CAPABILITY_RECORD), JSON.stringify({
193
+ enabled: [...answers.enabled],
194
+ ...answers.mineruEndpoint === undefined ? {} : { mineruEndpoint: answers.mineruEndpoint },
195
+ }, null, 2) + '\n');
196
+ if (enabled.has('mineru')) {
197
+ console.log('');
198
+ console.log(' MinerU: installing the parser and starting its service...');
199
+ const installed = installMineru(home);
200
+ if (installed && startMineru(home)) {
201
+ ready.push('mineru');
202
+ console.log(' MinerU: ready at ' + (answers.mineruEndpoint ?? DEFAULT_MINERU_ENDPOINT));
203
+ }
204
+ else {
205
+ console.log(' MinerU: not installed. Install it later with:');
206
+ console.log(' pip install -U "mineru[core]"');
207
+ console.log(' mineru-api --host 127.0.0.1 --port 8000');
208
+ }
209
+ }
210
+ if (enabled.has('officecli')) {
211
+ // OfficeCLI ships inside the mounted bundle and carries its own binary, so the
212
+ // capability needs no host step; the profile row is what turns it on.
213
+ ready.push('officecli');
214
+ }
215
+ if (enabled.has('computer-use')) {
216
+ if (desktopDriverAvailable()) {
217
+ ready.push('computer-use');
218
+ }
219
+ else {
220
+ console.log('');
221
+ console.log(' computer-use: the cua driver was not found on PATH. The plugin is');
222
+ console.log(' still mounted, so it starts once the driver is installed:');
223
+ console.log(' install the cua-driver release for this architecture, then restart');
224
+ }
225
+ }
226
+ return ready;
227
+ }
228
+ /**
229
+ * Install MinerU when it is absent and start its API service.
230
+ *
231
+ * MinerU is a Python package whose API server answers on a local port; the profile
232
+ * points at that port. The parser is installed into a virtual environment under the
233
+ * deployment home and an existing one is upgraded rather than skipped, so enabling
234
+ * the capability keeps the parser current without touching the system interpreter.
235
+ *
236
+ * @param home - the deployment home that owns the venv.
237
+ * @returns whether the package is installed; not whether it answered.
238
+ */
239
+ function installMineru(home) {
240
+ const python = pythonInterpreter();
241
+ if (python === undefined) {
242
+ console.log(' MinerU: no Python interpreter found on PATH.');
243
+ return false;
244
+ }
245
+ // The venv keeps MinerU's torch and model dependencies away from the system
246
+ // interpreter, which is also what PEP 668 requires: a system pip refuses the install
247
+ // outright, and `--break-system-packages` would put a multi-gigabyte torch tree into
248
+ // the OS packages. `all` is the extra both the 3.x and 4.x lines publish; `core`
249
+ // existed only through 3.x and installing it on 4.x silently drops the extras.
250
+ const venv = join(home, '.mineru-venv');
251
+ if (!runStep(python, ['-m', 'venv', venv]))
252
+ return false;
253
+ const venvPython = join(venv, 'bin', 'python');
254
+ return runStep(venvPython, ['-m', 'pip', 'install', '-U', 'mineru[all]']);
255
+ }
256
+ /**
257
+ * Start the MinerU API server as a background service when none answers yet.
258
+ *
259
+ * The service has to outlive the install command, so it is registered with the
260
+ * host's service manager when one is available and otherwise started detached.
261
+ *
262
+ * @returns whether the server is running after this call.
263
+ */
264
+ function startMineru(home) {
265
+ const api = mineruApiBinary(home);
266
+ if (api === undefined)
267
+ return false;
268
+ if (mineruAnswers())
269
+ return true;
270
+ if (spawnSync('systemctl', ['--version'], { stdio: 'ignore' }).status === 0) {
271
+ writeFileSync('/etc/systemd/system/mineru-api.service', [
272
+ '[Unit]',
273
+ 'Description=MinerU document parsing API for DeepSeek Harness Plus',
274
+ 'After=network-online.target',
275
+ 'Wants=network-online.target',
276
+ '',
277
+ '[Service]',
278
+ 'Type=simple',
279
+ 'User=root',
280
+ 'ExecStart=' + api + ' --host 127.0.0.1 --port 8000',
281
+ 'Restart=on-failure',
282
+ 'RestartSec=2',
283
+ 'TimeoutStopSec=15',
284
+ '',
285
+ '[Install]',
286
+ 'WantedBy=multi-user.target',
287
+ '',
288
+ ].join('\n'));
289
+ spawnSync('systemctl', ['daemon-reload'], { stdio: 'ignore' });
290
+ return spawnSync('systemctl', ['enable', '--now', 'mineru-api.service'], { stdio: 'ignore' }).status === 0;
291
+ }
292
+ console.log(' MinerU: start the API server with:');
293
+ console.log(' ' + api + ' --host 127.0.0.1 --port 8000');
294
+ return false;
295
+ }
296
+ /** The Python interpreter to install MinerU with, preferring python3. */
297
+ function pythonInterpreter() {
298
+ return ['python3', 'python'].find(candidate => spawnSync(candidate, ['--version'], { stdio: 'ignore' }).status === 0);
299
+ }
300
+ /**
301
+ * The MinerU API entry point, wherever the install placed it.
302
+ *
303
+ * The venv this command creates comes first: it is the interpreter the parser was
304
+ * installed into, so its `mineru-api` is the one that can import MinerU. A binary on
305
+ * PATH is a fallback for a deployment that installed MinerU some other way.
306
+ *
307
+ * @param home - the deployment home that owns the venv.
308
+ * @returns the command to run, or `undefined` when none answers.
309
+ */
310
+ function mineruApiBinary(home) {
311
+ const candidates = [join(home, '.mineru-venv', 'bin', 'mineru-api'), 'mineru-api'];
312
+ return candidates.find(candidate => spawnSync(candidate, ['--help'], { stdio: 'ignore' }).status === 0);
313
+ }
314
+ /** Whether a MinerU API server already answers on the default port. */
315
+ function mineruAnswers() {
316
+ const probe = spawnSync('curl', ['-sf', '-o', '/dev/null', '--max-time', '3', 'http://127.0.0.1:8000/docs'], { stdio: 'ignore' });
317
+ return probe.status === 0;
318
+ }
319
+ /** Whether the Windows-side desktop driver answers from this shell. */
320
+ function desktopDriverAvailable() {
321
+ const probe = spawnSync('cua-driver', ['--version'], { stdio: 'ignore' });
322
+ return probe.status === 0;
323
+ }
324
+ //# sourceMappingURL=standalone-capabilities.js.map
@@ -13,10 +13,13 @@ import { createRequire } from 'node:module';
13
13
  import { dirname, join } from 'node:path';
14
14
  import { fileURLToPath } from 'node:url';
15
15
  import { newerVersion } from "./registry-versions.js";
16
+ import { CAPABILITY_ENV_FILE, CAPABILITY_MARKER, CAPABILITY_RECORD, capabilityEnvironment, capabilityPatchLayer, installCapabilityServices, interviewCapabilities, } from "./standalone-capabilities.js";
16
17
  import { DEFAULT_PORT, STOP_GRACE_MILLISECONDS, choosePort, clearState, isRunning, portAvailable, readState, spawnServer, stateDirectory, waitForAuthenticatedUrl, waitForServer, writeState, } from "./standalone-server.js";
17
18
  import { STANDALONE_PROFILE, applyProfileNpmPatches, ensureProfile, pnpmAvailable, pnpmInstallCommand, pnpmInstallCommands, registryOrder, readDistributionProfile, resolvePaths, } from "./standalone-profile.js";
18
19
  /** Milliseconds a start waits for the server to answer before reporting failure. */
19
20
  const READY_TIMEOUT_MILLISECONDS = 90_000;
21
+ /** Profile patch file the capability interview rewrites (the profile's user layer). */
22
+ const CAPABILITY_PATCH_FILE = 'cordis.patch.yml';
20
23
  function parseStartOptions(argv) {
21
24
  let port = DEFAULT_PORT;
22
25
  let host = '127.0.0.1';
@@ -149,6 +152,24 @@ async function start(argv) {
149
152
  for (const label of applyProfileNpmPatches(paths.distributionDirectory, paths.profileDirectory)) {
150
153
  console.log('Applied the reviewed patch ' + label);
151
154
  }
155
+ // The capability interview runs once, when the profile is created: a deployment
156
+ // that already answered keeps its answers, and a re-run only reports them. Asking
157
+ // on every start would make a restart look like a first install.
158
+ const needsInterview = created || !existsSync(join(paths.home, CAPABILITY_RECORD));
159
+ const answers = needsInterview
160
+ ? await interviewCapabilities(true)
161
+ : undefined;
162
+ if (answers !== undefined) {
163
+ const ready = await installCapabilityServices(answers, paths.home);
164
+ writeCapabilityPatch(paths.profileDirectory, answers);
165
+ writeCapabilityEnvironment(paths.home, answers);
166
+ console.log(' Enabled: ' + (answers.enabled.length === 0 ? '(none)' : answers.enabled.join(', ')));
167
+ if (answers.enabled.length > 0)
168
+ console.log(' Services ready: ' + (ready.length === 0 ? '(none)' : ready.join(', ')));
169
+ if (answers.enabled.includes('exa') && answers.exaApiKey === undefined) {
170
+ console.log(' Exa: add EXA_API_KEY to ' + join(paths.home, CAPABILITY_ENV_FILE) + ' when you have a key.');
171
+ }
172
+ }
152
173
  const entry = launcherEntry(anchor);
153
174
  if (options.foreground)
154
175
  return runForeground(entry, options.port, options.host, options.open);
@@ -282,6 +303,44 @@ async function update(argv) {
282
303
  }
283
304
  return 0;
284
305
  }
306
+ /**
307
+ * Write the profile's capability patch layer.
308
+ *
309
+ * The profile's loader merges this file, so enabling a capability is a data change
310
+ * rather than an edit to the deployment. Writing it on every configured run also
311
+ * turns a capability back off when the interview no longer selects it.
312
+ *
313
+ * @param profileDirectory - the profile whose layer is replaced.
314
+ * @param answers - the interview's answers.
315
+ */
316
+ function writeCapabilityPatch(profileDirectory, answers) {
317
+ const path = join(profileDirectory, CAPABILITY_PATCH_FILE);
318
+ // The profile has exactly one user layer, so the capability rows live in it. A file
319
+ // this command did not write belongs to the deployment, and replacing it would drop
320
+ // whatever the operator put there; keep it beside the new layer instead.
321
+ const existing = existsSync(path) ? readFileSync(path, 'utf8') : undefined;
322
+ if (existing !== undefined && !existing.includes(CAPABILITY_MARKER)) {
323
+ writeFileSync(path + '.before-capabilities', existing);
324
+ console.log(' Kept the existing profile layer at ' + CAPABILITY_PATCH_FILE + '.before-capabilities');
325
+ }
326
+ writeFileSync(path, capabilityPatchLayer(answers));
327
+ }
328
+ /**
329
+ * Write the deployment's capability environment file.
330
+ *
331
+ * MinerU is switched on by the presence of its endpoint and Exa reads its key from
332
+ * the launch environment, so the answers reach those two through `$DSH_HOME/.env`
333
+ * rather than through the profile layer. The file is replaced whole on every
334
+ * configured start, which is also how a capability the user dropped stops applying.
335
+ *
336
+ * @param home - the deployment home whose env file the launcher reads.
337
+ * @param answers - the interview's answers.
338
+ */
339
+ function writeCapabilityEnvironment(home, answers) {
340
+ const path = join(home, CAPABILITY_ENV_FILE);
341
+ const existing = existsSync(path) ? readFileSync(path, 'utf8') : '';
342
+ writeFileSync(path, capabilityEnvironment(answers, existing));
343
+ }
285
344
  /** Ask one yes/no question on the terminal. */
286
345
  function confirm(question) {
287
346
  return new Promise((resolveAnswer) => {
package/package.json CHANGED
@@ -3,27 +3,31 @@
3
3
  "dsh-plus": "lib/bin.js"
4
4
  },
5
5
  "dependencies": {
6
+ "@deepseek-ai/dsh-computer-use": ">=0.1.6-alpha.1",
7
+ "@deepseek-ai/dsh-experimental-computer-use-cua-driver-mcp": ">=0.1.6-alpha.1",
8
+ "@deepseek-ai/dsh-tool-session-query": ">=0.1.6-alpha.1",
9
+ "@deepseek-ai/dsh-web-search-exa": ">=0.1.6-alpha.1",
6
10
  "@sparkelf/dsh-client-ui-skill-center": ">=0.2.0-rc.9",
7
11
  "@sparkelf/dsh-mobile-bridge": ">=0.2.11",
8
- "@sparkelf/dsh-patch-better-sidebar-browser-url-seed": ">=0.2.0-rc.13",
9
- "@sparkelf/dsh-patch-better-sidebar-html-preview-path": ">=0.2.0-rc.13",
10
- "@sparkelf/dsh-patch-better-sidebar-main-view-session": ">=0.2.0-rc.13",
11
- "@sparkelf/dsh-patch-better-sidebar-media-path": ">=0.2.0-rc.13",
12
- "@sparkelf/dsh-patch-browser-auth-mode": ">=0.2.0-rc.13",
13
- "@sparkelf/dsh-patch-composer-popover-boundaries": ">=0.2.0-rc.13",
14
- "@sparkelf/dsh-patch-legacy-code-preset": ">=0.2.0-rc.13",
15
- "@sparkelf/dsh-patch-mobile-journal-generation": ">=0.2.0-rc.13",
16
- "@sparkelf/dsh-patch-officecli-deliverables": ">=0.2.0-rc.13",
17
- "@sparkelf/dsh-patch-ptc-mcp-schema-types": ">=0.2.0-rc.13",
18
- "@sparkelf/dsh-patch-responses-reasoning-status": ">=0.2.0-rc.13",
19
- "@sparkelf/dsh-patch-session-export-chinese": ">=0.2.0-rc.13",
20
- "@sparkelf/dsh-patch-session-format-legacy-restart": ">=0.2.0-rc.13",
21
- "@sparkelf/dsh-patch-session-log-trajectory-toolbar": ">=0.2.0-rc.13",
22
- "@sparkelf/dsh-patch-session-query-unindexable-session": ">=0.2.0-rc.13",
23
- "@sparkelf/dsh-patch-subagent-settings-presets": ">=0.2.0-rc.13",
24
- "@sparkelf/dsh-patch-web-base-path": ">=0.2.0-rc.13",
25
- "@sparkelf/dsh-patch-workspace-storage-restore": ">=0.2.0-rc.13",
26
- "@sparkelf/dsh-patch-wsl-native-open": ">=0.2.0-rc.13",
12
+ "@sparkelf/dsh-patch-better-sidebar-browser-url-seed": ">=0.2.0-rc.15",
13
+ "@sparkelf/dsh-patch-better-sidebar-html-preview-path": ">=0.2.0-rc.15",
14
+ "@sparkelf/dsh-patch-better-sidebar-main-view-session": ">=0.2.0-rc.15",
15
+ "@sparkelf/dsh-patch-better-sidebar-media-path": ">=0.2.0-rc.15",
16
+ "@sparkelf/dsh-patch-browser-auth-mode": ">=0.2.0-rc.15",
17
+ "@sparkelf/dsh-patch-composer-popover-boundaries": ">=0.2.0-rc.15",
18
+ "@sparkelf/dsh-patch-legacy-code-preset": ">=0.2.0-rc.15",
19
+ "@sparkelf/dsh-patch-mobile-journal-generation": ">=0.2.0-rc.15",
20
+ "@sparkelf/dsh-patch-officecli-deliverables": ">=0.2.0-rc.15",
21
+ "@sparkelf/dsh-patch-ptc-mcp-schema-types": ">=0.2.0-rc.15",
22
+ "@sparkelf/dsh-patch-responses-reasoning-status": ">=0.2.0-rc.15",
23
+ "@sparkelf/dsh-patch-session-export-chinese": ">=0.2.0-rc.15",
24
+ "@sparkelf/dsh-patch-session-format-legacy-restart": ">=0.2.0-rc.15",
25
+ "@sparkelf/dsh-patch-session-log-trajectory-toolbar": ">=0.2.0-rc.15",
26
+ "@sparkelf/dsh-patch-session-query-unindexable-session": ">=0.2.0-rc.15",
27
+ "@sparkelf/dsh-patch-subagent-settings-presets": ">=0.2.0-rc.15",
28
+ "@sparkelf/dsh-patch-web-base-path": ">=0.2.0-rc.15",
29
+ "@sparkelf/dsh-patch-workspace-storage-restore": ">=0.2.0-rc.15",
30
+ "@sparkelf/dsh-patch-wsl-native-open": ">=0.2.0-rc.15",
27
31
  "@sparkelf/dsh-plugin-backup": ">=0.2.0-rc.8",
28
32
  "@sparkelf/dsh-plugin-dataops": ">=0.2.0-rc.8",
29
33
  "@sparkelf/dsh-plugin-mcp-credentials": ">=0.2.0-rc.8",
@@ -185,5 +189,5 @@
185
189
  },
186
190
  "type": "module",
187
191
  "types": "lib/types/index.d.ts",
188
- "version": "0.2.0-rc.13"
192
+ "version": "0.2.0-rc.15"
189
193
  }