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 +63 -15
- package/lib/lib/remove.js +7 -2
- package/lib/lib/ux.js +36 -0
- package/lib/types/lib/ux.d.ts +11 -0
- package/package.json +70 -70
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 {
|
|
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
|
-
|
|
142
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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]
|
|
218
|
+
console.log(`[dry-run] 将卸载 "${plugin}" from profile "${activeProfile}"(bundles + 补丁行 + 依赖)`);
|
|
171
219
|
return 0;
|
|
172
220
|
}
|
|
173
|
-
const result = removePlugin(resolvedHome,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
+
}
|