item-wms-public-api-tool 2.0.4

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.
Files changed (110) hide show
  1. package/AI_INTEGRATION_GUIDE.md +492 -0
  2. package/HELP.md +297 -0
  3. package/README.md +259 -0
  4. package/api/README.md +9 -0
  5. package/api/openapi.apifox.yaml +20771 -0
  6. package/dist/cli.d.ts +10 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +326 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/client.d.ts +8 -0
  11. package/dist/client.d.ts.map +1 -0
  12. package/dist/client.js +76 -0
  13. package/dist/client.js.map +1 -0
  14. package/dist/config.d.ts +32 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +109 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +31 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/openapi-tools.d.ts +25 -0
  23. package/dist/openapi-tools.d.ts.map +1 -0
  24. package/dist/openapi-tools.js +287 -0
  25. package/dist/openapi-tools.js.map +1 -0
  26. package/dist/server.d.ts +5 -0
  27. package/dist/server.d.ts.map +1 -0
  28. package/dist/server.js +442 -0
  29. package/dist/server.js.map +1 -0
  30. package/dist/tools/batchImportItemMaster.d.ts +59 -0
  31. package/dist/tools/batchImportItemMaster.d.ts.map +1 -0
  32. package/dist/tools/batchImportItemMaster.js +27 -0
  33. package/dist/tools/batchImportItemMaster.js.map +1 -0
  34. package/dist/tools/batchUploadOrderFiles.d.ts +29 -0
  35. package/dist/tools/batchUploadOrderFiles.d.ts.map +1 -0
  36. package/dist/tools/batchUploadOrderFiles.js +96 -0
  37. package/dist/tools/batchUploadOrderFiles.js.map +1 -0
  38. package/dist/tools/importInbound.d.ts +62 -0
  39. package/dist/tools/importInbound.d.ts.map +1 -0
  40. package/dist/tools/importInbound.js +22 -0
  41. package/dist/tools/importInbound.js.map +1 -0
  42. package/dist/tools/importOutbound.d.ts +76 -0
  43. package/dist/tools/importOutbound.d.ts.map +1 -0
  44. package/dist/tools/importOutbound.js +25 -0
  45. package/dist/tools/importOutbound.js.map +1 -0
  46. package/dist/tools/itemGet.d.ts +15 -0
  47. package/dist/tools/itemGet.d.ts.map +1 -0
  48. package/dist/tools/itemGet.js +12 -0
  49. package/dist/tools/itemGet.js.map +1 -0
  50. package/dist/tools/itemSearch.d.ts +8 -0
  51. package/dist/tools/itemSearch.d.ts.map +1 -0
  52. package/dist/tools/itemSearch.js +17 -0
  53. package/dist/tools/itemSearch.js.map +1 -0
  54. package/dist/tools/login.d.ts +26 -0
  55. package/dist/tools/login.d.ts.map +1 -0
  56. package/dist/tools/login.js +15 -0
  57. package/dist/tools/login.js.map +1 -0
  58. package/dist/tools/queryItemMaster.d.ts +63 -0
  59. package/dist/tools/queryItemMaster.d.ts.map +1 -0
  60. package/dist/tools/queryItemMaster.js +9 -0
  61. package/dist/tools/queryItemMaster.js.map +1 -0
  62. package/dist/tools/queryOrder.d.ts +74 -0
  63. package/dist/tools/queryOrder.d.ts.map +1 -0
  64. package/dist/tools/queryOrder.js +19 -0
  65. package/dist/tools/queryOrder.js.map +1 -0
  66. package/dist/tools/queryOrderDC.d.ts +25 -0
  67. package/dist/tools/queryOrderDC.d.ts.map +1 -0
  68. package/dist/tools/queryOrderDC.js +19 -0
  69. package/dist/tools/queryOrderDC.js.map +1 -0
  70. package/dist/tools/queryOrderDetail.d.ts +70 -0
  71. package/dist/tools/queryOrderDetail.d.ts.map +1 -0
  72. package/dist/tools/queryOrderDetail.js +19 -0
  73. package/dist/tools/queryOrderDetail.js.map +1 -0
  74. package/dist/tools/queryReceipt.d.ts +70 -0
  75. package/dist/tools/queryReceipt.d.ts.map +1 -0
  76. package/dist/tools/queryReceipt.js +19 -0
  77. package/dist/tools/queryReceipt.js.map +1 -0
  78. package/dist/tools/queryReceiptDetail.d.ts +70 -0
  79. package/dist/tools/queryReceiptDetail.d.ts.map +1 -0
  80. package/dist/tools/queryReceiptDetail.js +19 -0
  81. package/dist/tools/queryReceiptDetail.js.map +1 -0
  82. package/dist/tools/queryReceiptRC.d.ts +25 -0
  83. package/dist/tools/queryReceiptRC.d.ts.map +1 -0
  84. package/dist/tools/queryReceiptRC.js +19 -0
  85. package/dist/tools/queryReceiptRC.js.map +1 -0
  86. package/dist/tools/uploadOrderFile.d.ts +25 -0
  87. package/dist/tools/uploadOrderFile.d.ts.map +1 -0
  88. package/dist/tools/uploadOrderFile.js +94 -0
  89. package/dist/tools/uploadOrderFile.js.map +1 -0
  90. package/dist/tools/validateInbound.d.ts +13 -0
  91. package/dist/tools/validateInbound.d.ts.map +1 -0
  92. package/dist/tools/validateInbound.js +18 -0
  93. package/dist/tools/validateInbound.js.map +1 -0
  94. package/llms.txt +23 -0
  95. package/mcp.json +6562 -0
  96. package/mcp.schema.json +100 -0
  97. package/openapi.yaml +20685 -0
  98. package/package.json +34 -0
  99. package/scripts/sync-openapi.js +289 -0
  100. package/src/cli.ts +353 -0
  101. package/src/client.ts +90 -0
  102. package/src/config.ts +126 -0
  103. package/src/index.ts +31 -0
  104. package/src/openapi-tools.ts +346 -0
  105. package/src/server.ts +472 -0
  106. package/test-ai-integration.js +128 -0
  107. package/test-fix.js +68 -0
  108. package/test-mcp.js +92 -0
  109. package/test-order.json +25 -0
  110. package/tsconfig.json +18 -0
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "item-wms-public-api-tool",
3
+ "version": "2.0.4",
4
+ "description": "Public APIs for auth, EDI import, queries, uploads, and item master data.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "item-wms-public-api-tool": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "clean": "rm -rf dist",
11
+ "build": "tsc",
12
+ "dev": "tsc -w",
13
+ "sync:api": "node scripts/sync-openapi.js",
14
+ "start": "node dist/index.js",
15
+ "lint": "echo \"lint not configured\"",
16
+ "test": "echo \"tests not configured\""
17
+ },
18
+ "keywords": [
19
+ "wms",
20
+ "mcp",
21
+ "api",
22
+ "cli"
23
+ ],
24
+ "author": "",
25
+ "license": "ISC",
26
+ "dependencies": {
27
+ "commander": "^12.0.0",
28
+ "yaml": "^2.6.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^20.0.0",
32
+ "typescript": "^5.0.0"
33
+ }
34
+ }
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const http = require('http');
6
+ const https = require('https');
7
+ let YAML = null;
8
+ try {
9
+ // Optional dependency; fallback to JSON if not installed.
10
+ // eslint-disable-next-line global-require
11
+ YAML = require('yaml');
12
+ } catch (error) {
13
+ YAML = null;
14
+ }
15
+
16
+ const DEFAULT_OPENAPI_URL = 'http://127.0.0.1:4523/export/openapi/2?version=3.0';
17
+
18
+ const SOURCE_URL = process.env.APIFOX_OPENAPI_URL || DEFAULT_OPENAPI_URL;
19
+ const SOURCE_FILE = process.env.APIFOX_OPENAPI_FILE;
20
+
21
+ const SNAPSHOT_PATH = process.env.OPENAPI_SNAPSHOT_PATH
22
+ || path.join('api', 'openapi.apifox.yaml');
23
+ const OPENAPI_OUTPUT_PATH = process.env.OPENAPI_OUTPUT_PATH || 'openapi.yaml';
24
+ const MCP_OUTPUT_PATH = process.env.MCP_OUTPUT_PATH || 'mcp.json';
25
+
26
+ const PATH_PREFIX = process.env.APIFOX_PATH_PREFIX || '/v1/public/';
27
+ const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete'];
28
+
29
+ function isJsonLike(raw) {
30
+ const trimmed = raw.trimStart();
31
+ return trimmed.startsWith('{') || trimmed.startsWith('[');
32
+ }
33
+
34
+ function normalizeToolName(method, pathName) {
35
+ const normalizedPath = pathName.replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_+|_+$/g, '');
36
+ return `${method.toLowerCase()}_${normalizedPath.toLowerCase()}`;
37
+ }
38
+
39
+ function fetchText(url, redirects = 0) {
40
+ if (redirects > 5) {
41
+ return Promise.reject(new Error(`Too many redirects for ${url}`));
42
+ }
43
+ return new Promise((resolve, reject) => {
44
+ const lib = url.startsWith('https') ? https : http;
45
+ const req = lib.get(url, (res) => {
46
+ const { statusCode, headers } = res;
47
+ if (statusCode && statusCode >= 300 && statusCode < 400 && headers.location) {
48
+ res.resume();
49
+ return resolve(fetchText(headers.location, redirects + 1));
50
+ }
51
+ if (statusCode && statusCode >= 400) {
52
+ res.resume();
53
+ return reject(new Error(`Request failed: ${statusCode} ${url}`));
54
+ }
55
+ let data = '';
56
+ res.setEncoding('utf8');
57
+ res.on('data', (chunk) => {
58
+ data += chunk;
59
+ });
60
+ res.on('end', () => resolve(data));
61
+ });
62
+ req.on('error', reject);
63
+ });
64
+ }
65
+
66
+ function ensureDir(dirPath) {
67
+ if (!fs.existsSync(dirPath)) {
68
+ fs.mkdirSync(dirPath, { recursive: true });
69
+ }
70
+ }
71
+
72
+ function parseOpenApi(raw) {
73
+ if (!raw || !raw.trim()) {
74
+ throw new Error('OpenAPI content is empty.');
75
+ }
76
+ if (isJsonLike(raw)) {
77
+ return JSON.parse(raw);
78
+ }
79
+ if (!YAML) {
80
+ throw new Error('YAML parser not installed. Install "yaml" or export OpenAPI as JSON.');
81
+ }
82
+ return YAML.parse(raw);
83
+ }
84
+
85
+ function stringifyOpenApi(openapi) {
86
+ if (YAML) {
87
+ return YAML.stringify(openapi);
88
+ }
89
+ return JSON.stringify(openapi, null, 2);
90
+ }
91
+
92
+ function resolveSchema(schema, components, seen = new Set()) {
93
+ if (!schema || typeof schema !== 'object') {
94
+ return schema;
95
+ }
96
+ if (schema.$ref && typeof schema.$ref === 'string') {
97
+ const match = schema.$ref.match(/^#\/components\/schemas\/(.+)$/);
98
+ if (!match) {
99
+ return { ...schema };
100
+ }
101
+ const name = match[1];
102
+ if (seen.has(name)) {
103
+ return { ...schema };
104
+ }
105
+ const target = components && components.schemas ? components.schemas[name] : undefined;
106
+ if (!target) {
107
+ return { ...schema };
108
+ }
109
+ seen.add(name);
110
+ const resolved = resolveSchema(target, components, seen);
111
+ seen.delete(name);
112
+ return resolved;
113
+ }
114
+
115
+ if (Array.isArray(schema.allOf)) {
116
+ const merged = schema.allOf.reduce((acc, item) => mergeSchema(acc, resolveSchema(item, components, seen)), {
117
+ type: 'object',
118
+ properties: {}
119
+ });
120
+ const combined = mergeSchema(merged, schema);
121
+ delete combined.allOf;
122
+ return combined;
123
+ }
124
+
125
+ const copy = { ...schema };
126
+ if (Array.isArray(copy.anyOf)) {
127
+ copy.anyOf = copy.anyOf.map((item) => resolveSchema(item, components, seen));
128
+ }
129
+ if (Array.isArray(copy.oneOf)) {
130
+ copy.oneOf = copy.oneOf.map((item) => resolveSchema(item, components, seen));
131
+ }
132
+ if (copy.properties && typeof copy.properties === 'object') {
133
+ const nextProps = {};
134
+ for (const [key, value] of Object.entries(copy.properties)) {
135
+ nextProps[key] = resolveSchema(value, components, seen);
136
+ }
137
+ copy.properties = nextProps;
138
+ }
139
+ if (copy.items) {
140
+ copy.items = resolveSchema(copy.items, components, seen);
141
+ }
142
+ return copy;
143
+ }
144
+
145
+ function mergeSchema(base, extra) {
146
+ const merged = { ...base, ...extra };
147
+ merged.properties = {
148
+ ...(base && base.properties ? base.properties : {}),
149
+ ...(extra && extra.properties ? extra.properties : {})
150
+ };
151
+ if (extra && extra.anyOf) {
152
+ merged.anyOf = extra.anyOf;
153
+ } else if (base && base.anyOf) {
154
+ merged.anyOf = base.anyOf;
155
+ }
156
+ if (extra && extra.required) {
157
+ merged.required = extra.required;
158
+ } else if (base && base.required) {
159
+ merged.required = base.required;
160
+ }
161
+ return merged;
162
+ }
163
+
164
+ function ensureObjectSchema(schema) {
165
+ if (!schema || typeof schema !== 'object') {
166
+ return { type: 'object', properties: {} };
167
+ }
168
+ if (!schema.type && !schema.properties) {
169
+ return { type: 'object', properties: {}, ...schema };
170
+ }
171
+ if (schema.type && schema.type !== 'object' && !schema.properties) {
172
+ return { type: 'object', properties: { body: schema }, required: ['body'] };
173
+ }
174
+ if (!schema.properties) {
175
+ return { ...schema, properties: {} };
176
+ }
177
+ return schema;
178
+ }
179
+
180
+ function buildInputSchema(operation, parameters, components) {
181
+ const requestSchema = operation.requestBody
182
+ && operation.requestBody.content
183
+ && operation.requestBody.content['application/json']
184
+ && operation.requestBody.content['application/json'].schema;
185
+ const resolvedSchema = resolveSchema(requestSchema, components);
186
+ const baseSchema = ensureObjectSchema(resolvedSchema);
187
+ const requiredSet = new Set(baseSchema.required || []);
188
+ const properties = { ...(baseSchema.properties || {}) };
189
+
190
+ for (const param of parameters) {
191
+ if (!param || !param.name) {
192
+ continue;
193
+ }
194
+ if (!properties[param.name]) {
195
+ properties[param.name] = resolveSchema(param.schema, components) || { type: 'string' };
196
+ }
197
+ if (param.required) {
198
+ requiredSet.add(param.name);
199
+ }
200
+ }
201
+
202
+ const inputSchema = { ...baseSchema, properties };
203
+ if (requiredSet.size > 0) {
204
+ inputSchema.required = Array.from(requiredSet);
205
+ } else {
206
+ delete inputSchema.required;
207
+ }
208
+ return inputSchema;
209
+ }
210
+
211
+ function buildTools(openapi) {
212
+ const tools = [];
213
+ const operations = openapi.paths || {};
214
+ for (const [pathName, pathItem] of Object.entries(operations)) {
215
+ if (PATH_PREFIX && !pathName.startsWith(PATH_PREFIX)) {
216
+ continue;
217
+ }
218
+ const commonParams = Array.isArray(pathItem.parameters) ? pathItem.parameters : [];
219
+ for (const method of HTTP_METHODS) {
220
+ const operation = pathItem[method];
221
+ if (!operation) {
222
+ continue;
223
+ }
224
+ const baseName = normalizeToolName(method, pathName);
225
+ let name = baseName;
226
+ let counter = 2;
227
+ while (tools.find((tool) => tool.name === name)) {
228
+ name = `${baseName}_${counter}`;
229
+ counter += 1;
230
+ }
231
+ const parameters = [
232
+ ...commonParams,
233
+ ...(Array.isArray(operation.parameters) ? operation.parameters : [])
234
+ ];
235
+ const description = operation.summary || operation.description || `${method.toUpperCase()} ${pathName}`;
236
+ const inputSchema = buildInputSchema(operation, parameters, openapi.components);
237
+ tools.push({ name, description, inputSchema });
238
+ }
239
+ }
240
+ tools.sort((a, b) => a.name.localeCompare(b.name));
241
+ return tools;
242
+ }
243
+
244
+ async function loadSource() {
245
+ if (SOURCE_FILE) {
246
+ return fs.readFileSync(SOURCE_FILE, 'utf8');
247
+ }
248
+ return fetchText(SOURCE_URL);
249
+ }
250
+
251
+ async function main() {
252
+ const raw = await loadSource();
253
+ ensureDir(path.dirname(SNAPSHOT_PATH));
254
+ fs.writeFileSync(SNAPSHOT_PATH, raw.endsWith('\n') ? raw : `${raw}\n`);
255
+
256
+ const openapi = parseOpenApi(raw);
257
+ if (!openapi || !openapi.paths) {
258
+ throw new Error('Invalid OpenAPI content: missing "paths".');
259
+ }
260
+
261
+ const openapiYaml = stringifyOpenApi(openapi);
262
+ fs.writeFileSync(OPENAPI_OUTPUT_PATH, openapiYaml.endsWith('\n') ? openapiYaml : `${openapiYaml}\n`);
263
+
264
+ const existingMcp = fs.existsSync(MCP_OUTPUT_PATH)
265
+ ? JSON.parse(fs.readFileSync(MCP_OUTPUT_PATH, 'utf8'))
266
+ : {
267
+ id: 'wms.item.public',
268
+ name: 'item-wms-public-api-tool',
269
+ version: '0.1.0',
270
+ description: 'Public APIs for auth, EDI import, queries, uploads, and item master data.',
271
+ entrypoint: 'item-wms-public-api-tool'
272
+ };
273
+
274
+ const tools = buildTools(openapi);
275
+ const nextMcp = { ...existingMcp, tools };
276
+ fs.writeFileSync(MCP_OUTPUT_PATH, `${JSON.stringify(nextMcp, null, 2)}\n`);
277
+
278
+ console.log('OK: OpenAPI snapshot saved:', SNAPSHOT_PATH);
279
+ console.log('OK: openapi.yaml updated:', OPENAPI_OUTPUT_PATH);
280
+ console.log('OK: mcp.json updated:', MCP_OUTPUT_PATH);
281
+ if (tools.length === 0) {
282
+ console.warn('WARN: No tools generated. Check APIFOX_PATH_PREFIX or OpenAPI paths.');
283
+ }
284
+ }
285
+
286
+ main().catch((error) => {
287
+ console.error('ERROR: sync-openapi failed:', error.message);
288
+ process.exit(1);
289
+ });
package/src/cli.ts ADDED
@@ -0,0 +1,353 @@
1
+ // src/cli.ts
2
+ import { Command } from "commander";
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import {
6
+ DEFAULT_BASE_URL,
7
+ DEFAULT_COMPANY_ID,
8
+ DEFAULT_CUSTOMER_ID,
9
+ DEFAULT_FACILITY_ID
10
+ } from "./config";
11
+ import {
12
+ buildOpenApiIndex,
13
+ callOpenApiOperation,
14
+ loadOpenApiSpec,
15
+ type McpTool,
16
+ type OpenApiIndex,
17
+ type OpenApiOperation
18
+ } from "./openapi-tools";
19
+
20
+ type ParamPair = {
21
+ key: string;
22
+ value: any;
23
+ };
24
+
25
+ const RESERVED_COMMANDS = new Set(["list", "describe", "call", "help"]);
26
+
27
+ function parseJsonString(value: string, label: string): any {
28
+ try {
29
+ return JSON.parse(value);
30
+ } catch (error) {
31
+ throw new Error(`${label} must be valid JSON: ${(error as Error).message}`);
32
+ }
33
+ }
34
+
35
+ function readJsonFile(filePath: string, label: string): any {
36
+ if (!fs.existsSync(filePath)) {
37
+ throw new Error(`File not found: ${filePath}`);
38
+ }
39
+ const fileContent = fs.readFileSync(filePath, 'utf8');
40
+ return parseJsonString(fileContent, label);
41
+ }
42
+
43
+ function resolveJsonInput(value: string | undefined, filePath: string | undefined, label: string): any {
44
+ if (value && filePath) {
45
+ throw new Error(`Provide either ${label} or ${label}File, not both`);
46
+ }
47
+ if (value) {
48
+ return parseJsonString(value, label);
49
+ }
50
+ if (filePath) {
51
+ return readJsonFile(filePath, `${label}File`);
52
+ }
53
+ return undefined;
54
+ }
55
+
56
+ function parseValue(raw: string): any {
57
+ const trimmed = raw.trim();
58
+ if (!trimmed) {
59
+ return '';
60
+ }
61
+ const jsonNumber = /^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
62
+ const shouldParse =
63
+ trimmed.startsWith('{') ||
64
+ trimmed.startsWith('[') ||
65
+ trimmed === 'true' ||
66
+ trimmed === 'false' ||
67
+ trimmed === 'null' ||
68
+ jsonNumber.test(trimmed) ||
69
+ (trimmed.startsWith('"') && trimmed.endsWith('"'));
70
+
71
+ if (shouldParse) {
72
+ try {
73
+ return JSON.parse(trimmed);
74
+ } catch (error) {
75
+ return raw;
76
+ }
77
+ }
78
+
79
+ return raw;
80
+ }
81
+
82
+ function parseParamPair(pair: string): ParamPair {
83
+ const index = pair.indexOf('=');
84
+ if (index <= 0) {
85
+ throw new Error(`Invalid param format: ${pair}. Use key=value`);
86
+ }
87
+ const key = pair.slice(0, index).trim();
88
+ if (!key) {
89
+ throw new Error(`Invalid param format: ${pair}. Key is required`);
90
+ }
91
+ const rawValue = pair.slice(index + 1);
92
+ return { key, value: parseValue(rawValue) };
93
+ }
94
+
95
+ function collectParams(value: string, previous: string[]): string[] {
96
+ return previous.concat(value);
97
+ }
98
+
99
+ function resolveOpenApiPath(): string | null {
100
+ const candidates = [
101
+ process.env.WMS_OPENAPI_PATH,
102
+ process.env.OPENAPI_PATH,
103
+ path.resolve(__dirname, '..', 'openapi.yaml'),
104
+ path.resolve(process.cwd(), 'openapi.yaml')
105
+ ];
106
+
107
+ for (const candidate of candidates) {
108
+ if (candidate && fs.existsSync(candidate)) {
109
+ return candidate;
110
+ }
111
+ }
112
+ return null;
113
+ }
114
+
115
+ function readPackageVersion(): string | undefined {
116
+ const candidates = [
117
+ path.resolve(__dirname, '..', 'package.json'),
118
+ path.resolve(process.cwd(), 'package.json')
119
+ ];
120
+
121
+ for (const candidate of candidates) {
122
+ if (!fs.existsSync(candidate)) {
123
+ continue;
124
+ }
125
+ try {
126
+ const raw = fs.readFileSync(candidate, 'utf8');
127
+ const data = JSON.parse(raw) as { version?: string };
128
+ if (data.version) {
129
+ return data.version;
130
+ }
131
+ } catch (error) {
132
+ continue;
133
+ }
134
+ }
135
+
136
+ return undefined;
137
+ }
138
+
139
+ function loadOpenApiIndex(): OpenApiIndex {
140
+ const openApiPath = resolveOpenApiPath();
141
+ if (!openApiPath) {
142
+ throw new Error('OpenAPI spec not found. Run sync:api or set WMS_OPENAPI_PATH.');
143
+ }
144
+ const spec = loadOpenApiSpec(openApiPath);
145
+ const hasPrefixEnv = Object.prototype.hasOwnProperty.call(process.env, 'APIFOX_PATH_PREFIX');
146
+ const pathPrefix = hasPrefixEnv ? process.env.APIFOX_PATH_PREFIX : '/v1/public/';
147
+ const normalizedPrefix = pathPrefix && pathPrefix.length > 0 ? pathPrefix : undefined;
148
+ return buildOpenApiIndex(spec, { pathPrefix: normalizedPrefix });
149
+ }
150
+
151
+ function buildPayload(options: { json?: string; file?: string; param?: string[] }): Record<string, any> {
152
+ const base = resolveJsonInput(options.json, options.file, 'json');
153
+ let payload: Record<string, any> = {};
154
+
155
+ if (base !== undefined) {
156
+ if (base && typeof base === 'object' && !Array.isArray(base)) {
157
+ payload = { ...base };
158
+ } else {
159
+ payload = { body: base };
160
+ }
161
+ }
162
+
163
+ const params = options.param || [];
164
+ for (const entry of params) {
165
+ const { key, value } = parseParamPair(entry);
166
+ payload[key] = value;
167
+ }
168
+
169
+ return payload;
170
+ }
171
+
172
+ function listOperations(index: OpenApiIndex): Array<{ name: string; method: string; path: string; description: string }> {
173
+ return Object.values(index.operations)
174
+ .map((operation) => ({
175
+ name: operation.name,
176
+ method: operation.method.toUpperCase(),
177
+ path: operation.path,
178
+ description: operation.description
179
+ }))
180
+ .sort((a, b) => a.name.localeCompare(b.name));
181
+ }
182
+
183
+ function formatOperationLine(operation: { name: string; method: string; path: string; description: string }): string {
184
+ const detail = operation.description ? ` - ${operation.description}` : '';
185
+ return `${operation.name}\t${operation.method} ${operation.path}${detail}`;
186
+ }
187
+
188
+ function getToolByName(index: OpenApiIndex, name: string): McpTool | undefined {
189
+ return index.tools.find((tool) => tool.name === name);
190
+ }
191
+
192
+ function createCallAction(
193
+ operation: OpenApiOperation,
194
+ program: Command
195
+ ): (options: { json?: string; file?: string; param?: string[] }) => Promise<void> {
196
+ return async (options) => {
197
+ const payload = buildPayload(options);
198
+ const global = program.opts();
199
+ const result = await callOpenApiOperation(operation, payload, {
200
+ baseUrl: global.baseUrl,
201
+ token: global.token
202
+ });
203
+ console.log(JSON.stringify(result, null, 2));
204
+ };
205
+ }
206
+
207
+ /**
208
+ * 创建并配置CLI命令
209
+ */
210
+ export function createCli(): Command {
211
+ const program = new Command();
212
+
213
+ program
214
+ .name("item-wms-public-api-tool")
215
+ .option("--baseUrl <url>", "API base url", process.env.WMS_BASE_URL || DEFAULT_BASE_URL)
216
+ .option("--token <token>", "Auth token", process.env.WMS_TOKEN)
217
+ .option(
218
+ "--companyId <id>",
219
+ "Default CompanyID for requests",
220
+ process.env.WMS_COMPANY_ID || DEFAULT_COMPANY_ID
221
+ )
222
+ .option(
223
+ "--facilityId <id>",
224
+ "Default FacilityID for requests",
225
+ process.env.WMS_FACILITY_ID || DEFAULT_FACILITY_ID
226
+ )
227
+ .option(
228
+ "--customerId <id>",
229
+ "Default CustomerID for requests",
230
+ process.env.WMS_CUSTOMER_ID || DEFAULT_CUSTOMER_ID
231
+ );
232
+
233
+ const version = readPackageVersion();
234
+ if (version) {
235
+ program.version(version);
236
+ }
237
+
238
+ let openApiIndex: OpenApiIndex | null = null;
239
+ let openApiError: Error | null = null;
240
+
241
+ try {
242
+ openApiIndex = loadOpenApiIndex();
243
+ } catch (error) {
244
+ openApiError = error as Error;
245
+ }
246
+
247
+ const requireIndex = (): OpenApiIndex => {
248
+ if (openApiIndex) {
249
+ return openApiIndex;
250
+ }
251
+ throw openApiError || new Error('OpenAPI spec not loaded.');
252
+ };
253
+
254
+ program
255
+ .command('list')
256
+ .description('List available OpenAPI operations')
257
+ .option('--json', 'Output JSON')
258
+ .action((options) => {
259
+ const index = requireIndex();
260
+ const operations = listOperations(index);
261
+ if (options.json) {
262
+ console.log(JSON.stringify(operations, null, 2));
263
+ return;
264
+ }
265
+ operations.forEach((operation) => {
266
+ console.log(formatOperationLine(operation));
267
+ });
268
+ });
269
+
270
+ program
271
+ .command('describe')
272
+ .description('Show OpenAPI details for an operation')
273
+ .argument('<name>', 'Operation/tool name')
274
+ .option('--json', 'Output JSON')
275
+ .action((name, options) => {
276
+ const index = requireIndex();
277
+ const operation = index.operations[name];
278
+ if (!operation) {
279
+ throw new Error(`Unknown operation: ${name}`);
280
+ }
281
+ const tool = getToolByName(index, name);
282
+ if (options.json) {
283
+ console.log(JSON.stringify({
284
+ name,
285
+ method: operation.method.toUpperCase(),
286
+ path: operation.path,
287
+ description: operation.description,
288
+ inputSchema: tool?.inputSchema
289
+ }, null, 2));
290
+ return;
291
+ }
292
+ console.log(`${operation.method.toUpperCase()} ${operation.path}`);
293
+ if (operation.description) {
294
+ console.log(operation.description);
295
+ }
296
+ if (tool?.inputSchema) {
297
+ console.log('Input schema:');
298
+ console.log(JSON.stringify(tool.inputSchema, null, 2));
299
+ }
300
+ });
301
+
302
+ program
303
+ .command('call')
304
+ .description('Call an OpenAPI operation by name')
305
+ .argument('<name>', 'Operation/tool name')
306
+ .option('--json <json>', 'JSON payload for all params/body')
307
+ .option('--file <file>', 'Path to JSON payload file')
308
+ .option('-p, --param <key=value>', 'Set a param (repeatable)', collectParams, [])
309
+ .action(async (name, options) => {
310
+ const index = requireIndex();
311
+ const operation = index.operations[name];
312
+ if (!operation) {
313
+ throw new Error(`Unknown operation: ${name}`);
314
+ }
315
+ const payload = buildPayload(options);
316
+ const global = program.opts();
317
+ const result = await callOpenApiOperation(operation, payload, {
318
+ baseUrl: global.baseUrl,
319
+ token: global.token
320
+ });
321
+ console.log(JSON.stringify(result, null, 2));
322
+ });
323
+
324
+ if (openApiIndex) {
325
+ const operations = Object.values(openApiIndex.operations)
326
+ .filter((operation) => !RESERVED_COMMANDS.has(operation.name))
327
+ .sort((a, b) => a.name.localeCompare(b.name));
328
+
329
+ operations.forEach((operation) => {
330
+ const summary = operation.description
331
+ ? `${operation.method.toUpperCase()} ${operation.path} - ${operation.description}`
332
+ : `${operation.method.toUpperCase()} ${operation.path}`;
333
+
334
+ program
335
+ .command(operation.name)
336
+ .description(summary)
337
+ .option('--json <json>', 'JSON payload for all params/body')
338
+ .option('--file <file>', 'Path to JSON payload file')
339
+ .option('-p, --param <key=value>', 'Set a param (repeatable)', collectParams, [])
340
+ .action(createCallAction(operation, program));
341
+ });
342
+ }
343
+
344
+ return program;
345
+ }
346
+
347
+ /**
348
+ * 解析CLI命令并执行
349
+ */
350
+ export async function parseAndExecuteCli(): Promise<void> {
351
+ const program = createCli();
352
+ await program.parseAsync();
353
+ }