@crazx/dsh 0.2.1-rc.2.zw.1 → 0.2.1-rc.2.zw.3

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
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync } from "node:fs";
3
- import { fileURLToPath } from "node:url";
4
- import { loadLayeredEnv } from "@deepseek-ai/dsh-app-boot";
5
- import { Command, CommanderError } from "commander";
2
+ import { StartupError, getDshRuntimeVersion, loadLayeredEnv } from "@deepseek-ai/dsh-app-boot";
3
+ import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
4
+ import { Command, CommanderError, InvalidArgumentError } from "commander";
5
+ import { randomUUID } from "node:crypto";
6
+ import { mkdir, writeFile } from "node:fs/promises";
7
+ import { join } from "node:path";
8
+ import { inspect } from "node:util";
6
9
  //#region lib/types/args.js
7
10
  /**
8
11
  * Commander adapter for the `dsh` command line.
@@ -16,9 +19,9 @@ import { Command, CommanderError } from "commander";
16
19
  * `dsh --profile tui --resume abc` boots the tui profile with `--resume abc`,
17
20
  * and `dsh --profile web -h` prints the web app's help, not this one's.
18
21
  *
19
- * `web` is a hardcoded alias for `--profile web`; `plugin` manages a profile's
20
- * plugin dependencies by forwarding to pnpm. The fork adds `web:log` /
21
- * `web:log:tmp`, which boot the web profile while teeing output to a log file.
22
+ * `dsh <name>` abbreviates `dsh --profile <name>`; `plugin` manages a profile's
23
+ * plugin dependencies by forwarding to pnpm.
24
+ * The fork adds `web:log` / `web:log:tmp`, which boot the web profile while teeing output to a log file.
22
25
  * @module @deepseek-ai/dsh/args
23
26
  */
24
27
  /**
@@ -26,21 +29,30 @@ import { Command, CommanderError } from "commander";
26
29
  * variadic — a variadic `--patch` would swallow the inner arguments.
27
30
  */
28
31
  const collect = (value, previous = []) => [...previous, value];
32
+ function selectProfile(value, previous) {
33
+ if (previous !== void 0) throw new InvalidArgumentError("select a profile only once");
34
+ return value;
35
+ }
36
+ function rejectElectronProfile(program, profile) {
37
+ if (profile.toLowerCase() === "desktop") program.error("error: profile \"desktop\" is managed exclusively by the Electron application");
38
+ }
29
39
  /** The launcher's own help text; each app prints its own. */
30
40
  const HELP_EXAMPLES = `
31
41
  Examples:
32
- dsh --profile web boot the web profile (same as: dsh web)
33
- dsh --profile headless "run the tests" answer one task, print the result, and exit
34
- dsh --profile tui --patch ./extra.yml boot a custom profile with one extra overlay
35
- dsh --profile tui --resume <session> arguments after the launcher flags reach the app
36
- dsh --profile web --help the web app's own flags and help
37
- dsh plugin --profile tui add <package> install a plugin into the tui profile
42
+ dsh web boot the web profile (same as: dsh --profile web)
43
+ dsh rescue --from-default-profile web
44
+ recreate rescue from the shipped web template, then boot it
45
+ dsh headless "run the tests" answer one task, print the result, and exit
46
+ dsh tui --patch ./extra.yml boot a custom profile with one extra overlay
47
+ dsh tui --resume <session> arguments after the launcher flags reach the app
48
+ dsh web --help the web app's own flags and help
49
+ dsh plugin --profile tui add <package> install a plugin into the tui profile
38
50
  dsh web:log --port 0 boot web with output teed to a timestamped log file (fork-local)
39
51
  `;
40
52
  /**
41
53
  * Resolve a boot or dump invocation from the launcher flags and the leftover
42
54
  * inner arguments.
43
- * @param program - the command whose options were parsed (the root, or the `web` alias).
55
+ * @param program - the command whose options were parsed.
44
56
  * @param profile - the profile these flags boot.
45
57
  * @param options - the launcher flags commander collected.
46
58
  * @param args - the leftover arguments, in argv order.
@@ -49,19 +61,33 @@ Examples:
49
61
  function resolveBoot(program, profile, options, args) {
50
62
  const patches = options.patch ?? [];
51
63
  if (patches.includes("")) program.error("error: --patch needs a path");
52
- if (options.dumpConfig !== true && options.dumpDefaultConfig !== true) return {
64
+ if (options.fromDefaultProfile === "") program.error("error: --from-default-profile needs a name");
65
+ const dumps = [
66
+ options.dumpConfig,
67
+ options.dumpDefaultConfig,
68
+ options.dumpConfigSchema
69
+ ].filter(Boolean);
70
+ if (dumps.length === 0) return {
53
71
  mode: "profile",
54
72
  profile,
73
+ fromDefaultProfile: options.fromDefaultProfile,
55
74
  patches,
56
75
  args
57
76
  };
58
- if (options.dumpConfig === true && options.dumpDefaultConfig === true) program.error("error: --dump-config and --dump-default-config are mutually exclusive");
77
+ if (dumps.length > 1) program.error("error: --dump-config, --dump-default-config, and --dump-config-schema are mutually exclusive");
59
78
  if (args.length > 0) program.error(`error: config dumps take no app arguments, got ${args.map((argument) => JSON.stringify(argument)).join(" ")}`);
79
+ if (options.dumpConfigSchema === true) return {
80
+ mode: "dump-config-schema",
81
+ profile,
82
+ fromDefaultProfile: options.fromDefaultProfile,
83
+ patches
84
+ };
60
85
  const defaultOnly = options.dumpDefaultConfig === true;
61
86
  if (defaultOnly && patches.length > 0) program.error("error: --dump-default-config prints the bundle layers and takes no --patch");
62
87
  return {
63
88
  mode: "dump-config",
64
89
  profile,
90
+ fromDefaultProfile: options.fromDefaultProfile,
65
91
  defaultOnly,
66
92
  patches
67
93
  };
@@ -71,30 +97,28 @@ function resolveBoot(program, profile, options, args) {
71
97
  * error.
72
98
  * @param argv - arguments after the Node binary and script.
73
99
  * @param version - version string printed by `--version`.
100
+ * @param manageDesktopProfile - permit Desktop's installed carrier to manage its reserved profile's plugins.
74
101
  * @returns the resolved invocation.
75
102
  */
76
- function parseDshArgs(argv, version) {
103
+ function parseDshArgs(argv, version, manageDesktopProfile = false) {
104
+ const first = argv[0];
77
105
  let resolved;
78
106
  const program = new Command();
79
- program.name("dsh").version(version, "-V, --version", "output the version number").description("dsh: boot a DeepSeek Harness profile — an ordered stack of plugin-bundle patch layers under your own overrides.").addHelpText("after", HELP_EXAMPLES).exitOverride().helpOption(false).allowUnknownOption().passThroughOptions().enablePositionalOptions().argument("[args...]", "arguments for the booted profile's app (see: dsh --profile <name> --help)").option("--profile <name>", "the profile under $DSH_HOME/profiles to boot").option("--patch <path>", "extra patch-list overlay applied after the profile layer (repeatable)", collect).option("--dump-config", "print the composed profile tree and exit").option("--dump-default-config", "print the profile tree without its user layer or --patch overlays and exit").action((args, options) => {
107
+ program.name("dsh").version(version, "-V, --version", "output the version number").usage("[--profile] <name> [options] [app-args...]\n dsh plugin --profile <name> <pnpm-args...>").description("dsh: boot a DeepSeek Harness profile — an ordered stack of plugin-bundle patch layers under your own overrides.").addHelpText("after", HELP_EXAMPLES).exitOverride().helpOption(false).helpCommand(false).allowUnknownOption().passThroughOptions().enablePositionalOptions().argument("[args...]", "arguments for the booted profile's app (see: dsh --profile <name> --help)").option("--profile <name>", "the profile under $DSH_HOME/profiles to boot", selectProfile).option("--from-default-profile <name>", "replace a custom profile with a fresh copy of a shipped profile template").option("--patch <path>", "extra patch-list overlay applied after the profile layer (repeatable)", collect).option("--dump-config", "print the composed profile tree and exit").option("--dump-config-schema", "print JSON Schema for profile entries and patches without mounting").option("--dump-default-config", "print the profile tree without its user layer or --patch overlays and exit").action((args, options) => {
80
108
  if (options.profile === void 0) {
81
109
  if (args.some((argument) => argument === "-h" || argument === "--help")) program.help();
82
110
  program.error("error: --profile <name> is required");
83
111
  }
84
112
  const profile = options.profile;
85
113
  if (profile === "") program.error("error: --profile needs a name");
114
+ rejectElectronProfile(program, profile);
86
115
  resolved = resolveBoot(program, profile, options, args);
87
116
  });
88
117
  /** Reject parent options supplied before a subcommand. */
89
118
  const rejectParentOptions = (command) => {
90
119
  const parent = program.opts();
91
- if (parent.profile !== void 0 || parent.patch !== void 0 || parent.dumpConfig !== void 0 || parent.dumpDefaultConfig !== void 0) program.error(`error: ${command} takes none of parent --profile, --patch, --dump-config, or --dump-default-config`);
120
+ if (parent.profile !== void 0 || parent.patch !== void 0 || parent.dumpConfig !== void 0 || parent.dumpDefaultConfig !== void 0 || parent.fromDefaultProfile !== void 0) program.error(`error: ${command} takes none of parent --profile, --from-default-profile, --patch, --dump-config, or --dump-default-config`);
92
121
  };
93
- const web = program.command("web").description("boot the web profile (alias of --profile web); the web app's own flags follow");
94
- web.helpOption(false).allowUnknownOption().passThroughOptions().enablePositionalOptions().argument("[args...]", "arguments for the web app (see: dsh web --help)").option("--patch <path>", "extra patch-list overlay applied after the profile layer (repeatable)", collect).option("--dump-config", "print the composed web-profile tree (with the user layer and any --patch) and exit").option("--dump-default-config", "print the web profile's bundle layers (no user layer) and exit").action((args, options) => {
95
- rejectParentOptions("web");
96
- resolved = resolveBoot(web, "web", options, args);
97
- });
98
122
  /**
99
123
  * Fork-local logged web boots: `dsh web:log` tees the web app's output to a
100
124
  * timestamped file under $DSH_HOME/logs, `dsh web:log:tmp` under the OS temp
@@ -115,18 +139,23 @@ function parseDshArgs(argv, version) {
115
139
  };
116
140
  registerWebLog("web:log", false);
117
141
  registerWebLog("web:log:tmp", true);
118
- program.command("plugin").description("manage a profile's plugins by forwarding the remaining arguments to pnpm in the profile directory").requiredOption("--profile <name>", "the profile whose plugins to manage (initialized on first use)").allowUnknownOption().argument("[args...]", "pnpm arguments, forwarded verbatim (add <pkg>, remove <pkg>, why <pkg>, ...)").action((args, options) => {
119
- rejectParentOptions("plugin");
120
- if (options.profile === "") program.error("error: --profile needs a name");
121
- if (args.length === 0) program.error("error: plugin needs pnpm arguments to forward (e.g. add <package>)");
122
- resolved = {
123
- mode: "plugin",
124
- profile: options.profile,
125
- args
126
- };
127
- });
142
+ if (first === "plugin") {
143
+ const plugin = program.command("plugin").description("manage a profile's plugins by forwarding the remaining arguments to pnpm in the profile directory");
144
+ plugin.helpOption("-h, --help", "show this help").passThroughOptions().requiredOption("--profile <name>", "the profile whose plugins to manage (initialized on first use)", selectProfile).allowUnknownOption().argument("[args...]", "pnpm arguments, forwarded verbatim (add <pkg>, remove <pkg>, why <pkg>, ...)").action((args, options) => {
145
+ if (options.profile === "") program.error("error: --profile needs a name");
146
+ if (!manageDesktopProfile) rejectElectronProfile(plugin, options.profile);
147
+ if (args.length === 0) program.error("error: plugin needs pnpm arguments to forward (e.g. add <package>)");
148
+ resolved = {
149
+ mode: "plugin",
150
+ profile: options.profile.toLowerCase() === "desktop" ? "desktop" : options.profile,
151
+ args
152
+ };
153
+ });
154
+ }
128
155
  try {
129
- program.parse(argv, { from: "user" });
156
+ const forkCommand = first === "plugin" || first === "web:log" || first === "web:log:tmp";
157
+ const expanded = first !== void 0 && !first.startsWith("-") && !forkCommand ? ["--profile", ...argv] : argv;
158
+ program.parse(expanded, { from: "user" });
130
159
  } catch (error) {
131
160
  return process.exit(error instanceof CommanderError ? error.exitCode : 1);
132
161
  }
@@ -135,51 +164,128 @@ function parseDshArgs(argv, version) {
135
164
  return resolved;
136
165
  }
137
166
  //#endregion
138
- //#region lib/types/bin.js
167
+ //#region lib/types/startup-diagnostics.js
168
+ /** Save original startup diagnostics while keeping the terminal report concise. */
169
+ /** Wait for stderr to finish the write before the failed process exits. */
170
+ function writeStderr(text) {
171
+ return new Promise((resolve, reject) => {
172
+ process.stderr.write(text, (error) => {
173
+ if (error) reject(error);
174
+ else resolve();
175
+ });
176
+ });
177
+ }
139
178
  /**
140
- * dsh — command-line entry. Dynamic imports per mode keep unrelated modes out
141
- * of each dispatch path; the adapter prints and exits for
142
- * `--help`/`--version`/a parse error, so only a valid mode reaches the switch.
143
- * @module @deepseek-ai/dsh/bin
179
+ * Print the startup summary and save a private, uniquely named report under DSH_HOME/logs.
180
+ * Failed writes print the complete report to stderr instead of claiming a saved path.
181
+ * @param error - startup audit failure retaining plugin metadata and original errors.
182
+ * @param context - resolved Harness home, application version, and selected profile.
183
+ * @param write - terminal output sink; awaited before returning, defaults to stderr.
184
+ * @returns after saving or printing the report and completing terminal writes.
144
185
  */
145
- /* v8 ignore file -- built-bin acceptance exercises this self-executing dispatch. */
146
- /** This app's version, read from its checked-in package.json. */
147
- function readVersion() {
148
- const manifest = JSON.parse(readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
149
- return typeof manifest.version === "string" ? manifest.version : "0.0.0";
150
- }
151
- const invocation = parseDshArgs(process.argv.slice(2), readVersion());
152
- switch (invocation.mode) {
153
- case "profile": {
154
- const { runProfile } = await import("./profile-boot-BnJoK_kl.js");
155
- await runProfile({
156
- environment: loadLayeredEnv("dsh"),
157
- profile: invocation.profile,
158
- patchFiles: invocation.patches,
159
- args: invocation.args
186
+ async function reportStartupFailure(error, context, write = writeStderr) {
187
+ const now = (/* @__PURE__ */ new Date()).toISOString();
188
+ const report = "WARNING: Raw diagnostics may contain configuration or credential values from plugin errors. Review before sharing.\n\n" + inspect({
189
+ timestamp: now,
190
+ dshVersion: context.version,
191
+ nodeVersion: process.version,
192
+ platform: process.platform,
193
+ arch: process.arch,
194
+ profile: context.profile,
195
+ error
196
+ }, {
197
+ depth: null,
198
+ maxArrayLength: null,
199
+ maxStringLength: null,
200
+ showHidden: true,
201
+ customInspect: false,
202
+ getters: false,
203
+ colors: false
204
+ }) + "\n";
205
+ await write(`${error.message}\n`);
206
+ const logDir = join(context.home, "logs");
207
+ const logPath = join(logDir, `startup-${now.replaceAll(":", "-")}-${randomUUID()}.log`);
208
+ try {
209
+ await mkdir(logDir, {
210
+ recursive: true,
211
+ mode: 448
160
212
  });
161
- break;
162
- }
163
- case "plugin": {
164
- const { runPlugin } = await import("./plugin-9h8shc4d.js");
165
- process.exit(runPlugin(invocation.profile, invocation.args));
166
- break;
167
- }
168
- case "web-log": {
169
- const { runWebLog } = await import("./web-log-DZLBFfre.js");
170
- runWebLog({
171
- tmp: invocation.tmp,
172
- patches: invocation.patches,
173
- args: invocation.args
213
+ await writeFile(logPath, report, {
214
+ flag: "wx",
215
+ mode: 384
174
216
  });
175
- break;
217
+ } catch (writeError) {
218
+ await write(`\ndsh: warning: could not write startup diagnostics: ${String(writeError)}\nFull diagnostics:\n${report}`);
219
+ return;
176
220
  }
177
- case "dump-config": {
178
- const { runDumpConfig } = await import("./dump-config-D-jtgwY3.js");
179
- runDumpConfig(invocation.profile, invocation.defaultOnly, invocation.patches);
180
- break;
221
+ await write(`\nFull diagnostics: ${logPath}\n`);
222
+ }
223
+ //#endregion
224
+ //#region lib/types/bin.js
225
+ /**
226
+ * Command-line entry for dsh.
227
+ * @module @deepseek-ai/dsh/bin
228
+ */
229
+ /* v8 ignore file -- built-bin acceptance exercises this self-executing dispatch. */
230
+ /**
231
+ * Run the public dsh command-line interface.
232
+ * @param options - Package runtime and Desktop profile access supplied by the installation.
233
+ * @returns a promise that settles when the selected command mode finishes.
234
+ */
235
+ async function runCli(options = {}) {
236
+ const version = getDshRuntimeVersion();
237
+ const { manageDesktopProfile, ...profileOptions } = options;
238
+ const invocation = parseDshArgs(process.argv.slice(2), version, manageDesktopProfile);
239
+ switch (invocation.mode) {
240
+ case "profile": {
241
+ const { runProfile } = await import("./profile-boot.js");
242
+ try {
243
+ await runProfile({
244
+ environment: loadLayeredEnv("dsh"),
245
+ profile: invocation.profile,
246
+ fromDefaultProfile: invocation.fromDefaultProfile,
247
+ patchFiles: invocation.patches,
248
+ args: invocation.args,
249
+ ...profileOptions
250
+ });
251
+ } catch (error) {
252
+ if (!(error instanceof StartupError)) throw error;
253
+ await reportStartupFailure(error, {
254
+ home: resolveDshHome(),
255
+ version,
256
+ profile: invocation.profile
257
+ });
258
+ process.exit(1);
259
+ }
260
+ break;
261
+ }
262
+ case "plugin": {
263
+ const { runPlugin } = await import("./plugin-BiveYA7h.js");
264
+ process.exit(await runPlugin(invocation.profile, invocation.args, options.packageManager));
265
+ break;
266
+ }
267
+ case "web-log": {
268
+ const { runWebLog } = await import("./web-log-DVrp_oFP.js");
269
+ runWebLog({
270
+ tmp: invocation.tmp,
271
+ patches: invocation.patches,
272
+ args: invocation.args
273
+ });
274
+ break;
275
+ }
276
+ case "dump-config": {
277
+ const { runDumpConfig } = await import("./dump-config-CxNkSYcK.js");
278
+ runDumpConfig(invocation.profile, invocation.defaultOnly, invocation.patches, invocation.fromDefaultProfile);
279
+ break;
280
+ }
281
+ case "dump-config-schema": {
282
+ const { runDumpConfigSchema } = await import("./dump-config-schema-D_PxIzpe.js");
283
+ await runDumpConfigSchema(invocation.profile, invocation.patches, invocation.fromDefaultProfile);
284
+ break;
285
+ }
286
+ default: throw new Error(`dsh: unhandled invocation mode ${JSON.stringify(invocation)}`);
181
287
  }
182
- default: throw new Error(`dsh: unhandled invocation mode ${JSON.stringify(invocation)}`);
183
288
  }
289
+ if (import.meta.main) await runCli();
184
290
  //#endregion
185
- export {};
291
+ export { runCli };
@@ -1,7 +1,7 @@
1
- import { i as prepareProfile, n as PROFILE_ROOT_FILENAME, r as homePatchPath } from "./profile-boot-DG5t9aNs.js";
2
- import { existsSync } from "node:fs";
1
+ import { a as prepareProfile, n as PROFILE_ROOT_FILENAME, r as homePatchPath } from "./profile-boot-C14i-8IN.js";
3
2
  import { loadOptionalPatches, loadOverlayPatches, renderConfigDump } from "@deepseek-ai/dsh-app-boot";
4
3
  import { join, resolve } from "node:path";
4
+ import { existsSync } from "node:fs";
5
5
  //#region lib/types/dump-config.js
6
6
  /**
7
7
  * Config-dump entry for `dsh --profile <name> --dump-config`: compose the
@@ -19,9 +19,21 @@ const NAME = "dsh";
19
19
  * (the recovery diagnostic for a broken `cordis.patch.yml`, which is then
20
20
  * never parsed).
21
21
  * @param patches - `--patch` overlay paths, in argv order.
22
+ * @param fromDefaultProfile - shipped template that replaces the profile before loading it.
22
23
  */
23
- function runDumpConfig(profile, defaultOnly, patches) {
24
- const loaded = prepareProfile(profile, !defaultOnly);
24
+ function runDumpConfig(profile, defaultOnly, patches, fromDefaultProfile) {
25
+ const loaded = prepareProfile(profile, !defaultOnly, fromDefaultProfile);
26
+ const layers = collectConfigDumpLayers(loaded, defaultOnly, patches);
27
+ process.stdout.write(renderConfigDump(NAME, join(loaded.dir, PROFILE_ROOT_FILENAME), layers));
28
+ }
29
+ /**
30
+ * Read dump layers in bundle, profile, home, then argv order without composing them.
31
+ * @param loaded - prepared profile and parsed bundle and profile patches.
32
+ * @param defaultOnly - omit profile, home, and argv layers without reading their files.
33
+ * @param patches - overlay paths relative to the invoking directory, in argv order.
34
+ * @returns the labeled layers shared by YAML and schema dumps.
35
+ */
36
+ function collectConfigDumpLayers(loaded, defaultOnly, patches) {
25
37
  const layers = loaded.layers.map((layer) => ({
26
38
  label: layer.packageName,
27
39
  patches: layer.patches
@@ -45,8 +57,8 @@ function runDumpConfig(profile, defaultOnly, patches) {
45
57
  });
46
58
  }
47
59
  }
48
- process.stdout.write(renderConfigDump(NAME, join(loaded.dir, PROFILE_ROOT_FILENAME), layers));
60
+ return layers;
49
61
  }
50
62
  /* v8 ignore stop */
51
63
  //#endregion
52
- export { runDumpConfig };
64
+ export { runDumpConfig as n, collectConfigDumpLayers as t };
@@ -0,0 +1,2 @@
1
+ import { n as runDumpConfig } from "./dump-config-CWs_HdOc.js";
2
+ export { runDumpConfig };
@@ -0,0 +1,41 @@
1
+ import { a as prepareProfile, t as INSTALL_ANCHOR } from "./profile-boot-C14i-8IN.js";
2
+ import { t as collectConfigDumpLayers } from "./dump-config-CWs_HdOc.js";
3
+ import { generateConfigSchema } from "@deepseek-ai/dsh-app-boot";
4
+ //#region lib/types/dump-config-schema.js
5
+ /**
6
+ * Schema-dump entry: inspect the composed profile without applying its plugins.
7
+ * Imports and lazy schema builders execute trusted module code.
8
+ * @module @deepseek-ai/dsh/dump-config-schema
9
+ */
10
+ /* v8 ignore file -- built-bin acceptance drives schema collection and output. */
11
+ const NAME = "dsh";
12
+ /**
13
+ * Print one JSON Schema document; incomplete collection or projection sets exitCode to 1.
14
+ * Profile initialization writes match the YAML dump. Plugin imports may execute
15
+ * top-level code, but collection never applies plugins or evaluates `!!js`.
16
+ * During schema generation, ordinary stdout writes are redirected to stderr.
17
+ * @param profile - the profile name.
18
+ * @param patches - overlay paths, in argv order.
19
+ * @param fromDefaultProfile - shipped template that replaces the profile before loading it.
20
+ * @returns after collection and output, with diagnostics also written to stderr.
21
+ */
22
+ async function runDumpConfigSchema(profile, patches, fromDefaultProfile) {
23
+ const loaded = prepareProfile(profile, true, fromDefaultProfile);
24
+ const layers = collectConfigDumpLayers(loaded, false, patches);
25
+ const stdoutWrite = process.stdout.write;
26
+ let dump;
27
+ process.stdout.write = process.stderr.write.bind(process.stderr);
28
+ try {
29
+ dump = await generateConfigSchema(loaded, layers.map((layer) => layer.patches), INSTALL_ANCHOR);
30
+ } finally {
31
+ process.stdout.write = stdoutWrite;
32
+ }
33
+ process.stdout.write(`${JSON.stringify(dump, null, 2)}\n`);
34
+ for (const diagnostic of dump["x-cordis"].diagnostics) {
35
+ const location = diagnostic.path === void 0 ? "" : ` [${diagnostic.path}]`;
36
+ process.stderr.write(`${NAME}: ${diagnostic.level}:${location} ${diagnostic.message}\n`);
37
+ }
38
+ if (!dump["x-cordis"].complete) process.exitCode = 1;
39
+ }
40
+ //#endregion
41
+ export { runDumpConfigSchema };
@@ -0,0 +1,101 @@
1
+ import { t as INSTALL_ANCHOR } from "./profile-boot-C14i-8IN.js";
2
+ import { DEFAULT_PROFILE_BUNDLES, PROFILE_TEMPLATES, initProfile, readProfileCompatibility, resolveProfileDir } from "@deepseek-ai/dsh-app-boot";
3
+ import { mkdir } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { existsSync } from "node:fs";
6
+ import { runPluginCommand, runProfilePnpm, setProfileVersionExemption } from "@deepseek-ai/dsh-plugin-manager/operations";
7
+ import { withFileLock } from "@deepseek-ai/dsh-atomic-write";
8
+ //#region lib/types/plugin.js
9
+ /** Profile package management and explicit, exact-version compatibility approvals. */
10
+ function requireDesktopProfile(dir) {
11
+ if (!existsSync(join(dir, "package.json"))) throw new Error("Open DeepSeek Harness Desktop once to initialize its profile, then fully quit it before running dsh plugin --profile desktop.");
12
+ }
13
+ /** Parse only DSH-owned commands; all other arguments remain pnpm's responsibility. */
14
+ async function versionCommand(profile, args) {
15
+ const [command, ...rest] = args;
16
+ if (command !== "allow-version" && command !== "revoke-version" && command !== "version-exemptions") return void 0;
17
+ try {
18
+ let packageVersion;
19
+ let runtimeVersion;
20
+ let acceptRisk = false;
21
+ const argumentsIterator = rest.values();
22
+ for (const argument of argumentsIterator) if (argument === "--accept-risk" && command === "allow-version" && !acceptRisk) acceptRisk = true;
23
+ else if (argument === "--dsh-version" && runtimeVersion === void 0) runtimeVersion = argumentsIterator.next().value;
24
+ else if (argument.startsWith("--dsh-version=") && runtimeVersion === void 0) runtimeVersion = argument.slice(14);
25
+ else if (!argument.startsWith("-") && packageVersion === void 0) packageVersion = argument;
26
+ else throw new Error(`unexpected argument ${JSON.stringify(argument)}`);
27
+ if (command === "version-exemptions" && rest.length > 0) throw new Error("usage: dsh plugin version-exemptions");
28
+ let request;
29
+ if (command !== "version-exemptions") {
30
+ if (packageVersion === void 0 || runtimeVersion === void 0) throw new Error(`usage: dsh plugin ${command} <package@version> --dsh-version <exact>${command === "allow-version" ? " --accept-risk" : ""}`);
31
+ request = {
32
+ packageVersion,
33
+ runtimeVersion
34
+ };
35
+ }
36
+ if (command === "allow-version") process.stderr.write("dsh: warning: allowing incompatible plugin versions can break the application or corrupt data. Approval applies only to the exact package and DSH versions.\n");
37
+ const dir = resolveProfileDir(profile);
38
+ if (profile !== "desktop") await mkdir(dir, { recursive: true });
39
+ await withFileLock(join(dir, "package.json"), async () => {
40
+ if (profile === "desktop") requireDesktopProfile(dir);
41
+ else if (!existsSync(join(dir, "package.json"))) initProfile(dir, PROFILE_TEMPLATES[profile]?.bundles ?? DEFAULT_PROFILE_BUNDLES);
42
+ if (request === void 0) {
43
+ const { exemptions, warnings } = readProfileCompatibility(dir);
44
+ for (const warning of warnings) process.stderr.write(`dsh: warning: ${warning}\n`);
45
+ process.stdout.write(JSON.stringify(exemptions, void 0, 2) + "\n");
46
+ } else {
47
+ await setProfileVersionExemption(dir, request.packageVersion, request.runtimeVersion, command === "allow-version", acceptRisk);
48
+ process.stdout.write(`dsh: ${command === "allow-version" ? "allowed" : "revoked"} ${request.packageVersion} for DSH ${request.runtimeVersion}\n`);
49
+ }
50
+ }, { waitMs: 12e4 });
51
+ return 0;
52
+ } catch (error) {
53
+ process.stderr.write(`dsh: ${String(error)}\n`);
54
+ return 1;
55
+ }
56
+ }
57
+ /** Run package management for a profile.
58
+ * @param profile Profile name; Desktop's reserved profile must already be initialized by the application.
59
+ * @param args DSH exemption command or pnpm arguments relative to the invoking directory.
60
+ * @param packageManager Installation-owned executable and environment for pnpm operations.
61
+ * @returns Zero on success; nonzero on invalid approval or package-manager failure.
62
+ */
63
+ async function runPlugin(profile, args, packageManager) {
64
+ if (profile === "desktop") try {
65
+ requireDesktopProfile(resolveProfileDir(profile));
66
+ } catch (error) {
67
+ process.stderr.write(`dsh: ${String(error)}\n`);
68
+ return 1;
69
+ }
70
+ const versionResult = await versionCommand(profile, args);
71
+ if (versionResult !== void 0) return versionResult;
72
+ const dir = resolveProfileDir(profile);
73
+ if (existsSync(join(dir, "package.json"))) for (const warning of readProfileCompatibility(dir).warnings) process.stderr.write(`dsh: warning: ${warning}\n`);
74
+ const context = {
75
+ profile,
76
+ dir,
77
+ installAnchor: INSTALL_ANCHOR,
78
+ cwd: process.cwd()
79
+ };
80
+ const options = {
81
+ ...packageManager,
82
+ execution: "cli",
83
+ outputBytes: 16384,
84
+ lockWaitMs: 12e4,
85
+ lookupTimeoutMs: 12e4,
86
+ onOutput: (text, stream) => {
87
+ process[stream].write(text);
88
+ }
89
+ };
90
+ const result = profile === "desktop" ? await withFileLock(join(dir, "package.json"), async () => {
91
+ requireDesktopProfile(dir);
92
+ return runProfilePnpm(context, args, options);
93
+ }, { waitMs: 12e4 }) : await runPluginCommand(context, args, options);
94
+ if (result.exitCode === 127) process.stderr.write("dsh: pnpm was not found; install pnpm and make it available on PATH.\n");
95
+ for (const { name, version, runtimeVersion } of result.incompatible ?? []) process.stderr.write(`dsh: to accept the risk, run: dsh plugin --profile ${profile} allow-version ${name}@${version} --dsh-version ${runtimeVersion} --accept-risk\n`);
96
+ if (result.exitCode !== 0) process.stderr.write(`dsh: plugin command failed; diagnostics: ${result.logPath}\n`);
97
+ if (result.exitCode !== 0 && args.some((argument) => /^git\+|^github:|\.git(?:#|$)/.test(argument))) process.stderr.write(`dsh: git-hosted plugins build on install via their prepare script, which pnpm blocks until allowed — add the exact key pnpm printed above under allowBuilds in ${join(resolveProfileDir(profile), "pnpm-workspace.yaml")}, then re-run\n`);
98
+ return result.exitCode;
99
+ }
100
+ //#endregion
101
+ export { runPlugin };