@feiyang666/dsh-usage-plugin 1.11.1 → 1.13.0
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/CHANGELOG.md +268 -205
- package/README.md +311 -308
- package/README.zh.md +4 -1
- package/lib/balance.js +117 -17
- package/lib/client.js +518 -266
- package/lib/index.js +176 -3
- package/package.json +80 -77
- package/scripts/release.mjs +6 -4
package/lib/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
matchesModelProvider,
|
|
23
23
|
parseBalanceResponse,
|
|
24
24
|
providerList,
|
|
25
|
+
missingCredentialError,
|
|
25
26
|
resolveBalanceEndpoint
|
|
26
27
|
} from './balance.js'
|
|
27
28
|
|
|
@@ -286,10 +287,21 @@ export default {
|
|
|
286
287
|
return undefined
|
|
287
288
|
}
|
|
288
289
|
|
|
290
|
+
// SSE 实时推送:数据变化(新记录写入 / 导入 / 清除等)时通知订阅者。
|
|
291
|
+
// 订阅端点 /usage/api/events。无订阅者时是 no-op,插件独立使用不受影响。
|
|
292
|
+
const sseClients = new Set()
|
|
293
|
+
function notifyChanged() {
|
|
294
|
+
if (sseClients.size === 0) return
|
|
295
|
+
const payload = 'data: {"type":"changed","at":' + Date.now() + '}\n\n'
|
|
296
|
+
for (const res of sseClients) {
|
|
297
|
+
try { res.write(payload) } catch (e) { sseClients.delete(res) }
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
289
301
|
function persistNow() {
|
|
290
302
|
if (!dataPath || !persistOk) return Promise.resolve()
|
|
291
303
|
const text = JSON.stringify(records)
|
|
292
|
-
writeChain = writeChain.then(() => nfsWriteText(dataPath, text)).catch(() => {})
|
|
304
|
+
writeChain = writeChain.then(() => nfsWriteText(dataPath, text)).then(() => { notifyChanged() }).catch(() => {})
|
|
293
305
|
return writeChain
|
|
294
306
|
}
|
|
295
307
|
|
|
@@ -497,6 +509,11 @@ export default {
|
|
|
497
509
|
if (provider === 'digital-ocean' || provider === 'digitalocean') {
|
|
498
510
|
try { await refreshFxRate(false) } catch (e) {}
|
|
499
511
|
}
|
|
512
|
+
// 兼容性:DeepSeek 系 provider 的 usage 不单独上报 cacheWriteTokens
|
|
513
|
+
// (缓存写入发生在未命中时,即 cacheWriteTokens == inputTokens)。
|
|
514
|
+
// 当上游未提供该字段时,用未命中 token 数(inputTokens)兜底,避免
|
|
515
|
+
// "缓存写入"列长期为空。
|
|
516
|
+
const cacheWrite = usage.cacheWriteTokens || usage.inputTokens || 0
|
|
500
517
|
records.push({
|
|
501
518
|
time: startedAt,
|
|
502
519
|
model,
|
|
@@ -505,7 +522,7 @@ export default {
|
|
|
505
522
|
inputTokens: usage.inputTokens || 0,
|
|
506
523
|
outputTokens: usage.outputTokens || 0,
|
|
507
524
|
cacheReadTokens: usage.cacheReadTokens || 0,
|
|
508
|
-
cacheWriteTokens:
|
|
525
|
+
cacheWriteTokens: cacheWrite,
|
|
509
526
|
reasoningTokens: usage.reasoningTokens || 0,
|
|
510
527
|
finishReason,
|
|
511
528
|
usdCnyRate: (provider === 'digital-ocean' || provider === 'digitalocean') ? (FX.rate || 0) : 0,
|
|
@@ -787,6 +804,124 @@ export default {
|
|
|
787
804
|
credentialName: DIGITALOCEAN_CREDENTIAL
|
|
788
805
|
}
|
|
789
806
|
}
|
|
807
|
+
// ── Qwen / 百炼 Token Plan (console-token) ────────────────────────────
|
|
808
|
+
// 复用 bl CLI 保存的百炼控制台 OAuth access_token(~/.bailian/config.json),
|
|
809
|
+
// 调用百炼内部门户网关查询 Token Plan 配额用量,不需要阿里云 AccessKey。
|
|
810
|
+
const QWEN_GATEWAYS = {
|
|
811
|
+
'cn-beijing-domestic': { host: 'bailian-cs.console.aliyun.com', action: 'BroadScopeAspnGateway' },
|
|
812
|
+
'cn-beijing-international': { host: 'bailian-cs.console.alibabacloud.com', action: 'BroadScopeAspnGateway' },
|
|
813
|
+
'ap-southeast-1-domestic': { host: 'modelstudio-cs.console.aliyun.com', action: 'IntlBroadScopeAspnGateway' },
|
|
814
|
+
'ap-southeast-1-international': { host: 'bailian-singapore-cs.alibabacloud.com', action: 'IntlBroadScopeAspnGateway' }
|
|
815
|
+
}
|
|
816
|
+
const QWEN_API = 'zeldaHttp.apikeyMgr./tokenplan/personal/api/v2/usage'
|
|
817
|
+
|
|
818
|
+
function readBailianConfigPath() {
|
|
819
|
+
const base = process.env.BAILIAN_CONFIG_DIR || joinPath(os.homedir(), '.bailian')
|
|
820
|
+
return joinPath(base, 'config.json')
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
async function readQwenConsoleToken() {
|
|
824
|
+
const cfgPath = readBailianConfigPath()
|
|
825
|
+
let raw
|
|
826
|
+
try { raw = await nfsReadText(cfgPath) } catch (e) {
|
|
827
|
+
throw new Error('未找到 ' + cfgPath + '。请先运行 `bl auth login --console` 完成百炼控制台登录。')
|
|
828
|
+
}
|
|
829
|
+
let cfg
|
|
830
|
+
try { cfg = JSON.parse(raw) } catch (e) {
|
|
831
|
+
throw new Error(cfgPath + ' 不是合法的 JSON 配置')
|
|
832
|
+
}
|
|
833
|
+
let token = cfg.access_token || cfg.accessToken
|
|
834
|
+
const active = cfg.active_config || 'default'
|
|
835
|
+
const profile = cfg[active]
|
|
836
|
+
if (profile && typeof profile === 'object') {
|
|
837
|
+
token = token || profile.access_token || profile.accessToken
|
|
838
|
+
}
|
|
839
|
+
if (!token || !String(token).trim()) {
|
|
840
|
+
throw new Error('配置中没有 access_token,请先运行 `bl auth login --console` 完成百炼控制台登录。')
|
|
841
|
+
}
|
|
842
|
+
return { token: String(token).trim(), region: String(cfg.console_region || 'cn-beijing'), site: String(cfg.console_site || 'domestic') }
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
async function queryQwenTokenPlan() {
|
|
846
|
+
const { token, region, site } = await readQwenConsoleToken()
|
|
847
|
+
const gw = QWEN_GATEWAYS[region + '-' + site] || QWEN_GATEWAYS['cn-beijing-domestic']
|
|
848
|
+
if (!gw) throw new Error('不支持的控制台地域/站点:' + region + '/' + site)
|
|
849
|
+
const payload = {
|
|
850
|
+
Api: QWEN_API,
|
|
851
|
+
V: '1.0',
|
|
852
|
+
Data: {
|
|
853
|
+
cornerstoneParam: {
|
|
854
|
+
protocol: 'V2',
|
|
855
|
+
console: 'ONE_CONSOLE',
|
|
856
|
+
productCode: 'p_efm',
|
|
857
|
+
switchUserType: 3,
|
|
858
|
+
consoleSite: 'BAILIAN_ALIYUN'
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
const body = 'params=' + encodeURIComponent(JSON.stringify(payload)) + '®ion=' + encodeURIComponent(region)
|
|
863
|
+
const url = 'https://' + gw.host + '/cli/api.json'
|
|
864
|
+
+ '?action=' + encodeURIComponent(gw.action)
|
|
865
|
+
+ '&product=sfm_bailian&api=' + encodeURIComponent(QWEN_API)
|
|
866
|
+
// 与 bl CLI 一致:用 undici 的 EnvHttpProxyAgent 自动读取 HTTPS_PROXY/https_proxy
|
|
867
|
+
// 环境变量,走代理转发;未设置代理时则直连(EnvHttpProxyAgent 内部处理)。
|
|
868
|
+
// 因为子进程不保证能按模块名解析到 undici,这里预先算出 undici 的绝对入口路径并传给子进程。
|
|
869
|
+
let undiciPath = ''
|
|
870
|
+
try {
|
|
871
|
+
undiciPath = new URL(import.meta.resolve('undici')).pathname
|
|
872
|
+
} catch (e) {
|
|
873
|
+
// undici 是插件依赖,正常安装后必然存在;这里留空交由子进程报清楚错误。
|
|
874
|
+
undiciPath = ''
|
|
875
|
+
}
|
|
876
|
+
const script = [
|
|
877
|
+
'const token=process.env.BAILIAN_TOKEN||"";',
|
|
878
|
+
'const url=process.env.BAILIAN_API_URL||"";',
|
|
879
|
+
'const body=process.env.BAILIAN_BODY||"";',
|
|
880
|
+
'const undiciPath=process.env.UNDICI_PATH||"";',
|
|
881
|
+
'if(!undiciPath){process.stdout.write(JSON.stringify({error:"undici 不可用,请确认插件依赖已安装"}));process.exit(0);}',
|
|
882
|
+
'const m=require(undiciPath);',
|
|
883
|
+
'try{m.setGlobalDispatcher(new m.EnvHttpProxyAgent())}catch(e){}',
|
|
884
|
+
'const timeout=setTimeout(()=>{process.stdout.write(JSON.stringify({error:"timeout"}));process.exit(0)},20000);',
|
|
885
|
+
'(async()=>{',
|
|
886
|
+
' try{',
|
|
887
|
+
' const res=await m.fetch(url,{method:"POST",headers:{Authorization:"Bearer "+token,Accept:"*/*","Content-Type":"application/x-www-form-urlencoded","User-Agent":"dsh-usage-plugin"},body:body});',
|
|
888
|
+
' clearTimeout(timeout);',
|
|
889
|
+
' const b=await res.text();',
|
|
890
|
+
' process.stdout.write(JSON.stringify({statusCode:res.status,contentType:String(res.headers.get("content-type")||""),body:b}));',
|
|
891
|
+
' }catch(e){',
|
|
892
|
+
' clearTimeout(timeout);',
|
|
893
|
+
' process.stdout.write(JSON.stringify({error:String(e&&e.message||e)}));',
|
|
894
|
+
' }',
|
|
895
|
+
'})();'
|
|
896
|
+
].join('\n')
|
|
897
|
+
const r = await spawnNode(script, null, {
|
|
898
|
+
BAILIAN_TOKEN: token,
|
|
899
|
+
BAILIAN_API_URL: url,
|
|
900
|
+
BAILIAN_BODY: body,
|
|
901
|
+
UNDICI_PATH: undiciPath
|
|
902
|
+
})
|
|
903
|
+
if (!r.ok) throw new Error(r.error || '请求失败')
|
|
904
|
+
let parsed
|
|
905
|
+
try { parsed = JSON.parse(r.out) } catch (e) { throw new Error('无法解析百炼网关输出') }
|
|
906
|
+
if (parsed.error) throw new Error(parsed.error)
|
|
907
|
+
if (parsed.statusCode !== 200) {
|
|
908
|
+
const hint = parsed.statusCode === 401 || parsed.statusCode === 403 ? ' 登录可能已过期,请重新运行 `bl auth login --console`。' : ''
|
|
909
|
+
throw new Error('接口返回 HTTP ' + parsed.statusCode + ':' + String(parsed.body || '').slice(0, 300) + hint)
|
|
910
|
+
}
|
|
911
|
+
// 网关返回的是带外层包装的 JSON(data.DataV2.data.data 才是配额字段),
|
|
912
|
+
// 这里解包后把扁平化的 JSON 文本交给 parseBalanceResponse。
|
|
913
|
+
let wrapper
|
|
914
|
+
try { wrapper = JSON.parse(parsed.body) } catch (e) { throw new Error('无法解析百炼网关响应') }
|
|
915
|
+
let inner = wrapper && wrapper.data
|
|
916
|
+
if (inner && inner.DataV2) {
|
|
917
|
+
const d2 = inner.DataV2.data
|
|
918
|
+
inner = (d2 && d2.data) != null ? d2.data : (d2 != null ? d2 : inner.DataV2)
|
|
919
|
+
} else if (inner) {
|
|
920
|
+
inner = inner.data || inner
|
|
921
|
+
}
|
|
922
|
+
if (!inner || typeof inner !== 'object') throw new Error('百炼网关响应格式不符合预期')
|
|
923
|
+
return JSON.stringify(inner)
|
|
924
|
+
}
|
|
790
925
|
|
|
791
926
|
async function queryBalance(providerId) {
|
|
792
927
|
const provider = getBalanceProvider(providerId)
|
|
@@ -794,6 +929,23 @@ export default {
|
|
|
794
929
|
if (provider.queryMode === 'unsupported') {
|
|
795
930
|
return balanceFailure(provider, 'AMD GPU Cloud 当前未公开可由推理 API Key 调用的余额查询端点;请在 AMD Developer Cloud 控制台查看 credits。', { unsupported: true, errorCode: 'unsupported' })
|
|
796
931
|
}
|
|
932
|
+
if (provider.queryMode === 'console-token') {
|
|
933
|
+
try {
|
|
934
|
+
const bodyText = await queryQwenTokenPlan()
|
|
935
|
+
const normalized = parseBalanceResponse(provider.id, bodyText)
|
|
936
|
+
if (!normalized.ok) {
|
|
937
|
+
normalized.provider = provider.id
|
|
938
|
+
normalized.providerName = provider.name
|
|
939
|
+
normalized.credentialHelpUrl = provider.credentialHelpUrl || ''
|
|
940
|
+
} else {
|
|
941
|
+
normalized.credentialName = '百炼控制台 token'
|
|
942
|
+
normalized.credentialSource = readBailianConfigPath()
|
|
943
|
+
}
|
|
944
|
+
return normalized
|
|
945
|
+
} catch (e) {
|
|
946
|
+
return balanceFailure(provider, msg(e), { errorCode: 'console-token-error' })
|
|
947
|
+
}
|
|
948
|
+
}
|
|
797
949
|
const credentials = ctx.get('credentials')
|
|
798
950
|
if (!credentials) return balanceFailure(provider, '凭据服务不可用', { errorCode: 'credentials-unavailable' })
|
|
799
951
|
const modelProfile = await configuredModelProvider(provider)
|
|
@@ -814,7 +966,7 @@ export default {
|
|
|
814
966
|
? '尚未保存 DigitalOcean 账户 Personal Access Token。请在此页面输入 dop_v1_ Token,保存后查询。'
|
|
815
967
|
: provider.id === 'siliconflow'
|
|
816
968
|
? '模型提供商 ' + modelProfile.route + ' 引用了 ' + modelProfile.apiKeyEnv + ',但该凭据未配置。请在“设置 → 模型”中重新填写 API Key 并保存。'
|
|
817
|
-
:
|
|
969
|
+
: missingCredentialError(provider).error
|
|
818
970
|
return balanceFailure(provider, message, { errorCode: 'missing-credential', modelProviderRoute: modelProfile ? modelProfile.route : '' })
|
|
819
971
|
}
|
|
820
972
|
const endpoint = resolveBalanceEndpoint(provider.id, modelProfile && modelProfile.baseURL)
|
|
@@ -1182,6 +1334,27 @@ export default {
|
|
|
1182
1334
|
} catch (e) {
|
|
1183
1335
|
push('route-register-threw: ' + (e && e.stack ? e.stack : msg(e)))
|
|
1184
1336
|
}
|
|
1337
|
+
try {
|
|
1338
|
+
// SSE 实时推送端点:数据变化时向订阅者推送 {"type":"changed"}。
|
|
1339
|
+
// 桌面端数据中心订阅该流即可即时感知用量数据更新。
|
|
1340
|
+
webServer.register({
|
|
1341
|
+
kind: 'exact',
|
|
1342
|
+
path: '/usage/api/events',
|
|
1343
|
+
handler: (req, res) => {
|
|
1344
|
+
res.writeHead(200, {
|
|
1345
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
1346
|
+
'Cache-Control': 'no-cache',
|
|
1347
|
+
Connection: 'keep-alive',
|
|
1348
|
+
'X-Accel-Buffering': 'no',
|
|
1349
|
+
})
|
|
1350
|
+
res.write(':ok\n\n')
|
|
1351
|
+
sseClients.add(res)
|
|
1352
|
+
req.on('close', () => { sseClients.delete(res) })
|
|
1353
|
+
}
|
|
1354
|
+
})
|
|
1355
|
+
} catch (e) {
|
|
1356
|
+
push('events-route-register-threw: ' + msg(e))
|
|
1357
|
+
}
|
|
1185
1358
|
} else {
|
|
1186
1359
|
push('route-not-registered (no webServer)')
|
|
1187
1360
|
}
|
package/package.json
CHANGED
|
@@ -1,77 +1,80 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@feiyang666/dsh-usage-plugin",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "DeepSeek Harness usage & cost tracker plugin: per-call token/cache-hit stats, peak/off-peak billing, DeepSeek balance query, CSV/JSON/PNG export with custom destination, and persistent local storage. Ships a host half plus a web client half in one npm package; installs into a DSH profile as a dsh.bundle with one command (dsh plugin --profile web add @feiyang666/dsh-usage-plugin).",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"deepseek",
|
|
7
|
-
"harness",
|
|
8
|
-
"dsh",
|
|
9
|
-
"plugin",
|
|
10
|
-
"usage",
|
|
11
|
-
"cost",
|
|
12
|
-
"tokens",
|
|
13
|
-
"cache",
|
|
14
|
-
"balance"
|
|
15
|
-
],
|
|
16
|
-
"license": "MIT",
|
|
17
|
-
"type": "module",
|
|
18
|
-
"main": "lib/index.js",
|
|
19
|
-
"repository": {
|
|
20
|
-
"type": "git",
|
|
21
|
-
"url": "git+https://github.com/feiyang-dev/dsh-usage-plugin.git"
|
|
22
|
-
},
|
|
23
|
-
"contributors": [
|
|
24
|
-
{
|
|
25
|
-
"name": "Martin-soaring-dev",
|
|
26
|
-
"url": "https://github.com/Martin-soaring-dev"
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"name": "liu3734",
|
|
30
|
-
"url": "https://github.com/liu3734"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"name": "mumuer1024",
|
|
34
|
-
"url": "https://github.com/mumuer1024"
|
|
35
|
-
}
|
|
36
|
-
],
|
|
37
|
-
"homepage": "https://github.com/feiyang-dev/dsh-usage-plugin#readme",
|
|
38
|
-
"bugs": {
|
|
39
|
-
"url": "https://github.com/feiyang-dev/dsh-usage-plugin/issues"
|
|
40
|
-
},
|
|
41
|
-
"exports": {
|
|
42
|
-
".": "./lib/index.js",
|
|
43
|
-
"./client": "./lib/client.js",
|
|
44
|
-
"./package.json": "./package.json"
|
|
45
|
-
},
|
|
46
|
-
"engines": {
|
|
47
|
-
"node": ">=18"
|
|
48
|
-
},
|
|
49
|
-
"dsh": {
|
|
50
|
-
"bundle": {
|
|
51
|
-
"patch": "./cordis.patch.yml"
|
|
52
|
-
},
|
|
53
|
-
"client": {
|
|
54
|
-
"platform": "web",
|
|
55
|
-
"inject": [
|
|
56
|
-
"@deepseek-ai/dsh-client-ui-conversation"
|
|
57
|
-
]
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
"files": [
|
|
61
|
-
"lib",
|
|
62
|
-
"scripts",
|
|
63
|
-
"cordis.patch.yml",
|
|
64
|
-
"README.md",
|
|
65
|
-
"README.zh.md",
|
|
66
|
-
"CHANGELOG.md"
|
|
67
|
-
],
|
|
68
|
-
"scripts": {
|
|
69
|
-
"wire": "node scripts/wire.js",
|
|
70
|
-
"check": "node scripts/check-package.js",
|
|
71
|
-
"prepublishOnly": "node scripts/check-package.js",
|
|
72
|
-
"pack": "npm pack"
|
|
73
|
-
},
|
|
74
|
-
"peerDependencies": {
|
|
75
|
-
"@deepseek-ai/cordis": "^4.0.1"
|
|
76
|
-
}
|
|
77
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@feiyang666/dsh-usage-plugin",
|
|
3
|
+
"version": "1.13.0",
|
|
4
|
+
"description": "DeepSeek Harness usage & cost tracker plugin: per-call token/cache-hit stats, peak/off-peak billing, DeepSeek balance query, CSV/JSON/PNG export with custom destination, and persistent local storage. Ships a host half plus a web client half in one npm package; installs into a DSH profile as a dsh.bundle with one command (dsh plugin --profile web add @feiyang666/dsh-usage-plugin).",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"deepseek",
|
|
7
|
+
"harness",
|
|
8
|
+
"dsh",
|
|
9
|
+
"plugin",
|
|
10
|
+
"usage",
|
|
11
|
+
"cost",
|
|
12
|
+
"tokens",
|
|
13
|
+
"cache",
|
|
14
|
+
"balance"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "lib/index.js",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/feiyang-dev/dsh-usage-plugin.git"
|
|
22
|
+
},
|
|
23
|
+
"contributors": [
|
|
24
|
+
{
|
|
25
|
+
"name": "Martin-soaring-dev",
|
|
26
|
+
"url": "https://github.com/Martin-soaring-dev"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "liu3734",
|
|
30
|
+
"url": "https://github.com/liu3734"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "mumuer1024",
|
|
34
|
+
"url": "https://github.com/mumuer1024"
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"homepage": "https://github.com/feiyang-dev/dsh-usage-plugin#readme",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/feiyang-dev/dsh-usage-plugin/issues"
|
|
40
|
+
},
|
|
41
|
+
"exports": {
|
|
42
|
+
".": "./lib/index.js",
|
|
43
|
+
"./client": "./lib/client.js",
|
|
44
|
+
"./package.json": "./package.json"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18"
|
|
48
|
+
},
|
|
49
|
+
"dsh": {
|
|
50
|
+
"bundle": {
|
|
51
|
+
"patch": "./cordis.patch.yml"
|
|
52
|
+
},
|
|
53
|
+
"client": {
|
|
54
|
+
"platform": "web",
|
|
55
|
+
"inject": [
|
|
56
|
+
"@deepseek-ai/dsh-client-ui-conversation"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"files": [
|
|
61
|
+
"lib",
|
|
62
|
+
"scripts",
|
|
63
|
+
"cordis.patch.yml",
|
|
64
|
+
"README.md",
|
|
65
|
+
"README.zh.md",
|
|
66
|
+
"CHANGELOG.md"
|
|
67
|
+
],
|
|
68
|
+
"scripts": {
|
|
69
|
+
"wire": "node scripts/wire.js",
|
|
70
|
+
"check": "node scripts/check-package.js",
|
|
71
|
+
"prepublishOnly": "node scripts/check-package.js",
|
|
72
|
+
"pack": "npm pack"
|
|
73
|
+
},
|
|
74
|
+
"peerDependencies": {
|
|
75
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
76
|
+
},
|
|
77
|
+
"dependencies": {
|
|
78
|
+
"undici": "^7.0.0"
|
|
79
|
+
}
|
|
80
|
+
}
|
package/scripts/release.mjs
CHANGED
|
@@ -4,7 +4,8 @@ import fs from 'node:fs'
|
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
|
|
6
6
|
const REPO = 'feiyang-dev/dsh-usage-plugin'
|
|
7
|
-
const
|
|
7
|
+
const VERSION = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8')).version
|
|
8
|
+
const TAG = `v${VERSION}`
|
|
8
9
|
|
|
9
10
|
function getCredential() {
|
|
10
11
|
const input = 'protocol=https\nhost=github.com\n\n'
|
|
@@ -76,9 +77,10 @@ if (created.status !== 201) {
|
|
|
76
77
|
const release = JSON.parse(created.body)
|
|
77
78
|
console.log('release id:', release.id, 'url:', release.html_url)
|
|
78
79
|
|
|
79
|
-
// 打包并上传 tarball 作为附件(Windows 下 npm 为 npm.cmd
|
|
80
|
-
const
|
|
81
|
-
|
|
80
|
+
// 打包并上传 tarball 作为附件(Windows 下 npm 为 npm.cmd 批处理脚本,经 cmd /c 执行)
|
|
81
|
+
const pack = process.platform === 'win32'
|
|
82
|
+
? spawnSync('cmd', ['/c', 'npm', 'pack'], { encoding: 'utf8' })
|
|
83
|
+
: spawnSync('npm', ['pack'], { encoding: 'utf8' })
|
|
82
84
|
const tgzName = (pack.stdout || '').trim().split('\n').pop()
|
|
83
85
|
if (tgzName && fs.existsSync(tgzName)) {
|
|
84
86
|
const buf = fs.readFileSync(tgzName)
|