@crazx/dsh 0.1.6-alpha.2.zw.2 → 0.2.1-rc.2.zw.1
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/README.i18n.yaml +21 -5
- package/README.md +8 -4
- package/README.zh.md +8 -4
- package/lib/bin.js +66 -158
- package/lib/{dump-config-Bb5YvFIX.js → dump-config-D-jtgwY3.js} +3 -4
- package/lib/plugin-9h8shc4d.js +129 -0
- package/lib/profile-boot-BnJoK_kl.js +2 -0
- package/lib/profile-boot-DG5t9aNs.js +283 -0
- package/lib/types/args.d.ts +13 -4
- package/lib/types/bin.d.ts +8 -1
- package/lib/types/dump-config-schema.d.ts +17 -0
- package/lib/types/dump-config.d.ts +10 -1
- package/lib/types/plugin.d.ts +6 -4
- package/lib/types/profile-boot.d.ts +17 -12
- package/lib/web-log-CKn_UFWp.js +83 -0
- package/package.json +136 -127
- package/LICENSE +0 -21
- package/lib/plugin-DJ-rVHUS.js +0 -31
- package/lib/profile-boot-BNu17Y9U.js +0 -304
- package/lib/profile-boot.js +0 -2
package/README.i18n.yaml
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
# Bilingual-pair consistency record (docs/i18n/README.md):
|
|
2
|
-
#
|
|
3
|
-
#
|
|
1
|
+
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
|
2
|
+
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
|
3
|
+
# After editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write apps/cli/README.md
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/deepseek-ai-dsh:
|
|
6
|
+
en: ebdc75fa0da18801
|
|
7
|
+
zh: 5c6cd2554b8f8a98
|
|
8
|
+
/deepseek-ai-dsh/entry-modes:
|
|
9
|
+
en: a1e035a813554366
|
|
10
|
+
zh: b87053214afe71b6
|
|
11
|
+
/deepseek-ai-dsh/app-arguments:
|
|
12
|
+
en: cc03e6b75206661b
|
|
13
|
+
zh: 99e41f4143198352
|
|
14
|
+
/deepseek-ai-dsh/profiles:
|
|
15
|
+
en: 523477a093beb0c2
|
|
16
|
+
zh: f88ea21e5fbea969
|
|
17
|
+
/deepseek-ai-dsh/optional-overlays:
|
|
18
|
+
en: 3a40ddc44e55f79c
|
|
19
|
+
zh: 8d3949132e1ef93c
|
|
20
|
+
/deepseek-ai-dsh/development:
|
|
21
|
+
en: 0a289c1235869927
|
|
22
|
+
zh: a1f473f70036ad37
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ The `dsh` command is the sole supported Node application launcher: profiles are
|
|
|
9
9
|
| Command | Purpose |
|
|
10
10
|
|---|---|
|
|
11
11
|
| `dsh <name>` / `dsh --profile <name>` | Boot the named profile under `$DSH_HOME/profiles/<name>`. |
|
|
12
|
-
| `dsh --profile <name> --from-default-profile <template>` |
|
|
12
|
+
| `dsh --profile <name> --from-default-profile <template>` | Replace a custom profile with a fresh copy of a shipped template, then boot it. |
|
|
13
13
|
| `dsh --profile acp` | Serve automation clients over ACP stdio until disconnect. |
|
|
14
14
|
| `dsh --profile headless "job"` | Run one fresh persisted session, print the final answer, and exit. |
|
|
15
15
|
| `dsh --profile sdk` | Serve SDK clients over JSON-RPC stdio until shutdown or disconnect. |
|
|
@@ -17,7 +17,7 @@ The `dsh` command is the sole supported Node application launcher: profiles are
|
|
|
17
17
|
| `dsh web` | Boot the Web profile. |
|
|
18
18
|
| `dsh plugin --profile <name> <pnpm args>` | Manage a profile's plugins by forwarding to pnpm in the profile directory. |
|
|
19
19
|
|
|
20
|
-
The invoking directory is the default workspace root. The `web`, `headless`, `sdk`, `sdk-minimal`, and `acp` profiles auto-initialize on first use from shipped templates. Create another profile at
|
|
20
|
+
The invoking directory is the default workspace root. The `web`, `headless`, `sdk`, `sdk-minimal`, and `acp` profiles auto-initialize on first use from shipped templates. Create or reset another profile at a non-shipped name with `--from-default-profile`, or initialize a base-backed profile through `dsh plugin`. The `desktop` name is reserved for the Electron-owned profile, so the CLI rejects boot and config-dump requests for it. The npm CLI also rejects its plugin-management requests; the [Desktop-installed command](../desktop/README.md#bundled-command-runtime) can manage the initialized Desktop profile using that installation’s runtime.
|
|
21
21
|
|
|
22
22
|
## App arguments
|
|
23
23
|
|
|
@@ -36,6 +36,8 @@ dsh --help # the launcher's own help
|
|
|
36
36
|
|
|
37
37
|
A profile directory holds a `package.json` (out-of-tree plugin dependencies plus the profile manifest `dsh.profile` with its ordered `bundles` list) and a `cordis.patch.yml` (the user's own patch layer). `dsh-hmr`, when enabled in YAML, watches the profile manifest and both profile and home patch files, then recomposes all layers through one serialized reload. Without HMR, changes apply on restart. Edits arriving during watcher registration use the same nonfatal reload reporting as later edits. [Plugin Manager](../../packages/boot/plugin-manager/README.md) shares package operations and the profile write lock with `dsh plugin`; package updates retain disabled bundle selections. CLI package commands inherit authentication variables and terminal descriptors, including interactive build approval; service calls retain their scrubbed environment and captured diagnostics.
|
|
38
38
|
|
|
39
|
+
Installation and profile startup enforce the declared `engines.dsh` range, or DSH peer ranges when it is absent, against the same runtime version shown by `dsh --version`. Incompatible plugins require an explicitly acknowledged exact-version exemption. The [plugin manager's compatibility reference](../../packages/boot/plugin-manager/README.md#version-compatibility-and-exemptions) documents `version-exemptions`, `allow-version`, `revoke-version`, persistence, and risks.
|
|
40
|
+
|
|
39
41
|
The tree composes over an empty root:
|
|
40
42
|
- each bundle's patch in `dsh.profile.bundles` order
|
|
41
43
|
- then the profile's `cordis.patch.yml`, then the home-level `$DSH_HOME/cordis.patch.yml`
|
|
@@ -43,13 +45,13 @@ The tree composes over an empty root:
|
|
|
43
45
|
|
|
44
46
|
Bundles named in `dsh.profile.bundles` resolve from the dsh installation first (`@deepseek-ai/dsh-base`, `@deepseek-ai/dsh-web-app`, `@deepseek-ai/dsh-headless`, `@deepseek-ai/dsh-sdk-app`, `@deepseek-ai/dsh-sdk-minimal`, `@deepseek-ai/dsh-acp-app`), then from the profile's own `node_modules`, where pnpm installs out-of-tree plugins.
|
|
45
47
|
|
|
46
|
-
Use `--dump-default-config` and `--dump-config` to inspect the composed tree without booting it.
|
|
48
|
+
Use `--dump-default-config` and `--dump-config` to inspect the composed tree without booting it. `--dump-config-schema` imports the composed tree's declared plugin schemas and prints JSON Schema for entries and patches instead of configuration values; read the [schema-dump safety and scope](reference/README.md#config-schema-dump) before inspecting untrusted plugins.
|
|
47
49
|
|
|
48
50
|
The [CLI behavior reference](reference/README.md) owns exact layer precedence, flags, shutdown behavior, deployment defaults, and source execution. The [startup and reload failure table](../../packages/boot/app-boot/README.md#startup-and-reload-failures) compares optional and required plugin failures with configuration HMR.
|
|
49
51
|
|
|
50
52
|
## Optional overlays
|
|
51
53
|
|
|
52
|
-
`config/examples/` ships opt-in overlays for GitHub review webhooks,
|
|
54
|
+
`config/examples/` ships opt-in overlays for GitHub review webhooks, memory MCP servers, and runtime Cordis tools. They are never part of a default profile; the [user guides](../../docs/user/guide/index.md) and [developer practice guides](../../docs/user/develop/practice/index.md) own setup and safety instructions.
|
|
53
55
|
|
|
54
56
|
## Development
|
|
55
57
|
|
|
@@ -57,4 +59,6 @@ Production runs require built package and frontend artifacts. From the repositor
|
|
|
57
59
|
|
|
58
60
|
The `@deepseek-ai/dsh/profile-boot` export provides the shared profile lifecycle to the Desktop host. A resolved application profile supplies its own installation anchor for runtime package resolution while retaining the Harness home patch, proxy environment, telemetry switch, patch reload, and bounded shutdown.
|
|
59
61
|
|
|
62
|
+
Packaged installations call the same `runCli()` entry with their package-manager executable. The Desktop carrier also enables plugin operations for its initialized profile; npm launches omit these options. Installation-owned package environments apply only to plugin package operations; the invoking directory, ordinary profile selection, and agent-shell PATH retain their CLI meanings.
|
|
63
|
+
|
|
60
64
|
The [Web failure matrix](tests/profiles/web/tests/web-failure-matrix.expected.e2e.ts) runs the built CLI through startup failures and native configuration HMR with `awaitWriteFinish` enabled in `test:expected`. It verifies authenticated HTTP responses, diagnostics, recovery, process exits, and disposal without model API calls; the [startup acceptance](tests/profiles/web/tests/web-best-effort-startup.expected.e2e.ts) also covers the shipped required Web dependencies and port conflicts.
|
package/README.zh.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
| 命令 | 用途 |
|
|
10
10
|
|---|---|
|
|
11
11
|
| `dsh <name>` / `dsh --profile <name>` | 启动位于 `$DSH_HOME/profiles/<name>` 的指定 profile。 |
|
|
12
|
-
| `dsh --profile <name> --from-default-profile <template>` |
|
|
12
|
+
| `dsh --profile <name> --from-default-profile <template>` | 用随附模板的全新副本替换自定义 profile,然后启动它。 |
|
|
13
13
|
| `dsh --profile acp` | 通过 ACP stdio 为自动化客户端提供服务,直至断开连接。 |
|
|
14
14
|
| `dsh --profile headless "job"` | 运行一个全新的持久化会话,打印最终答案并退出。 |
|
|
15
15
|
| `dsh --profile sdk` | 通过 JSON-RPC stdio 为 SDK 客户端提供服务,直至关闭或断开连接。 |
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
| `dsh web` | 启动 Web profile。 |
|
|
18
18
|
| `dsh plugin --profile <name> <pnpm args>` | 通过在 profile 目录中转发给 pnpm 来管理该 profile 的插件。 |
|
|
19
19
|
|
|
20
|
-
运行命令时所在的目录将作为默认 workspace 根目录。`web`、`headless`、`sdk`、`sdk-minimal` 和 `acp` profile 在首次使用时会从随附模板自动初始化。使用 `--from-default-profile`
|
|
20
|
+
运行命令时所在的目录将作为默认 workspace 根目录。`web`、`headless`、`sdk`、`sdk-minimal` 和 `acp` profile 在首次使用时会从随附模板自动初始化。使用 `--from-default-profile` 可以基于这些模板之一,在非内置名称处创建或重置其他 profile;通过 `dsh plugin` 则可以初始化一个以 base 为基础的 profile。`desktop` 名称保留给 Electron 持有的 profile,因此 CLI(命令行界面)会拒绝针对它的启动和配置 dump 请求。npm CLI 也会拒绝其插件管理请求;[Desktop 内置命令](../desktop/README.zh.md#bundled-command-runtime)可以使用该安装的运行时管理已初始化的 Desktop profile。
|
|
21
21
|
|
|
22
22
|
## 应用参数
|
|
23
23
|
|
|
@@ -36,6 +36,8 @@ dsh --help # the launcher's own help
|
|
|
36
36
|
|
|
37
37
|
profile 目录包含一个 `package.json`,其中记录树外插件依赖,以及 profile manifest(元数据清单)`dsh.profile`、其中按顺序排列的 `bundles` 列表;还包含一个 `cordis.patch.yml`,其中保存用户自己的 patch 层。在 YAML 中启用的 `dsh-hmr` 监视 profile manifest、profile 与 home 级 patch 文件,再通过统一串行重载重新组合所有层。未启用 HMR 时,更改在重启后生效。监听器注册期间发生的编辑与后续编辑使用相同的非致命重载错误报告。[插件管理器](../../packages/boot/plugin-manager/README.zh.md) 与 `dsh plugin` 共享包操作和 profile 写锁;更新依赖会保留已停用的组合包选择。CLI 包操作继承认证环境和终端描述符,支持交互式构建批准;service 调用保留清理后的环境并捕获诊断。
|
|
38
38
|
|
|
39
|
+
安装和 profile 启动会按声明的 `engines.dsh` 范围(未声明时按 DSH peer 范围),检查与 `dsh --version` 显示值相同的运行时版本。不兼容插件需要用户明确确认精确版本豁免。[插件管理器的兼容性参考](../../packages/boot/plugin-manager/README.zh.md#version-compatibility-and-exemptions)说明 `version-exemptions`、`allow-version`、`revoke-version`、持久化规则与风险。
|
|
40
|
+
|
|
39
41
|
配置树以空根为起点,依次叠加以下配置层:
|
|
40
42
|
- `dsh.profile.bundles` 中各组合包的 patch
|
|
41
43
|
- profile 自身的 `cordis.patch.yml`,然后是 home 级的 `$DSH_HOME/cordis.patch.yml`
|
|
@@ -43,13 +45,13 @@ profile 目录包含一个 `package.json`,其中记录树外插件依赖,以
|
|
|
43
45
|
|
|
44
46
|
`dsh.profile.bundles` 中列出的组合包先从 dsh 安装目录解析(`@deepseek-ai/dsh-base`、`@deepseek-ai/dsh-web-app`、`@deepseek-ai/dsh-headless`、`@deepseek-ai/dsh-sdk-app`、`@deepseek-ai/dsh-sdk-minimal`、`@deepseek-ai/dsh-acp-app`),再从 profile 自身的 `node_modules` 解析;pnpm 会将树外插件安装到该目录。
|
|
45
47
|
|
|
46
|
-
使用 `--dump-default-config` 和 `--dump-config`
|
|
48
|
+
使用 `--dump-default-config` 和 `--dump-config` 可在不启动的情况下检查组合后的配置树。`--dump-config-schema` 会导入组合树中插件声明的 schema,并打印描述 entry 与 patch 的 JSON Schema,而不是配置值;检查不受信任的插件前,请阅读 [schema dump 的安全性与范围](reference/README.zh.md#config-schema-dump)。
|
|
47
49
|
|
|
48
50
|
层的确切优先级、flag、关闭行为、部署默认值和源码执行方式,以 [CLI 行为参考](reference/README.zh.md)为准。[启动与重载失败表](../../packages/boot/app-boot/README.zh.md#startup-and-reload-failures)对比 optional、required 插件启动失败与配置 HMR 的行为。
|
|
49
51
|
|
|
50
52
|
## 可选覆盖层
|
|
51
53
|
|
|
52
|
-
`config/examples/` 交付 GitHub 评审 webhook
|
|
54
|
+
`config/examples/` 交付 GitHub 评审 webhook、记忆 MCP 服务器与运行时 Cordis 工具的可选覆盖层。它们绝不属于默认 profile;设置与安全说明由[用户指南](../../docs/user/guide/index.zh.md)和[开发实战指南](../../docs/user/develop/practice/index.zh.md)负责。
|
|
53
55
|
|
|
54
56
|
## 开发
|
|
55
57
|
|
|
@@ -57,4 +59,6 @@ profile 目录包含一个 `package.json`,其中记录树外插件依赖,以
|
|
|
57
59
|
|
|
58
60
|
`@deepseek-ai/dsh/profile-boot` 导出向 Desktop Host 提供共享 profile 生命周期。已解析的应用 profile 为运行时包解析指定自己的安装锚点,同时沿用 Harness home patch、代理环境、遥测开关、patch 热重载和有界关闭。
|
|
59
61
|
|
|
62
|
+
打包安装通过同一个 `runCli()` 入口传入包管理器可执行文件。Desktop 载体还会启用其已初始化 profile 的插件操作;npm 启动不传入这些选项。安装包提供的包管理环境仅用于插件包操作;调用目录、普通 profile 选择与 agent shell 的 PATH 保留 CLI 语义。
|
|
63
|
+
|
|
60
64
|
[Web 失败矩阵](tests/profiles/web/tests/web-failure-matrix.expected.e2e.ts)在 `test:expected` 中通过构建后的 CLI 验证启动失败与启用 `awaitWriteFinish` 的原生配置 HMR。它不调用模型 API,而是检查经过认证的 HTTP 响应、诊断、恢复、进程退出与 dispose;[启动验收测试](tests/profiles/web/tests/web-best-effort-startup.expected.e2e.ts)还覆盖随附 Web 的必需依赖与端口冲突。
|
package/lib/bin.js
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { Command, CommanderError, InvalidArgumentError } from "commander";
|
|
7
|
-
import { randomUUID } from "node:crypto";
|
|
8
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
9
|
-
import { join } from "node:path";
|
|
10
|
-
import { inspect } from "node:util";
|
|
4
|
+
import { loadLayeredEnv } from "@deepseek-ai/dsh-app-boot";
|
|
5
|
+
import { Command, CommanderError } from "commander";
|
|
11
6
|
//#region lib/types/args.js
|
|
12
7
|
/**
|
|
13
8
|
* Commander adapter for the `dsh` command line.
|
|
@@ -21,9 +16,9 @@ import { inspect } from "node:util";
|
|
|
21
16
|
* `dsh --profile tui --resume abc` boots the tui profile with `--resume abc`,
|
|
22
17
|
* and `dsh --profile web -h` prints the web app's help, not this one's.
|
|
23
18
|
*
|
|
24
|
-
* `
|
|
25
|
-
* plugin dependencies by forwarding to pnpm.
|
|
26
|
-
*
|
|
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.
|
|
27
22
|
* @module @deepseek-ai/dsh/args
|
|
28
23
|
*/
|
|
29
24
|
/**
|
|
@@ -31,30 +26,21 @@ import { inspect } from "node:util";
|
|
|
31
26
|
* variadic — a variadic `--patch` would swallow the inner arguments.
|
|
32
27
|
*/
|
|
33
28
|
const collect = (value, previous = []) => [...previous, value];
|
|
34
|
-
function selectProfile(value, previous) {
|
|
35
|
-
if (previous !== void 0) throw new InvalidArgumentError("select a profile only once");
|
|
36
|
-
return value;
|
|
37
|
-
}
|
|
38
|
-
function rejectElectronProfile(program, profile) {
|
|
39
|
-
if (profile.toLowerCase() === "desktop") program.error("error: profile \"desktop\" is managed exclusively by the Electron application");
|
|
40
|
-
}
|
|
41
29
|
/** The launcher's own help text; each app prints its own. */
|
|
42
30
|
const HELP_EXAMPLES = `
|
|
43
31
|
Examples:
|
|
44
|
-
dsh web
|
|
45
|
-
dsh
|
|
46
|
-
|
|
47
|
-
dsh
|
|
48
|
-
dsh
|
|
49
|
-
dsh
|
|
50
|
-
dsh web --help the web app's own flags and help
|
|
51
|
-
dsh plugin --profile tui add <package> install a plugin into the tui profile
|
|
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
|
|
52
38
|
dsh web:log --port 0 boot web with output teed to a timestamped log file (fork-local)
|
|
53
39
|
`;
|
|
54
40
|
/**
|
|
55
41
|
* Resolve a boot or dump invocation from the launcher flags and the leftover
|
|
56
42
|
* inner arguments.
|
|
57
|
-
* @param program - the command whose options were parsed.
|
|
43
|
+
* @param program - the command whose options were parsed (the root, or the `web` alias).
|
|
58
44
|
* @param profile - the profile these flags boot.
|
|
59
45
|
* @param options - the launcher flags commander collected.
|
|
60
46
|
* @param args - the leftover arguments, in argv order.
|
|
@@ -63,11 +49,9 @@ Examples:
|
|
|
63
49
|
function resolveBoot(program, profile, options, args) {
|
|
64
50
|
const patches = options.patch ?? [];
|
|
65
51
|
if (patches.includes("")) program.error("error: --patch needs a path");
|
|
66
|
-
if (options.fromDefaultProfile === "") program.error("error: --from-default-profile needs a name");
|
|
67
52
|
if (options.dumpConfig !== true && options.dumpDefaultConfig !== true) return {
|
|
68
53
|
mode: "profile",
|
|
69
54
|
profile,
|
|
70
|
-
fromDefaultProfile: options.fromDefaultProfile,
|
|
71
55
|
patches,
|
|
72
56
|
args
|
|
73
57
|
};
|
|
@@ -78,7 +62,6 @@ function resolveBoot(program, profile, options, args) {
|
|
|
78
62
|
return {
|
|
79
63
|
mode: "dump-config",
|
|
80
64
|
profile,
|
|
81
|
-
fromDefaultProfile: options.fromDefaultProfile,
|
|
82
65
|
defaultOnly,
|
|
83
66
|
patches
|
|
84
67
|
};
|
|
@@ -91,24 +74,27 @@ function resolveBoot(program, profile, options, args) {
|
|
|
91
74
|
* @returns the resolved invocation.
|
|
92
75
|
*/
|
|
93
76
|
function parseDshArgs(argv, version) {
|
|
94
|
-
const first = argv[0];
|
|
95
77
|
let resolved;
|
|
96
78
|
const program = new Command();
|
|
97
|
-
program.name("dsh").version(version, "-V, --version", "output the version number").
|
|
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) => {
|
|
98
80
|
if (options.profile === void 0) {
|
|
99
81
|
if (args.some((argument) => argument === "-h" || argument === "--help")) program.help();
|
|
100
82
|
program.error("error: --profile <name> is required");
|
|
101
83
|
}
|
|
102
84
|
const profile = options.profile;
|
|
103
85
|
if (profile === "") program.error("error: --profile needs a name");
|
|
104
|
-
rejectElectronProfile(program, profile);
|
|
105
86
|
resolved = resolveBoot(program, profile, options, args);
|
|
106
87
|
});
|
|
107
88
|
/** Reject parent options supplied before a subcommand. */
|
|
108
89
|
const rejectParentOptions = (command) => {
|
|
109
90
|
const parent = program.opts();
|
|
110
|
-
if (parent.profile !== void 0 || parent.patch !== void 0 || parent.dumpConfig !== void 0 || parent.dumpDefaultConfig !== void 0
|
|
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`);
|
|
111
92
|
};
|
|
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
|
+
});
|
|
112
98
|
/**
|
|
113
99
|
* Fork-local logged web boots: `dsh web:log` tees the web app's output to a
|
|
114
100
|
* timestamped file under $DSH_HOME/logs, `dsh web:log:tmp` under the OS temp
|
|
@@ -129,23 +115,18 @@ function parseDshArgs(argv, version) {
|
|
|
129
115
|
};
|
|
130
116
|
registerWebLog("web:log", false);
|
|
131
117
|
registerWebLog("web:log:tmp", true);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
};
|
|
143
|
-
});
|
|
144
|
-
}
|
|
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
|
+
});
|
|
145
128
|
try {
|
|
146
|
-
|
|
147
|
-
const expanded = first !== void 0 && !first.startsWith("-") && !forkCommand ? ["--profile", ...argv] : argv;
|
|
148
|
-
program.parse(expanded, { from: "user" });
|
|
129
|
+
program.parse(argv, { from: "user" });
|
|
149
130
|
} catch (error) {
|
|
150
131
|
return process.exit(error instanceof CommanderError ? error.exitCode : 1);
|
|
151
132
|
}
|
|
@@ -154,124 +135,51 @@ function parseDshArgs(argv, version) {
|
|
|
154
135
|
return resolved;
|
|
155
136
|
}
|
|
156
137
|
//#endregion
|
|
157
|
-
//#region lib/types/startup-diagnostics.js
|
|
158
|
-
/** Save original startup diagnostics while keeping the terminal report concise. */
|
|
159
|
-
/** Wait for stderr to finish the write before the failed process exits. */
|
|
160
|
-
function writeStderr(text) {
|
|
161
|
-
return new Promise((resolve, reject) => {
|
|
162
|
-
process.stderr.write(text, (error) => {
|
|
163
|
-
if (error) reject(error);
|
|
164
|
-
else resolve();
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Print the startup summary and save a private, uniquely named report under DSH_HOME/logs.
|
|
170
|
-
* Failed writes print the complete report to stderr instead of claiming a saved path.
|
|
171
|
-
* @param error - startup audit failure retaining plugin metadata and original errors.
|
|
172
|
-
* @param context - resolved Harness home, application version, and selected profile.
|
|
173
|
-
* @param write - terminal output sink; awaited before returning, defaults to stderr.
|
|
174
|
-
* @returns after saving or printing the report and completing terminal writes.
|
|
175
|
-
*/
|
|
176
|
-
async function reportStartupFailure(error, context, write = writeStderr) {
|
|
177
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
178
|
-
const report = "WARNING: Raw diagnostics may contain configuration or credential values from plugin errors. Review before sharing.\n\n" + inspect({
|
|
179
|
-
timestamp: now,
|
|
180
|
-
dshVersion: context.version,
|
|
181
|
-
nodeVersion: process.version,
|
|
182
|
-
platform: process.platform,
|
|
183
|
-
arch: process.arch,
|
|
184
|
-
profile: context.profile,
|
|
185
|
-
error
|
|
186
|
-
}, {
|
|
187
|
-
depth: null,
|
|
188
|
-
maxArrayLength: null,
|
|
189
|
-
maxStringLength: null,
|
|
190
|
-
showHidden: true,
|
|
191
|
-
customInspect: false,
|
|
192
|
-
getters: false,
|
|
193
|
-
colors: false
|
|
194
|
-
}) + "\n";
|
|
195
|
-
await write(`${error.message}\n`);
|
|
196
|
-
const logDir = join(context.home, "logs");
|
|
197
|
-
const logPath = join(logDir, `startup-${now.replaceAll(":", "-")}-${randomUUID()}.log`);
|
|
198
|
-
try {
|
|
199
|
-
await mkdir(logDir, {
|
|
200
|
-
recursive: true,
|
|
201
|
-
mode: 448
|
|
202
|
-
});
|
|
203
|
-
await writeFile(logPath, report, {
|
|
204
|
-
flag: "wx",
|
|
205
|
-
mode: 384
|
|
206
|
-
});
|
|
207
|
-
} catch (writeError) {
|
|
208
|
-
await write(`\ndsh: warning: could not write startup diagnostics: ${String(writeError)}\nFull diagnostics:\n${report}`);
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
await write(`\nFull diagnostics: ${logPath}\n`);
|
|
212
|
-
}
|
|
213
|
-
//#endregion
|
|
214
138
|
//#region lib/types/bin.js
|
|
215
139
|
/**
|
|
216
|
-
*
|
|
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.
|
|
217
143
|
* @module @deepseek-ai/dsh/bin
|
|
218
144
|
*/
|
|
219
145
|
/* v8 ignore file -- built-bin acceptance exercises this self-executing dispatch. */
|
|
146
|
+
/** This app's version, read from its checked-in package.json. */
|
|
220
147
|
function readVersion() {
|
|
221
148
|
const manifest = JSON.parse(readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
|
|
222
149
|
return typeof manifest.version === "string" ? manifest.version : "0.0.0";
|
|
223
150
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const { runPlugin } = await import("./plugin-DJ-rVHUS.js");
|
|
255
|
-
process.exit(await runPlugin(invocation.profile, invocation.args));
|
|
256
|
-
break;
|
|
257
|
-
}
|
|
258
|
-
case "web-log": {
|
|
259
|
-
const { runWebLog } = await import("./web-log-DZLBFfre.js");
|
|
260
|
-
runWebLog({
|
|
261
|
-
tmp: invocation.tmp,
|
|
262
|
-
patches: invocation.patches,
|
|
263
|
-
args: invocation.args
|
|
264
|
-
});
|
|
265
|
-
break;
|
|
266
|
-
}
|
|
267
|
-
case "dump-config": {
|
|
268
|
-
const { runDumpConfig } = await import("./dump-config-Bb5YvFIX.js");
|
|
269
|
-
runDumpConfig(invocation.profile, invocation.defaultOnly, invocation.patches, invocation.fromDefaultProfile);
|
|
270
|
-
break;
|
|
271
|
-
}
|
|
272
|
-
default: throw new Error(`dsh: unhandled invocation mode ${JSON.stringify(invocation)}`);
|
|
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
|
|
160
|
+
});
|
|
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
|
|
174
|
+
});
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case "dump-config": {
|
|
178
|
+
const { runDumpConfig } = await import("./dump-config-D-jtgwY3.js");
|
|
179
|
+
runDumpConfig(invocation.profile, invocation.defaultOnly, invocation.patches);
|
|
180
|
+
break;
|
|
273
181
|
}
|
|
182
|
+
default: throw new Error(`dsh: unhandled invocation mode ${JSON.stringify(invocation)}`);
|
|
274
183
|
}
|
|
275
|
-
if (import.meta.main) await runCli();
|
|
276
184
|
//#endregion
|
|
277
|
-
export {
|
|
185
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as prepareProfile, n as PROFILE_ROOT_FILENAME, r as homePatchPath } from "./profile-boot-DG5t9aNs.js";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { loadOptionalPatches, loadOverlayPatches, renderConfigDump } from "@deepseek-ai/dsh-app-boot";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
@@ -19,10 +19,9 @@ 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 used once to initialize a missing profile.
|
|
23
22
|
*/
|
|
24
|
-
function runDumpConfig(profile, defaultOnly, patches
|
|
25
|
-
const loaded = prepareProfile(profile, !defaultOnly
|
|
23
|
+
function runDumpConfig(profile, defaultOnly, patches) {
|
|
24
|
+
const loaded = prepareProfile(profile, !defaultOnly);
|
|
26
25
|
const layers = loaded.layers.map((layer) => ({
|
|
27
26
|
label: layer.packageName,
|
|
28
27
|
patches: layer.patches
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { t as INSTALL_ANCHOR } from "./profile-boot-DG5t9aNs.js";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { DEFAULT_PROFILE_BUNDLES, PROFILE_TEMPLATES, initProfile, readProfileManifest, resolveBundleDir, resolveProfileDir, writeProfileManifest } from "@deepseek-ai/dsh-app-boot";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
//#region lib/types/plugin.js
|
|
7
|
+
/**
|
|
8
|
+
* `dsh plugin --profile <name> <args...>` — profile plugin management as a
|
|
9
|
+
* thin pnpm forwarder: initialize the profile on first use, run
|
|
10
|
+
* `pnpm <args...>` in the profile directory, then reconcile the
|
|
11
|
+
* `dsh.profile.bundles` layer list against the installed state (a dependency
|
|
12
|
+
* resolving to a package that declares `dsh.bundle` joins the layer stack; a
|
|
13
|
+
* removed or bundle-less dependency leaves it). Reconciling by installed
|
|
14
|
+
* state, not by dependency diff, means `update` activates a package that
|
|
15
|
+
* gained its `dsh.bundle` declaration in a newer version.
|
|
16
|
+
* @module @deepseek-ai/dsh/plugin
|
|
17
|
+
*/
|
|
18
|
+
const NAME = "dsh";
|
|
19
|
+
/**
|
|
20
|
+
* Whether a resolved dependency exports a profile patch, i.e. is a bundle.
|
|
21
|
+
* @param packageName - the dependency's package name.
|
|
22
|
+
* @param profileDir - the profile directory (resolution anchor).
|
|
23
|
+
* @returns true when the package manifest declares `dsh.bundle`.
|
|
24
|
+
*/
|
|
25
|
+
function exportsPatch(packageName, profileDir) {
|
|
26
|
+
let dir;
|
|
27
|
+
try {
|
|
28
|
+
dir = resolveBundleDir(NAME, packageName, INSTALL_ANCHOR, profileDir);
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return readProfileManifest(NAME, dir).dsh?.bundle?.patch !== void 0;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reconcile `dsh.profile.bundles` against the installed state: pnpm has
|
|
36
|
+
* already written the real installed names (so a git/path/tarball/alias spec
|
|
37
|
+
* on the command line reconciles by its true package name) and materialized
|
|
38
|
+
* the packages. A dependency that resolves to a `dsh.bundle`-declaring
|
|
39
|
+
* package joins the layer stack (appended in dependency order); a
|
|
40
|
+
* dependency-listed name that no longer does — removed, or the installed
|
|
41
|
+
* version dropped the declaration — leaves it. In-box bundles from the
|
|
42
|
+
* profile template are not dependencies and are never touched. Warns once
|
|
43
|
+
* per newly-added bundle-less dependency (a plain library is fine; the
|
|
44
|
+
* warning is orientation).
|
|
45
|
+
*/
|
|
46
|
+
function reconcilePlugins(before, profileDir) {
|
|
47
|
+
const after = readProfileManifest(NAME, profileDir);
|
|
48
|
+
const beforeDeps = new Set(Object.keys(before.dependencies ?? {}));
|
|
49
|
+
const dependencies = Object.keys(after.dependencies ?? {});
|
|
50
|
+
const plugins = after.dsh?.profile?.bundles ?? [];
|
|
51
|
+
let changed = false;
|
|
52
|
+
for (const packageName of dependencies) {
|
|
53
|
+
const isBundle = exportsPatch(packageName, profileDir);
|
|
54
|
+
if (isBundle && !plugins.includes(packageName)) {
|
|
55
|
+
plugins.push(packageName);
|
|
56
|
+
changed = true;
|
|
57
|
+
} else if (!isBundle && !beforeDeps.has(packageName)) process.stderr.write(`${NAME}: warning: ${packageName} declares no dsh.bundle — installed as a plain dependency, not a profile layer (a later update that gains one activates it automatically)
|
|
58
|
+
`);
|
|
59
|
+
}
|
|
60
|
+
const dependencySet = new Set(dependencies);
|
|
61
|
+
for (const packageName of [...plugins]) {
|
|
62
|
+
const wasDependency = beforeDeps.has(packageName) || dependencySet.has(packageName);
|
|
63
|
+
const stillBundle = dependencySet.has(packageName) && exportsPatch(packageName, profileDir);
|
|
64
|
+
if (wasDependency && !stillBundle) {
|
|
65
|
+
plugins.splice(plugins.indexOf(packageName), 1);
|
|
66
|
+
changed = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!changed) return;
|
|
70
|
+
after.dsh = {
|
|
71
|
+
...after.dsh,
|
|
72
|
+
profile: {
|
|
73
|
+
...after.dsh?.profile,
|
|
74
|
+
bundles: plugins
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
writeProfileManifest(profileDir, after);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Rewrite relative filesystem specs against the user's invoking directory.
|
|
81
|
+
* pnpm runs with cwd = the profile directory, so a bare `.` or `../plugin`
|
|
82
|
+
* (or their `file:`/`link:` forms) would silently resolve inside the profile
|
|
83
|
+
* — `add .` from a plugin checkout would self-link the profile. Absolute
|
|
84
|
+
* specs, registry names, and every other pnpm argument pass through
|
|
85
|
+
* untouched.
|
|
86
|
+
* @param argument - one pnpm argument, verbatim from argv.
|
|
87
|
+
* @param cwd - the directory `dsh` was invoked from.
|
|
88
|
+
* @returns the argument with a relative path spec anchored to `cwd`.
|
|
89
|
+
*/
|
|
90
|
+
function anchorPathSpec(argument, cwd) {
|
|
91
|
+
const match = /^(?<prefix>(?:file|link):)?(?<path>\.{1,2}(?:[/\\].*)?)$/.exec(argument);
|
|
92
|
+
if (match?.groups?.path === void 0) return argument;
|
|
93
|
+
return `${match.groups.prefix ?? ""}${resolve(cwd, match.groups.path)}`;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Run one `dsh plugin` invocation: init if needed, forward to pnpm, reconcile.
|
|
97
|
+
* @param profile - the profile name.
|
|
98
|
+
* @param args - pnpm arguments with relative path specs anchored to the invoking directory.
|
|
99
|
+
* @returns the pnpm exit code.
|
|
100
|
+
*/
|
|
101
|
+
function runPlugin(profile, args) {
|
|
102
|
+
const dir = resolveProfileDir(profile);
|
|
103
|
+
if (!existsSync(join(dir, "package.json"))) {
|
|
104
|
+
initProfile(dir, PROFILE_TEMPLATES[profile] ?? DEFAULT_PROFILE_BUNDLES);
|
|
105
|
+
process.stderr.write(`${NAME}: initialized profile ${profile} at ${dir}\n`);
|
|
106
|
+
}
|
|
107
|
+
const before = readProfileManifest(NAME, dir);
|
|
108
|
+
const result = spawnSync("pnpm", args.map((argument) => anchorPathSpec(argument, process.cwd())), {
|
|
109
|
+
cwd: dir,
|
|
110
|
+
stdio: "inherit",
|
|
111
|
+
shell: process.platform === "win32"
|
|
112
|
+
});
|
|
113
|
+
if (result.error !== void 0) {
|
|
114
|
+
if (result.error.code === "ENOENT") {
|
|
115
|
+
process.stderr.write(`${NAME}: pnpm not found on PATH — install pnpm to manage profile plugins\n`);
|
|
116
|
+
return 127;
|
|
117
|
+
}
|
|
118
|
+
throw result.error;
|
|
119
|
+
}
|
|
120
|
+
const exitCode = result.status ?? 1;
|
|
121
|
+
if (exitCode === 0) reconcilePlugins(before, dir);
|
|
122
|
+
else {
|
|
123
|
+
process.stderr.write(`${NAME}: pnpm failed in profile directory ${dir}\n`);
|
|
124
|
+
if (args.some((argument) => /^git\+|^github:|\.git(?:#|$)/.test(argument))) process.stderr.write(`${NAME}: 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(dir, "pnpm-workspace.yaml")}, then re-run\n`);
|
|
125
|
+
}
|
|
126
|
+
return exitCode;
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
export { runPlugin };
|