@tronsfey/ucli 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +349 -0
- package/README.zh.md +340 -0
- package/assets/logo.svg +138 -0
- package/dist/index.js +550 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
- package/skill.md +223 -0
package/assets/logo.svg
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 480 160" width="480" height="160">
|
|
2
|
+
<defs>
|
|
3
|
+
<!-- Background gradient border -->
|
|
4
|
+
<linearGradient id="borderGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
5
|
+
<stop offset="0%" stop-color="#7c3aed"/>
|
|
6
|
+
<stop offset="100%" stop-color="#2563eb"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
<!-- Icon accent gradient -->
|
|
9
|
+
<linearGradient id="hexGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
10
|
+
<stop offset="0%" stop-color="#7c3aed"/>
|
|
11
|
+
<stop offset="100%" stop-color="#38bdf8"/>
|
|
12
|
+
</linearGradient>
|
|
13
|
+
<!-- Glow filter -->
|
|
14
|
+
<filter id="glow" x="-30%" y="-30%" width="160%" height="160%">
|
|
15
|
+
<feGaussianBlur stdDeviation="3" result="blur"/>
|
|
16
|
+
<feMerge>
|
|
17
|
+
<feMergeNode in="blur"/>
|
|
18
|
+
<feMergeNode in="SourceGraphic"/>
|
|
19
|
+
</feMerge>
|
|
20
|
+
</filter>
|
|
21
|
+
<filter id="softGlow" x="-50%" y="-50%" width="200%" height="200%">
|
|
22
|
+
<feGaussianBlur stdDeviation="6" result="blur"/>
|
|
23
|
+
<feMerge>
|
|
24
|
+
<feMergeNode in="blur"/>
|
|
25
|
+
<feMergeNode in="SourceGraphic"/>
|
|
26
|
+
</feMerge>
|
|
27
|
+
</filter>
|
|
28
|
+
<!-- Clip path for rounded rect -->
|
|
29
|
+
<clipPath id="cardClip">
|
|
30
|
+
<rect width="480" height="160" rx="14" ry="14"/>
|
|
31
|
+
</clipPath>
|
|
32
|
+
</defs>
|
|
33
|
+
|
|
34
|
+
<!-- Card background -->
|
|
35
|
+
<rect width="480" height="160" rx="14" ry="14" fill="#0d1117"/>
|
|
36
|
+
|
|
37
|
+
<!-- Subtle gradient overlay -->
|
|
38
|
+
<rect width="480" height="160" rx="14" ry="14" fill="url(#borderGrad)" opacity="0.06"/>
|
|
39
|
+
|
|
40
|
+
<!-- Border stroke -->
|
|
41
|
+
<rect x="1" y="1" width="478" height="158" rx="13" ry="13"
|
|
42
|
+
fill="none" stroke="url(#borderGrad)" stroke-width="1.5" opacity="0.8"/>
|
|
43
|
+
|
|
44
|
+
<!-- ── Left panel: gateway icon ── -->
|
|
45
|
+
<!-- Central hexagon (API gateway node) -->
|
|
46
|
+
<g transform="translate(80, 80)" filter="url(#softGlow)">
|
|
47
|
+
<!-- Hexagon shape (flat-top) -->
|
|
48
|
+
<polygon
|
|
49
|
+
points="0,-36 31.2,-18 31.2,18 0,36 -31.2,18 -31.2,-18"
|
|
50
|
+
fill="#0d1117"
|
|
51
|
+
stroke="url(#hexGrad)"
|
|
52
|
+
stroke-width="2.5"
|
|
53
|
+
/>
|
|
54
|
+
<!-- Inner hex fill accent -->
|
|
55
|
+
<polygon
|
|
56
|
+
points="0,-26 22.5,-13 22.5,13 0,26 -22.5,13 -22.5,-13"
|
|
57
|
+
fill="#7c3aed"
|
|
58
|
+
opacity="0.15"
|
|
59
|
+
/>
|
|
60
|
+
|
|
61
|
+
<!-- Center dot -->
|
|
62
|
+
<circle cx="0" cy="0" r="5" fill="#38bdf8" opacity="0.9"/>
|
|
63
|
+
|
|
64
|
+
<!-- Route lines (3 spokes, 120° apart) -->
|
|
65
|
+
<!-- Right spoke (0°) -->
|
|
66
|
+
<line x1="5" y1="0" x2="28" y2="0" stroke="#38bdf8" stroke-width="1.8" stroke-linecap="round" opacity="0.85"/>
|
|
67
|
+
<circle cx="28" cy="0" r="3.5" fill="#38bdf8" opacity="0.85"/>
|
|
68
|
+
|
|
69
|
+
<!-- Lower-left spoke (120°) -->
|
|
70
|
+
<line x1="-2.5" y1="4.3" x2="-16" y2="27.7" stroke="#38bdf8" stroke-width="1.8" stroke-linecap="round" opacity="0.85"/>
|
|
71
|
+
<circle cx="-16" cy="27.7" r="3.5" fill="#38bdf8" opacity="0.85"/>
|
|
72
|
+
|
|
73
|
+
<!-- Upper-left spoke (240°) -->
|
|
74
|
+
<line x1="-2.5" y1="-4.3" x2="-16" y2="-27.7" stroke="#38bdf8" stroke-width="1.8" stroke-linecap="round" opacity="0.85"/>
|
|
75
|
+
<circle cx="-16" cy="-27.7" r="3.5" fill="#38bdf8" opacity="0.85"/>
|
|
76
|
+
</g>
|
|
77
|
+
|
|
78
|
+
<!-- Vertical divider -->
|
|
79
|
+
<line x1="142" y1="24" x2="142" y2="136" stroke="#30363d" stroke-width="1"/>
|
|
80
|
+
|
|
81
|
+
<!-- ── Right panel: text ── -->
|
|
82
|
+
<!-- Main title -->
|
|
83
|
+
<text
|
|
84
|
+
x="165" y="68"
|
|
85
|
+
font-family="'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', ui-monospace, monospace"
|
|
86
|
+
font-size="32"
|
|
87
|
+
font-weight="700"
|
|
88
|
+
letter-spacing="1"
|
|
89
|
+
fill="#e6edf3"
|
|
90
|
+
>OAS GATEWAY</text>
|
|
91
|
+
|
|
92
|
+
<!-- Subtitle -->
|
|
93
|
+
<text
|
|
94
|
+
x="166" y="95"
|
|
95
|
+
font-family="'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', ui-monospace, monospace"
|
|
96
|
+
font-size="13"
|
|
97
|
+
font-weight="400"
|
|
98
|
+
letter-spacing="0.5"
|
|
99
|
+
fill="#7d8590"
|
|
100
|
+
>by tronsfey</text>
|
|
101
|
+
|
|
102
|
+
<!-- Tag line -->
|
|
103
|
+
<text
|
|
104
|
+
x="166" y="122"
|
|
105
|
+
font-family="'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', ui-monospace, monospace"
|
|
106
|
+
font-size="11"
|
|
107
|
+
font-weight="400"
|
|
108
|
+
letter-spacing="0.3"
|
|
109
|
+
fill="#3d444d"
|
|
110
|
+
>OpenAPI · NestJS · CLI · AES-256-GCM</text>
|
|
111
|
+
|
|
112
|
+
<!-- Version badge -->
|
|
113
|
+
<g transform="translate(165, 132)">
|
|
114
|
+
<rect x="0" y="0" width="62" height="18" rx="9" fill="#1a2332" stroke="#2563eb" stroke-width="1" opacity="0.9"/>
|
|
115
|
+
<text
|
|
116
|
+
x="31" y="13"
|
|
117
|
+
text-anchor="middle"
|
|
118
|
+
font-family="'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', ui-monospace, monospace"
|
|
119
|
+
font-size="9.5"
|
|
120
|
+
font-weight="600"
|
|
121
|
+
fill="#2563eb"
|
|
122
|
+
letter-spacing="0.3"
|
|
123
|
+
>npm public</text>
|
|
124
|
+
</g>
|
|
125
|
+
|
|
126
|
+
<g transform="translate(234, 132)">
|
|
127
|
+
<rect x="0" y="0" width="52" height="18" rx="9" fill="#1a2a1a" stroke="#22c55e" stroke-width="1" opacity="0.9"/>
|
|
128
|
+
<text
|
|
129
|
+
x="26" y="13"
|
|
130
|
+
text-anchor="middle"
|
|
131
|
+
font-family="'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', ui-monospace, monospace"
|
|
132
|
+
font-size="9.5"
|
|
133
|
+
font-weight="600"
|
|
134
|
+
fill="#22c55e"
|
|
135
|
+
letter-spacing="0.3"
|
|
136
|
+
>v0.2.0</text>
|
|
137
|
+
</g>
|
|
138
|
+
</svg>
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { createRequire as createRequire2 } from "module";
|
|
6
|
+
|
|
7
|
+
// src/config.ts
|
|
8
|
+
import Conf from "conf";
|
|
9
|
+
import { homedir } from "os";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
var conf = new Conf({
|
|
12
|
+
projectName: "ucli",
|
|
13
|
+
schema: {
|
|
14
|
+
serverUrl: { type: "string" },
|
|
15
|
+
token: { type: "string" }
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
var cacheDir = join(homedir(), ".cache", "ucli");
|
|
19
|
+
function getConfig() {
|
|
20
|
+
const serverUrl = conf.get("serverUrl");
|
|
21
|
+
const token = conf.get("token");
|
|
22
|
+
if (!serverUrl || !token) {
|
|
23
|
+
console.error("ucli is not configured. Run: ucli configure --server <url> --token <jwt>");
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
return { serverUrl, token };
|
|
27
|
+
}
|
|
28
|
+
function saveConfig(cfg) {
|
|
29
|
+
conf.set("serverUrl", cfg.serverUrl);
|
|
30
|
+
conf.set("token", cfg.token);
|
|
31
|
+
}
|
|
32
|
+
function isConfigured() {
|
|
33
|
+
return Boolean(conf.get("serverUrl") && conf.get("token"));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/lib/server-client.ts
|
|
37
|
+
import axios from "axios";
|
|
38
|
+
var ServerClient = class {
|
|
39
|
+
http;
|
|
40
|
+
constructor(cfg) {
|
|
41
|
+
this.http = axios.create({
|
|
42
|
+
baseURL: cfg.serverUrl.replace(/\/$/, ""),
|
|
43
|
+
headers: {
|
|
44
|
+
Authorization: `Bearer ${cfg.token}`,
|
|
45
|
+
"Content-Type": "application/json"
|
|
46
|
+
},
|
|
47
|
+
timeout: 15e3
|
|
48
|
+
});
|
|
49
|
+
this.http.interceptors.response.use(
|
|
50
|
+
(r) => r,
|
|
51
|
+
(err) => {
|
|
52
|
+
if (axios.isAxiosError(err)) {
|
|
53
|
+
const status = err.response?.status;
|
|
54
|
+
const message = err.response?.data?.message ?? err.message;
|
|
55
|
+
if (status === 401) {
|
|
56
|
+
console.error("Authentication failed. Run: ucli configure --server <url> --token <jwt>");
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
throw new Error(`Server error ${status ?? "unknown"}: ${message}`);
|
|
60
|
+
}
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
async listOAS() {
|
|
66
|
+
const { data } = await this.http.get("/api/v1/oas");
|
|
67
|
+
return data;
|
|
68
|
+
}
|
|
69
|
+
async getOAS(name) {
|
|
70
|
+
const { data } = await this.http.get(`/api/v1/oas/${encodeURIComponent(name)}`);
|
|
71
|
+
return data;
|
|
72
|
+
}
|
|
73
|
+
async listMCP() {
|
|
74
|
+
const { data } = await this.http.get("/api/v1/mcp");
|
|
75
|
+
return data;
|
|
76
|
+
}
|
|
77
|
+
async getMCP(name) {
|
|
78
|
+
const { data } = await this.http.get(`/api/v1/mcp/${encodeURIComponent(name)}`);
|
|
79
|
+
return data;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// src/commands/configure.ts
|
|
84
|
+
function registerConfigure(program2) {
|
|
85
|
+
program2.command("configure").description("Configure the OAS Gateway server URL and authentication token").requiredOption("--server <url>", "OAS Gateway server URL (e.g. https://oas.example.com)").requiredOption("--token <jwt>", "Group JWT token issued by the server admin").action(async (opts) => {
|
|
86
|
+
const serverUrl = opts.server.replace(/\/$/, "");
|
|
87
|
+
const token = opts.token;
|
|
88
|
+
console.log(`Connecting to ${serverUrl}...`);
|
|
89
|
+
const client = new ServerClient({ serverUrl, token });
|
|
90
|
+
try {
|
|
91
|
+
await client.listOAS();
|
|
92
|
+
saveConfig({ serverUrl, token });
|
|
93
|
+
console.log("\u2713 Configuration saved successfully.");
|
|
94
|
+
console.log(` Server: ${serverUrl}`);
|
|
95
|
+
console.log(` Token: ${token.slice(0, 20)}...`);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.error("Connection failed:", err.message);
|
|
98
|
+
console.error("Please check the server URL and token.");
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/lib/cache.ts
|
|
105
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
106
|
+
import { join as join2 } from "path";
|
|
107
|
+
var LIST_CACHE_FILE = join2(cacheDir, "oas-list.json");
|
|
108
|
+
async function ensureCacheDir() {
|
|
109
|
+
await mkdir(cacheDir, { recursive: true });
|
|
110
|
+
}
|
|
111
|
+
async function readOASListCache() {
|
|
112
|
+
try {
|
|
113
|
+
const raw = await readFile(LIST_CACHE_FILE, "utf8");
|
|
114
|
+
const cached = JSON.parse(raw);
|
|
115
|
+
const age = (Date.now() - cached.fetchedAt) / 1e3;
|
|
116
|
+
if (cached.ttlSec === 0 || age > cached.ttlSec) return null;
|
|
117
|
+
return cached.entries;
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function writeOASListCache(entries, ttlSec) {
|
|
123
|
+
await ensureCacheDir();
|
|
124
|
+
const cached = { entries, fetchedAt: Date.now(), ttlSec };
|
|
125
|
+
await writeFile(LIST_CACHE_FILE, JSON.stringify(cached, null, 2), "utf8");
|
|
126
|
+
}
|
|
127
|
+
async function clearOASListCache() {
|
|
128
|
+
try {
|
|
129
|
+
await writeFile(LIST_CACHE_FILE, "{}", "utf8");
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/lib/oas-runner.ts
|
|
135
|
+
import { spawn } from "child_process";
|
|
136
|
+
import { createRequire } from "module";
|
|
137
|
+
import { join as join3, dirname } from "path";
|
|
138
|
+
var require2 = createRequire(import.meta.url);
|
|
139
|
+
function resolveOpenapi2CliBin() {
|
|
140
|
+
try {
|
|
141
|
+
const pkgPath = require2.resolve("@tronsfey/openapi2cli/package.json");
|
|
142
|
+
const pkgDir = dirname(pkgPath);
|
|
143
|
+
const pkg2 = require2("@tronsfey/openapi2cli/package.json");
|
|
144
|
+
const binEntry = pkg2.bin?.["openapi2cli"] ?? "bin/openapi2cli.js";
|
|
145
|
+
return join3(pkgDir, binEntry);
|
|
146
|
+
} catch {
|
|
147
|
+
throw new Error(
|
|
148
|
+
"@tronsfey/openapi2cli is not installed. Run: pnpm add @tronsfey/openapi2cli in packages/cli"
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function buildAuthEnv(entry) {
|
|
153
|
+
const cfg = entry.authConfig;
|
|
154
|
+
const prefix = entry.name.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
155
|
+
switch (cfg["type"]) {
|
|
156
|
+
case "bearer":
|
|
157
|
+
return { [`${prefix}_TOKEN`]: cfg["token"] };
|
|
158
|
+
case "api_key":
|
|
159
|
+
return { [`${prefix}_API_KEY`]: cfg["key"] };
|
|
160
|
+
case "basic":
|
|
161
|
+
return { [`${prefix}_CREDENTIALS`]: `${cfg["username"]}:${cfg["password"]}` };
|
|
162
|
+
case "oauth2_cc":
|
|
163
|
+
return {
|
|
164
|
+
[`${prefix}_CLIENT_ID`]: cfg["clientId"],
|
|
165
|
+
[`${prefix}_CLIENT_SECRET`]: cfg["clientSecret"],
|
|
166
|
+
[`${prefix}_SCOPES`]: cfg["scopes"].join(" ")
|
|
167
|
+
};
|
|
168
|
+
default:
|
|
169
|
+
return {};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async function runOperation(opts) {
|
|
173
|
+
const bin = resolveOpenapi2CliBin();
|
|
174
|
+
const { entry, operationArgs, format, query } = opts;
|
|
175
|
+
const args = [
|
|
176
|
+
"run",
|
|
177
|
+
"--oas",
|
|
178
|
+
entry.remoteUrl,
|
|
179
|
+
"--cache-ttl",
|
|
180
|
+
String(entry.cacheTtl),
|
|
181
|
+
...entry.baseEndpoint ? ["--endpoint", entry.baseEndpoint] : [],
|
|
182
|
+
...format ? ["--format", format] : [],
|
|
183
|
+
...query ? ["--query", query] : [],
|
|
184
|
+
...operationArgs
|
|
185
|
+
];
|
|
186
|
+
const authEnv = buildAuthEnv(entry);
|
|
187
|
+
await new Promise((resolve, reject) => {
|
|
188
|
+
const child = spawn(process.execPath, [bin, ...args], {
|
|
189
|
+
stdio: "inherit",
|
|
190
|
+
env: {
|
|
191
|
+
...process.env,
|
|
192
|
+
...authEnv
|
|
193
|
+
// inject auth — not visible to parent shell
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
child.on("close", (code) => {
|
|
197
|
+
if (code === 0) resolve();
|
|
198
|
+
else reject(new Error(`openapi2cli exited with code ${code}`));
|
|
199
|
+
});
|
|
200
|
+
child.on("error", reject);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
async function getServiceHelp(entry) {
|
|
204
|
+
const bin = resolveOpenapi2CliBin();
|
|
205
|
+
const args = [
|
|
206
|
+
"run",
|
|
207
|
+
"--oas",
|
|
208
|
+
entry.remoteUrl,
|
|
209
|
+
"--cache-ttl",
|
|
210
|
+
String(entry.cacheTtl),
|
|
211
|
+
"--help"
|
|
212
|
+
];
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
let output = "";
|
|
215
|
+
const child = spawn(process.execPath, [bin, ...args], {
|
|
216
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
217
|
+
});
|
|
218
|
+
child.stdout?.on("data", (d) => {
|
|
219
|
+
output += d.toString();
|
|
220
|
+
});
|
|
221
|
+
child.stderr?.on("data", (d) => {
|
|
222
|
+
output += d.toString();
|
|
223
|
+
});
|
|
224
|
+
child.on("close", () => resolve(output));
|
|
225
|
+
child.on("error", reject);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/commands/services.ts
|
|
230
|
+
function registerServices(program2) {
|
|
231
|
+
const services = program2.command("services").description("Manage and inspect available OAS services");
|
|
232
|
+
services.command("list").description("List all OAS services available in the current group").option("--no-cache", "Bypass local cache and fetch fresh from server").action(async (opts) => {
|
|
233
|
+
const cfg = getConfig();
|
|
234
|
+
const client = new ServerClient(cfg);
|
|
235
|
+
let entries = opts.cache ? await readOASListCache() : null;
|
|
236
|
+
if (!entries) {
|
|
237
|
+
entries = await client.listOAS();
|
|
238
|
+
if (entries.length > 0) {
|
|
239
|
+
const maxTtl = Math.min(...entries.map((e) => e.cacheTtl));
|
|
240
|
+
await writeOASListCache(entries, maxTtl);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (entries.length === 0) {
|
|
244
|
+
console.log("No services registered in this group.");
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const nameWidth = Math.max(10, ...entries.map((e) => e.name.length));
|
|
248
|
+
console.log(`
|
|
249
|
+
${"SERVICE".padEnd(nameWidth)} AUTH DESCRIPTION`);
|
|
250
|
+
console.log(`${"-".repeat(nameWidth)} -------- ${"-".repeat(40)}`);
|
|
251
|
+
for (const e of entries) {
|
|
252
|
+
const auth = e.authType.padEnd(8);
|
|
253
|
+
const desc = e.description.length > 60 ? e.description.slice(0, 57) + "..." : e.description;
|
|
254
|
+
console.log(`${e.name.padEnd(nameWidth)} ${auth} ${desc}`);
|
|
255
|
+
}
|
|
256
|
+
console.log();
|
|
257
|
+
});
|
|
258
|
+
services.command("info <name>").description("Show detailed information and available operations for a service").action(async (name) => {
|
|
259
|
+
const cfg = getConfig();
|
|
260
|
+
const client = new ServerClient(cfg);
|
|
261
|
+
let entry;
|
|
262
|
+
try {
|
|
263
|
+
entry = await client.getOAS(name);
|
|
264
|
+
} catch (err) {
|
|
265
|
+
console.error(`Service not found: ${name}`);
|
|
266
|
+
console.error("Run `ucli services list` to see available services.");
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
console.log(`
|
|
270
|
+
Service: ${entry.name}`);
|
|
271
|
+
console.log(`Description: ${entry.description || "(none)"}`);
|
|
272
|
+
console.log(`OAS URL: ${entry.remoteUrl}`);
|
|
273
|
+
if (entry.baseEndpoint) console.log(`Base endpoint: ${entry.baseEndpoint}`);
|
|
274
|
+
console.log(`Auth type: ${entry.authType}`);
|
|
275
|
+
console.log(`Cache TTL: ${entry.cacheTtl}s`);
|
|
276
|
+
console.log("\nAvailable operations:");
|
|
277
|
+
console.log("\u2500".repeat(60));
|
|
278
|
+
const help = await getServiceHelp(entry);
|
|
279
|
+
console.log(help);
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/commands/run.ts
|
|
284
|
+
function registerRun(program2) {
|
|
285
|
+
program2.command("run <service> [args...]").description("Execute an operation on a service").option("--format <fmt>", "Output format: json | table | yaml", "json").option("--query <jmespath>", "Filter response with JMESPath expression").option("--data <json>", "Request body (JSON string or @filename)").allowUnknownOption(true).action(async (service, args, opts) => {
|
|
286
|
+
const cfg = getConfig();
|
|
287
|
+
const client = new ServerClient(cfg);
|
|
288
|
+
let entry;
|
|
289
|
+
try {
|
|
290
|
+
entry = await client.getOAS(service);
|
|
291
|
+
} catch {
|
|
292
|
+
console.error(`Unknown service: ${service}`);
|
|
293
|
+
console.error("Run `ucli services list` to see available services.");
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
const extraArgs = opts.args ?? [];
|
|
297
|
+
const operationArgs = [
|
|
298
|
+
...args,
|
|
299
|
+
...extraArgs,
|
|
300
|
+
...opts.data ? ["--data", opts.data] : []
|
|
301
|
+
];
|
|
302
|
+
const format = opts.format;
|
|
303
|
+
const query = opts.query;
|
|
304
|
+
try {
|
|
305
|
+
await runOperation({
|
|
306
|
+
entry,
|
|
307
|
+
operationArgs,
|
|
308
|
+
...format !== void 0 ? { format } : {},
|
|
309
|
+
...query !== void 0 ? { query } : {}
|
|
310
|
+
});
|
|
311
|
+
} catch (err) {
|
|
312
|
+
console.error("Operation failed:", err.message);
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/commands/refresh.ts
|
|
319
|
+
function registerRefresh(program2) {
|
|
320
|
+
program2.command("refresh").description("Force-refresh the local OAS cache from the server").action(async () => {
|
|
321
|
+
const cfg = getConfig();
|
|
322
|
+
const client = new ServerClient(cfg);
|
|
323
|
+
console.log("Refreshing OAS list from server...");
|
|
324
|
+
await clearOASListCache();
|
|
325
|
+
const entries = await client.listOAS();
|
|
326
|
+
if (entries.length > 0) {
|
|
327
|
+
const maxTtl = Math.min(...entries.map((e) => e.cacheTtl));
|
|
328
|
+
await writeOASListCache(entries, maxTtl);
|
|
329
|
+
}
|
|
330
|
+
console.log(`\u2713 Refreshed ${entries.length} service(s).`);
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// src/commands/help-cmd.ts
|
|
335
|
+
function registerHelp(program2) {
|
|
336
|
+
program2.command("help [service]").description("Show usage guide. Pass a service name for service-specific operations.").action(async (service) => {
|
|
337
|
+
if (!service) {
|
|
338
|
+
printGeneralHelp();
|
|
339
|
+
if (isConfigured()) {
|
|
340
|
+
const cfg2 = getConfig();
|
|
341
|
+
const client2 = new ServerClient(cfg2);
|
|
342
|
+
let entries = await readOASListCache();
|
|
343
|
+
if (!entries) {
|
|
344
|
+
entries = await client2.listOAS();
|
|
345
|
+
if (entries.length > 0) {
|
|
346
|
+
const maxTtl = Math.min(...entries.map((e) => e.cacheTtl));
|
|
347
|
+
await writeOASListCache(entries, maxTtl);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (entries.length > 0) {
|
|
351
|
+
console.log("\nAvailable services:");
|
|
352
|
+
for (const e of entries) {
|
|
353
|
+
console.log(` ${e.name.padEnd(20)} ${e.description}`);
|
|
354
|
+
}
|
|
355
|
+
console.log("\nTip: Run `ucli help <service>` for service-specific operations.");
|
|
356
|
+
}
|
|
357
|
+
} else {
|
|
358
|
+
console.log("\nRun `ucli configure --server <url> --token <jwt>` to get started.");
|
|
359
|
+
}
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const cfg = getConfig();
|
|
363
|
+
const client = new ServerClient(cfg);
|
|
364
|
+
let entry;
|
|
365
|
+
try {
|
|
366
|
+
entry = await client.getOAS(service);
|
|
367
|
+
} catch {
|
|
368
|
+
console.error(`Unknown service: ${service}`);
|
|
369
|
+
console.error("Run `ucli services list` to see available services.");
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
console.log(`
|
|
373
|
+
=== ${entry.name} ===`);
|
|
374
|
+
console.log(`${entry.description}`);
|
|
375
|
+
console.log(`
|
|
376
|
+
OAS spec: ${entry.remoteUrl}`);
|
|
377
|
+
console.log("\nOperations:");
|
|
378
|
+
console.log("\u2500".repeat(60));
|
|
379
|
+
const help = await getServiceHelp(entry);
|
|
380
|
+
console.log(help);
|
|
381
|
+
console.log("\nExamples:");
|
|
382
|
+
console.log(` ucli run ${entry.name} <operation>`);
|
|
383
|
+
console.log(` ucli run ${entry.name} <operation> --format table`);
|
|
384
|
+
console.log(` ucli run ${entry.name} <operation> --query "results[*].id"`);
|
|
385
|
+
console.log(` ucli run ${entry.name} <operation> --data '{"key":"value"}'`);
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
function printGeneralHelp() {
|
|
389
|
+
console.log(`
|
|
390
|
+
ucli \u2014 OpenAPI & MCP Gateway for AI Agents
|
|
391
|
+
\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
392
|
+
|
|
393
|
+
SETUP
|
|
394
|
+
ucli configure --server <url> --token <jwt>
|
|
395
|
+
Configure server connection and authentication.
|
|
396
|
+
|
|
397
|
+
DISCOVERY
|
|
398
|
+
ucli services list
|
|
399
|
+
List all OAS services available in your group.
|
|
400
|
+
|
|
401
|
+
ucli services info <service>
|
|
402
|
+
Show detailed service info and all available operations.
|
|
403
|
+
|
|
404
|
+
ucli help [service]
|
|
405
|
+
Show this guide, or service-specific operations.
|
|
406
|
+
|
|
407
|
+
EXECUTION
|
|
408
|
+
ucli run <service> <operation> [options]
|
|
409
|
+
Execute a service operation.
|
|
410
|
+
|
|
411
|
+
Options:
|
|
412
|
+
--format json|table|yaml Output format (default: json)
|
|
413
|
+
--query <jmespath> Filter response with JMESPath
|
|
414
|
+
--data <json|@file> Request body for POST/PUT/PATCH
|
|
415
|
+
|
|
416
|
+
MAINTENANCE
|
|
417
|
+
ucli refresh
|
|
418
|
+
Force-refresh the local OAS cache from the server.
|
|
419
|
+
|
|
420
|
+
ERRORS
|
|
421
|
+
401 Unauthorized \u2192 Run: ucli configure --server <url> --token <jwt>
|
|
422
|
+
404 Not Found \u2192 Check service name: ucli services list
|
|
423
|
+
4xx Client Error \u2192 Check operation args: ucli services info <service>
|
|
424
|
+
5xx Server Error \u2192 Retry or run: ucli refresh
|
|
425
|
+
`);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// src/lib/mcp-runner.ts
|
|
429
|
+
async function getMcp2cli() {
|
|
430
|
+
const clientMod = await import("@tronsfey/mcp2cli/dist/client/index.js");
|
|
431
|
+
const runnerMod = await import("@tronsfey/mcp2cli/dist/runner/index.js");
|
|
432
|
+
return { createMcpClient: clientMod.createMcpClient, getTools: runnerMod.getTools, runTool: runnerMod.runTool };
|
|
433
|
+
}
|
|
434
|
+
function buildMcpConfig(entry) {
|
|
435
|
+
const base = { type: entry.transport };
|
|
436
|
+
if (entry.transport === "http") {
|
|
437
|
+
base.url = entry.serverUrl;
|
|
438
|
+
} else {
|
|
439
|
+
base.command = entry.command;
|
|
440
|
+
}
|
|
441
|
+
const auth = entry.authConfig;
|
|
442
|
+
if (auth.type === "http_headers") {
|
|
443
|
+
base.headers = auth.headers;
|
|
444
|
+
} else if (auth.type === "env") {
|
|
445
|
+
base.env = auth.env;
|
|
446
|
+
}
|
|
447
|
+
return base;
|
|
448
|
+
}
|
|
449
|
+
async function listMcpTools(entry) {
|
|
450
|
+
const { createMcpClient, getTools } = await getMcp2cli();
|
|
451
|
+
const config = buildMcpConfig(entry);
|
|
452
|
+
const client = await createMcpClient(config);
|
|
453
|
+
const tools = await getTools(client, config, { noCache: true });
|
|
454
|
+
return tools;
|
|
455
|
+
}
|
|
456
|
+
async function runMcpTool(entry, toolName, rawArgs) {
|
|
457
|
+
const { createMcpClient, getTools, runTool } = await getMcp2cli();
|
|
458
|
+
const config = buildMcpConfig(entry);
|
|
459
|
+
const client = await createMcpClient(config);
|
|
460
|
+
const tools = await getTools(client, config, { noCache: false, cacheTtl: 3600 });
|
|
461
|
+
const tool = tools.find((t) => t.name === toolName);
|
|
462
|
+
if (!tool) throw new Error(`Tool "${toolName}" not found on MCP server "${entry.name}"`);
|
|
463
|
+
await runTool(client, tool, rawArgs, {});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// src/commands/mcp.ts
|
|
467
|
+
function registerMcp(program2) {
|
|
468
|
+
const mcp = program2.command("mcp").description("Interact with MCP servers registered in your group");
|
|
469
|
+
mcp.command("list").description("List all MCP servers available in the current group").action(async () => {
|
|
470
|
+
const cfg = getConfig();
|
|
471
|
+
const client = new ServerClient(cfg);
|
|
472
|
+
const entries = await client.listMCP();
|
|
473
|
+
if (entries.length === 0) {
|
|
474
|
+
console.log("No MCP servers registered in this group.");
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const nameWidth = Math.max(10, ...entries.map((e) => e.name.length));
|
|
478
|
+
console.log(`
|
|
479
|
+
${"SERVER".padEnd(nameWidth)} TRANSPORT DESCRIPTION`);
|
|
480
|
+
console.log(`${"-".repeat(nameWidth)} --------- ${"-".repeat(40)}`);
|
|
481
|
+
for (const e of entries) {
|
|
482
|
+
const desc = e.description.length > 60 ? e.description.slice(0, 57) + "..." : e.description;
|
|
483
|
+
console.log(`${e.name.padEnd(nameWidth)} ${e.transport.padEnd(9)} ${desc}`);
|
|
484
|
+
}
|
|
485
|
+
console.log();
|
|
486
|
+
});
|
|
487
|
+
mcp.command("tools <server>").description("List tools available on a MCP server").action(async (serverName) => {
|
|
488
|
+
const cfg = getConfig();
|
|
489
|
+
const client = new ServerClient(cfg);
|
|
490
|
+
let entry;
|
|
491
|
+
try {
|
|
492
|
+
entry = await client.getMCP(serverName);
|
|
493
|
+
} catch {
|
|
494
|
+
console.error(`Unknown MCP server: ${serverName}`);
|
|
495
|
+
console.error("Run `ucli mcp list` to see available servers.");
|
|
496
|
+
process.exit(1);
|
|
497
|
+
}
|
|
498
|
+
let tools;
|
|
499
|
+
try {
|
|
500
|
+
tools = await listMcpTools(entry);
|
|
501
|
+
} catch (err) {
|
|
502
|
+
console.error("Failed to fetch tools:", err.message);
|
|
503
|
+
process.exit(1);
|
|
504
|
+
}
|
|
505
|
+
if (tools.length === 0) {
|
|
506
|
+
console.log(`No tools found on MCP server "${serverName}".`);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
console.log(`
|
|
510
|
+
Tools on "${serverName}":`);
|
|
511
|
+
console.log("\u2500".repeat(60));
|
|
512
|
+
for (const t of tools) {
|
|
513
|
+
console.log(` ${t.name}`);
|
|
514
|
+
if (t.description) console.log(` ${t.description}`);
|
|
515
|
+
}
|
|
516
|
+
console.log();
|
|
517
|
+
});
|
|
518
|
+
mcp.command("run <server> <tool> [args...]").description("Call a tool on a MCP server").action(async (serverName, toolName, args) => {
|
|
519
|
+
const cfg = getConfig();
|
|
520
|
+
const client = new ServerClient(cfg);
|
|
521
|
+
let entry;
|
|
522
|
+
try {
|
|
523
|
+
entry = await client.getMCP(serverName);
|
|
524
|
+
} catch {
|
|
525
|
+
console.error(`Unknown MCP server: ${serverName}`);
|
|
526
|
+
console.error("Run `ucli mcp list` to see available servers.");
|
|
527
|
+
process.exit(1);
|
|
528
|
+
}
|
|
529
|
+
try {
|
|
530
|
+
await runMcpTool(entry, toolName, args);
|
|
531
|
+
} catch (err) {
|
|
532
|
+
console.error("Tool execution failed:", err.message);
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// src/index.ts
|
|
539
|
+
var require3 = createRequire2(import.meta.url);
|
|
540
|
+
var pkg = require3("../package.json");
|
|
541
|
+
var program = new Command();
|
|
542
|
+
program.name("ucli").description(pkg.description).version(pkg.version, "-v, --version").addHelpCommand(false);
|
|
543
|
+
registerConfigure(program);
|
|
544
|
+
registerServices(program);
|
|
545
|
+
registerRun(program);
|
|
546
|
+
registerRefresh(program);
|
|
547
|
+
registerMcp(program);
|
|
548
|
+
registerHelp(program);
|
|
549
|
+
program.parse(process.argv);
|
|
550
|
+
//# sourceMappingURL=index.js.map
|