dsh-workbuddy2api-hub 0.0.0-stage → 1.0.7

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/package.json CHANGED
@@ -1,6 +1,95 @@
1
1
  {
2
2
  "name": "dsh-workbuddy2api-hub",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.7",
4
+ "description": "workbuddy2api-hub 集成:接入本地 hub 面板,同步模型(配额/上下文/思考档位)、实时额度与用量,支持服务地址与 pwd 配置",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/HimA6652/dsh-workbuddy2api-hub.git"
11
+ },
12
+ "homepage": "https://github.com/HimA6652/dsh-workbuddy2api-hub#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/HimA6652/dsh-workbuddy2api-hub/issues"
15
+ },
16
+ "keywords": [
17
+ "dsh",
18
+ "dsh-plugin",
19
+ "workbuddy2api-hub",
20
+ "workbuddy2api",
21
+ "codebuddy",
22
+ "tencent-workbuddy",
23
+ "openai-compatible",
24
+ "credits",
25
+ "api-gateway"
26
+ ],
27
+ "scripts": {
28
+ "test": "node --test tests/*.test.mjs",
29
+ "check": "node scripts/check-secrets.mjs",
30
+ "mock": "node scripts/mock-hub.mjs",
31
+ "smoke": "node scripts/smoke-live.mjs",
32
+ "install": "node scripts/install-local.mjs",
33
+ "install:dry": "node scripts/install-local.mjs --dry-run",
34
+ "uninstall": "node scripts/install-local.mjs --uninstall",
35
+ "release": "node scripts/release.mjs",
36
+ "notes": "node scripts/release-notes.mjs",
37
+ "prepublishOnly": "node scripts/check-secrets.mjs && npm test"
38
+ },
39
+ "dsh": {
40
+ "bundle": {
41
+ "patch": "./cordis.patch.yml"
42
+ },
43
+ "client": {
44
+ "inject": [
45
+ "@deepseek-ai/dsh-client-locale",
46
+ "@deepseek-ai/dsh-client-ui-settings"
47
+ ],
48
+ "platform": "web"
49
+ }
50
+ },
51
+ "exports": {
52
+ ".": "./lib/index.js",
53
+ "./client": "./client/client.js",
54
+ "./cordis.patch.yml": "./cordis.patch.yml",
55
+ "./package.json": "./package.json"
56
+ },
57
+ "files": [
58
+ "lib",
59
+ "client",
60
+ "scripts",
61
+ "docs",
62
+ "cordis.patch.yml",
63
+ "README.md",
64
+ "LICENSE",
65
+ "CHANGELOG.md"
66
+ ],
67
+ "publishConfig": {
68
+ "access": "public",
69
+ "registry": "https://registry.npmjs.org/"
70
+ },
71
+ "peerDependencies": {
72
+ "@deepseek-ai/cordis": "^4.0.2",
73
+ "@deepseek-ai/dsh-credentials": ">=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-alpha.1 <0.3.0-0",
74
+ "@deepseek-ai/dsh-host-webserver": ">=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-alpha.1 <0.3.0-0",
75
+ "@deepseek-ai/dsh-llm-pi-ai": ">=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-alpha.1 <0.3.0-0",
76
+ "@deepseek-ai/dsh-settings": ">=0.2.0-rc.1 <0.2.1-0 || >=0.2.1-alpha.1 <0.3.0-0"
77
+ },
78
+ "peerDependenciesMeta": {
79
+ "@deepseek-ai/dsh-credentials": {
80
+ "optional": true
81
+ },
82
+ "@deepseek-ai/dsh-host-webserver": {
83
+ "optional": true
84
+ },
85
+ "@deepseek-ai/dsh-llm-pi-ai": {
86
+ "optional": true
87
+ },
88
+ "@deepseek-ai/dsh-settings": {
89
+ "optional": true
90
+ }
91
+ },
92
+ "engines": {
93
+ "node": ">=20"
94
+ }
95
+ }
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ // 发布前自查:扫描项目里有没有凭据泄漏迹象。
3
+ // 用法:node scripts/check-secrets.mjs
4
+ //
5
+ // 只扫源码/配置/文档(.mjs/.js/.json/.yml/.md),跳过 .git、node_modules、tmp、coverage。
6
+ // 命中任何一条就以退出码 1 结束,方便挂到 pre-commit 钩子或 CI 上。
7
+ import { readdir, readFile } from 'node:fs/promises';
8
+ import { dirname, join, relative } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
12
+ const SKIP_DIRS = new Set(['.git', 'node_modules', 'tmp', 'coverage', 'docs']);
13
+ const SCAN_EXT = /\.(mjs|js|cjs|json|ya?ml|md)$/;
14
+
15
+ // 任意一行出现这条标记就整行豁免(写清楚理由,别滥用)。
16
+ const ALLOW_MARK = 'secret-ok';
17
+
18
+ // 明显的占位/测试值:命中也不算泄漏。
19
+ const PLACEHOLDER = /^(?:admin|wrong|bad|mock|fake|dummy|test|sample|example|your|placeholder|changeme|secret|password)/i;
20
+
21
+ const RULES = [
22
+ { name: '私钥', re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/ },
23
+ { name: 'GitHub Token', re: /gh[pousr]_[A-Za-z0-9]{20,}/ },
24
+ { name: 'OpenAI 风格 Key', re: /\bsk-[A-Za-z0-9_-]{20,}/ },
25
+ { name: 'AWS Key', re: /\bAKIA[0-9A-Z]{12,}/ },
26
+ { name: 'Slack Token', re: /\bxox[bpoa]r?-[A-Za-z0-9-]{10,}/ },
27
+ { name: '本机绝对路径', re: /[A-Za-z]:\\\\(?:Users|ProgramData)\\\\/ },
28
+ { name: 'Unix 家目录路径', re: /\/(?:Users|home)\/[A-Za-z0-9._-]+\// },
29
+ {
30
+ name: '硬编码密码赋值',
31
+ re: /(?:password|passwd|pwd)\s*[:=]\s*['"]([^'"]{8,})['"]/i,
32
+ // 值是中文界面文案(如 pwd: '面板密码(pwd)')或以占位词开头 → 不算泄漏
33
+ skip: (value) =>
34
+ // 值是中文界面文案、含空格/括号(如 pwd: 'Panel password (pwd)')、或以占位词开头 → 不算泄漏
35
+ /[\u3400-\u9fff]/.test(value) || /[\s()]/.test(value) || PLACEHOLDER.test(value),
36
+ },
37
+ ];
38
+
39
+ /** 逐行扫描单个文件。 */
40
+ async function scanFile(file) {
41
+ const text = await readFile(file, 'utf8');
42
+ const hits = [];
43
+ text.split('\n').forEach((line, index) => {
44
+ if (line.includes(ALLOW_MARK)) return;
45
+ for (const rule of RULES) {
46
+ const match = line.match(rule.re);
47
+ if (!match) continue;
48
+ if (rule.skip && rule.skip(match[1] ?? '', match)) continue;
49
+ hits.push({
50
+ file: relative(ROOT, file).split('\\').join('/'),
51
+ line: index + 1,
52
+ rule: rule.name,
53
+ text: line.trim().slice(0, 120),
54
+ });
55
+ }
56
+ });
57
+ return hits;
58
+ }
59
+
60
+ /** 递归收集需要扫描的文件。 */
61
+ async function collect(dir, out = []) {
62
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
63
+ if (entry.isDirectory()) {
64
+ if (!SKIP_DIRS.has(entry.name)) await collect(join(dir, entry.name), out);
65
+ continue;
66
+ }
67
+ if (SCAN_EXT.test(entry.name)) out.push(join(dir, entry.name));
68
+ }
69
+ return out;
70
+ }
71
+
72
+ const files = await collect(ROOT);
73
+ const results = await Promise.all(files.map(scanFile));
74
+ const hits = results.flat();
75
+
76
+ if (hits.length === 0) {
77
+ console.log(`OK:扫描 ${files.length} 个文件,未发现疑似凭据。`);
78
+ } else {
79
+ console.log(`发现 ${hits.length} 处疑似凭据:\n`);
80
+ for (const hit of hits) {
81
+ console.log(`${hit.file}:${hit.line} [${hit.rule}]`);
82
+ console.log(` ${hit.text}`);
83
+ }
84
+ console.log('\n确认是假数据就在本文件上方 RULES 里豁免,或改成占位符;确认是真凭据则先作废再改代码。');
85
+ process.exitCode = 1;
86
+ }
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 把本仓库挂进本地 DSH profile(桌面端默认 profile:`~/.dsh/profiles/desktop`)。
4
+ *
5
+ * 做的事情只有三件,全部可逆(`--uninstall` 原样退回去):
6
+ * 1. 在 profile 的 `package.json` 里加一条 `file:` 依赖与一条 bundle 声明;
7
+ * 2. 在 profile 的 `node_modules/` 下建一个指向本仓库的链接;
8
+ * 3. 打印下一步(重启 DSH 才会加载)。
9
+ *
10
+ * 不动 `cordis.patch.yml`:插件的挂载点由本包的 `cordis.patch.yml` 自己 insert。
11
+ *
12
+ * 用法:
13
+ * node scripts/install-local.mjs # 安装
14
+ * node scripts/install-local.mjs --dry-run # 只看会改什么
15
+ * node scripts/install-local.mjs --uninstall # 卸载
16
+ * node scripts/install-local.mjs --profile D:\path\to\profile
17
+ */
18
+
19
+ import { existsSync } from 'node:fs'
20
+ import { readFile, rm, symlink, writeFile, mkdir } from 'node:fs/promises'
21
+ import { homedir } from 'node:os'
22
+ import { dirname, join, resolve } from 'node:path'
23
+ import { fileURLToPath } from 'node:url'
24
+
25
+ const HERE = dirname(fileURLToPath(import.meta.url))
26
+ const PROJECT = resolve(HERE, '..')
27
+
28
+ /**
29
+ * 解析命令行参数。
30
+ * @param argv - `process.argv.slice(2)`。
31
+ * @returns 选项对象。
32
+ */
33
+ function parseArgs(argv) {
34
+ const options = { uninstall: false, dryRun: false, profile: '' }
35
+ for (let index = 0; index < argv.length; index += 1) {
36
+ const arg = argv[index]
37
+ if (arg === '--uninstall') options.uninstall = true
38
+ else if (arg === '--dry-run') options.dryRun = true
39
+ else if (arg === '--profile') {
40
+ options.profile = argv[index + 1] ?? ''
41
+ index += 1
42
+ } else if (arg === '--help' || arg === '-h') options.help = true
43
+ }
44
+ return options
45
+ }
46
+
47
+ /**
48
+ * 找到要装的 profile 目录。
49
+ * @param explicit - `--profile` 传入的值。
50
+ * @returns 绝对路径。
51
+ */
52
+ function profileDirOf(explicit) {
53
+ if (typeof explicit === 'string' && explicit !== '') return resolve(explicit)
54
+ const name = process.env.DSH_PROFILE !== undefined && process.env.DSH_PROFILE !== '' ? process.env.DSH_PROFILE : 'desktop'
55
+ return join(homedir(), '.dsh', 'profiles', name)
56
+ }
57
+
58
+ /**
59
+ * 读 profile 的 package.json。
60
+ * @param file - 路径。
61
+ * @returns 解析后的对象。
62
+ */
63
+ async function readProfile(file) {
64
+ const text = await readFile(file, 'utf8')
65
+ const parsed = JSON.parse(text)
66
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
67
+ throw new Error(`${file} 不是一个 JSON 对象,先手动看一眼`)
68
+ }
69
+ return parsed
70
+ }
71
+
72
+ /** 打印一行「会做什么」。 */
73
+ function plan(action, detail) {
74
+ console.log(` ${action.padEnd(8, ' ')} ${detail}`)
75
+ }
76
+
77
+ /** 主流程。 */
78
+ async function main() {
79
+ const options = parseArgs(process.argv.slice(2))
80
+ if (options.help) {
81
+ console.log('用法:node scripts/install-local.mjs [--dry-run] [--uninstall] [--profile <dir>]')
82
+ return 0
83
+ }
84
+
85
+ const manifest = JSON.parse(await readFile(join(PROJECT, 'package.json'), 'utf8'))
86
+ const packageName = manifest.name
87
+ const profileDir = profileDirOf(options.profile)
88
+ const packageFile = join(profileDir, 'package.json')
89
+ const linkPath = join(profileDir, 'node_modules', packageName)
90
+ const stateFile = join(profileDir, 'workbuddy2api-hub.json')
91
+
92
+ if (!existsSync(packageFile)) {
93
+ console.error(`找不到 profile:${packageFile}`)
94
+ console.error('DSH 桌面端默认在 ~/.dsh/profiles/desktop;用 --profile 指定其它 profile。')
95
+ return 2
96
+ }
97
+
98
+ const profile = await readProfile(packageFile)
99
+ profile.dependencies = profile.dependencies ?? {}
100
+ profile.dsh = profile.dsh ?? {}
101
+ profile.dsh.profile = profile.dsh.profile ?? {}
102
+ const bundles = Array.isArray(profile.dsh.profile.bundles) ? profile.dsh.profile.bundles : []
103
+
104
+ console.log(options.uninstall ? '卸载 dsh-workbuddy2api-hub' : '安装 dsh-workbuddy2api-hub')
105
+ console.log(` profile ${profileDir}`)
106
+ console.log(` 插件 ${PROJECT}`)
107
+
108
+ const changes = []
109
+ if (options.uninstall) {
110
+ if (packageName in profile.dependencies) {
111
+ delete profile.dependencies[packageName]
112
+ changes.push(['删除依赖', packageName])
113
+ }
114
+ const nextBundles = bundles.filter((entry) => entry !== packageName)
115
+ if (nextBundles.length !== bundles.length) {
116
+ profile.dsh.profile.bundles = nextBundles
117
+ changes.push(['移除 bundle', packageName])
118
+ }
119
+ if (existsSync(linkPath)) changes.push(['删除链接', linkPath])
120
+ if (existsSync(stateFile)) changes.push(['保留状态', `${stateFile}(要一起删就手动 rm)`])
121
+ } else {
122
+ const spec = `file:${PROJECT.replaceAll('\\', '/')}`
123
+ if (profile.dependencies[packageName] !== spec) {
124
+ profile.dependencies[packageName] = spec
125
+ changes.push(['写入依赖', `${packageName} → ${spec}`])
126
+ }
127
+ if (!bundles.includes(packageName)) {
128
+ profile.dsh.profile.bundles = [...bundles, packageName]
129
+ changes.push(['追加 bundle', packageName])
130
+ }
131
+ changes.push(['建立链接', `${linkPath} → ${PROJECT}`])
132
+ }
133
+
134
+ if (changes.length === 0) console.log(' 已经是目标状态,无需改动。')
135
+ for (const [action, detail] of changes) plan(action, detail)
136
+
137
+ if (options.dryRun) {
138
+ console.log('--dry-run:没有写入任何文件。')
139
+ return 0
140
+ }
141
+
142
+ // 先备份,再改 package.json:profile 起不来是最坏结果,得像改生产配置那样小心。
143
+ if (changes.length > 0) {
144
+ const stamp = new Date().toISOString().replace(/[-:]/g, '').replace(/\..+$/, '')
145
+ const backup = `${packageFile}.bak-workbuddy2api-hub-${stamp}`
146
+ await writeFile(backup, await readFile(packageFile))
147
+ console.log(` 备份 ${backup}`)
148
+ await writeFile(packageFile, `${JSON.stringify(profile, null, 2)}\n`)
149
+ }
150
+
151
+ if (options.uninstall) {
152
+ await rm(linkPath, { recursive: true, force: true })
153
+ } else {
154
+ await mkdir(dirname(linkPath), { recursive: true })
155
+ await rm(linkPath, { recursive: true, force: true })
156
+ // Windows 上没有管理员权限建目录符号链接会失败,junction 不需要额外权限。
157
+ await symlink(PROJECT, linkPath, process.platform === 'win32' ? 'junction' : 'dir')
158
+ }
159
+
160
+ console.log('')
161
+ if (options.uninstall) {
162
+ console.log('已卸载。重启 DSH 之后设置页里的这一块就没了。')
163
+ } else {
164
+ console.log('装好了。接下来:')
165
+ console.log(' 1. 重启 DSH 桌面端(插件只在启动时加载)')
166
+ console.log(' 2. 打开 设置 → workbuddy2api-hub,确认地址是 http://127.0.0.1:8788、密码是 admin')
167
+ console.log(' 3. 点「测试连接」,再点「立即同步」')
168
+ console.log(' 4. 模型会出现在 设置 → 模型 里,provider 名为 workbuddy2api-hub')
169
+ console.log('')
170
+ console.log('前置条件:本地 workbuddy2api-hub 服务已经跑起来(默认 http://127.0.0.1:8788)。')
171
+ }
172
+ return 0
173
+ }
174
+
175
+ main()
176
+ .then((code) => {
177
+ process.exitCode = code
178
+ })
179
+ .catch((error) => {
180
+ console.error(`安装脚本出错:${error?.message ?? error}`)
181
+ process.exitCode = 1
182
+ })