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 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-doc install-skill
70
+ yapi-sync
70
71
  yapi-doc <doc-id>
71
72
  ```
72
73
 
73
- - `install-skill` 默认把 `SKILL.md` 和 CLI 装到 `~/.agents/skills/yapi-doc`;支持 `SKILL.md` 的 agent 可直接读取。
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 sync-skills
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 安装:插件已自动注册 skill,CLI 在
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
 
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ /** Top-level alias for `yapi-doc sync-skills`. */
3
+ process.argv.splice(2, 0, 'sync-skills')
4
+ await import('../skills/yapi-doc/yapi-doc.mjs')
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
- '点击「测试并保存」后,插件会先用这组账号真实登录 YAPI;成功后才写入 ' +
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 BUNDLED_SKILL_DESCRIPTION =
97
- '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.'
98
- const BUNDLED_SKILL_WHEN =
99
- '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.'
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 the packaged `yapi-doc` skill when the plugin was installed by
103
- * `dsh plugin add` (no `~/.dsh/skills` copy present). The installer path still
104
- * wins because the user skill root ranks above this bundled provider.
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 skillDir = join(HERE, 'skills', 'yapi-doc')
109
- const skillPath = join(skillDir, 'SKILL.md')
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: BUNDLED_SKILL_NAME,
121
- description: BUNDLED_SKILL_DESCRIPTION,
122
- whenToUse: BUNDLED_SKILL_WHEN,
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
- if (!candidate || candidate.name !== BUNDLED_SKILL_NAME) return undefined
132
- const raw = await readFile(skillPath, 'utf8')
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(skillDir)
163
+ const content = body.split('$HOME/.dsh/skills/yapi-doc').join(join(HERE, 'skills', 'yapi-doc'))
135
164
  return {
136
- name: BUNDLED_SKILL_NAME,
137
- description: BUNDLED_SKILL_DESCRIPTION,
138
- whenToUse: BUNDLED_SKILL_WHEN,
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
- response = await fetch(`${baseUrl}/api/user/login`, {
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 next = { baseUrl, email, password, timeoutMs: current.timeoutMs }
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.0",
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",
@@ -29,9 +29,11 @@ Windows 下把 `$HOME` 换成 `%USERPROFILE%`,路径为
29
29
  DSH 用户由插件自动注册,不需要执行本节。
30
30
 
31
31
  ```bash
32
- npm install -g github:jinshan3690/dsh-plugin-yapi
33
- yapi-doc sync-skills # 自动同步到检测到的 Trae / WorkBuddy / Qoder CN / Copilot / CodeBuddy / Agent Skills / DSH
34
- yapi-doc install-skill # 默认装到 ~/.agents/skills/yapi-doc
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, 'yapi-doc'))
413
+ if (existsSync(join(home, detect))) add(label, join(home, skills))
404
414
  }
405
- add('Agent Skills(Codex / Cline / Zed / 通用)', join(home, '.agents', 'skills', 'yapi-doc'))
406
- if (existsSync(DSH_HOME)) add('DSH', join(DSH_HOME, 'skills', 'yapi-doc'))
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
- const target = copySkillTo(item.dir)
420
- console.log(' ✓ ' + item.label + ' ' + target)
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
- response = await fetch(settings.baseUrl + '/api/user/login', {
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`。