@woosau/dsh-custom-env 0.1.4 → 0.1.6

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.md CHANGED
@@ -1,84 +1,84 @@
1
- # dsh-custom-env
2
-
3
- English | [中文](README.zh-CN.md)
4
-
5
- Community DeepSeek Harness plugin for managing environment variables in **Settings → Environment**. Not an official DeepSeek AI product.
6
-
7
- Changes apply to the running app right away, and on Windows / macOS also to the user environment so new terminals can see them. Linux user-environment sync is not supported.
8
-
9
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
10
-
11
- ## Install
12
-
13
- Requires DSH with the `dsh` CLI (`desktop` or `web`).
14
-
15
- ```powershell
16
- dsh plugin --profile desktop add @woosau/dsh-custom-env
17
- # or: dsh plugin --profile web add @woosau/dsh-custom-env
18
- # or: dsh plugin --profile desktop add github:ownduck/dsh-custom-env
19
- ```
20
-
21
- If install fails because Desktop is locking files, quit Desktop first. After install, restart DSH and open **Settings → Environment**.
22
-
23
- ```powershell
24
- dsh plugin --profile desktop remove @woosau/dsh-custom-env
25
- ```
26
-
27
- ## For users
28
-
29
- 1. Open **Settings → Environment**
30
- 2. Edit a row as a draft → **Confirm** to save, **Cancel** to discard
31
- 3. **Restore** brings back the value from when this app session started
32
- 4. Restart the app or open a new terminal if other tools should pick up OS-level changes
33
- 5. Use **Install** only when shown, to wire this Host’s `dsh` command into your user PATH (Windows / macOS)
34
-
35
- System-reserved names (such as `PATH` and names starting with `DSH_` / `XDG_`) cannot be edited here.
36
-
37
- ### When saving a value
38
-
39
- | You save | Variable does not exist yet | Variable already exists |
40
- |----------|-----------------------------|-------------------------|
41
- | A non-empty value | Created | Replaced |
42
- | An empty value | Created as empty | Left unchanged |
43
-
44
- ## For other plugins (Remote API)
45
-
46
- Call the Host `customEnv` service (`ctx.get('customEnv')` or injected `ctx.customEnv`).
47
-
48
- **Everyday use — by variable name**
49
-
50
- ```ts
51
- const env = ctx.get('customEnv')
52
-
53
- await env?.setVar('MY_KEY', 'value') // create or replace
54
- await env?.setVar('MY_KEY', '') // create only if missing
55
- await env?.removeVar('MY_KEY')
56
-
57
- await env?.setVars([
58
- { name: 'A', value: '1' },
59
- { name: 'B', value: '' }, // no-op if B already exists
60
- ])
61
- await env?.removeVars(['A', 'B'])
62
- ```
63
-
64
- Reserved names are rejected (batch calls fail entirely).
65
-
66
- **Settings UI — by row id**
67
-
68
- | Call | Purpose |
69
- |------|---------|
70
- | `list()` | Current variables and row ids |
71
- | `upsert(id, name, value)` | Create (`id` empty) or update/rename a row |
72
- | `deleteEntry(id)` | Delete that row |
73
- | `restore(id)` | Restore session-start value, or remove if the row was added later |
74
-
75
- **CLI helpers**
76
-
77
- | Call | Purpose |
78
- |------|---------|
79
- | `cliStatus()` | Whether this Host’s `dsh` CLI is ready / can be installed |
80
- | `cliInstall()` | Install or repair that CLI entry |
81
-
82
- ## License
83
-
84
- MIT
1
+ # dsh-custom-env
2
+
3
+ English | [中文](README.zh-CN.md)
4
+
5
+ Community DeepSeek Harness plugin for managing environment variables in **Settings → Environment**. Not an official DeepSeek AI product.
6
+
7
+ Changes apply to the running app right away, and on Windows / macOS also to the user environment so new terminals can see them. Linux user-environment sync is not supported.
8
+
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
10
+
11
+ ## Install
12
+
13
+ Requires DSH with the `dsh` CLI (`desktop` or `web`).
14
+
15
+ ```powershell
16
+ dsh plugin --profile desktop add @woosau/dsh-custom-env
17
+ # or: dsh plugin --profile web add @woosau/dsh-custom-env
18
+ # or: dsh plugin --profile desktop add github:ownduck/dsh-custom-env
19
+ ```
20
+
21
+ If install fails because Desktop is locking files, quit Desktop first. After install, restart DSH and open **Settings → Environment**.
22
+
23
+ ```powershell
24
+ dsh plugin --profile desktop remove @woosau/dsh-custom-env
25
+ ```
26
+
27
+ ## For users
28
+
29
+ 1. Open **Settings → Environment**
30
+ 2. Edit a row as a draft → **Confirm** to save, **Cancel** to discard
31
+ 3. **Restore** brings back the value from when this app session started
32
+ 4. At startup, values are injected into the Host `process.env` first; writing the user environment (registry / launchctl) is deferred off the boot path and is not broadcast. Log off or reboot before other tools need those values
33
+ 5. Use **Install** only when shown, to wire this Host’s `dsh` command into your user PATH (Windows / macOS)
34
+
35
+ System-reserved names (such as `PATH` and names starting with `DSH_` / `XDG_`) cannot be edited here.
36
+
37
+ ### When saving a value
38
+
39
+ | You save | Variable does not exist yet | Variable already exists |
40
+ |----------|-----------------------------|-------------------------|
41
+ | A non-empty value | Created | Replaced |
42
+ | An empty value | Created as empty | Left unchanged |
43
+
44
+ ## For other plugins (Remote API)
45
+
46
+ Call the Host `customEnv` service (`ctx.get('customEnv')` or injected `ctx.customEnv`).
47
+
48
+ **Everyday use — by variable name**
49
+
50
+ ```ts
51
+ const env = ctx.get('customEnv')
52
+
53
+ await env?.setVar('MY_KEY', 'value') // create or replace
54
+ await env?.setVar('MY_KEY', '') // create only if missing
55
+ await env?.removeVar('MY_KEY')
56
+
57
+ await env?.setVars([
58
+ { name: 'A', value: '1' },
59
+ { name: 'B', value: '' }, // no-op if B already exists
60
+ ])
61
+ await env?.removeVars(['A', 'B'])
62
+ ```
63
+
64
+ Reserved names are rejected (batch calls fail entirely).
65
+
66
+ **Settings UI — by row id**
67
+
68
+ | Call | Purpose |
69
+ |------|---------|
70
+ | `list()` | Current variables and row ids |
71
+ | `upsert(id, name, value)` | Create (`id` empty) or update/rename a row |
72
+ | `deleteEntry(id)` | Delete that row |
73
+ | `restore(id)` | Restore session-start value, or remove if the row was added later |
74
+
75
+ **CLI helpers**
76
+
77
+ | Call | Purpose |
78
+ |------|---------|
79
+ | `cliStatus()` | Whether this Host’s `dsh` CLI is ready / can be installed |
80
+ | `cliInstall()` | Install or repair that CLI entry |
81
+
82
+ ## License
83
+
84
+ MIT
package/README.zh-CN.md CHANGED
@@ -1,84 +1,84 @@
1
- # dsh-custom-env
2
-
3
- [English](README.md) | 中文
4
-
5
- 社区 DeepSeek Harness 插件:在 **设置 → 环境变量** 里管理环境变量。非 DeepSeek AI 官方产品。
6
-
7
- 保存后立即作用于当前应用;在 Windows / macOS 上还会写入用户环境,方便新开的终端读到。不支持同步 Linux 用户环境。
8
-
9
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
10
-
11
- ## 安装
12
-
13
- 需已安装 DSH,并能使用 `dsh` 命令(`desktop` 或 `web`)。
14
-
15
- ```powershell
16
- dsh plugin --profile desktop add @woosau/dsh-custom-env
17
- # 或: dsh plugin --profile web add @woosau/dsh-custom-env
18
- # 或: dsh plugin --profile desktop add github:ownduck/dsh-custom-env
19
- ```
20
-
21
- 若因 Desktop 占用文件导致安装失败,先退出 Desktop。安装后重启 DSH,打开 **设置 → 环境变量**。
22
-
23
- ```powershell
24
- dsh plugin --profile desktop remove @woosau/dsh-custom-env
25
- ```
26
-
27
- ## 给使用者
28
-
29
- 1. 打开 **设置 → 环境变量**
30
- 2. 先改草稿 → **确定** 保存,**取消** 放弃
31
- 3. **恢复** 可回到本次应用启动时的值
32
- 4. 若希望其他工具也读到系统侧变更,请重启应用或新开终端
33
- 5. 仅在出现 **安装** 时使用:把本 Host 的 `dsh` 接到用户 PATH(Windows / macOS)
34
-
35
- 系统保留名(如 `PATH`,以及 `DSH_` / `XDG_` 开头)不可在此编辑。
36
-
37
- ### 保存时怎么处理
38
-
39
- | 你保存的内容 | 变量还不存在 | 变量已存在 |
40
- |--------------|--------------|------------|
41
- | 非空 | 新建 | 覆盖 |
42
- | 空 | 建为空变量 | 保持原样不动 |
43
-
44
- ## 给其他插件(Remote API)
45
-
46
- 通过 Host 的 `customEnv` 服务调用(`ctx.get('customEnv')` 或已注入的 `ctx.customEnv`)。
47
-
48
- **常用:按变量名**
49
-
50
- ```ts
51
- const env = ctx.get('customEnv')
52
-
53
- await env?.setVar('MY_KEY', 'value') // 新建或覆盖
54
- await env?.setVar('MY_KEY', '') // 仅当不存在时新建
55
- await env?.removeVar('MY_KEY')
56
-
57
- await env?.setVars([
58
- { name: 'A', value: '1' },
59
- { name: 'B', value: '' }, // B 已存在则跳过
60
- ])
61
- await env?.removeVars(['A', 'B'])
62
- ```
63
-
64
- 保留名会被拒绝(批量调用整批失败)。
65
-
66
- **设置页:按行 id**
67
-
68
- | 调用 | 用途 |
69
- |------|------|
70
- | `list()` | 列出当前变量与行 id |
71
- | `upsert(id, name, value)` | 新建(`id` 为空)或更新/重命名一行 |
72
- | `deleteEntry(id)` | 删除该行 |
73
- | `restore(id)` | 恢复启动时的值;若是启动后新增的行则删除 |
74
-
75
- **CLI**
76
-
77
- | 调用 | 用途 |
78
- |------|------|
79
- | `cliStatus()` | 本 Host 的 `dsh` 是否可用 / 能否安装 |
80
- | `cliInstall()` | 安装或修复该 CLI 入口 |
81
-
82
- ## 许可证
83
-
84
- MIT
1
+ # dsh-custom-env
2
+
3
+ [English](README.md) | 中文
4
+
5
+ 社区 DeepSeek Harness 插件:在 **设置 → 环境变量** 里管理环境变量。非 DeepSeek AI 官方产品。
6
+
7
+ 保存后立即作用于当前应用;在 Windows / macOS 上还会写入用户环境,方便新开的终端读到。不支持同步 Linux 用户环境。
8
+
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
10
+
11
+ ## 安装
12
+
13
+ 需已安装 DSH,并能使用 `dsh` 命令(`desktop` 或 `web`)。
14
+
15
+ ```powershell
16
+ dsh plugin --profile desktop add @woosau/dsh-custom-env
17
+ # 或: dsh plugin --profile web add @woosau/dsh-custom-env
18
+ # 或: dsh plugin --profile desktop add github:ownduck/dsh-custom-env
19
+ ```
20
+
21
+ 若因 Desktop 占用文件导致安装失败,先退出 Desktop。安装后重启 DSH,打开 **设置 → 环境变量**。
22
+
23
+ ```powershell
24
+ dsh plugin --profile desktop remove @woosau/dsh-custom-env
25
+ ```
26
+
27
+ ## 给使用者
28
+
29
+ 1. 打开 **设置 → 环境变量**
30
+ 2. 先改草稿 → **确定** 保存,**取消** 放弃
31
+ 3. **恢复** 可回到本次应用启动时的值
32
+ 4. 启动时先注入当前 Host 的 `process.env`;写入用户环境(注册表 / launchctl)会延后到启动路径之后,且不会广播。其它工具若要读到,请注销/重启后再开终端
33
+ 5. 仅在出现 **安装** 时使用:把本 Host 的 `dsh` 接到用户 PATH(Windows / macOS)
34
+
35
+ 系统保留名(如 `PATH`,以及 `DSH_` / `XDG_` 开头)不可在此编辑。
36
+
37
+ ### 保存时怎么处理
38
+
39
+ | 你保存的内容 | 变量还不存在 | 变量已存在 |
40
+ |--------------|--------------|------------|
41
+ | 非空 | 新建 | 覆盖 |
42
+ | 空 | 建为空变量 | 保持原样不动 |
43
+
44
+ ## 给其他插件(Remote API)
45
+
46
+ 通过 Host 的 `customEnv` 服务调用(`ctx.get('customEnv')` 或已注入的 `ctx.customEnv`)。
47
+
48
+ **常用:按变量名**
49
+
50
+ ```ts
51
+ const env = ctx.get('customEnv')
52
+
53
+ await env?.setVar('MY_KEY', 'value') // 新建或覆盖
54
+ await env?.setVar('MY_KEY', '') // 仅当不存在时新建
55
+ await env?.removeVar('MY_KEY')
56
+
57
+ await env?.setVars([
58
+ { name: 'A', value: '1' },
59
+ { name: 'B', value: '' }, // B 已存在则跳过
60
+ ])
61
+ await env?.removeVars(['A', 'B'])
62
+ ```
63
+
64
+ 保留名会被拒绝(批量调用整批失败)。
65
+
66
+ **设置页:按行 id**
67
+
68
+ | 调用 | 用途 |
69
+ |------|------|
70
+ | `list()` | 列出当前变量与行 id |
71
+ | `upsert(id, name, value)` | 新建(`id` 为空)或更新/重命名一行 |
72
+ | `deleteEntry(id)` | 删除该行 |
73
+ | `restore(id)` | 恢复启动时的值;若是启动后新增的行则删除 |
74
+
75
+ **CLI**
76
+
77
+ | 调用 | 用途 |
78
+ |------|------|
79
+ | `cliStatus()` | 本 Host 的 `dsh` 是否可用 / 能否安装 |
80
+ | `cliInstall()` | 安装或修复该 CLI 入口 |
81
+
82
+ ## 许可证
83
+
84
+ MIT
package/lib/index.js CHANGED
@@ -691,16 +691,12 @@ var KEY_SET_VALUE = 2;
691
691
  var REG_SZ = 1;
692
692
  var ERROR_SUCCESS = 0;
693
693
  var ERROR_FILE_NOT_FOUND = 2;
694
- var HWND_BROADCAST = 65535;
695
- var WM_SETTINGCHANGE = 26;
696
- var SMTO_ABORTIFHUNG = 2;
697
694
  var bindings;
698
695
  function loadBindings() {
699
696
  if (bindings !== void 0) return bindings;
700
697
  const require2 = createRequire(import.meta.url);
701
698
  const koffi = require2("koffi");
702
699
  const advapi32 = koffi.load("advapi32.dll");
703
- const user32 = koffi.load("user32.dll");
704
700
  bindings = {
705
701
  openKey: advapi32.func(
706
702
  "long __stdcall RegOpenKeyExW(uintptr_t hKey, const char16_t *subKey, uint32_t options, uint32_t samDesired, _Out_ void **result)"
@@ -714,28 +710,13 @@ function loadBindings() {
714
710
  deleteValue: advapi32.func(
715
711
  "long __stdcall RegDeleteValueW(void *hKey, const char16_t *valueName)"
716
712
  ),
717
- closeKey: advapi32.func("long __stdcall RegCloseKey(void *hKey)"),
718
- sendMessageTimeout: user32.func(
719
- "intptr_t __stdcall SendMessageTimeoutW(uintptr_t hWnd, uint32_t msg, uintptr_t wParam, const char16_t *lParam, uint32_t flags, uint32_t timeout, _Out_ uintptr_t *result)"
720
- )
713
+ closeKey: advapi32.func("long __stdcall RegCloseKey(void *hKey)")
721
714
  };
722
715
  return bindings;
723
716
  }
724
717
  function encodeRegSz(value) {
725
718
  return Buffer.from(`${value}\0`, "utf16le");
726
719
  }
727
- function broadcastEnvironmentChange(api) {
728
- const broadcastResult = [0];
729
- api.sendMessageTimeout(
730
- HWND_BROADCAST,
731
- WM_SETTINGCHANGE,
732
- 0,
733
- "Environment",
734
- SMTO_ABORTIFHUNG,
735
- 1e3,
736
- broadcastResult
737
- );
738
- }
739
720
  function hasWindowsUserEnv(name, platform = process.platform) {
740
721
  if (platform !== "win32") return false;
741
722
  const api = loadBindings();
@@ -779,7 +760,6 @@ function writeWindowsUserEnv(entries, platform = process.platform) {
779
760
  } finally {
780
761
  api.closeKey(hKey);
781
762
  }
782
- broadcastEnvironmentChange(api);
783
763
  }
784
764
  function removeWindowsUserEnvs(names, platform = process.platform) {
785
765
  if (platform !== "win32" || names.length === 0) return;
@@ -800,7 +780,6 @@ function removeWindowsUserEnvs(names, platform = process.platform) {
800
780
  } finally {
801
781
  api.closeKey(hKey);
802
782
  }
803
- broadcastEnvironmentChange(api);
804
783
  }
805
784
 
806
785
  // src/os-user-env/index.ts
@@ -984,8 +963,11 @@ function classifyEntry(id, name, value, baselineById) {
984
963
  function asMessage(error) {
985
964
  return error instanceof Error ? error.message : String(error);
986
965
  }
987
- function writeLive(entries) {
966
+ function applyProcessEnv(entries) {
988
967
  for (const [key, value] of Object.entries(entries)) process.env[key] = value;
968
+ }
969
+ function applyOsUserEnv(entries) {
970
+ if (Object.keys(entries).length === 0) return;
989
971
  try {
990
972
  writeUserEnv(entries);
991
973
  } catch (error) {
@@ -993,6 +975,10 @@ function writeLive(entries) {
993
975
  `);
994
976
  }
995
977
  }
978
+ function writeLive(entries) {
979
+ applyProcessEnv(entries);
980
+ applyOsUserEnv(entries);
981
+ }
996
982
  function removeLives(names) {
997
983
  if (names.length === 0) return;
998
984
  for (const name of names) delete process.env[name];
@@ -1023,7 +1009,15 @@ var CustomEnvController = class extends (_a = TypertRemoteService, _list_dec = [
1023
1009
  this.bind(id, name);
1024
1010
  this.baselineById.set(id, { name, value });
1025
1011
  }
1026
- writeLive(loaded);
1012
+ applyProcessEnv(loaded);
1013
+ if (Object.keys(loaded).length > 0) {
1014
+ const handle = setImmediate(() => {
1015
+ applyOsUserEnv(loaded);
1016
+ });
1017
+ ctx.effect(() => () => {
1018
+ clearImmediate(handle);
1019
+ }, "custom-env: deferred OS env sync");
1020
+ }
1027
1021
  }
1028
1022
  allocId() {
1029
1023
  return `env-${++this.idSeq}`;
@@ -1,15 +1,14 @@
1
1
  /**
2
2
  * Persist or delete variables in the Windows user environment (HKCU\\Environment)
3
- * via koffi + advapi32/user32 — same approach as the former in-tree custom-env.
3
+ * via koffi + advapi32 — no WM_SETTINGCHANGE broadcast (Host already updates process.env).
4
4
  */
5
- /** Win32 registry and broadcast calls used to publish user environment values. */
5
+ /** Win32 registry calls used to persist user environment values. */
6
6
  export interface WindowsUserEnvBindings {
7
7
  readonly openKey: (hKey: number, subKey: string, options: number, samDesired: number, result: [unknown]) => number;
8
8
  readonly queryValue: (hKey: unknown, valueName: string, reserved: unknown, type: [number] | null, data: Buffer | null, cbData: [number]) => number;
9
9
  readonly setValue: (hKey: unknown, valueName: string, reserved: number, type: number, data: Buffer, cbData: number) => number;
10
10
  readonly deleteValue: (hKey: unknown, valueName: string) => number;
11
11
  readonly closeKey: (hKey: unknown) => number;
12
- readonly sendMessageTimeout: (hWnd: number, msg: number, wParam: number, lParam: string, flags: number, timeoutMs: number, result: [number]) => number;
13
12
  }
14
13
  /**
15
14
  * Install FFI bindings for tests (skips loading koffi).
@@ -30,14 +29,14 @@ export declare function encodeRegSz(value: string): Buffer;
30
29
  */
31
30
  export declare function hasWindowsUserEnv(name: string, platform?: NodeJS.Platform): boolean;
32
31
  /**
33
- * Write entries into HKCU\\Environment and broadcast WM_SETTINGCHANGE.
32
+ * Write entries into HKCU\\Environment (no desktop-wide Environment broadcast).
34
33
  * No-op on non-Windows hosts or when `entries` is empty.
35
34
  * @param entries - Variable map to persist.
36
35
  * @param platform - Override for tests; defaults to `process.platform`.
37
36
  */
38
37
  export declare function writeWindowsUserEnv(entries: Readonly<Record<string, string>>, platform?: NodeJS.Platform): void;
39
38
  /**
40
- * Delete many user environment values from HKCU\\Environment in one open/broadcast.
39
+ * Delete many user environment values from HKCU\\Environment in one open.
41
40
  * Missing values are ignored.
42
41
  * @param names - Variable names to remove.
43
42
  * @param platform - Override for tests; defaults to `process.platform`.
@@ -46,7 +45,7 @@ export declare function removeWindowsUserEnvs(names: readonly string[], platform
46
45
  /**
47
46
  * Delete one user environment value from HKCU\\Environment.
48
47
  * Missing values are ignored.
49
- * @param name - Variable name to remove.
48
+ * @param name - Variable name to delete.
50
49
  * @param platform - Override for tests; defaults to `process.platform`.
51
50
  */
52
51
  export declare function removeWindowsUserEnv(name: string, platform?: NodeJS.Platform): void;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@woosau/dsh-custom-env",
3
3
  "description": "DSH plugin: manage $DSH_HOME/.env from Settings",
4
- "version": "0.1.4",
4
+ "version": "0.1.6",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",