@sparkelf/dsh-plus 0.2.0-rc.18 → 0.2.0-rc.19
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
|
@@ -210,6 +210,7 @@ function capabilityPatchLayer(answers) {
|
|
|
210
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
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
212
|
if (rows.length > 0) lines.push("- insert:", ...rows);
|
|
213
|
+
else lines.push("[]");
|
|
213
214
|
if (enabled.has("exa")) lines.push("", "# Route web_search through Exa rather than the built-in provider.", "- id: web", " config:", " searchProvider: exa");
|
|
214
215
|
return lines.join("\n") + "\n";
|
|
215
216
|
}
|
|
@@ -1029,6 +1030,7 @@ function parseStartOptions(argv) {
|
|
|
1029
1030
|
let host = "127.0.0.1";
|
|
1030
1031
|
let open = true;
|
|
1031
1032
|
let foreground = false;
|
|
1033
|
+
let capabilities;
|
|
1032
1034
|
for (let index = 0; index < argv.length; index += 1) {
|
|
1033
1035
|
const token = argv[index];
|
|
1034
1036
|
if (token === "--port" || token === "-p") {
|
|
@@ -1053,13 +1055,41 @@ function parseStartOptions(argv) {
|
|
|
1053
1055
|
foreground = true;
|
|
1054
1056
|
continue;
|
|
1055
1057
|
}
|
|
1058
|
+
if (token === "--capabilities") {
|
|
1059
|
+
const value = argv[index + 1];
|
|
1060
|
+
if (value === void 0) throw new Error("--capabilities requires a comma-separated list, or an empty string for none");
|
|
1061
|
+
const stated = value === "" ? [] : value.split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
|
|
1062
|
+
const offered = new Set(CAPABILITIES.map((capability) => capability.id));
|
|
1063
|
+
for (const id of stated) if (!offered.has(id)) throw new Error("unknown capability \"" + id + "\"; this release offers " + [...offered].join(", "));
|
|
1064
|
+
capabilities = stated;
|
|
1065
|
+
index += 1;
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1056
1068
|
throw new Error("unknown option: " + String(token));
|
|
1057
1069
|
}
|
|
1058
1070
|
return {
|
|
1059
1071
|
port,
|
|
1060
1072
|
host,
|
|
1061
1073
|
open,
|
|
1062
|
-
foreground
|
|
1074
|
+
foreground,
|
|
1075
|
+
capabilities
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Resolve a stated capability selection.
|
|
1080
|
+
*
|
|
1081
|
+
* An unattended install states what it wants instead of answering the interview, so a
|
|
1082
|
+
* name it does not offer has to fail here rather than silently enable nothing: the
|
|
1083
|
+
* deployment would otherwise start with a capability the caller believes it selected.
|
|
1084
|
+
*
|
|
1085
|
+
* @param ids - capability ids the caller selected.
|
|
1086
|
+
* @returns the answers the interview would have returned.
|
|
1087
|
+
*/
|
|
1088
|
+
function selectCapabilities(ids) {
|
|
1089
|
+
const selected = [...new Set(ids)];
|
|
1090
|
+
return {
|
|
1091
|
+
enabled: selected,
|
|
1092
|
+
...selected.includes("mineru") ? { mineruEndpoint: DEFAULT_MINERU_ENDPOINT } : {}
|
|
1063
1093
|
};
|
|
1064
1094
|
}
|
|
1065
1095
|
/**
|
|
@@ -1090,6 +1120,25 @@ function installationRoot() {
|
|
|
1090
1120
|
function installationAnchor() {
|
|
1091
1121
|
return join(installationRoot(), "package.json");
|
|
1092
1122
|
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Path to the installed command's own file, which is where a declaration lookup starts.
|
|
1125
|
+
*
|
|
1126
|
+
* The declaration belongs to the package the user installed, and that package is a
|
|
1127
|
+
* sibling of this module rather than an ancestor: a variant's forwarder imports this
|
|
1128
|
+
* CLI in-process, so `import.meta.url` names `@sparkelf/dsh-plus` while the installed
|
|
1129
|
+
* command is the variant. `process.argv[1]` is the entry that was actually invoked,
|
|
1130
|
+
* which is the package whose declaration applies.
|
|
1131
|
+
*
|
|
1132
|
+
* Walking out from the installation root cannot reach it either: that root is the
|
|
1133
|
+
* project the user ran the command in, so every variant would fall back to the full
|
|
1134
|
+
* profile and keep the capabilities it excluded.
|
|
1135
|
+
*
|
|
1136
|
+
* @returns absolute path to the invoked command, or this module when argv carries none.
|
|
1137
|
+
*/
|
|
1138
|
+
function declarationAnchor() {
|
|
1139
|
+
const invoked = process.argv[1];
|
|
1140
|
+
return invoked === void 0 || invoked === "" ? fileURLToPath(import.meta.url) : resolve(invoked);
|
|
1141
|
+
}
|
|
1093
1142
|
/** The launcher entry this installation must drive. */
|
|
1094
1143
|
function launcherEntry(anchor) {
|
|
1095
1144
|
return createRequire(anchor).resolve("@deepseek-ai/dsh/lib/bin.js");
|
|
@@ -1111,7 +1160,7 @@ function runForeground(entry, profileName, port, host, open) {
|
|
|
1111
1160
|
async function start(argv) {
|
|
1112
1161
|
const options = parseStartOptions(argv);
|
|
1113
1162
|
const anchor = installationAnchor();
|
|
1114
|
-
const paths = resolvePaths(
|
|
1163
|
+
const paths = resolvePaths(declarationAnchor());
|
|
1115
1164
|
if (!pnpmAvailable()) {
|
|
1116
1165
|
const command = pnpmInstallCommand();
|
|
1117
1166
|
console.log("pnpm is required to install the plus profile, and was not found.");
|
|
@@ -1144,7 +1193,7 @@ async function start(argv) {
|
|
|
1144
1193
|
const created = ensureProfile(paths, installationRoot());
|
|
1145
1194
|
console.log(created ? "Created the " + paths.profileName + " profile at " + paths.profileDirectory : "Using the existing " + paths.profileName + " profile");
|
|
1146
1195
|
for (const label of applyProfileNpmPatches(paths.distributionDirectory, paths.profileDirectory)) console.log("Applied the reviewed patch " + label);
|
|
1147
|
-
const answers = created || !existsSync(join(paths.home, "capabilities.json")) ? await interviewCapabilities(true) : void 0;
|
|
1196
|
+
const answers = created || !existsSync(join(paths.home, "capabilities.json")) ? options.capabilities === void 0 ? await interviewCapabilities(true) : selectCapabilities(options.capabilities) : void 0;
|
|
1148
1197
|
if (answers !== void 0) {
|
|
1149
1198
|
const ready = await installCapabilityServices(answers, paths.home);
|
|
1150
1199
|
writeCapabilityPatch(paths.profileDirectory, answers);
|
|
@@ -159,8 +159,16 @@ export function capabilityPatchLayer(answers) {
|
|
|
159
159
|
'# ' + CAPABILITY_MARKER + ' Enabling or disabling a capability',
|
|
160
160
|
'# rewrites this file; edits here are replaced.',
|
|
161
161
|
];
|
|
162
|
-
|
|
162
|
+
// The loader requires a top-level YAML array, so a selection that mounts nothing
|
|
163
|
+
// still has to produce one. Comments alone parse as `null`, and the profile then
|
|
164
|
+
// refuses to boot with "must be a top-level YAML array of loader patch entries" —
|
|
165
|
+
// which is the whole deployment, not just the missing capability.
|
|
166
|
+
if (rows.length > 0) {
|
|
163
167
|
lines.push('- insert:', ...rows);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
lines.push('[]');
|
|
171
|
+
}
|
|
164
172
|
if (enabled.has('exa')) {
|
|
165
173
|
lines.push('', '# Route web_search through Exa rather than the built-in provider.', '- id: web', ' config:', ' searchProvider: exa');
|
|
166
174
|
}
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
import { spawnSync } from 'node:child_process';
|
|
11
11
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
12
12
|
import { createRequire } from 'node:module';
|
|
13
|
-
import { dirname, join } from 'node:path';
|
|
13
|
+
import { dirname, join, resolve } 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
|
+
import { CAPABILITIES, CAPABILITY_ENV_FILE, CAPABILITY_MARKER, CAPABILITY_RECORD, DEFAULT_MINERU_ENDPOINT, capabilityEnvironment, capabilityPatchLayer, installCapabilityServices, interviewCapabilities, } from "./standalone-capabilities.js";
|
|
17
17
|
import { DEFAULT_PORT, STOP_GRACE_MILLISECONDS, choosePort, clearState, isRunning, portAvailable, readState, spawnServer, stateDirectory, waitForAuthenticatedUrl, waitForServer, writeState, } from "./standalone-server.js";
|
|
18
18
|
import { STANDALONE_PROFILE, applyProfileNpmPatches, ensureProfile, pnpmAvailable, pnpmInstallCommand, pnpmInstallCommands, registryOrder, readDistributionProfile, resolvePaths, } from "./standalone-profile.js";
|
|
19
19
|
/** Milliseconds a start waits for the server to answer before reporting failure. */
|
|
@@ -25,6 +25,7 @@ function parseStartOptions(argv) {
|
|
|
25
25
|
let host = '127.0.0.1';
|
|
26
26
|
let open = true;
|
|
27
27
|
let foreground = false;
|
|
28
|
+
let capabilities;
|
|
28
29
|
for (let index = 0; index < argv.length; index += 1) {
|
|
29
30
|
const token = argv[index];
|
|
30
31
|
if (token === '--port' || token === '-p') {
|
|
@@ -51,9 +52,47 @@ function parseStartOptions(argv) {
|
|
|
51
52
|
foreground = true;
|
|
52
53
|
continue;
|
|
53
54
|
}
|
|
55
|
+
if (token === '--capabilities') {
|
|
56
|
+
const value = argv[index + 1];
|
|
57
|
+
if (value === undefined)
|
|
58
|
+
throw new Error('--capabilities requires a comma-separated list, or an empty string for none');
|
|
59
|
+
const stated = value === '' ? [] : value.split(',').map(entry => entry.trim()).filter(entry => entry !== '');
|
|
60
|
+
// A stated selection is the only way an unattended install chooses capabilities, so
|
|
61
|
+
// a name this release does not offer fails here: the deployment would otherwise
|
|
62
|
+
// start without a capability its caller believes it selected. Validating at parse
|
|
63
|
+
// time also makes the failure independent of what is installed.
|
|
64
|
+
const offered = new Set(CAPABILITIES.map(capability => capability.id));
|
|
65
|
+
for (const id of stated) {
|
|
66
|
+
if (!offered.has(id)) {
|
|
67
|
+
throw new Error('unknown capability "' + id + '"; this release offers ' + [...offered].join(', '));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
capabilities = stated;
|
|
71
|
+
index += 1;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
54
74
|
throw new Error('unknown option: ' + String(token));
|
|
55
75
|
}
|
|
56
|
-
return { port, host, open, foreground };
|
|
76
|
+
return { port, host, open, foreground, capabilities };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Resolve a stated capability selection.
|
|
80
|
+
*
|
|
81
|
+
* An unattended install states what it wants instead of answering the interview, so a
|
|
82
|
+
* name it does not offer has to fail here rather than silently enable nothing: the
|
|
83
|
+
* deployment would otherwise start with a capability the caller believes it selected.
|
|
84
|
+
*
|
|
85
|
+
* @param ids - capability ids the caller selected.
|
|
86
|
+
* @returns the answers the interview would have returned.
|
|
87
|
+
*/
|
|
88
|
+
function selectCapabilities(ids) {
|
|
89
|
+
const selected = [...new Set(ids)];
|
|
90
|
+
// MinerU is started from an endpoint the interview collects; an unattended install
|
|
91
|
+
// gets the documented default rather than an unset one.
|
|
92
|
+
return {
|
|
93
|
+
enabled: selected,
|
|
94
|
+
...selected.includes('mineru') ? { mineruEndpoint: DEFAULT_MINERU_ENDPOINT } : {},
|
|
95
|
+
};
|
|
57
96
|
}
|
|
58
97
|
/**
|
|
59
98
|
* Where the installation that owns this command keeps its packages.
|
|
@@ -87,6 +126,25 @@ function installationRoot() {
|
|
|
87
126
|
function installationAnchor() {
|
|
88
127
|
return join(installationRoot(), 'package.json');
|
|
89
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Path to the installed command's own file, which is where a declaration lookup starts.
|
|
131
|
+
*
|
|
132
|
+
* The declaration belongs to the package the user installed, and that package is a
|
|
133
|
+
* sibling of this module rather than an ancestor: a variant's forwarder imports this
|
|
134
|
+
* CLI in-process, so `import.meta.url` names `@sparkelf/dsh-plus` while the installed
|
|
135
|
+
* command is the variant. `process.argv[1]` is the entry that was actually invoked,
|
|
136
|
+
* which is the package whose declaration applies.
|
|
137
|
+
*
|
|
138
|
+
* Walking out from the installation root cannot reach it either: that root is the
|
|
139
|
+
* project the user ran the command in, so every variant would fall back to the full
|
|
140
|
+
* profile and keep the capabilities it excluded.
|
|
141
|
+
*
|
|
142
|
+
* @returns absolute path to the invoked command, or this module when argv carries none.
|
|
143
|
+
*/
|
|
144
|
+
function declarationAnchor() {
|
|
145
|
+
const invoked = process.argv[1];
|
|
146
|
+
return invoked === undefined || invoked === '' ? fileURLToPath(import.meta.url) : resolve(invoked);
|
|
147
|
+
}
|
|
90
148
|
/** The launcher entry this installation must drive. */
|
|
91
149
|
function launcherEntry(anchor) {
|
|
92
150
|
return createRequire(anchor).resolve('@deepseek-ai/dsh/lib/bin.js');
|
|
@@ -102,7 +160,9 @@ function runForeground(entry, profileName, port, host, open) {
|
|
|
102
160
|
async function start(argv) {
|
|
103
161
|
const options = parseStartOptions(argv);
|
|
104
162
|
const anchor = installationAnchor();
|
|
105
|
-
|
|
163
|
+
// The profile's own name and omissions come from the package that installed this
|
|
164
|
+
// command, which is a different tree position than the dependencies it resolves.
|
|
165
|
+
const paths = resolvePaths(declarationAnchor());
|
|
106
166
|
// The profile installs its own dependency tree, which needs pnpm. Asking here rather
|
|
107
167
|
// than failing inside the install turns a missing prerequisite into a decision the
|
|
108
168
|
// consumer makes, and the install it can run is the one command that provides it.
|
|
@@ -157,7 +217,9 @@ async function start(argv) {
|
|
|
157
217
|
// on every start would make a restart look like a first install.
|
|
158
218
|
const needsInterview = created || !existsSync(join(paths.home, CAPABILITY_RECORD));
|
|
159
219
|
const answers = needsInterview
|
|
160
|
-
?
|
|
220
|
+
? options.capabilities === undefined
|
|
221
|
+
? await interviewCapabilities(true)
|
|
222
|
+
: selectCapabilities(options.capabilities)
|
|
161
223
|
: undefined;
|
|
162
224
|
if (answers !== undefined) {
|
|
163
225
|
const ready = await installCapabilityServices(answers, paths.home);
|
package/package.json
CHANGED
|
@@ -120,9 +120,10 @@
|
|
|
120
120
|
"dsh-video-preview": "0.1.4"
|
|
121
121
|
},
|
|
122
122
|
"overrides": {
|
|
123
|
-
"@deepseek-ai/dsh-agent-presets": "npm:@sparkelf/dsh-agent-presets@0.1.6-alpha.
|
|
123
|
+
"@deepseek-ai/dsh-agent-presets": "npm:@sparkelf/dsh-agent-presets@0.1.6-alpha.3",
|
|
124
124
|
"@deepseek-ai/dsh-api-gateway": "npm:@sparkelf/dsh-api-gateway@0.1.6-alpha.2",
|
|
125
|
-
"@deepseek-ai/dsh-api-session-controller": "npm:@sparkelf/dsh-api-session-controller@0.1.6-alpha.
|
|
125
|
+
"@deepseek-ai/dsh-api-session-controller": "npm:@sparkelf/dsh-api-session-controller@0.1.6-alpha.3",
|
|
126
|
+
"@deepseek-ai/dsh-api-terminal-controller": "npm:@sparkelf/dsh-api-terminal-controller@0.1.6-alpha.3",
|
|
126
127
|
"@deepseek-ai/dsh-client-connection": "npm:@sparkelf/dsh-client-connection@0.1.6-alpha.2",
|
|
127
128
|
"@deepseek-ai/dsh-client-ui-agent-preset": "npm:@sparkelf/dsh-client-ui-agent-preset@0.1.6-alpha.2",
|
|
128
129
|
"@deepseek-ai/dsh-client-ui-deliverables": "npm:@sparkelf/dsh-client-ui-deliverables@0.1.6-alpha.2",
|
|
@@ -203,5 +204,5 @@
|
|
|
203
204
|
},
|
|
204
205
|
"type": "module",
|
|
205
206
|
"types": "lib/types/index.d.ts",
|
|
206
|
-
"version": "0.2.0-rc.
|
|
207
|
+
"version": "0.2.0-rc.19"
|
|
207
208
|
}
|