dsh-unplug 0.3.0 → 0.3.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/lib/cli.js CHANGED
@@ -8,6 +8,7 @@ import { dshHome } from './lib/paths.js';
8
8
  import { audit } from './lib/audit.js';
9
9
  import { removePlugin, setPluginEnabled } from './lib/remove.js';
10
10
  import { reconcile } from './lib/reconcile.js';
11
+ import { installedPlugins, listProfiles, resolveProfile } from './lib/ux.js';
11
12
  function usage() {
12
13
  return [
13
14
  'dsh-unplug — 让 DSH 插件插拔自如 / plug/unplug any DeepSeek Harness plugin cleanly',
@@ -29,6 +30,7 @@ function usage() {
29
30
  ' --home <path> DSH_HOME 路径(默认 env DSH_HOME 或 ~/.dsh) / DSH_HOME path',
30
31
  ' --yes 确认破坏性操作(remove/reconcile 需要) / confirm destructive ops',
31
32
  ' --dry-run 只预览不执行(remove/reconcile) / preview without executing',
33
+ ' --all 对所有 profile 执行(list/audit) / act on all profiles',
32
34
  ' --json 输出机器可读 JSON / machine-readable JSON report',
33
35
  ' --help 显示帮助 / show this help',
34
36
  '',
@@ -37,10 +39,11 @@ function usage() {
37
39
  ' dsh-unplug remove dsh-disk-audit --yes',
38
40
  ' dsh-unplug disable dsh-readme-forge --profile my-profile',
39
41
  ' dsh-unplug enable dsh-readme-forge --json',
42
+ ' dsh-unplug list --all',
40
43
  ].join('\n');
41
44
  }
42
45
  function parseArgs(argv) {
43
- const options = { command: '', profile: 'default', home: '', yes: false, dryRun: false, json: false };
46
+ const options = { command: '', profile: 'default', home: '', yes: false, dryRun: false, all: false, json: false };
44
47
  const positional = [];
45
48
  for (let i = 0; i < argv.length; i++) {
46
49
  const arg = argv[i];
@@ -56,6 +59,9 @@ function parseArgs(argv) {
56
59
  case '--dry-run':
57
60
  options.dryRun = true;
58
61
  break;
62
+ case '--all':
63
+ options.all = true;
64
+ break;
59
65
  case '--profile':
60
66
  options.profile = argv[++i];
61
67
  if (options.profile === undefined)
@@ -73,7 +79,7 @@ function parseArgs(argv) {
73
79
  }
74
80
  }
75
81
  if (positional.length === 0)
76
- return { error: 'missing command (try --help)' };
82
+ return { menu: true, ...options };
77
83
  options.command = positional[0];
78
84
  options.plugin = positional[1];
79
85
  return options;
@@ -123,6 +129,17 @@ function printResult(value, json) {
123
129
  }
124
130
  export async function main(argv) {
125
131
  const parsed = parseArgs(argv);
132
+ if ('menu' in parsed) {
133
+ console.log('dsh-unplug — 让 DSH 插件插拔自如 / plug/unplug any plugin cleanly');
134
+ console.log('');
135
+ console.log('不知道做什么?直接输入下面任一命令:');
136
+ console.log(' dsh-unplug list 看看装了什么插件');
137
+ console.log(' dsh-unplug fix --yes 一键修复坏掉的插件状态');
138
+ console.log(' dsh-unplug remove <名字> --yes 卸载插件');
139
+ console.log(' dsh-unplug disable <名字> 临时禁用(不删除)');
140
+ console.log(' dsh-unplug --help 查看全部命令');
141
+ return 0;
142
+ }
126
143
  if ('help' in parsed) {
127
144
  console.log(usage());
128
145
  return 0;
@@ -132,30 +149,53 @@ export async function main(argv) {
132
149
  console.error(usage());
133
150
  return 2;
134
151
  }
135
- const { command, plugin, profile, home, yes, dryRun, json } = parsed;
152
+ const { command, plugin, profile, home, yes, dryRun, all, json } = parsed;
136
153
  const resolvedHome = home || dshHome();
137
154
  try {
155
+ const resolved = resolveProfile(resolvedHome, profile);
156
+ const activeProfile = resolved.profile;
138
157
  switch (command) {
139
158
  case 'list':
140
159
  case 'audit': {
141
- const result = audit(resolvedHome, profile);
142
- return printResult({ schema: 'dsh-unplug/v1', ok: true, ...result, command, profile }, json);
160
+ if (all) {
161
+ const profiles = listProfiles(resolvedHome);
162
+ if (profiles.length === 0) {
163
+ console.error('没有找到任何 profile(目录 ' + resolvedHome + '\\profiles 为空)。先 `dsh plugin --profile default add <插件>` 创建一个。');
164
+ return 1;
165
+ }
166
+ let failed = false;
167
+ for (const name of profiles) {
168
+ try {
169
+ const result = audit(resolvedHome, name);
170
+ const code = printResult({ schema: 'dsh-unplug/v1', ok: true, ...result, command, profile: name }, json);
171
+ if (code !== 0)
172
+ failed = true;
173
+ }
174
+ catch (error) {
175
+ console.error(`profile ${name}: ${error instanceof Error ? error.message : String(error)}`);
176
+ failed = true;
177
+ }
178
+ }
179
+ return failed ? 1 : 0;
180
+ }
181
+ const result = audit(resolvedHome, activeProfile);
182
+ return printResult({ schema: 'dsh-unplug/v1', ok: true, ...result, command, profile: activeProfile }, json);
143
183
  }
144
184
  case 'reconcile': {
145
185
  if (!yes) {
146
186
  console.error('reconcile requires --yes to confirm');
147
187
  return 2;
148
188
  }
149
- const result = reconcile(resolvedHome, profile);
150
- return printResult({ ...result, command, profile }, json);
189
+ const result = reconcile(resolvedHome, activeProfile);
190
+ return printResult({ ...result, command, profile: activeProfile }, json);
151
191
  }
152
192
  case 'fix': {
153
193
  if (!yes) {
154
194
  console.error('fix requires --yes to confirm');
155
195
  return 2;
156
196
  }
157
- const result = reconcile(resolvedHome, profile);
158
- return printResult({ ...result, command: 'reconcile', profile }, json);
197
+ const result = reconcile(resolvedHome, activeProfile);
198
+ return printResult({ ...result, command: 'reconcile', profile: activeProfile }, json);
159
199
  }
160
200
  case 'remove': {
161
201
  if (!yes) {
@@ -163,15 +203,23 @@ export async function main(argv) {
163
203
  return 2;
164
204
  }
165
205
  if (!plugin) {
166
- console.error('remove requires a plugin name');
206
+ const installed = installedPlugins(resolvedHome, activeProfile);
207
+ if (installed.length === 0) {
208
+ console.error(`profile "${activeProfile}" 里没有可卸载的插件。先 dsh-unplug list 看看。`);
209
+ }
210
+ else {
211
+ console.error('你要卸载哪个?当前已装:');
212
+ for (const name of installed)
213
+ console.error(` dsh-unplug remove ${name} --yes`);
214
+ }
167
215
  return 2;
168
216
  }
169
217
  if (dryRun) {
170
- console.log(`[dry-run] would remove "${plugin}" from profile "${profile}" (bundles + patch rows + dependencies)`);
218
+ console.log(`[dry-run] 将卸载 "${plugin}" from profile "${activeProfile}"bundles + 补丁行 + 依赖)`);
171
219
  return 0;
172
220
  }
173
- const result = removePlugin(resolvedHome, profile, plugin, { force: yes });
174
- return printResult({ ...result, command }, json);
221
+ const result = removePlugin(resolvedHome, activeProfile, plugin, { force: yes });
222
+ return printResult({ ...result, command, profile: activeProfile }, json);
175
223
  }
176
224
  case 'disable':
177
225
  case 'enable': {
@@ -179,8 +227,8 @@ export async function main(argv) {
179
227
  console.error(`${command} requires a plugin name`);
180
228
  return 2;
181
229
  }
182
- const result = setPluginEnabled(resolvedHome, profile, plugin, command === 'enable');
183
- return printResult({ ...result, command }, json);
230
+ const result = setPluginEnabled(resolvedHome, activeProfile, plugin, command === 'enable');
231
+ return printResult({ ...result, command, profile: activeProfile }, json);
184
232
  }
185
233
  default:
186
234
  console.error(`unknown command: ${command} (try --help)`);
package/lib/lib/remove.js CHANGED
@@ -7,6 +7,7 @@ import { readFileSync, writeFileSync } from 'node:fs';
7
7
  import { audit } from './audit.js';
8
8
  import { parsePatch } from './patches.js';
9
9
  import { resolveBundlePatchPath } from './resolve.js';
10
+ import { listProfiles } from './ux.js';
10
11
  function resolvePlugin(home, profileDir, profileName, plugin) {
11
12
  const manifest = readProfileManifest(profileDir);
12
13
  const allBundles = [...(manifest.dsh?.profile?.bundles ?? []), ...(manifest.dsh?.profile?.disabledBundles ?? [])];
@@ -60,7 +61,9 @@ export function removePlugin(home, profileName, plugin, options) {
60
61
  const profileDir = path.join(home, 'profiles', profileName);
61
62
  const manifestPath = path.join(profileDir, 'package.json');
62
63
  if (!existsSync(manifestPath)) {
63
- throw new Error(`profile "${profileName}" not found at ${profileDir}`);
64
+ const available = listProfiles(home);
65
+ throw new Error(`profile "${profileName}" not found at ${profileDir}`
66
+ + (available.length > 0 ? `;可用 profiles: ${available.join(', ')}` : ';还没有任何 profile'));
64
67
  }
65
68
  const resolved = resolvePlugin(home, profileDir, profileName, plugin);
66
69
  const pkgName = resolved.bundleName ?? plugin;
@@ -108,7 +111,9 @@ export function setPluginEnabled(home, profileName, plugin, enabled) {
108
111
  const profileDir = path.join(home, 'profiles', profileName);
109
112
  const manifestPath = path.join(profileDir, 'package.json');
110
113
  if (!existsSync(manifestPath)) {
111
- throw new Error(`profile "${profileName}" not found at ${profileDir}`);
114
+ const available = listProfiles(home);
115
+ throw new Error(`profile "${profileName}" not found at ${profileDir}`
116
+ + (available.length > 0 ? `;可用 profiles: ${available.join(', ')}` : ';还没有任何 profile'));
112
117
  }
113
118
  const resolved = resolvePlugin(home, profileDir, profileName, plugin);
114
119
  const pkgName = resolved.bundleName ?? plugin;
package/lib/lib/ux.js ADDED
@@ -0,0 +1,36 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { audit } from './audit.js';
4
+ /**
5
+ * Resolve which profile to act on. If the requested profile is missing,
6
+ * fall back to the first real profile so novices do not need to know names.
7
+ */
8
+ export function resolveProfile(home, requested) {
9
+ const profiles = listProfiles(home);
10
+ if (profiles.includes(requested))
11
+ return { profile: requested, fallback: false };
12
+ if (profiles.length > 0)
13
+ return { profile: profiles[0], fallback: true };
14
+ throw new Error(`没有找到任何 profile(目录 ${path.join(home, 'profiles')} 为空)。`
15
+ + ' 先运行 `dsh plugin --profile default add <插件>` 创建第一个。');
16
+ }
17
+ export function listProfiles(home) {
18
+ const dir = path.join(home, 'profiles');
19
+ if (!existsSync(dir))
20
+ return [];
21
+ return readdirSync(dir, { withFileTypes: true })
22
+ .filter((entry) => entry.isDirectory() && existsSync(path.join(dir, entry.name, 'package.json')))
23
+ .map((entry) => entry.name)
24
+ .sort();
25
+ }
26
+ /** Human-friendly list of installed plugin names/ids for a profile. */
27
+ export function installedPlugins(home, profile) {
28
+ const result = audit(home, profile);
29
+ const names = new Set();
30
+ for (const row of result.rows) {
31
+ if (row.name !== undefined)
32
+ names.add(row.name);
33
+ names.add(row.id);
34
+ }
35
+ return [...names].sort();
36
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Resolve which profile to act on. If the requested profile is missing,
3
+ * fall back to the first real profile so novices do not need to know names.
4
+ */
5
+ export declare function resolveProfile(home: string, requested: string): {
6
+ profile: string;
7
+ fallback: boolean;
8
+ };
9
+ export declare function listProfiles(home: string): string[];
10
+ /** Human-friendly list of installed plugin names/ids for a profile. */
11
+ export declare function installedPlugins(home: string, profile: string): string[];
package/package.json CHANGED
@@ -1,70 +1,70 @@
1
- {
2
- "name": "dsh-unplug",
3
- "version": "0.3.0",
4
- "description": "Plug/unplug any DeepSeek Harness plugin cleanly: list every mounted layer, disable/enable without deleting, remove bundles + patch rows + dependencies in one pass, and audit for orphaned/dangling plugin state. Zero runtime dependencies; read-only by default.",
5
- "type": "module",
6
- "main": "lib/index.js",
7
- "types": "lib/types/index.d.ts",
8
- "bin": {
9
- "dsh-unplug": "lib/bin.js"
10
- },
11
- "files": [
12
- "lib",
13
- "cordis.patch.yml",
14
- "README.md",
15
- "README.zh.md",
16
- "SECURITY.md",
17
- "CONTRIBUTING.md",
18
- "CHANGELOG.md"
19
- ],
20
- "scripts": {
21
- "build": "tsc -p tsconfig.json",
22
- "prepare": "pnpm run build",
23
- "typecheck": "tsc -p tsconfig.json --noEmit",
24
- "test": "vitest run",
25
- "test:integration": "node scripts/integration-test.mjs"
26
- },
27
- "keywords": [
28
- "dsh",
29
- "dsh-plugin",
30
- "plugin-manager",
31
- "uninstall",
32
- "lifecycle",
33
- "unplug"
34
- ],
35
- "peerDependencies": {
36
- "@deepseek-ai/cordis": "^4.0.1",
37
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6"
38
- },
39
- "dependencies": {
40
- "@deepseek-ai/schemastery": "^3.18.1"
41
- },
42
- "devDependencies": {
43
- "@deepseek-ai/cordis": "^4.0.1",
44
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
45
- "@types/node": "^22.20.0",
46
- "typescript": "^5.6.3",
47
- "vitest": "^2.1.9"
48
- },
49
- "engines": {
50
- "node": ">=18"
51
- },
52
- "pnpm": {
53
- "onlyBuiltDependencies": [
54
- "esbuild"
55
- ]
56
- },
57
- "dsh": {
58
- "bundle": {
59
- "patch": "./cordis.patch.yml"
60
- }
61
- },
62
- "repository": {
63
- "type": "git",
64
- "url": "git+https://github.com/zoahdev/dsh-unplug.git"
65
- },
66
- "bugs": {
67
- "url": "https://github.com/zoahdev/dsh-unplug/issues"
68
- },
69
- "homepage": "https://github.com/zoahdev/dsh-unplug"
70
- }
1
+ {
2
+ "name": "dsh-unplug",
3
+ "version": "0.3.1",
4
+ "description": "Plug/unplug any DeepSeek Harness plugin cleanly: list every mounted layer, disable/enable without deleting, remove bundles + patch rows + dependencies in one pass, and audit for orphaned/dangling plugin state. Zero runtime dependencies; read-only by default.",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "types": "lib/types/index.d.ts",
8
+ "bin": {
9
+ "dsh-unplug": "lib/bin.js"
10
+ },
11
+ "files": [
12
+ "lib",
13
+ "cordis.patch.yml",
14
+ "README.md",
15
+ "README.zh.md",
16
+ "SECURITY.md",
17
+ "CONTRIBUTING.md",
18
+ "CHANGELOG.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.json",
22
+ "prepare": "pnpm run build",
23
+ "typecheck": "tsc -p tsconfig.json --noEmit",
24
+ "test": "vitest run",
25
+ "test:integration": "node scripts/integration-test.mjs"
26
+ },
27
+ "keywords": [
28
+ "dsh",
29
+ "dsh-plugin",
30
+ "plugin-manager",
31
+ "uninstall",
32
+ "lifecycle",
33
+ "unplug"
34
+ ],
35
+ "peerDependencies": {
36
+ "@deepseek-ai/cordis": "^4.0.1",
37
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.6"
38
+ },
39
+ "dependencies": {
40
+ "@deepseek-ai/schemastery": "^3.18.1"
41
+ },
42
+ "devDependencies": {
43
+ "@deepseek-ai/cordis": "^4.0.1",
44
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
45
+ "@types/node": "^22.20.0",
46
+ "typescript": "^5.6.3",
47
+ "vitest": "^2.1.9"
48
+ },
49
+ "engines": {
50
+ "node": ">=18"
51
+ },
52
+ "pnpm": {
53
+ "onlyBuiltDependencies": [
54
+ "esbuild"
55
+ ]
56
+ },
57
+ "dsh": {
58
+ "bundle": {
59
+ "patch": "./cordis.patch.yml"
60
+ }
61
+ },
62
+ "repository": {
63
+ "type": "git",
64
+ "url": "git+https://github.com/zoahdev/dsh-unplug.git"
65
+ },
66
+ "bugs": {
67
+ "url": "https://github.com/zoahdev/dsh-unplug/issues"
68
+ },
69
+ "homepage": "https://github.com/zoahdev/dsh-unplug"
70
+ }