@leeguoo/yapi-mcp 0.2.4 → 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/README.md +38 -13
- package/dist/cli.js +24 -9
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +48 -4
- package/dist/config.js.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +5 -0
- package/dist/server.js +262 -221
- package/dist/server.js.map +1 -1
- package/dist/services/yapi/api.d.ts +9 -1
- package/dist/services/yapi/api.js +55 -13
- package/dist/services/yapi/api.js.map +1 -1
- package/dist/services/yapi/auth.d.ts +13 -1
- package/dist/services/yapi/auth.js +42 -6
- package/dist/services/yapi/auth.js.map +1 -1
- package/dist/services/yapi/authCache.js +49 -4
- package/dist/services/yapi/authCache.js.map +1 -1
- package/dist/services/yapi/cache.d.ts +4 -1
- package/dist/services/yapi/cache.js +104 -15
- package/dist/services/yapi/cache.js.map +1 -1
- package/dist/skill/install.d.ts +1 -0
- package/dist/skill/install.js +724 -0
- package/dist/skill/install.js.map +1 -0
- package/dist/yapi-cli.d.ts +2 -0
- package/dist/yapi-cli.js +592 -0
- package/dist/yapi-cli.js.map +1 -0
- package/package.json +6 -5
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runInstallSkill = runInstallSkill;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const readline_1 = __importDefault(require("readline"));
|
|
11
|
+
const yargs_1 = __importDefault(require("yargs"));
|
|
12
|
+
const SKILL_NAME = "yapi";
|
|
13
|
+
const PLACEHOLDER_EMAIL = "YOUR_EMAIL";
|
|
14
|
+
const PLACEHOLDER_PASSWORD = "YOUR_PASSWORD";
|
|
15
|
+
const SKILL_MD = [
|
|
16
|
+
"---",
|
|
17
|
+
"name: yapi",
|
|
18
|
+
'description: Query and summarize YApi interface documentation. Use when user mentions \"yapi 接口文档\", YAPI docs, or asks for request/response details from YApi.',
|
|
19
|
+
"---",
|
|
20
|
+
"",
|
|
21
|
+
"# YApi interface docs",
|
|
22
|
+
"",
|
|
23
|
+
"## Workflow",
|
|
24
|
+
"1. Load config from `~/.yapi/config.toml` (preferred) or `config.toml` in this skill directory (base_url, auth_mode, email/password or token, optional project_id).",
|
|
25
|
+
"2. Identify the target interface by id or keyword; ask for project/category ids if needed.",
|
|
26
|
+
"3. Call YApi endpoints with `scripts/yapi_request.js` to fetch raw JSON.",
|
|
27
|
+
"4. Summarize method, path, headers, query/body schema, response schema, and examples.",
|
|
28
|
+
"",
|
|
29
|
+
"## Script",
|
|
30
|
+
"- Basic: `node <skill-dir>/scripts/yapi_request.js --path /api/interface/get --query id=123`",
|
|
31
|
+
"- The script reads `~/.yapi/config.toml` first, then `<skill-dir>/config.toml`; pass `--config` for a custom path.",
|
|
32
|
+
"- Use `--base-url`, `--token`, `--project-id` to override config.",
|
|
33
|
+
"- For `auth_mode=global`, the script logs in via `/api/user/login` and uses the returned cookie automatically.",
|
|
34
|
+
"- For POST: `--method POST --data '{\"key\":\"value\"}'`",
|
|
35
|
+
"- If your instance differs, pass a full URL with `--url`.",
|
|
36
|
+
"",
|
|
37
|
+
].join("\n");
|
|
38
|
+
const REQUEST_SCRIPT = [
|
|
39
|
+
"#!/usr/bin/env node",
|
|
40
|
+
"\"use strict\";",
|
|
41
|
+
"",
|
|
42
|
+
"const fs = require(\"fs\");",
|
|
43
|
+
"const os = require(\"os\");",
|
|
44
|
+
"const path = require(\"path\");",
|
|
45
|
+
"const readline = require(\"readline\");",
|
|
46
|
+
"",
|
|
47
|
+
"function parseKeyValue(raw) {",
|
|
48
|
+
" if (!raw || !raw.includes(\"=\")) throw new Error(\"expected key=value\");",
|
|
49
|
+
" const idx = raw.indexOf(\"=\");",
|
|
50
|
+
" return [raw.slice(0, idx), raw.slice(idx + 1)];",
|
|
51
|
+
"}",
|
|
52
|
+
"",
|
|
53
|
+
"function parseHeader(raw) {",
|
|
54
|
+
" if (!raw || !raw.includes(\":\")) throw new Error(\"expected Header:Value\");",
|
|
55
|
+
" const idx = raw.indexOf(\":\");",
|
|
56
|
+
" return [raw.slice(0, idx).trim(), raw.slice(idx + 1).trim()];",
|
|
57
|
+
"}",
|
|
58
|
+
"",
|
|
59
|
+
"function joinUrl(baseUrl, endpoint) {",
|
|
60
|
+
" if (!baseUrl) return endpoint;",
|
|
61
|
+
" if (baseUrl.endsWith(\"/\") && endpoint.startsWith(\"/\")) return baseUrl.slice(0, -1) + endpoint;",
|
|
62
|
+
" if (!baseUrl.endsWith(\"/\") && !endpoint.startsWith(\"/\")) return baseUrl + \"/\" + endpoint;",
|
|
63
|
+
" return baseUrl + endpoint;",
|
|
64
|
+
"}",
|
|
65
|
+
"",
|
|
66
|
+
"function globalConfigPath() {",
|
|
67
|
+
" const yapiHome = process.env.YAPI_HOME || path.join(os.homedir(), \".yapi\");",
|
|
68
|
+
" return path.join(yapiHome, \"config.toml\");",
|
|
69
|
+
"}",
|
|
70
|
+
"",
|
|
71
|
+
"function localConfigPath() {",
|
|
72
|
+
" return path.resolve(__dirname, \"..\", \"config.toml\");",
|
|
73
|
+
"}",
|
|
74
|
+
"",
|
|
75
|
+
"function ensureDir(dirPath) {",
|
|
76
|
+
" try {",
|
|
77
|
+
" fs.mkdirSync(dirPath, { recursive: true });",
|
|
78
|
+
" } catch {",
|
|
79
|
+
" // ignore",
|
|
80
|
+
" }",
|
|
81
|
+
"}",
|
|
82
|
+
"",
|
|
83
|
+
"function escapeTomlValue(value) {",
|
|
84
|
+
" return String(value || \"\").replace(/\\\\/g, \"\\\\\\\\\").replace(/\\\"/g, \"\\\\\\\"\");",
|
|
85
|
+
"}",
|
|
86
|
+
"",
|
|
87
|
+
"function formatToml(config) {",
|
|
88
|
+
" const orderedKeys = [\"base_url\", \"auth_mode\", \"email\", \"password\", \"token\", \"project_id\"];",
|
|
89
|
+
" const lines = [\"# YApi CLI config\"];",
|
|
90
|
+
" for (const key of orderedKeys) {",
|
|
91
|
+
" const value = config[key] || \"\";",
|
|
92
|
+
" lines.push(`${key} = \\\"${escapeTomlValue(value)}\\\"`);",
|
|
93
|
+
" }",
|
|
94
|
+
" return lines.join(\"\\n\") + \"\\n\";",
|
|
95
|
+
"}",
|
|
96
|
+
"",
|
|
97
|
+
"function writeConfig(filePath, config) {",
|
|
98
|
+
" ensureDir(path.dirname(filePath));",
|
|
99
|
+
" fs.writeFileSync(filePath, formatToml(config), \"utf8\");",
|
|
100
|
+
"}",
|
|
101
|
+
"",
|
|
102
|
+
"function promptText(question) {",
|
|
103
|
+
" const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });",
|
|
104
|
+
" return new Promise((resolve) => {",
|
|
105
|
+
" rl.question(question, (answer) => {",
|
|
106
|
+
" rl.close();",
|
|
107
|
+
" resolve(answer);",
|
|
108
|
+
" });",
|
|
109
|
+
" });",
|
|
110
|
+
"}",
|
|
111
|
+
"",
|
|
112
|
+
"function promptHidden(question) {",
|
|
113
|
+
" const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });",
|
|
114
|
+
" const originalWrite = rl._writeToOutput;",
|
|
115
|
+
" rl._writeToOutput = function writeToOutput(stringToWrite) {",
|
|
116
|
+
" if (rl.stdoutMuted) return;",
|
|
117
|
+
" if (typeof originalWrite === \"function\") {",
|
|
118
|
+
" originalWrite.call(this, stringToWrite);",
|
|
119
|
+
" } else {",
|
|
120
|
+
" rl.output.write(stringToWrite);",
|
|
121
|
+
" }",
|
|
122
|
+
" };",
|
|
123
|
+
" rl.stdoutMuted = true;",
|
|
124
|
+
" return new Promise((resolve) => {",
|
|
125
|
+
" rl.question(question, (answer) => {",
|
|
126
|
+
" rl.stdoutMuted = false;",
|
|
127
|
+
" rl.close();",
|
|
128
|
+
" resolve(answer);",
|
|
129
|
+
" });",
|
|
130
|
+
" });",
|
|
131
|
+
"}",
|
|
132
|
+
"",
|
|
133
|
+
"async function promptRequired(question, hidden) {",
|
|
134
|
+
" while (true) {",
|
|
135
|
+
" const answer = hidden ? await promptHidden(question) : await promptText(question);",
|
|
136
|
+
" const trimmed = String(answer || \"\").trim();",
|
|
137
|
+
" if (trimmed) return trimmed;",
|
|
138
|
+
" }",
|
|
139
|
+
"}",
|
|
140
|
+
"",
|
|
141
|
+
"async function initConfigIfMissing(options) {",
|
|
142
|
+
" const hasBaseUrl = Boolean(options.baseUrl);",
|
|
143
|
+
" const hasEmail = Boolean(options.email);",
|
|
144
|
+
" const hasPassword = Boolean(options.password);",
|
|
145
|
+
" if (!hasBaseUrl || !hasEmail || !hasPassword) {",
|
|
146
|
+
" if (!process.stdin.isTTY || !process.stdout.isTTY) return null;",
|
|
147
|
+
" }",
|
|
148
|
+
" const baseUrl = hasBaseUrl ? options.baseUrl : await promptRequired(\"YApi base URL: \", false);",
|
|
149
|
+
" const email = hasEmail ? options.email : await promptRequired(\"YApi email: \", false);",
|
|
150
|
+
" const password = hasPassword ? options.password : await promptRequired(\"YApi password: \", true);",
|
|
151
|
+
" const config = {",
|
|
152
|
+
" base_url: baseUrl,",
|
|
153
|
+
" auth_mode: \"global\",",
|
|
154
|
+
" email,",
|
|
155
|
+
" password,",
|
|
156
|
+
" token: options.token || \"\",",
|
|
157
|
+
" project_id: options.projectId || \"\",",
|
|
158
|
+
" };",
|
|
159
|
+
" const configPath = globalConfigPath();",
|
|
160
|
+
" writeConfig(configPath, config);",
|
|
161
|
+
" return { configPath, config };",
|
|
162
|
+
"}",
|
|
163
|
+
"",
|
|
164
|
+
"function parseSimpleToml(text) {",
|
|
165
|
+
" const data = {};",
|
|
166
|
+
" const lines = String(text || \"\").split(/\\r?\\n/);",
|
|
167
|
+
" for (const rawLine of lines) {",
|
|
168
|
+
" const line = rawLine.split(\"#\", 1)[0].split(\";\", 1)[0].trim();",
|
|
169
|
+
" if (!line || line.startsWith(\"[\")) continue;",
|
|
170
|
+
" const idx = line.indexOf(\"=\");",
|
|
171
|
+
" if (idx === -1) continue;",
|
|
172
|
+
" const key = line.slice(0, idx).trim();",
|
|
173
|
+
" let value = line.slice(idx + 1).trim();",
|
|
174
|
+
" if (!key) continue;",
|
|
175
|
+
" if ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {",
|
|
176
|
+
" value = value.slice(1, -1);",
|
|
177
|
+
" }",
|
|
178
|
+
" data[key] = value;",
|
|
179
|
+
" }",
|
|
180
|
+
" return data;",
|
|
181
|
+
"}",
|
|
182
|
+
"",
|
|
183
|
+
"function resolveToken(tokenValue, projectId) {",
|
|
184
|
+
" if (!tokenValue) return \"\";",
|
|
185
|
+
" if (tokenValue.includes(\",\") || tokenValue.includes(\":\")) {",
|
|
186
|
+
" let defaultToken = \"\";",
|
|
187
|
+
" const mapping = {};",
|
|
188
|
+
" tokenValue.split(\",\").forEach((rawPair) => {",
|
|
189
|
+
" const pair = rawPair.trim();",
|
|
190
|
+
" if (!pair) return;",
|
|
191
|
+
" const idx = pair.indexOf(\":\");",
|
|
192
|
+
" if (idx === -1) {",
|
|
193
|
+
" defaultToken = pair;",
|
|
194
|
+
" return;",
|
|
195
|
+
" }",
|
|
196
|
+
" const pid = pair.slice(0, idx).trim();",
|
|
197
|
+
" const token = pair.slice(idx + 1).trim();",
|
|
198
|
+
" if (pid && token) mapping[pid] = token;",
|
|
199
|
+
" });",
|
|
200
|
+
" if (projectId && mapping[projectId]) return mapping[projectId];",
|
|
201
|
+
" if (defaultToken) return defaultToken;",
|
|
202
|
+
" const keys = Object.keys(mapping);",
|
|
203
|
+
" if (keys.length) return mapping[keys[0]];",
|
|
204
|
+
" }",
|
|
205
|
+
" return tokenValue;",
|
|
206
|
+
"}",
|
|
207
|
+
"",
|
|
208
|
+
"function getSetCookie(headers) {",
|
|
209
|
+
" if (!headers) return [];",
|
|
210
|
+
" if (typeof headers.getSetCookie === \"function\") return headers.getSetCookie();",
|
|
211
|
+
" const value = headers.get(\"set-cookie\");",
|
|
212
|
+
" return value ? [value] : [];",
|
|
213
|
+
"}",
|
|
214
|
+
"",
|
|
215
|
+
"function extractCookieValue(setCookies, name) {",
|
|
216
|
+
" if (!setCookies || !setCookies.length) return \"\";",
|
|
217
|
+
" for (const entry of setCookies) {",
|
|
218
|
+
" const parts = String(entry || \"\").split(\";\");",
|
|
219
|
+
" for (const part of parts) {",
|
|
220
|
+
" const item = part.trim();",
|
|
221
|
+
" if (item.startsWith(name + \"=\")) return item.split(\"=\").slice(1).join(\"=\");",
|
|
222
|
+
" }",
|
|
223
|
+
" }",
|
|
224
|
+
" return \"\";",
|
|
225
|
+
"}",
|
|
226
|
+
"",
|
|
227
|
+
"async function fetchWithTimeout(url, options, timeoutMs) {",
|
|
228
|
+
" const controller = new AbortController();",
|
|
229
|
+
" const timer = setTimeout(() => controller.abort(), timeoutMs);",
|
|
230
|
+
" try {",
|
|
231
|
+
" return await fetch(url, { ...options, signal: controller.signal });",
|
|
232
|
+
" } finally {",
|
|
233
|
+
" clearTimeout(timer);",
|
|
234
|
+
" }",
|
|
235
|
+
"}",
|
|
236
|
+
"",
|
|
237
|
+
"async function loginGetCookie(baseUrl, email, password, timeoutMs) {",
|
|
238
|
+
" const url = joinUrl(baseUrl, \"/api/user/login\");",
|
|
239
|
+
" const payload = JSON.stringify({ email, password });",
|
|
240
|
+
" const response = await fetchWithTimeout(url, {",
|
|
241
|
+
" method: \"POST\",",
|
|
242
|
+
" headers: { \"Content-Type\": \"application/json;charset=UTF-8\" },",
|
|
243
|
+
" body: payload,",
|
|
244
|
+
" }, timeoutMs);",
|
|
245
|
+
" const bodyText = await response.text();",
|
|
246
|
+
" const setCookies = getSetCookie(response.headers);",
|
|
247
|
+
" const yapiToken = extractCookieValue(setCookies, \"_yapi_token\");",
|
|
248
|
+
" const yapiUid = extractCookieValue(setCookies, \"_yapi_uid\");",
|
|
249
|
+
" if (!yapiToken) {",
|
|
250
|
+
" let message = \"login failed: missing _yapi_token cookie\";",
|
|
251
|
+
" try {",
|
|
252
|
+
" const payload = JSON.parse(bodyText);",
|
|
253
|
+
" if (payload && typeof payload === \"object\" && payload.errmsg) message = payload.errmsg;",
|
|
254
|
+
" } catch {",
|
|
255
|
+
" // ignore",
|
|
256
|
+
" }",
|
|
257
|
+
" throw new Error(message);",
|
|
258
|
+
" }",
|
|
259
|
+
" let cookie = `_yapi_token=${yapiToken}`;",
|
|
260
|
+
" if (yapiUid) cookie = `${cookie}; _yapi_uid=${yapiUid}`;",
|
|
261
|
+
" return cookie;",
|
|
262
|
+
"}",
|
|
263
|
+
"",
|
|
264
|
+
"function buildUrl(baseUrl, endpoint, queryItems, token, tokenParam) {",
|
|
265
|
+
" const url = baseUrl ? joinUrl(baseUrl, endpoint) : endpoint;",
|
|
266
|
+
" const parsed = new URL(url);",
|
|
267
|
+
" if (Array.isArray(queryItems)) {",
|
|
268
|
+
" for (const [key, value] of queryItems) {",
|
|
269
|
+
" if (key) parsed.searchParams.append(key, value ?? \"\");",
|
|
270
|
+
" }",
|
|
271
|
+
" }",
|
|
272
|
+
" if (token) {",
|
|
273
|
+
" if (!parsed.searchParams.has(tokenParam)) {",
|
|
274
|
+
" parsed.searchParams.append(tokenParam, token);",
|
|
275
|
+
" }",
|
|
276
|
+
" }",
|
|
277
|
+
" return parsed.toString();",
|
|
278
|
+
"}",
|
|
279
|
+
"",
|
|
280
|
+
"function parseArgs(argv) {",
|
|
281
|
+
" const options = {",
|
|
282
|
+
" query: [],",
|
|
283
|
+
" header: [],",
|
|
284
|
+
" method: \"GET\",",
|
|
285
|
+
" tokenParam: \"token\",",
|
|
286
|
+
" timeout: 30000,",
|
|
287
|
+
" };",
|
|
288
|
+
" for (let i = 0; i < argv.length; i += 1) {",
|
|
289
|
+
" const arg = argv[i];",
|
|
290
|
+
" if (!arg) continue;",
|
|
291
|
+
" if (arg === \"--config\") { options.config = argv[++i]; continue; }",
|
|
292
|
+
" if (arg.startsWith(\"--config=\")) { options.config = arg.slice(9); continue; }",
|
|
293
|
+
" if (arg === \"--base-url\") { options.baseUrl = argv[++i]; continue; }",
|
|
294
|
+
" if (arg.startsWith(\"--base-url=\")) { options.baseUrl = arg.slice(11); continue; }",
|
|
295
|
+
" if (arg === \"--token\") { options.token = argv[++i]; continue; }",
|
|
296
|
+
" if (arg.startsWith(\"--token=\")) { options.token = arg.slice(8); continue; }",
|
|
297
|
+
" if (arg === \"--project-id\") { options.projectId = argv[++i]; continue; }",
|
|
298
|
+
" if (arg.startsWith(\"--project-id=\")) { options.projectId = arg.slice(13); continue; }",
|
|
299
|
+
" if (arg === \"--auth-mode\") { options.authMode = argv[++i]; continue; }",
|
|
300
|
+
" if (arg.startsWith(\"--auth-mode=\")) { options.authMode = arg.slice(12); continue; }",
|
|
301
|
+
" if (arg === \"--email\") { options.email = argv[++i]; continue; }",
|
|
302
|
+
" if (arg.startsWith(\"--email=\")) { options.email = arg.slice(8); continue; }",
|
|
303
|
+
" if (arg === \"--password\") { options.password = argv[++i]; continue; }",
|
|
304
|
+
" if (arg.startsWith(\"--password=\")) { options.password = arg.slice(11); continue; }",
|
|
305
|
+
" if (arg === \"--cookie\") { options.cookie = argv[++i]; continue; }",
|
|
306
|
+
" if (arg.startsWith(\"--cookie=\")) { options.cookie = arg.slice(9); continue; }",
|
|
307
|
+
" if (arg === \"--token-param\") { options.tokenParam = argv[++i]; continue; }",
|
|
308
|
+
" if (arg.startsWith(\"--token-param=\")) { options.tokenParam = arg.slice(14); continue; }",
|
|
309
|
+
" if (arg === \"--method\") { options.method = argv[++i]; continue; }",
|
|
310
|
+
" if (arg.startsWith(\"--method=\")) { options.method = arg.slice(9); continue; }",
|
|
311
|
+
" if (arg === \"--path\") { options.path = argv[++i]; continue; }",
|
|
312
|
+
" if (arg.startsWith(\"--path=\")) { options.path = arg.slice(7); continue; }",
|
|
313
|
+
" if (arg === \"--url\") { options.url = argv[++i]; continue; }",
|
|
314
|
+
" if (arg.startsWith(\"--url=\")) { options.url = arg.slice(6); continue; }",
|
|
315
|
+
" if (arg === \"--query\") { options.query.push(argv[++i]); continue; }",
|
|
316
|
+
" if (arg.startsWith(\"--query=\")) { options.query.push(arg.slice(8)); continue; }",
|
|
317
|
+
" if (arg === \"--header\") { options.header.push(argv[++i]); continue; }",
|
|
318
|
+
" if (arg.startsWith(\"--header=\")) { options.header.push(arg.slice(9)); continue; }",
|
|
319
|
+
" if (arg === \"--data\") { options.data = argv[++i]; continue; }",
|
|
320
|
+
" if (arg.startsWith(\"--data=\")) { options.data = arg.slice(7); continue; }",
|
|
321
|
+
" if (arg === \"--data-file\") { options.dataFile = argv[++i]; continue; }",
|
|
322
|
+
" if (arg.startsWith(\"--data-file=\")) { options.dataFile = arg.slice(12); continue; }",
|
|
323
|
+
" if (arg === \"--timeout\") { options.timeout = Number(argv[++i]); continue; }",
|
|
324
|
+
" if (arg.startsWith(\"--timeout=\")) { options.timeout = Number(arg.slice(10)); continue; }",
|
|
325
|
+
" if (arg === \"--no-pretty\") { options.noPretty = true; continue; }",
|
|
326
|
+
" if (arg === \"--help\" || arg === \"-h\") { options.help = true; continue; }",
|
|
327
|
+
" }",
|
|
328
|
+
" return options;",
|
|
329
|
+
"}",
|
|
330
|
+
"",
|
|
331
|
+
"function usage() {",
|
|
332
|
+
" return [",
|
|
333
|
+
" \"Usage:\",",
|
|
334
|
+
" \" node yapi_request.js --path /api/interface/get --query id=123\",",
|
|
335
|
+
" \"Options:\",",
|
|
336
|
+
" \" --config <path> config file path (default: ~/.yapi/config.toml)\",",
|
|
337
|
+
" \" --base-url <url> YApi base URL\",",
|
|
338
|
+
" \" --token <token> project token (supports projectId:token)\",",
|
|
339
|
+
" \" --project-id <id> select token for project\",",
|
|
340
|
+
" \" --auth-mode <mode> token or global\",",
|
|
341
|
+
" \" --email <email> login email for global mode\",",
|
|
342
|
+
" \" --password <pwd> login password for global mode\",",
|
|
343
|
+
" \" --path <path> API path (e.g., /api/interface/get)\",",
|
|
344
|
+
" \" --url <url> full URL (overrides base-url/path)\",",
|
|
345
|
+
" \" --query key=value query param (repeatable)\",",
|
|
346
|
+
" \" --header Header:Value request header (repeatable)\",",
|
|
347
|
+
" \" --method <method> HTTP method\",",
|
|
348
|
+
" \" --data <payload> request body (JSON or text)\",",
|
|
349
|
+
" \" --data-file <file> request body file\",",
|
|
350
|
+
" \" --timeout <ms> request timeout in ms\",",
|
|
351
|
+
" \" --no-pretty print raw response\",",
|
|
352
|
+
" ].join(\"\\n\");",
|
|
353
|
+
"}",
|
|
354
|
+
"",
|
|
355
|
+
"async function main() {",
|
|
356
|
+
" const options = parseArgs(process.argv.slice(2));",
|
|
357
|
+
" if (options.help) {",
|
|
358
|
+
" console.log(usage());",
|
|
359
|
+
" return 0;",
|
|
360
|
+
" }",
|
|
361
|
+
"",
|
|
362
|
+
" if (options.url && options.path) {",
|
|
363
|
+
" console.error(\"use --url or --path, not both\");",
|
|
364
|
+
" return 2;",
|
|
365
|
+
" }",
|
|
366
|
+
"",
|
|
367
|
+
" if (!options.url && !options.path) {",
|
|
368
|
+
" console.error(\"missing --path or --url\");",
|
|
369
|
+
" console.error(usage());",
|
|
370
|
+
" return 2;",
|
|
371
|
+
" }",
|
|
372
|
+
"",
|
|
373
|
+
" let config = {};",
|
|
374
|
+
" let configPath = options.config || \"\";",
|
|
375
|
+
" if (options.config) {",
|
|
376
|
+
" if (fs.existsSync(configPath)) {",
|
|
377
|
+
" config = parseSimpleToml(fs.readFileSync(configPath, \"utf8\"));",
|
|
378
|
+
" } else {",
|
|
379
|
+
" const init = await initConfigIfMissing(options);",
|
|
380
|
+
" if (init) {",
|
|
381
|
+
" config = init.config;",
|
|
382
|
+
" configPath = init.configPath;",
|
|
383
|
+
" } else {",
|
|
384
|
+
" console.error(`missing config file: ${configPath}`);",
|
|
385
|
+
" return 2;",
|
|
386
|
+
" }",
|
|
387
|
+
" }",
|
|
388
|
+
" } else {",
|
|
389
|
+
" const globalPath = globalConfigPath();",
|
|
390
|
+
" const localPath = localConfigPath();",
|
|
391
|
+
" if (fs.existsSync(globalPath)) {",
|
|
392
|
+
" configPath = globalPath;",
|
|
393
|
+
" config = parseSimpleToml(fs.readFileSync(globalPath, \"utf8\"));",
|
|
394
|
+
" } else if (fs.existsSync(localPath)) {",
|
|
395
|
+
" configPath = localPath;",
|
|
396
|
+
" config = parseSimpleToml(fs.readFileSync(localPath, \"utf8\"));",
|
|
397
|
+
" } else {",
|
|
398
|
+
" const init = await initConfigIfMissing(options);",
|
|
399
|
+
" if (init) {",
|
|
400
|
+
" config = init.config;",
|
|
401
|
+
" configPath = init.configPath;",
|
|
402
|
+
" } else {",
|
|
403
|
+
" console.error(\"missing config: create ~/.yapi/config.toml or pass --config\");",
|
|
404
|
+
" return 2;",
|
|
405
|
+
" }",
|
|
406
|
+
" }",
|
|
407
|
+
" }",
|
|
408
|
+
"",
|
|
409
|
+
" const baseUrl = options.url ? null : (options.baseUrl || config.base_url || \"\");",
|
|
410
|
+
" const endpoint = options.url || options.path;",
|
|
411
|
+
" if (!options.url && !baseUrl) {",
|
|
412
|
+
" console.error(\"missing --base-url or config base_url\");",
|
|
413
|
+
" return 2;",
|
|
414
|
+
" }",
|
|
415
|
+
"",
|
|
416
|
+
" const projectId = options.projectId || config.project_id || \"\";",
|
|
417
|
+
" const rawToken = options.token || config.token || \"\";",
|
|
418
|
+
" const token = resolveToken(rawToken, projectId);",
|
|
419
|
+
"",
|
|
420
|
+
" let authMode = (options.authMode || config.auth_mode || \"\").trim().toLowerCase();",
|
|
421
|
+
" if (!authMode) {",
|
|
422
|
+
" authMode = token ? \"token\" : (options.email || options.password || config.email || config.password) ? \"global\" : \"token\";",
|
|
423
|
+
" }",
|
|
424
|
+
" if (authMode !== \"token\" && authMode !== \"global\") {",
|
|
425
|
+
" console.error(\"invalid --auth-mode (use token or global)\");",
|
|
426
|
+
" return 2;",
|
|
427
|
+
" }",
|
|
428
|
+
"",
|
|
429
|
+
" const headers = {};",
|
|
430
|
+
" for (const header of options.header || []) {",
|
|
431
|
+
" const [key, value] = parseHeader(header);",
|
|
432
|
+
" headers[key] = value;",
|
|
433
|
+
" }",
|
|
434
|
+
"",
|
|
435
|
+
" if (options.cookie) {",
|
|
436
|
+
" headers.Cookie = options.cookie;",
|
|
437
|
+
" } else if (authMode === \"global\") {",
|
|
438
|
+
" const email = options.email || config.email;",
|
|
439
|
+
" const password = options.password || config.password;",
|
|
440
|
+
" if (!email || !password) {",
|
|
441
|
+
" console.error(\"missing email/password for global auth\");",
|
|
442
|
+
" return 2;",
|
|
443
|
+
" }",
|
|
444
|
+
" try {",
|
|
445
|
+
" headers.Cookie = await loginGetCookie(baseUrl, email, password, options.timeout);",
|
|
446
|
+
" } catch (error) {",
|
|
447
|
+
" console.error(error && error.message ? error.message : String(error));",
|
|
448
|
+
" return 2;",
|
|
449
|
+
" }",
|
|
450
|
+
" }",
|
|
451
|
+
"",
|
|
452
|
+
" const queryItems = [];",
|
|
453
|
+
" for (const query of options.query || []) {",
|
|
454
|
+
" queryItems.push(parseKeyValue(query));",
|
|
455
|
+
" }",
|
|
456
|
+
" const url = buildUrl(baseUrl, endpoint, queryItems, authMode === \"token\" ? token : \"\", options.tokenParam);",
|
|
457
|
+
"",
|
|
458
|
+
" let dataRaw = null;",
|
|
459
|
+
" if (options.dataFile) {",
|
|
460
|
+
" dataRaw = fs.readFileSync(options.dataFile, \"utf8\");",
|
|
461
|
+
" } else if (options.data !== undefined) {",
|
|
462
|
+
" dataRaw = options.data;",
|
|
463
|
+
" }",
|
|
464
|
+
"",
|
|
465
|
+
" let body;",
|
|
466
|
+
" if (dataRaw !== null && options.method.toUpperCase() !== \"GET\" && options.method.toUpperCase() !== \"HEAD\") {",
|
|
467
|
+
" try {",
|
|
468
|
+
" const parsed = JSON.parse(dataRaw);",
|
|
469
|
+
" body = JSON.stringify(parsed);",
|
|
470
|
+
" if (!headers[\"Content-Type\"]) headers[\"Content-Type\"] = \"application/json\";",
|
|
471
|
+
" } catch {",
|
|
472
|
+
" body = String(dataRaw);",
|
|
473
|
+
" if (!headers[\"Content-Type\"]) headers[\"Content-Type\"] = \"text/plain\";",
|
|
474
|
+
" }",
|
|
475
|
+
" }",
|
|
476
|
+
"",
|
|
477
|
+
" let response;",
|
|
478
|
+
" try {",
|
|
479
|
+
" response = await fetchWithTimeout(url, {",
|
|
480
|
+
" method: options.method.toUpperCase(),",
|
|
481
|
+
" headers,",
|
|
482
|
+
" body,",
|
|
483
|
+
" }, options.timeout);",
|
|
484
|
+
" } catch (error) {",
|
|
485
|
+
" console.error(\"request failed: \" + (error && error.message ? error.message : String(error)));",
|
|
486
|
+
" return 2;",
|
|
487
|
+
" }",
|
|
488
|
+
"",
|
|
489
|
+
" const text = await response.text();",
|
|
490
|
+
" if (options.noPretty) {",
|
|
491
|
+
" console.log(text);",
|
|
492
|
+
" return 0;",
|
|
493
|
+
" }",
|
|
494
|
+
" try {",
|
|
495
|
+
" const payload = JSON.parse(text);",
|
|
496
|
+
" console.log(JSON.stringify(payload, null, 2));",
|
|
497
|
+
" } catch {",
|
|
498
|
+
" console.log(text);",
|
|
499
|
+
" }",
|
|
500
|
+
" return 0;",
|
|
501
|
+
"}",
|
|
502
|
+
"",
|
|
503
|
+
"main().then((code) => {",
|
|
504
|
+
" process.exitCode = code;",
|
|
505
|
+
"});",
|
|
506
|
+
"",
|
|
507
|
+
].join("\n");
|
|
508
|
+
function parseSimpleToml(text) {
|
|
509
|
+
const data = {};
|
|
510
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
511
|
+
const line = rawLine.split("#", 1)[0].split(";", 1)[0].trim();
|
|
512
|
+
if (!line || line.startsWith("["))
|
|
513
|
+
continue;
|
|
514
|
+
const idx = line.indexOf("=");
|
|
515
|
+
if (idx === -1)
|
|
516
|
+
continue;
|
|
517
|
+
const key = line.slice(0, idx).trim();
|
|
518
|
+
let value = line.slice(idx + 1).trim();
|
|
519
|
+
if (!key)
|
|
520
|
+
continue;
|
|
521
|
+
if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
522
|
+
value = value.slice(1, -1);
|
|
523
|
+
}
|
|
524
|
+
data[key] = value;
|
|
525
|
+
}
|
|
526
|
+
return data;
|
|
527
|
+
}
|
|
528
|
+
function escapeTomlValue(value) {
|
|
529
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
530
|
+
}
|
|
531
|
+
function formatToml(config) {
|
|
532
|
+
const orderedKeys = ["base_url", "auth_mode", "email", "password", "token", "project_id"];
|
|
533
|
+
const lines = ["# YApi skill config"];
|
|
534
|
+
for (const key of orderedKeys) {
|
|
535
|
+
const value = config[key] ?? "";
|
|
536
|
+
lines.push(`${key} = "${escapeTomlValue(value)}"`);
|
|
537
|
+
}
|
|
538
|
+
const extras = Object.keys(config).filter((key) => !orderedKeys.includes(key)).sort();
|
|
539
|
+
for (const key of extras) {
|
|
540
|
+
const value = config[key] ?? "";
|
|
541
|
+
lines.push(`${key} = "${escapeTomlValue(value)}"`);
|
|
542
|
+
}
|
|
543
|
+
return `${lines.join("\n")}\n`;
|
|
544
|
+
}
|
|
545
|
+
function writeFileIfNeeded(filePath, contents, force) {
|
|
546
|
+
if (!force && fs_1.default.existsSync(filePath))
|
|
547
|
+
return false;
|
|
548
|
+
fs_1.default.writeFileSync(filePath, contents, "utf8");
|
|
549
|
+
return true;
|
|
550
|
+
}
|
|
551
|
+
function ensureExecutable(filePath) {
|
|
552
|
+
try {
|
|
553
|
+
fs_1.default.chmodSync(filePath, 0o755);
|
|
554
|
+
}
|
|
555
|
+
catch {
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
function ensurePrivate(filePath) {
|
|
559
|
+
try {
|
|
560
|
+
fs_1.default.chmodSync(filePath, 0o600);
|
|
561
|
+
}
|
|
562
|
+
catch {
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
async function promptHidden(question) {
|
|
566
|
+
const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
567
|
+
const originalWrite = rl._writeToOutput;
|
|
568
|
+
rl._writeToOutput = function writeToOutput(stringToWrite) {
|
|
569
|
+
if (rl.stdoutMuted)
|
|
570
|
+
return;
|
|
571
|
+
if (typeof originalWrite === "function") {
|
|
572
|
+
originalWrite.call(this, stringToWrite);
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
rl.output.write(stringToWrite);
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
rl.stdoutMuted = true;
|
|
579
|
+
return new Promise((resolve) => {
|
|
580
|
+
rl.question(question, (answer) => {
|
|
581
|
+
rl.stdoutMuted = false;
|
|
582
|
+
rl.close();
|
|
583
|
+
resolve(answer);
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
async function promptText(question) {
|
|
588
|
+
const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
589
|
+
return new Promise((resolve) => {
|
|
590
|
+
rl.question(question, (answer) => {
|
|
591
|
+
rl.close();
|
|
592
|
+
resolve(answer);
|
|
593
|
+
});
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
async function promptRequired(question, hidden) {
|
|
597
|
+
while (true) {
|
|
598
|
+
const answer = hidden ? await promptHidden(question) : await promptText(question);
|
|
599
|
+
const trimmed = String(answer || "").trim();
|
|
600
|
+
if (trimmed)
|
|
601
|
+
return trimmed;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function resolveAuthMode(config, args) {
|
|
605
|
+
const explicit = args["yapi-auth-mode"];
|
|
606
|
+
if (explicit === "token" || explicit === "global")
|
|
607
|
+
return explicit;
|
|
608
|
+
if (args["yapi-token"] || config.token)
|
|
609
|
+
return "token";
|
|
610
|
+
if (args["yapi-email"] || args["yapi-password"] || config.email || config.password)
|
|
611
|
+
return "global";
|
|
612
|
+
return "global";
|
|
613
|
+
}
|
|
614
|
+
async function runInstallSkill(rawArgs) {
|
|
615
|
+
const argv = (0, yargs_1.default)(rawArgs)
|
|
616
|
+
.options({
|
|
617
|
+
"yapi-base-url": { type: "string", describe: "YApi base URL" },
|
|
618
|
+
"yapi-token": { type: "string", describe: "YApi project token" },
|
|
619
|
+
"yapi-auth-mode": { type: "string", choices: ["token", "global"], describe: "Auth mode" },
|
|
620
|
+
"yapi-email": { type: "string", describe: "Login email for global auth" },
|
|
621
|
+
"yapi-password": { type: "string", describe: "Login password for global auth" },
|
|
622
|
+
"project-id": { type: "string", describe: "Select project token by id" },
|
|
623
|
+
"yapi-home": { type: "string", describe: "Override YApi config home (default: ~/.yapi)" },
|
|
624
|
+
"codex-home": { type: "string", describe: "Override CODEX_HOME" },
|
|
625
|
+
"claude-home": { type: "string", describe: "Override Claude home (default: ~/.claude)" },
|
|
626
|
+
force: { type: "boolean", default: false, describe: "Overwrite skill files if they already exist" },
|
|
627
|
+
})
|
|
628
|
+
.help()
|
|
629
|
+
.parseSync();
|
|
630
|
+
const yapiHome = argv["yapi-home"] || process.env.YAPI_HOME || path_1.default.join(os_1.default.homedir(), ".yapi");
|
|
631
|
+
const codexHome = argv["codex-home"] || process.env.CODEX_HOME || path_1.default.join(os_1.default.homedir(), ".codex");
|
|
632
|
+
const claudeHome = argv["claude-home"] || process.env.CLAUDE_HOME || path_1.default.join(os_1.default.homedir(), ".claude");
|
|
633
|
+
const globalConfigPath = path_1.default.join(yapiHome, "config.toml");
|
|
634
|
+
const targets = [
|
|
635
|
+
{ label: "Codex", root: path_1.default.join(codexHome, "skills", SKILL_NAME) },
|
|
636
|
+
{ label: "Claude", root: path_1.default.join(claudeHome, "skills", SKILL_NAME) },
|
|
637
|
+
];
|
|
638
|
+
const seenRoots = new Set();
|
|
639
|
+
const uniqueTargets = targets.filter((target) => {
|
|
640
|
+
if (seenRoots.has(target.root))
|
|
641
|
+
return false;
|
|
642
|
+
seenRoots.add(target.root);
|
|
643
|
+
return true;
|
|
644
|
+
});
|
|
645
|
+
let existingConfig = {};
|
|
646
|
+
if (fs_1.default.existsSync(globalConfigPath)) {
|
|
647
|
+
existingConfig = parseSimpleToml(fs_1.default.readFileSync(globalConfigPath, "utf8"));
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
for (const target of uniqueTargets) {
|
|
651
|
+
const configPath = path_1.default.join(target.root, "config.toml");
|
|
652
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
653
|
+
existingConfig = parseSimpleToml(fs_1.default.readFileSync(configPath, "utf8"));
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
const merged = { ...existingConfig };
|
|
659
|
+
if (argv["yapi-base-url"] || process.env.YAPI_BASE_URL) {
|
|
660
|
+
merged.base_url = argv["yapi-base-url"] || process.env.YAPI_BASE_URL || "";
|
|
661
|
+
}
|
|
662
|
+
if (argv["yapi-token"] || process.env.YAPI_TOKEN) {
|
|
663
|
+
merged.token = argv["yapi-token"] || process.env.YAPI_TOKEN || "";
|
|
664
|
+
}
|
|
665
|
+
if (argv["project-id"] || process.env.YAPI_PROJECT_ID) {
|
|
666
|
+
merged.project_id = argv["project-id"] || process.env.YAPI_PROJECT_ID || "";
|
|
667
|
+
}
|
|
668
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
669
|
+
if (!merged.base_url && isInteractive) {
|
|
670
|
+
merged.base_url = await promptRequired("YApi base URL: ", false);
|
|
671
|
+
}
|
|
672
|
+
if (!merged.base_url) {
|
|
673
|
+
throw new Error("Missing --yapi-base-url or YAPI_BASE_URL; cannot write config.");
|
|
674
|
+
}
|
|
675
|
+
const authMode = resolveAuthMode(merged, argv);
|
|
676
|
+
merged.auth_mode = authMode;
|
|
677
|
+
if (argv["yapi-email"] || process.env.YAPI_EMAIL) {
|
|
678
|
+
merged.email = argv["yapi-email"] || process.env.YAPI_EMAIL || "";
|
|
679
|
+
}
|
|
680
|
+
let password = argv["yapi-password"] || process.env.YAPI_PASSWORD || merged.password || "";
|
|
681
|
+
if (authMode === "global") {
|
|
682
|
+
if (!merged.email && isInteractive) {
|
|
683
|
+
merged.email = await promptRequired("YApi email: ", false);
|
|
684
|
+
}
|
|
685
|
+
if (!merged.email) {
|
|
686
|
+
merged.email = PLACEHOLDER_EMAIL;
|
|
687
|
+
}
|
|
688
|
+
if (!password && isInteractive) {
|
|
689
|
+
password = await promptRequired("YApi password: ", true);
|
|
690
|
+
}
|
|
691
|
+
if (!password) {
|
|
692
|
+
password = merged.password || PLACEHOLDER_PASSWORD;
|
|
693
|
+
console.warn("Warning: password not provided; writing placeholder to config.");
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
merged.password = password;
|
|
697
|
+
const installedRoots = [];
|
|
698
|
+
const configPaths = [];
|
|
699
|
+
fs_1.default.mkdirSync(yapiHome, { recursive: true });
|
|
700
|
+
fs_1.default.writeFileSync(globalConfigPath, formatToml(merged), "utf8");
|
|
701
|
+
ensurePrivate(globalConfigPath);
|
|
702
|
+
configPaths.push(globalConfigPath);
|
|
703
|
+
for (const target of uniqueTargets) {
|
|
704
|
+
const scriptsDir = path_1.default.join(target.root, "scripts");
|
|
705
|
+
const configPath = path_1.default.join(target.root, "config.toml");
|
|
706
|
+
const skillPath = path_1.default.join(target.root, "SKILL.md");
|
|
707
|
+
const scriptPath = path_1.default.join(scriptsDir, "yapi_request.js");
|
|
708
|
+
fs_1.default.mkdirSync(scriptsDir, { recursive: true });
|
|
709
|
+
writeFileIfNeeded(skillPath, SKILL_MD, argv.force ?? false);
|
|
710
|
+
const scriptWritten = writeFileIfNeeded(scriptPath, REQUEST_SCRIPT, argv.force ?? false);
|
|
711
|
+
if (scriptWritten)
|
|
712
|
+
ensureExecutable(scriptPath);
|
|
713
|
+
fs_1.default.writeFileSync(configPath, formatToml(merged), "utf8");
|
|
714
|
+
ensurePrivate(configPath);
|
|
715
|
+
installedRoots.push(`${target.label}: ${target.root}`);
|
|
716
|
+
configPaths.push(configPath);
|
|
717
|
+
}
|
|
718
|
+
console.log(`Installed skill '${SKILL_NAME}' at:`);
|
|
719
|
+
installedRoots.forEach((entry) => console.log(`- ${entry}`));
|
|
720
|
+
console.log("Config written to:");
|
|
721
|
+
configPaths.forEach((entry) => console.log(`- ${entry}`));
|
|
722
|
+
console.log("Restart Codex/Claude Code to pick up new skills.");
|
|
723
|
+
}
|
|
724
|
+
//# sourceMappingURL=install.js.map
|