@sparkelf/dsh-plus 0.2.0-rc.39 → 0.2.0-rc.40
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/bin.js
CHANGED
|
@@ -216,7 +216,52 @@ function capabilityEnvironment(answers, existing = "") {
|
|
|
216
216
|
* @param answers - the interview's answers.
|
|
217
217
|
* @returns the patch layer's YAML text.
|
|
218
218
|
*/
|
|
219
|
-
|
|
219
|
+
/**
|
|
220
|
+
* The patch rows a previous version of this file carried, minus the ones this module owns.
|
|
221
|
+
*
|
|
222
|
+
* The module writes a fixed set of rows, so everything else in the file belongs to the
|
|
223
|
+
* deployment. A row starts at a top-level `- ` entry and runs to the next one, which keeps
|
|
224
|
+
* each row's own config and comments with it.
|
|
225
|
+
*
|
|
226
|
+
* @param existing - the file's current text.
|
|
227
|
+
* @returns the rows to write back after this module's own.
|
|
228
|
+
*/
|
|
229
|
+
function foreignPatchRows(existing) {
|
|
230
|
+
if (existing.trim() === "") return [];
|
|
231
|
+
const lines = existing.split("\n");
|
|
232
|
+
const rows = [];
|
|
233
|
+
let current = null;
|
|
234
|
+
for (const line of lines) {
|
|
235
|
+
if (/^-\s/.test(line)) {
|
|
236
|
+
if (current !== null) rows.push(current);
|
|
237
|
+
current = [line];
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (current !== null) current.push(line);
|
|
241
|
+
}
|
|
242
|
+
if (current !== null) rows.push(current);
|
|
243
|
+
const ownedHeads = new Set(["insert", "web"]);
|
|
244
|
+
return rows.filter((row) => {
|
|
245
|
+
const head = row[0] ?? "";
|
|
246
|
+
const id = /^-\s+id:\s*(\S+)/.exec(head)?.[1];
|
|
247
|
+
if (id !== void 0) return !ownedHeads.has(id);
|
|
248
|
+
if (/^-\s+insert:\s*$/.test(head)) {
|
|
249
|
+
const ownedChildren = new Set([
|
|
250
|
+
"web-search-exa",
|
|
251
|
+
"computer-use",
|
|
252
|
+
"computer-use-cua-driver-mcp",
|
|
253
|
+
"mineru"
|
|
254
|
+
]);
|
|
255
|
+
return row.filter((line, index) => {
|
|
256
|
+
if (index === 0) return false;
|
|
257
|
+
const childId = /^\s+-\s+id:\s*(\S+)/.exec(line)?.[1];
|
|
258
|
+
return childId === void 0 || !ownedChildren.has(childId);
|
|
259
|
+
}).some((line) => /^\s+-\s+/.test(line));
|
|
260
|
+
}
|
|
261
|
+
return true;
|
|
262
|
+
}).map((row) => row.join("\n").trimEnd());
|
|
263
|
+
}
|
|
264
|
+
function capabilityPatchLayer(answers, existing = "") {
|
|
220
265
|
const enabled = new Set(answers.enabled);
|
|
221
266
|
const rows = [];
|
|
222
267
|
if (enabled.has("exa")) rows.push(" - id: web-search-exa", " name: '@deepseek-ai/dsh-web-search-exa'", " config:", " searchType: auto", " numResults: 8");
|
|
@@ -224,9 +269,11 @@ function capabilityPatchLayer(answers) {
|
|
|
224
269
|
if (enabled.has("officecli")) rows.push(" - id: officecli", " name: '@sparkelf/dsh-officecli'");
|
|
225
270
|
if (enabled.has("mineru")) rows.push(" - id: mineru", " name: '@sparkelf/dsh-mineru'", " config:", " endpoint: " + (answers.mineruEndpoint ?? "http://127.0.0.1:8000/file_parse"));
|
|
226
271
|
const lines = ["# Written by dsh-plus start from the capability interview. Enabling or disabling a capability", "# rewrites this file; edits here are replaced."];
|
|
272
|
+
const foreign = foreignPatchRows(existing);
|
|
227
273
|
if (rows.length > 0) lines.push("- insert:", ...rows);
|
|
228
274
|
else lines.push("[]");
|
|
229
275
|
if (enabled.has("exa")) lines.push("", "# Route web_search through Exa rather than the built-in provider.", "- id: web", " config:", " searchProvider: exa");
|
|
276
|
+
if (foreign.length > 0) lines.push("", "# Rows this file carried before the capability interview; kept verbatim.", ...foreign);
|
|
230
277
|
return lines.join("\n") + "\n";
|
|
231
278
|
}
|
|
232
279
|
/** Run one command, reporting a failure rather than throwing. */
|
|
@@ -1582,7 +1629,7 @@ function writeCapabilityPatch(profileDirectory, answers) {
|
|
|
1582
1629
|
writeFileSync(path + ".before-capabilities", existing);
|
|
1583
1630
|
console.log(" Kept the existing profile layer at cordis.patch.yml.before-capabilities");
|
|
1584
1631
|
}
|
|
1585
|
-
writeFileSync(path, capabilityPatchLayer(answers));
|
|
1632
|
+
writeFileSync(path, capabilityPatchLayer(answers, existing ?? ""));
|
|
1586
1633
|
}
|
|
1587
1634
|
/**
|
|
1588
1635
|
* Write the deployment's capability environment file.
|
|
@@ -76,22 +76,7 @@ export declare function interviewCapabilities(interactive: boolean): Promise<Cap
|
|
|
76
76
|
* @returns the environment file's text.
|
|
77
77
|
*/
|
|
78
78
|
export declare function capabilityEnvironment(answers: CapabilityAnswers, existing?: string): string;
|
|
79
|
-
|
|
80
|
-
* The profile patch layer that turns the selected capabilities on.
|
|
81
|
-
*
|
|
82
|
-
* The file is data the profile's loader merges, so enabling a capability writes a
|
|
83
|
-
* row rather than editing the deployment. An empty selection still writes the file,
|
|
84
|
-
* because a layer that exists and mounts nothing is how a previously enabled
|
|
85
|
-
* capability is turned back off.
|
|
86
|
-
*
|
|
87
|
-
* The profile's own layer is a separate file with its own name: a deployment that
|
|
88
|
-
* configured something by hand keeps it, and the capability rows are rewritten
|
|
89
|
-
* whole on every start.
|
|
90
|
-
*
|
|
91
|
-
* @param answers - the interview's answers.
|
|
92
|
-
* @returns the patch layer's YAML text.
|
|
93
|
-
*/
|
|
94
|
-
export declare function capabilityPatchLayer(answers: CapabilityAnswers): string;
|
|
79
|
+
export declare function capabilityPatchLayer(answers: CapabilityAnswers, existing?: string): string;
|
|
95
80
|
/**
|
|
96
81
|
* The capability record a previous configured run wrote, or `undefined` when none exists.
|
|
97
82
|
*
|
|
@@ -162,7 +162,64 @@ export function capabilityEnvironment(answers, existing = '') {
|
|
|
162
162
|
* @param answers - the interview's answers.
|
|
163
163
|
* @returns the patch layer's YAML text.
|
|
164
164
|
*/
|
|
165
|
-
|
|
165
|
+
/**
|
|
166
|
+
* The patch rows a previous version of this file carried, minus the ones this module owns.
|
|
167
|
+
*
|
|
168
|
+
* The module writes a fixed set of rows, so everything else in the file belongs to the
|
|
169
|
+
* deployment. A row starts at a top-level `- ` entry and runs to the next one, which keeps
|
|
170
|
+
* each row's own config and comments with it.
|
|
171
|
+
*
|
|
172
|
+
* @param existing - the file's current text.
|
|
173
|
+
* @returns the rows to write back after this module's own.
|
|
174
|
+
*/
|
|
175
|
+
function foreignPatchRows(existing) {
|
|
176
|
+
if (existing.trim() === '')
|
|
177
|
+
return [];
|
|
178
|
+
const lines = existing.split('\n');
|
|
179
|
+
const rows = [];
|
|
180
|
+
let current = null;
|
|
181
|
+
for (const line of lines) {
|
|
182
|
+
if (/^-\s/.test(line)) {
|
|
183
|
+
if (current !== null)
|
|
184
|
+
rows.push(current);
|
|
185
|
+
current = [line];
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (current !== null)
|
|
189
|
+
current.push(line);
|
|
190
|
+
}
|
|
191
|
+
if (current !== null)
|
|
192
|
+
rows.push(current);
|
|
193
|
+
// The capability rows this module writes are the `insert` block's children plus the
|
|
194
|
+
// `web` row that routes search. A block whose head is not one of them belongs to the
|
|
195
|
+
// deployment; a block that is one of them is replaced by the current selection whole.
|
|
196
|
+
const ownedHeads = new Set(['insert', 'web']);
|
|
197
|
+
return rows
|
|
198
|
+
.filter((row) => {
|
|
199
|
+
const head = row[0] ?? '';
|
|
200
|
+
const id = /^-\s+id:\s*(\S+)/.exec(head)?.[1];
|
|
201
|
+
if (id !== undefined)
|
|
202
|
+
return !ownedHeads.has(id);
|
|
203
|
+
// A block keyed by something else, such as `- insert:`, is this module's when the
|
|
204
|
+
// module is what writes it. Only its own capability rows are recognised, so a
|
|
205
|
+
// deployment block that happens to use `insert` is the one ambiguous case; the
|
|
206
|
+
// module resolves it by removing only the rows it names.
|
|
207
|
+
if (/^-\s+insert:\s*$/.test(head)) {
|
|
208
|
+
const ownedChildren = new Set(['web-search-exa', 'computer-use', 'computer-use-cua-driver-mcp', 'mineru']);
|
|
209
|
+
const kept = row.filter((line, index) => {
|
|
210
|
+
if (index === 0)
|
|
211
|
+
return false;
|
|
212
|
+
const childId = /^\s+-\s+id:\s*(\S+)/.exec(line)?.[1];
|
|
213
|
+
return childId === undefined || !ownedChildren.has(childId);
|
|
214
|
+
});
|
|
215
|
+
// Nothing but the block head is left: the module wrote it and now mounts nothing.
|
|
216
|
+
return kept.some(line => /^\s+-\s+/.test(line));
|
|
217
|
+
}
|
|
218
|
+
return true;
|
|
219
|
+
})
|
|
220
|
+
.map(row => row.join('\n').trimEnd());
|
|
221
|
+
}
|
|
222
|
+
export function capabilityPatchLayer(answers, existing = '') {
|
|
166
223
|
const enabled = new Set(answers.enabled);
|
|
167
224
|
const rows = [];
|
|
168
225
|
if (enabled.has('exa')) {
|
|
@@ -193,6 +250,12 @@ export function capabilityPatchLayer(answers) {
|
|
|
193
250
|
// still has to produce one. Comments alone parse as `null`, and the profile then
|
|
194
251
|
// refuses to boot with "must be a top-level YAML array of loader patch entries" —
|
|
195
252
|
// which is the whole deployment, not just the missing capability.
|
|
253
|
+
// A deployment that configured something by hand keeps it: this file is the profile's
|
|
254
|
+
// only user layer, so a capability change must not drop rows it does not own. Only the
|
|
255
|
+
// rows this module writes are rewritten, which is what the file's own header promises.
|
|
256
|
+
// Parsing is line-based rather than a YAML round trip because the entries carry comments
|
|
257
|
+
// that a parse-and-serialize cycle would discard.
|
|
258
|
+
const foreign = foreignPatchRows(existing);
|
|
196
259
|
if (rows.length > 0) {
|
|
197
260
|
lines.push('- insert:', ...rows);
|
|
198
261
|
}
|
|
@@ -202,6 +265,9 @@ export function capabilityPatchLayer(answers) {
|
|
|
202
265
|
if (enabled.has('exa')) {
|
|
203
266
|
lines.push('', '# Route web_search through Exa rather than the built-in provider.', '- id: web', ' config:', ' searchProvider: exa');
|
|
204
267
|
}
|
|
268
|
+
if (foreign.length > 0) {
|
|
269
|
+
lines.push('', '# Rows this file carried before the capability interview; kept verbatim.', ...foreign);
|
|
270
|
+
}
|
|
205
271
|
return lines.join('\n') + '\n';
|
|
206
272
|
}
|
|
207
273
|
/** Run one command, reporting a failure rather than throwing. */
|
|
@@ -382,11 +382,14 @@ function writeCapabilityPatch(profileDirectory, answers) {
|
|
|
382
382
|
// this command did not write belongs to the deployment, and replacing it would drop
|
|
383
383
|
// whatever the operator put there; keep it beside the new layer instead.
|
|
384
384
|
const existing = existsSync(path) ? readFileSync(path, 'utf8') : undefined;
|
|
385
|
+
// The layer is rewritten whole, so a row this command does not own is carried over
|
|
386
|
+
// rather than dropped. Backing the file up is not a substitute: the operator's rows
|
|
387
|
+
// would stop applying until someone restored it by hand.
|
|
385
388
|
if (existing !== undefined && !existing.includes(CAPABILITY_MARKER)) {
|
|
386
389
|
writeFileSync(path + '.before-capabilities', existing);
|
|
387
390
|
console.log(' Kept the existing profile layer at ' + CAPABILITY_PATCH_FILE + '.before-capabilities');
|
|
388
391
|
}
|
|
389
|
-
writeFileSync(path, capabilityPatchLayer(answers));
|
|
392
|
+
writeFileSync(path, capabilityPatchLayer(answers, existing ?? ''));
|
|
390
393
|
}
|
|
391
394
|
/**
|
|
392
395
|
* Write the deployment's capability environment file.
|
package/package.json
CHANGED