dsh-plugin-yapi 1.4.0 → 1.4.3
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 +21 -12
- package/bin/yapi-sync.mjs +4 -0
- package/client.js +22 -6
- package/index.js +73 -34
- package/package.json +3 -2
- package/skills/yapi-doc/SKILL.md +7 -5
- package/skills/yapi-doc/yapi-doc.mjs +22 -8
- package/skills/yapi-sync/SKILL.md +20 -0
package/README.md
CHANGED
|
@@ -43,12 +43,13 @@ dsh plugin --profile desktop add dsh-plugin-yapi
|
|
|
43
43
|
打开 DSH → 左侧 **YAPI** → 右上角 **账号设置**,填写:
|
|
44
44
|
|
|
45
45
|
- YAPI 地址(如 `https://yapi.example.com`,带 `http://` 或 `https://`)
|
|
46
|
+
- 登录方式:**普通登录** / **LDAP**(选 LDAP 前确认 YAPI 服务端已开启 LDAP)
|
|
46
47
|
- 账号 / 密码
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
点「测试并保存」,验证通过后生效。普通登录走 `/api/user/login`,LDAP 走 `/api/user/login_by_ldap`。
|
|
49
50
|
|
|
50
|
-
- 账号保存在 `~/.dsh/dsh-plugin-yapi.json`(权限 `0600
|
|
51
|
-
- 也支持环境变量 `YAPI_BASE_URL` / `YAPI_EMAIL` / `YAPI_PASSWORD
|
|
51
|
+
- 账号保存在 `~/.dsh/dsh-plugin-yapi.json`(权限 `0600`),不会进入仓库;`authType` 为 `normal` 或 `ldap`。
|
|
52
|
+
- 也支持环境变量 `YAPI_BASE_URL` / `YAPI_EMAIL` / `YAPI_PASSWORD` / `YAPI_AUTH_TYPE`(`ldap` 时用 LDAP 登录)。
|
|
52
53
|
- 搜索需要项目 ID;打开一个文档后会自动跟随该文档所属项目。
|
|
53
54
|
- 插件**没有内置任何 YAPI 地址**,必须自己配置。
|
|
54
55
|
|
|
@@ -66,21 +67,28 @@ CLI 是零依赖 Node 脚本,不需要 DSH。直接命令行安装:
|
|
|
66
67
|
|
|
67
68
|
```bash
|
|
68
69
|
npm install -g dsh-plugin-yapi
|
|
69
|
-
yapi-
|
|
70
|
+
yapi-sync
|
|
70
71
|
yapi-doc <doc-id>
|
|
71
72
|
```
|
|
72
73
|
|
|
73
|
-
- `
|
|
74
|
+
- `yapi-sync` 和 `yapi-doc` 一样是独立命令:一次把 **`yapi-doc` 和 `yapi-sync` 两个 skill**同步到所有检测到的工具。
|
|
75
|
+
- 自动检测 Trae、WorkBuddy、Qoder CN、GitHub Copilot、CodeBuddy、Claude Code、Cursor、Gemini CLI、Amp、OpenCode、Codex/通用 Agent Skills、DSH 等目录;`--dir` 可追加自定义 skills 根目录。
|
|
76
|
+
- 同步完重启对应应用,技能列表里会出现 `yapi-doc` 和 `yapi-sync`。
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
yapi-sync
|
|
80
|
+
yapi-sync --dry-run # 只显示会写到哪些目录
|
|
81
|
+
yapi-sync --dir <skills根目录> # 追加自定义目录
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- `yapi-doc sync-skills` 是 `yapi-sync` 的别名,仍然可用。
|
|
74
85
|
- 如果 npm 暂时不可用,也可以 `npm install -g github:jinshan3690/dsh-plugin-yapi`。
|
|
75
|
-
-
|
|
86
|
+
- 只装 `yapi-doc`(不装 `yapi-sync`)时用:
|
|
76
87
|
|
|
77
88
|
```bash
|
|
78
|
-
yapi-doc
|
|
79
|
-
yapi-doc sync-skills --dry-run # 只显示会写到哪些目录
|
|
89
|
+
yapi-doc install-skill
|
|
80
90
|
```
|
|
81
91
|
|
|
82
|
-
会自动检测 Trae、WorkBuddy、Qoder CN、GitHub Copilot、CodeBuddy、Claude Code、Cursor、Gemini CLI、Amp、OpenCode、Codex/通用 Agent Skills、DSH 等目录;`--dir` 可追加自定义目录。同步完重启对应应用。
|
|
83
|
-
|
|
84
92
|
- 也可以单独指定目录:
|
|
85
93
|
|
|
86
94
|
```bash
|
|
@@ -107,6 +115,7 @@ yapi-doc <doc-id>
|
|
|
107
115
|
export YAPI_BASE_URL='https://yapi.example.com'
|
|
108
116
|
export YAPI_EMAIL='you@example.com'
|
|
109
117
|
export YAPI_PASSWORD='your-password'
|
|
118
|
+
export YAPI_AUTH_TYPE='normal' # 或 ldap
|
|
110
119
|
```
|
|
111
120
|
|
|
112
121
|
常用参数:
|
|
@@ -124,8 +133,8 @@ yapi-doc <doc-id> --json # 结构化输出
|
|
|
124
133
|
- `--json`:输出 `{ ok, data }` 结构化 JSON。
|
|
125
134
|
- `--project <id>`:`--search` 必填。
|
|
126
135
|
|
|
127
|
-
**DSH 用户**不需要上面的 npm
|
|
128
|
-
`~/.dsh/profiles/desktop/node_modules/.bin/yapi-doc`。
|
|
136
|
+
**DSH 用户**不需要上面的 npm 安装:插件会自动注册 `yapi-doc` 和 `yapi-sync` 两个 skill;CLI 在
|
|
137
|
+
`~/.dsh/profiles/desktop/node_modules/.bin/yapi-doc` 和 `.../.bin/yapi-sync`。
|
|
129
138
|
|
|
130
139
|
## 更新 / 卸载
|
|
131
140
|
|
package/client.js
CHANGED
|
@@ -710,8 +710,8 @@ window.__ModuleLoader__.load({
|
|
|
710
710
|
'.yapi-modal-state{padding:8px 10px;border:1px solid var(--yapi-line);border-radius:8px;background:var(--yapi-surface);font-size:12px;color:var(--yapi-text-2)}',
|
|
711
711
|
'.yapi-formrow{display:flex;flex-direction:column;gap:5px}',
|
|
712
712
|
'.yapi-formrow label{font-size:11.5px;font-weight:600;color:var(--yapi-text-3)}',
|
|
713
|
-
'.yapi-formrow input{width:100%;box-sizing:border-box;border:1px solid var(--yapi-line);border-radius:8px;background:var(--yapi-surface-2);color:inherit;font-family:inherit;font-size:13px;padding:8px 10px;outline:0}',
|
|
714
|
-
'.yapi-formrow input:focus{border-color:var(--yapi-accent)}',
|
|
713
|
+
'.yapi-formrow input,.yapi-formrow select{width:100%;box-sizing:border-box;border:1px solid var(--yapi-line);border-radius:8px;background:var(--yapi-surface-2);color:inherit;font-family:inherit;font-size:13px;padding:8px 10px;outline:0}',
|
|
714
|
+
'.yapi-formrow input:focus,.yapi-formrow select:focus{border-color:var(--yapi-accent)}',
|
|
715
715
|
'.yapi-formhint{font-size:11.5px;line-height:1.6;color:var(--yapi-text-3)}',
|
|
716
716
|
'.yapi-modal-error{border:1px solid rgba(220,38,38,.22);border-radius:8px;background:rgba(220,38,38,.07);padding:8px 10px;font-size:12px;line-height:1.55;color:var(--dsw-alias-state-error-primary,#dc2626);word-break:break-word}',
|
|
717
717
|
'.yapi-copymodal{max-width:520px}',
|
|
@@ -1155,7 +1155,7 @@ window.__ModuleLoader__.load({
|
|
|
1155
1155
|
})
|
|
1156
1156
|
const [account, setAccount] = useState(null)
|
|
1157
1157
|
const [accountOpen, setAccountOpen] = useState(false)
|
|
1158
|
-
const [accountForm, setAccountForm] = useState({ baseUrl: '', email: '', password: '' })
|
|
1158
|
+
const [accountForm, setAccountForm] = useState({ baseUrl: '', email: '', password: '', authType: 'normal' })
|
|
1159
1159
|
const [accountSaving, setAccountSaving] = useState(false)
|
|
1160
1160
|
const [accountError, setAccountError] = useState('')
|
|
1161
1161
|
|
|
@@ -1462,7 +1462,7 @@ window.__ModuleLoader__.load({
|
|
|
1462
1462
|
throw new Error((payload && payload.error && payload.error.message) || '读取账号设置失败')
|
|
1463
1463
|
}
|
|
1464
1464
|
setAccount(payload)
|
|
1465
|
-
setAccountForm({ baseUrl: payload.baseUrl || '', email: payload.email || '', password: '' })
|
|
1465
|
+
setAccountForm({ baseUrl: payload.baseUrl || '', email: payload.email || '', password: '', authType: payload.authType || 'normal' })
|
|
1466
1466
|
})
|
|
1467
1467
|
.catch(function (error) {
|
|
1468
1468
|
setAccountError((error && error.message) || String(error))
|
|
@@ -1487,7 +1487,7 @@ window.__ModuleLoader__.load({
|
|
|
1487
1487
|
if (!payload) throw new Error('宿主返回了非 JSON 响应')
|
|
1488
1488
|
if (!payload.ok) throw new Error((payload.error && payload.error.message) || '保存失败')
|
|
1489
1489
|
setAccount(payload)
|
|
1490
|
-
setAccountForm({ baseUrl: payload.baseUrl || '', email: payload.email || '', password: '' })
|
|
1490
|
+
setAccountForm({ baseUrl: payload.baseUrl || '', email: payload.email || '', password: '', authType: payload.authType || 'normal' })
|
|
1491
1491
|
setAccountOpen(false)
|
|
1492
1492
|
showToast('账号已保存并登录成功')
|
|
1493
1493
|
if (activeId !== null) fetchDoc(activeId)
|
|
@@ -2362,6 +2362,22 @@ window.__ModuleLoader__.load({
|
|
|
2362
2362
|
},
|
|
2363
2363
|
}),
|
|
2364
2364
|
),
|
|
2365
|
+
h(
|
|
2366
|
+
'div',
|
|
2367
|
+
{ className: 'yapi-formrow' },
|
|
2368
|
+
h('label', null, '登录方式'),
|
|
2369
|
+
h(
|
|
2370
|
+
'select',
|
|
2371
|
+
{
|
|
2372
|
+
value: accountForm.authType || 'normal',
|
|
2373
|
+
onChange: function (event) {
|
|
2374
|
+
updateAccountForm({ authType: event.target.value })
|
|
2375
|
+
},
|
|
2376
|
+
},
|
|
2377
|
+
h('option', { value: 'normal' }, '普通登录'),
|
|
2378
|
+
h('option', { value: 'ldap' }, 'LDAP'),
|
|
2379
|
+
),
|
|
2380
|
+
),
|
|
2365
2381
|
h(
|
|
2366
2382
|
'div',
|
|
2367
2383
|
{ className: 'yapi-formrow' },
|
|
@@ -2393,7 +2409,7 @@ window.__ModuleLoader__.load({
|
|
|
2393
2409
|
h(
|
|
2394
2410
|
'div',
|
|
2395
2411
|
{ className: 'yapi-formhint' },
|
|
2396
|
-
'
|
|
2412
|
+
'普通登录使用 YAPI 账号密码;LDAP 使用 LDAP 账号密码(需 YAPI 服务端开启 LDAP)。点击「测试并保存」后,插件会先用这组账号真实登录 YAPI;成功后才写入 ' +
|
|
2397
2413
|
((account && account.configFile) || '~/.dsh/dsh-plugin-yapi.json') +
|
|
2398
2414
|
'(权限 0600)。密码只保存在 Host 端,不会回传页面。',
|
|
2399
2415
|
),
|
package/index.js
CHANGED
|
@@ -91,56 +91,85 @@ function harnessHome() {
|
|
|
91
91
|
/** Location of the optional credential file written by the installer. */
|
|
92
92
|
export const CONFIG_FILE = join(harnessHome(), 'dsh-plugin-yapi.json')
|
|
93
93
|
|
|
94
|
-
const BUNDLED_SKILL_NAME = 'yapi-doc'
|
|
95
94
|
const BUNDLED_SKILL_PROVIDER = 'yapi-doc-plugin'
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
const BUNDLED_SKILLS = [
|
|
96
|
+
{
|
|
97
|
+
name: 'yapi-doc',
|
|
98
|
+
dir: 'yapi-doc',
|
|
99
|
+
description:
|
|
100
|
+
'Read a YAPI interface document by ID, full URL, or path keyword. Use when a task needs an API method, path, headers, parameters, request body, response fields, or response example. The bundled CLI reads the local YAPI account and prints Markdown in one command.',
|
|
101
|
+
whenToUse:
|
|
102
|
+
'The user mentions YAPI, an interface document ID (for example 10001), an API path, or asks for the request or response fields of an API.',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'yapi-sync',
|
|
106
|
+
dir: 'yapi-sync',
|
|
107
|
+
description:
|
|
108
|
+
'Sync the yapi-doc and yapi-sync skills into every detected AI tool (Trae, WorkBuddy, Qoder CN, GitHub Copilot, CodeBuddy, Codex/Agent Skills, DSH, Claude Code, Cursor, Gemini CLI, Amp, OpenCode and more). Use when the user asks to install, update, or sync the YAPI skill across tools.',
|
|
109
|
+
whenToUse:
|
|
110
|
+
'The user asks to install, update, or sync the YAPI skill or CLI across local AI tools.',
|
|
111
|
+
},
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
/** Packaged skills whose user-level copy is absent. */
|
|
115
|
+
function bundledSkillDefs() {
|
|
116
|
+
return BUNDLED_SKILLS.map(function (skill) {
|
|
117
|
+
return Object.assign({}, skill, {
|
|
118
|
+
skillDir: join(HERE, 'skills', skill.dir),
|
|
119
|
+
skillPath: join(HERE, 'skills', skill.dir, 'SKILL.md'),
|
|
120
|
+
})
|
|
121
|
+
}).filter(function (skill) {
|
|
122
|
+
return (
|
|
123
|
+
existsSync(skill.skillPath) &&
|
|
124
|
+
!existsSync(join(harnessHome(), 'skills', skill.name, 'SKILL.md'))
|
|
125
|
+
)
|
|
126
|
+
})
|
|
127
|
+
}
|
|
100
128
|
|
|
101
129
|
/**
|
|
102
|
-
* Register
|
|
103
|
-
* `dsh plugin add` (no `~/.dsh/skills` copy present). The
|
|
104
|
-
*
|
|
130
|
+
* Register packaged `yapi-doc` / `yapi-sync` skills when the plugin was
|
|
131
|
+
* installed by `dsh plugin add` (no `~/.dsh/skills` copy present). The user
|
|
132
|
+
* skill root still wins because it ranks above this bundled provider.
|
|
105
133
|
* @param {any} ctx - DSH host context (must carry the `skills` service).
|
|
106
134
|
*/
|
|
107
135
|
function registerBundledSkill(ctx) {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
if (!existsSync(skillPath)) return
|
|
111
|
-
const userSkill = join(harnessHome(), 'skills', BUNDLED_SKILL_NAME, 'SKILL.md')
|
|
112
|
-
if (existsSync(userSkill)) return
|
|
136
|
+
const defs = bundledSkillDefs()
|
|
137
|
+
if (defs.length === 0) return
|
|
113
138
|
|
|
114
139
|
ctx.skills.registerProvider(function () {
|
|
115
140
|
return {
|
|
116
141
|
name: BUNDLED_SKILL_PROVIDER,
|
|
117
142
|
async list() {
|
|
118
|
-
return
|
|
119
|
-
{
|
|
120
|
-
name:
|
|
121
|
-
description:
|
|
122
|
-
whenToUse:
|
|
143
|
+
return defs.map(function (skill) {
|
|
144
|
+
return {
|
|
145
|
+
name: skill.name,
|
|
146
|
+
description: skill.description,
|
|
147
|
+
whenToUse: skill.whenToUse,
|
|
123
148
|
source: 'bundled',
|
|
124
149
|
provider: BUNDLED_SKILL_PROVIDER,
|
|
150
|
+
invocation: { modelInvocable: true, userInvocable: true },
|
|
125
151
|
rank: 1000,
|
|
126
|
-
path: skillPath,
|
|
127
|
-
}
|
|
128
|
-
|
|
152
|
+
path: skill.skillPath,
|
|
153
|
+
}
|
|
154
|
+
})
|
|
129
155
|
},
|
|
130
156
|
async get(candidate) {
|
|
131
|
-
|
|
132
|
-
|
|
157
|
+
const skill = defs.find(function (item) {
|
|
158
|
+
return item.name === (candidate && candidate.name)
|
|
159
|
+
})
|
|
160
|
+
if (skill === undefined) return undefined
|
|
161
|
+
const raw = await readFile(skill.skillPath, 'utf8')
|
|
133
162
|
const body = raw.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/u, '').trim()
|
|
134
|
-
const content = body.split('$HOME/.dsh/skills/yapi-doc').join(
|
|
163
|
+
const content = body.split('$HOME/.dsh/skills/yapi-doc').join(join(HERE, 'skills', 'yapi-doc'))
|
|
135
164
|
return {
|
|
136
|
-
name:
|
|
137
|
-
description:
|
|
138
|
-
whenToUse:
|
|
165
|
+
name: skill.name,
|
|
166
|
+
description: skill.description,
|
|
167
|
+
whenToUse: skill.whenToUse,
|
|
139
168
|
invocation: { modelInvocable: true, userInvocable: true },
|
|
140
169
|
source: 'bundled',
|
|
141
170
|
provider: BUNDLED_SKILL_PROVIDER,
|
|
142
|
-
resourceBase: { kind: 'directory', path: skillDir },
|
|
143
|
-
path: skillPath,
|
|
171
|
+
resourceBase: { kind: 'directory', path: skill.skillDir },
|
|
172
|
+
path: skill.skillPath,
|
|
144
173
|
content,
|
|
145
174
|
}
|
|
146
175
|
},
|
|
@@ -163,11 +192,16 @@ function readStoredConfig() {
|
|
|
163
192
|
}
|
|
164
193
|
}
|
|
165
194
|
|
|
195
|
+
/** YAPI login mode; unknown values fall back to the normal password login. */
|
|
196
|
+
function normalizeAuthType(value) {
|
|
197
|
+
return String(value ?? '').trim().toLowerCase() === 'ldap' ? 'ldap' : 'normal'
|
|
198
|
+
}
|
|
199
|
+
|
|
166
200
|
/**
|
|
167
201
|
* Atomically persist the account so the next start uses it. The temporary file
|
|
168
202
|
* is created with 0600 before the rename, so the password is never briefly
|
|
169
203
|
* readable by another user.
|
|
170
|
-
* @param {{ baseUrl: string, email: string, password: string, timeoutMs: number }} settings - verified account.
|
|
204
|
+
* @param {{ baseUrl: string, email: string, password: string, authType: string, timeoutMs: number }} settings - verified account.
|
|
171
205
|
*/
|
|
172
206
|
function writeConfigFile(settings) {
|
|
173
207
|
const directory = dirname(CONFIG_FILE)
|
|
@@ -178,6 +212,7 @@ function writeConfigFile(settings) {
|
|
|
178
212
|
baseUrl: settings.baseUrl,
|
|
179
213
|
email: settings.email,
|
|
180
214
|
password: settings.password,
|
|
215
|
+
authType: settings.authType,
|
|
181
216
|
timeoutMs: settings.timeoutMs,
|
|
182
217
|
},
|
|
183
218
|
null,
|
|
@@ -194,7 +229,7 @@ function writeConfigFile(settings) {
|
|
|
194
229
|
* (that path only accepts plain insert rows), while the file still carries the
|
|
195
230
|
* account for a fresh start.
|
|
196
231
|
* @param {Record<string, unknown>} config - loader-provided config.
|
|
197
|
-
* @returns {{ baseUrl: string, email: string, password: string, timeoutMs: number }} settings.
|
|
232
|
+
* @returns {{ baseUrl: string, email: string, password: string, authType: string, timeoutMs: number }} settings.
|
|
198
233
|
*/
|
|
199
234
|
function readConfig(config) {
|
|
200
235
|
const row = config && typeof config === 'object' ? config : {}
|
|
@@ -205,6 +240,7 @@ function readConfig(config) {
|
|
|
205
240
|
baseUrl: normalizeBaseUrl(row.baseUrl ?? env.YAPI_BASE_URL ?? stored.baseUrl ?? ''),
|
|
206
241
|
email: String(row.email ?? env.YAPI_EMAIL ?? stored.email ?? '').trim(),
|
|
207
242
|
password: String(row.password ?? env.YAPI_PASSWORD ?? stored.password ?? ''),
|
|
243
|
+
authType: normalizeAuthType(row.authType ?? env.YAPI_AUTH_TYPE ?? stored.authType ?? 'normal'),
|
|
208
244
|
timeoutMs: Number.isFinite(timeout) && timeout >= 1000 ? Math.min(timeout, 120_000) : 20_000,
|
|
209
245
|
}
|
|
210
246
|
}
|
|
@@ -216,7 +252,7 @@ function readConfig(config) {
|
|
|
216
252
|
* @returns {{ getJson: Function, ensureSession: Function, status: Function }} client face.
|
|
217
253
|
*/
|
|
218
254
|
function createYapiClient(settings, logger) {
|
|
219
|
-
const { baseUrl, email, password, timeoutMs } = settings
|
|
255
|
+
const { baseUrl, email, password, authType, timeoutMs } = settings
|
|
220
256
|
let cookie = ''
|
|
221
257
|
let expiresAt = 0
|
|
222
258
|
let pendingLogin = null
|
|
@@ -238,7 +274,8 @@ function createYapiClient(settings, logger) {
|
|
|
238
274
|
}
|
|
239
275
|
let response
|
|
240
276
|
try {
|
|
241
|
-
|
|
277
|
+
const loginPath = authType === 'ldap' ? '/api/user/login_by_ldap' : '/api/user/login'
|
|
278
|
+
response = await fetch(`${baseUrl}${loginPath}`, {
|
|
242
279
|
method: 'POST',
|
|
243
280
|
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
244
281
|
body: JSON.stringify({ email, password }),
|
|
@@ -367,6 +404,7 @@ export function apply(ctx, config = {}) {
|
|
|
367
404
|
return {
|
|
368
405
|
baseUrl: state.settings.baseUrl,
|
|
369
406
|
email: state.settings.email,
|
|
407
|
+
authType: state.settings.authType,
|
|
370
408
|
hasPassword: state.settings.password !== '',
|
|
371
409
|
loggedIn: status.loggedIn,
|
|
372
410
|
user: status.user ? { username: status.user.username ?? '', email: status.user.email ?? '' } : null,
|
|
@@ -510,7 +548,8 @@ export function apply(ctx, config = {}) {
|
|
|
510
548
|
)
|
|
511
549
|
}
|
|
512
550
|
|
|
513
|
-
const
|
|
551
|
+
const authType = normalizeAuthType((body && body.authType) ?? current.authType ?? stored.authType ?? 'normal')
|
|
552
|
+
const next = { baseUrl, email, password, authType, timeoutMs: current.timeoutMs }
|
|
514
553
|
const probe = createYapiClient(next, ctx.logger)
|
|
515
554
|
try {
|
|
516
555
|
await probe.ensureSession(true)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-yapi",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
4
4
|
"description": "YAPI 接口文档面板 for DeepSeek Harness:搜索、多标签、字段树、勾选复制,附带 yapi-doc skill 与零依赖 CLI。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"skills"
|
|
47
47
|
],
|
|
48
48
|
"bin": {
|
|
49
|
-
"yapi-doc": "bin/yapi-doc.mjs"
|
|
49
|
+
"yapi-doc": "bin/yapi-doc.mjs",
|
|
50
|
+
"yapi-sync": "bin/yapi-sync.mjs"
|
|
50
51
|
},
|
|
51
52
|
"license": "MIT",
|
|
52
53
|
"author": "jinshan3690",
|
package/skills/yapi-doc/SKILL.md
CHANGED
|
@@ -29,9 +29,11 @@ Windows 下把 `$HOME` 换成 `%USERPROFILE%`,路径为
|
|
|
29
29
|
DSH 用户由插件自动注册,不需要执行本节。
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
npm install -g
|
|
33
|
-
yapi-
|
|
34
|
-
yapi-
|
|
32
|
+
npm install -g dsh-plugin-yapi
|
|
33
|
+
yapi-sync # yapi-doc 和 yapi-sync 两个 skill 一起同步
|
|
34
|
+
yapi-sync --dry-run # 只显示会写到哪些目录
|
|
35
|
+
yapi-doc sync-skills # yapi-sync 的别名
|
|
36
|
+
yapi-doc install-skill # 只装 yapi-doc,默认 ~/.agents/skills/yapi-doc
|
|
35
37
|
yapi-doc install-skill --dir ~/.trae/skills/yapi-doc # Trae
|
|
36
38
|
yapi-doc install-skill --dir ~/.workbuddy/skills/yapi-doc # WorkBuddy
|
|
37
39
|
yapi-doc install-skill --dir <目录> # 其他 agent 的 skills 目录
|
|
@@ -57,8 +59,8 @@ yapi-doc install-skill --dir <目录> # 其他 agent 的 skills 目录
|
|
|
57
59
|
|
|
58
60
|
## 账号
|
|
59
61
|
|
|
60
|
-
CLI 读取 `~/.dsh/dsh-plugin-yapi.json`(YAPI 面板 →
|
|
61
|
-
`YAPI_BASE_URL` / `YAPI_EMAIL` / `YAPI_PASSWORD` 环境变量。不需要额外配置。
|
|
62
|
+
CLI 读取 `~/.dsh/dsh-plugin-yapi.json`(YAPI 面板 → 账号设置维护,`authType` 为 `normal` 或 `ldap`),也支持
|
|
63
|
+
`YAPI_BASE_URL` / `YAPI_EMAIL` / `YAPI_PASSWORD` / `YAPI_AUTH_TYPE` 环境变量。不需要额外配置。
|
|
62
64
|
|
|
63
65
|
## 可移植性
|
|
64
66
|
|
|
@@ -358,6 +358,16 @@ function installSkill(dirArg) {
|
|
|
358
358
|
console.log('Run: node "' + join(target, 'yapi-doc.mjs') + '" <doc-id>')
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
+
function copySyncSkillTo(root) {
|
|
362
|
+
const sourceDir = join(dirname(fileURLToPath(import.meta.url)), '..', 'yapi-sync')
|
|
363
|
+
const source = join(sourceDir, 'SKILL.md')
|
|
364
|
+
if (!existsSync(source)) return undefined
|
|
365
|
+
const directory = join(root, 'yapi-sync')
|
|
366
|
+
mkdirSync(directory, { recursive: true })
|
|
367
|
+
copyFileSync(source, join(directory, 'SKILL.md'))
|
|
368
|
+
return directory
|
|
369
|
+
}
|
|
370
|
+
|
|
361
371
|
const HOME_AGENTS = [
|
|
362
372
|
['Trae', '.trae', '.trae/skills'],
|
|
363
373
|
['WorkBuddy', '.workbuddy', '.workbuddy/skills'],
|
|
@@ -400,24 +410,25 @@ function detectedSkillTargets(extraDirs) {
|
|
|
400
410
|
}
|
|
401
411
|
}
|
|
402
412
|
for (const [label, detect, skills] of HOME_AGENTS) {
|
|
403
|
-
if (existsSync(join(home, detect))) add(label, join(home, skills
|
|
413
|
+
if (existsSync(join(home, detect))) add(label, join(home, skills))
|
|
404
414
|
}
|
|
405
|
-
add('Agent Skills(Codex / Cline / Zed / 通用)', join(home, '.agents', 'skills'
|
|
406
|
-
if (existsSync(DSH_HOME)) add('DSH', join(DSH_HOME, 'skills'
|
|
415
|
+
add('Agent Skills(Codex / Cline / Zed / 通用)', join(home, '.agents', 'skills'))
|
|
416
|
+
if (existsSync(DSH_HOME)) add('DSH', join(DSH_HOME, 'skills'))
|
|
407
417
|
for (const dir of extraDirs) add('custom', dir)
|
|
408
418
|
return targets
|
|
409
419
|
}
|
|
410
420
|
|
|
411
421
|
function syncSkills(extraDirs, dryRun) {
|
|
412
422
|
const targets = detectedSkillTargets(extraDirs)
|
|
413
|
-
console.log('yapi-doc skill 同步:')
|
|
423
|
+
console.log('yapi-doc / yapi-sync skill 同步:')
|
|
414
424
|
for (const item of targets) {
|
|
415
425
|
if (dryRun) {
|
|
416
426
|
console.log(' - ' + item.label + ' ' + item.dir)
|
|
417
427
|
continue
|
|
418
428
|
}
|
|
419
|
-
|
|
420
|
-
|
|
429
|
+
copySkillTo(join(item.dir, 'yapi-doc'))
|
|
430
|
+
copySyncSkillTo(item.dir)
|
|
431
|
+
console.log(' ✓ ' + item.label + ' ' + item.dir)
|
|
421
432
|
}
|
|
422
433
|
if (!dryRun) {
|
|
423
434
|
console.log('')
|
|
@@ -442,6 +453,7 @@ function readSettings() {
|
|
|
442
453
|
baseUrl: normalizeBaseUrl(env.YAPI_BASE_URL || stored.baseUrl),
|
|
443
454
|
email: String(env.YAPI_EMAIL || stored.email || '').trim(),
|
|
444
455
|
password: String(env.YAPI_PASSWORD || stored.password || ''),
|
|
456
|
+
authType: String(env.YAPI_AUTH_TYPE || stored.authType || 'normal').trim().toLowerCase() === 'ldap' ? 'ldap' : 'normal',
|
|
445
457
|
timeoutMs: Number.isFinite(timeout) && timeout >= 1000 ? Math.min(timeout, 120000) : 20000,
|
|
446
458
|
}
|
|
447
459
|
}
|
|
@@ -463,7 +475,8 @@ async function createSession(settings) {
|
|
|
463
475
|
async function login() {
|
|
464
476
|
let response
|
|
465
477
|
try {
|
|
466
|
-
|
|
478
|
+
const loginPath = settings.authType === 'ldap' ? '/api/user/login_by_ldap' : '/api/user/login'
|
|
479
|
+
response = await fetch(settings.baseUrl + loginPath, {
|
|
467
480
|
method: 'POST',
|
|
468
481
|
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
469
482
|
body: JSON.stringify({ email: settings.email, password: settings.password }),
|
|
@@ -659,7 +672,8 @@ function printHelp() {
|
|
|
659
672
|
|
|
660
673
|
账号:
|
|
661
674
|
读取 ${CONFIG_FILE}
|
|
662
|
-
或环境变量 YAPI_BASE_URL / YAPI_EMAIL / YAPI_PASSWORD
|
|
675
|
+
或环境变量 YAPI_BASE_URL / YAPI_EMAIL / YAPI_PASSWORD / YAPI_AUTH_TYPE
|
|
676
|
+
(YAPI_AUTH_TYPE=ldap 使用 LDAP 登录,默认普通登录)
|
|
663
677
|
|
|
664
678
|
示例:
|
|
665
679
|
yapi-doc 10001
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: yapi-sync
|
|
3
|
+
description: Sync the yapi-doc and yapi-sync skills into every detected AI tool (Trae, WorkBuddy, Qoder CN, GitHub Copilot, CodeBuddy, Codex/Agent Skills, DSH, Claude Code, Cursor, Gemini CLI, Amp, OpenCode and more). Use when the user asks to install, update, or sync the YAPI skill across tools.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# YAPI Skill 同步
|
|
7
|
+
|
|
8
|
+
把 `yapi-doc` 和 `yapi-sync` 同步到本机已安装的 AI 工具。
|
|
9
|
+
|
|
10
|
+
## 命令
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
yapi-sync
|
|
14
|
+
yapi-sync --dry-run
|
|
15
|
+
yapi-sync --dir <skills根目录>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- 自动检测 Trae、WorkBuddy、Qoder CN、GitHub Copilot、CodeBuddy、Codex/Cline/Zed/通用 Agent Skills、DSH 等。
|
|
19
|
+
- 如果 `yapi-sync` 不在 PATH,用 `yapi-doc sync-skills`。
|
|
20
|
+
- 同步完完全重启对应工具,技能列表里会出现 `yapi-doc` 和 `yapi-sync`。
|