@tronsfey/ucli 0.4.3 → 0.5.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/dist/index.js CHANGED
@@ -154,23 +154,71 @@ var ServerClient = class {
154
154
  }
155
155
  };
156
156
 
157
+ // src/lib/exit-codes.ts
158
+ var ExitCode = {
159
+ SUCCESS: 0,
160
+ GENERAL_ERROR: 1,
161
+ USAGE_ERROR: 2,
162
+ CONFIG_ERROR: 3,
163
+ AUTH_ERROR: 4,
164
+ CONNECTIVITY_ERROR: 5,
165
+ NOT_FOUND: 6,
166
+ SERVER_ERROR: 7
167
+ };
168
+
169
+ // src/lib/output.ts
170
+ var currentMode = "text";
171
+ function setOutputMode(mode) {
172
+ currentMode = mode;
173
+ }
174
+ function isJsonOutput() {
175
+ return currentMode === "json";
176
+ }
177
+ function outputSuccess(data) {
178
+ if (currentMode === "json") {
179
+ console.log(JSON.stringify({ success: true, data }, null, 2));
180
+ }
181
+ }
182
+ function outputError(code, message, hint) {
183
+ if (currentMode === "json") {
184
+ const envelope = {
185
+ success: false,
186
+ error: { code, message, ...hint ? { hint } : {} }
187
+ };
188
+ console.log(JSON.stringify(envelope, null, 2));
189
+ process.exit(code);
190
+ }
191
+ console.error(`
192
+ \u2716 Error: ${message}`);
193
+ if (hint) console.error(` Hint: ${hint}`);
194
+ process.exit(code);
195
+ }
196
+
157
197
  // src/commands/configure.ts
158
198
  function registerConfigure(program2) {
159
199
  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) => {
160
200
  const serverUrl = opts.server.replace(/\/$/, "");
161
201
  const token = opts.token;
162
- console.log(`Connecting to ${serverUrl}...`);
202
+ if (!isJsonOutput()) {
203
+ console.log(`Connecting to ${serverUrl}...`);
204
+ }
163
205
  const client = new ServerClient({ serverUrl, token });
164
206
  try {
165
207
  await client.listOAS();
166
208
  saveConfig({ serverUrl, token });
209
+ if (isJsonOutput()) {
210
+ outputSuccess({ serverUrl, configured: true });
211
+ return;
212
+ }
167
213
  console.log("\u2713 Configuration saved successfully.");
168
214
  console.log(` Server: ${serverUrl}`);
169
215
  console.log(` Token: ${token.slice(0, 20)}...`);
170
216
  } catch (err) {
171
- console.error("Connection failed:", err.message);
172
- console.error("Please check the server URL and token.");
173
- process.exit(1);
217
+ outputError(
218
+ ExitCode.CONNECTIVITY_ERROR,
219
+ `Connection failed: ${err.message}`,
220
+ "Check the server URL and token"
221
+ );
174
222
  }
175
223
  });
176
224
  }
@@ -374,6 +422,68 @@ async function getServiceHelp(entry) {
374
422
  });
375
423
  }
376
424
 
425
+ // src/lib/yaml.ts
426
+ function toYaml(value, indent = 0) {
427
+ if (value === null || value === void 0) {
428
+ return "null";
429
+ }
430
+ if (typeof value === "boolean") {
431
+ return value ? "true" : "false";
432
+ }
433
+ if (typeof value === "number") {
434
+ return String(value);
435
+ }
436
+ if (typeof value === "string") {
437
+ if (needsQuoting(value)) {
438
+ return JSON.stringify(value);
439
+ }
440
+ return value;
441
+ }
442
+ if (Array.isArray(value)) {
443
+ if (value.length === 0) return "[]";
444
+ const prefix = " ".repeat(indent);
445
+ return value.map((item) => {
446
+ const serialised = toYaml(item, indent + 2);
447
+ if (typeof item === "object" && item !== null && !Array.isArray(item)) {
448
+ const firstNewline = serialised.indexOf("\n");
449
+ if (firstNewline === -1) {
450
+ return `${prefix}- ${serialised.trimStart()}`;
451
+ }
452
+ const firstLine = serialised.slice(0, firstNewline);
453
+ const rest = serialised.slice(firstNewline + 1);
454
+ return `${prefix}- ${firstLine.trimStart()}
455
+ ${rest}`;
456
+ }
457
+ return `${prefix}- ${serialised}`;
458
+ }).join("\n");
459
+ }
460
+ if (typeof value === "object") {
461
+ const entries = Object.entries(value);
462
+ if (entries.length === 0) return "{}";
463
+ const prefix = " ".repeat(indent);
464
+ return entries.map(([key, val]) => {
465
+ if (val === null || val === void 0) {
466
+ return `${prefix}${key}: null`;
467
+ }
468
+ if (typeof val === "object") {
469
+ const nested = toYaml(val, indent + 2);
470
+ return `${prefix}${key}:
471
+ ${nested}`;
472
+ }
473
+ return `${prefix}${key}: ${toYaml(val, indent)}`;
474
+ }).join("\n");
475
+ }
476
+ return String(value);
477
+ }
478
+ function needsQuoting(s) {
479
+ if (s === "") return true;
480
+ if (s === "true" || s === "false" || s === "null") return true;
481
+ if (/^\d/.test(s)) return true;
482
+ if (/[:{}\[\],&*#?|<>=!%@`'"\\\n\r\t]/.test(s)) return true;
483
+ if (s.startsWith(" ") || s.endsWith(" ")) return true;
484
+ return false;
485
+ }
486
+
377
487
  // src/commands/services.ts
378
488
  function registerServices(program2) {
379
489
  const services = program2.command("services").description("Manage and inspect available OAS services");
@@ -392,19 +502,27 @@ function registerServices(program2) {
392
502
  await writeOASListCache(entries, maxTtl);
393
503
  }
394
504
  }
505
+ const safe = entries.map(({ authConfig, ...rest }) => ({
506
+ ...rest,
507
+ authConfig: { type: authConfig["type"] ?? rest.authType }
508
+ }));
509
+ if (isJsonOutput()) {
510
+ outputSuccess(safe);
511
+ return;
512
+ }
395
513
  const format = (opts.format ?? "table").toLowerCase();
396
514
  if (entries.length === 0) {
397
515
  console.log("No services registered in this group.");
398
516
  return;
399
517
  }
400
518
  if (format === "json") {
401
- const safe = entries.map(({ authConfig, ...rest }) => ({
402
- ...rest,
403
- authConfig: { type: authConfig["type"] ?? rest.authType }
404
- }));
405
519
  console.log(JSON.stringify(safe, null, 2));
406
520
  return;
407
521
  }
522
+ if (format === "yaml") {
523
+ console.log(toYaml(safe));
524
+ return;
525
+ }
408
526
  const nameWidth = Math.max(10, ...entries.map((e) => e.name.length));
409
527
  console.log(`
410
528
  ${"SERVICE".padEnd(nameWidth)} AUTH DESCRIPTION`);
@@ -422,23 +540,33 @@ ${"SERVICE".padEnd(nameWidth)} AUTH DESCRIPTION`);
422
540
  let entry;
423
541
  try {
424
542
  entry = await client.getOAS(name);
425
- } catch (err) {
426
- console.error(`Service not found: ${name}`);
427
- console.error("Run `ucli services list` to see available services.");
428
- process.exit(1);
543
+ } catch {
544
+ outputError(
545
+ ExitCode.NOT_FOUND,
546
+ `Service not found: ${name}`,
547
+ "Run: ucli services list to see available services"
548
+ );
429
549
  }
430
550
  const help = await getServiceHelp(entry);
551
+ const { authConfig, ...rest } = entry;
552
+ const safe = {
553
+ ...rest,
554
+ authConfig: { type: authConfig["type"] ?? rest.authType },
555
+ operationsHelp: help
556
+ };
557
+ if (isJsonOutput()) {
558
+ outputSuccess(safe);
559
+ return;
560
+ }
431
561
  const format = (opts.format ?? "table").toLowerCase();
432
562
  if (format === "json") {
433
- const { authConfig, ...rest } = entry;
434
- const safe = {
435
- ...rest,
436
- authConfig: { type: authConfig["type"] ?? rest.authType },
437
- operationsHelp: help
438
- };
439
563
  console.log(JSON.stringify(safe, null, 2));
440
564
  return;
441
565
  }
566
+ if (format === "yaml") {
567
+ console.log(toYaml(safe));
568
+ return;
569
+ }
442
570
  console.log(`
443
571
  Service: ${entry.name}`);
444
572
  console.log(`Description: ${entry.description || "(none)"}`);
@@ -456,13 +584,18 @@ Service: ${entry.name}`);
456
584
  function registerRun(program2) {
457
585
  program2.command("run [service] [args...]").description("Execute an operation on a service").option("--service <name>", "Service name (from `services list`)").option("--operation <id>", "OperationId to execute").option("--params <json>", "JSON string of operation parameters").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 (serviceArg, args, opts) => {
458
586
  if (serviceArg && opts.service && serviceArg !== opts.service) {
459
- console.error(`Conflicting service values: positional "${serviceArg}" and --service "${opts.service}". Use either the positional argument or --service flag, not both.`);
460
- process.exit(1);
587
+ outputError(
588
+ ExitCode.USAGE_ERROR,
589
+ `Conflicting service values: positional "${serviceArg}" and --service "${opts.service}". Use either the positional argument or --service flag, not both.`
590
+ );
461
591
  }
462
592
  const service = opts.service ?? serviceArg;
463
593
  if (!service) {
464
- console.error("Missing service name. Use positional <service> or --service <name>.");
465
- process.exit(1);
594
+ outputError(
595
+ ExitCode.USAGE_ERROR,
596
+ "Missing service name. Use positional <service> or --service <name>.",
597
+ "Run: ucli services list to see available services"
598
+ );
466
599
  }
467
600
  const cfg = getConfig();
468
601
  const client = new ServerClient(cfg);
@@ -470,9 +603,11 @@ function registerRun(program2) {
470
603
  try {
471
604
  entry = await client.getOAS(service);
472
605
  } catch {
473
- console.error(`Unknown service: ${service}`);
474
- console.error("Run `ucli services list` to see available services.");
475
- process.exit(1);
606
+ outputError(
607
+ ExitCode.NOT_FOUND,
608
+ `Unknown service: ${service}`,
609
+ "Run: ucli services list to see available services"
610
+ );
476
611
  }
477
612
  const extraArgs = opts.args ?? [];
478
613
  const operationArgs = [...args, ...extraArgs];
@@ -484,8 +619,11 @@ function registerRun(program2) {
484
619
  try {
485
620
  parsed = JSON.parse(opts.params);
486
621
  } catch {
487
- console.error(`Invalid --params JSON. Example: --params '{"petId": 1}'`);
488
- process.exit(1);
622
+ outputError(
623
+ ExitCode.USAGE_ERROR,
624
+ "Invalid --params JSON.",
625
+ `Example: --params '{"petId": 1}'`
626
+ );
489
627
  }
490
628
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
491
629
  for (const [k, v] of Object.entries(parsed)) {
@@ -508,8 +646,10 @@ function registerRun(program2) {
508
646
  ...query !== void 0 ? { query } : {}
509
647
  });
510
648
  } catch (err) {
511
- console.error("Operation failed:", err.message);
512
- process.exit(1);
649
+ outputError(
650
+ ExitCode.GENERAL_ERROR,
651
+ `Operation failed: ${err.message}`
652
+ );
513
653
  }
514
654
  });
515
655
  }
@@ -519,7 +659,9 @@ function registerRefresh(program2) {
519
659
  program2.command("refresh").description("Force-refresh the local OAS cache from the server").option("--service <name>", "Refresh only a specific service").action(async (opts) => {
520
660
  const cfg = getConfig();
521
661
  const client = new ServerClient(cfg);
522
- console.log("Refreshing OAS list from server...");
662
+ if (!isJsonOutput()) {
663
+ console.log("Refreshing OAS list from server...");
664
+ }
523
665
  if (opts.service) {
524
666
  await clearOASCache(opts.service);
525
667
  } else {
@@ -530,6 +672,10 @@ function registerRefresh(program2) {
530
672
  const maxTtl = Math.min(...entries.map((e) => e.cacheTtl));
531
673
  await writeOASListCache(entries, maxTtl);
532
674
  }
675
+ if (isJsonOutput()) {
676
+ outputSuccess({ refreshed: entries.length });
677
+ return;
678
+ }
533
679
  console.log(`\u2713 Refreshed ${entries.length} service(s).`);
534
680
  });
535
681
  }
@@ -570,7 +716,7 @@ function registerHelp(program2) {
570
716
  } catch {
571
717
  console.error(`Unknown service: ${service}`);
572
718
  console.error("Run `ucli services list` to see available services.");
573
- process.exit(1);
719
+ process.exit(ExitCode.NOT_FOUND);
574
720
  }
575
721
  console.log(`
576
722
  === ${entry.name} ===`);
@@ -604,6 +750,10 @@ DISCOVERY
604
750
  ucli services info <service>
605
751
  Show detailed service info and all available operations.
606
752
 
753
+ ucli introspect
754
+ Return complete capability manifest in a single call (JSON).
755
+ Ideal for AI agents: includes services, MCP servers, and command reference.
756
+
607
757
  ucli help [service]
608
758
  Show this guide, or service-specific operations.
609
759
 
@@ -616,15 +766,44 @@ EXECUTION
616
766
  --query <jmespath> Filter response with JMESPath
617
767
  --data <json|@file> Request body for POST/PUT/PATCH
618
768
 
769
+ MCP SERVERS
770
+ ucli mcp list
771
+ List all MCP servers in your group.
772
+
773
+ ucli mcp tools <server>
774
+ List tools available on a MCP server.
775
+
776
+ ucli mcp run <server> <tool> [key=value ...]
777
+ Call a tool on a MCP server.
778
+
619
779
  MAINTENANCE
620
780
  ucli refresh
621
781
  Force-refresh the local OAS cache from the server.
622
782
 
783
+ ucli doctor
784
+ Check configuration, server connectivity, and token validity.
785
+
786
+ SHELL COMPLETIONS
787
+ eval "$(ucli completions bash)"
788
+ eval "$(ucli completions zsh)"
789
+ ucli completions fish | source
790
+
791
+ GLOBAL FLAGS
792
+ --debug Enable verbose debug logging
793
+ --output json Wrap ALL output in structured JSON envelopes
794
+ (for agent/automation consumption)
795
+ -v, --version Show version number
796
+
623
797
  ERRORS
624
798
  401 Unauthorized \u2192 Run: ucli configure --server <url> --token <jwt>
625
799
  404 Not Found \u2192 Check service name: ucli services list
626
800
  4xx Client Error \u2192 Check operation args: ucli services info <service>
627
801
  5xx Server Error \u2192 Retry or run: ucli refresh
802
+
803
+ AI AGENT QUICK START
804
+ 1. ucli introspect # discover everything in one call
805
+ 2. ucli run <svc> <op> [args] # execute operations
806
+ 3. Use --output json globally # get structured { success, data/error } envelopes
628
807
  `);
629
808
  }
630
809
 
@@ -635,8 +814,8 @@ function resolve(mod, name) {
635
814
  throw new Error(`Cannot resolve export "${name}" from module`);
636
815
  }
637
816
  async function getMcp2cli() {
638
- const clientMod = await import("./client-3I7XBDJU.js");
639
- const runnerMod = await import("./runner-GVYIJNHN.js");
817
+ const clientMod = await import("./client-LCWUZRZX.js");
818
+ const runnerMod = await import("./runner-HH357SRR.js");
640
819
  return {
641
820
  createMcpClient: resolve(clientMod, "createMcpClient"),
642
821
  getTools: resolve(runnerMod, "getTools"),
@@ -706,19 +885,27 @@ function registerMcp(program2) {
706
885
  const cfg = getConfig();
707
886
  const client = new ServerClient(cfg);
708
887
  const entries = await client.listMCP();
888
+ const safe = entries.map(({ authConfig, ...rest }) => ({
889
+ ...rest,
890
+ authConfig: { type: authConfig.type }
891
+ }));
892
+ if (isJsonOutput()) {
893
+ outputSuccess(safe);
894
+ return;
895
+ }
709
896
  if (entries.length === 0) {
710
897
  console.log("No MCP servers registered in this group.");
711
898
  return;
712
899
  }
713
900
  const format = (opts.format ?? "table").toLowerCase();
714
901
  if (format === "json") {
715
- const safe = entries.map(({ authConfig, ...rest }) => ({
716
- ...rest,
717
- authConfig: { type: authConfig.type }
718
- }));
719
902
  console.log(JSON.stringify(safe, null, 2));
720
903
  return;
721
904
  }
905
+ if (format === "yaml") {
906
+ console.log(toYaml(safe));
907
+ return;
908
+ }
722
909
  const nameWidth = Math.max(10, ...entries.map((e) => e.name.length));
723
910
  console.log(`
724
911
  ${"SERVER".padEnd(nameWidth)} TRANSPORT DESCRIPTION`);
@@ -729,23 +916,28 @@ ${"SERVER".padEnd(nameWidth)} TRANSPORT DESCRIPTION`);
729
916
  }
730
917
  console.log();
731
918
  });
732
- mcp.command("tools <server>").description("List tools available on a MCP server").option("--format <fmt>", "Output format: table | json", "table").action(async (serverName, opts) => {
919
+ mcp.command("tools <server>").description("List tools available on a MCP server").option("--format <fmt>", "Output format: table | json | yaml", "table").action(async (serverName, opts) => {
733
920
  const cfg = getConfig();
734
921
  const client = new ServerClient(cfg);
735
922
  let entry;
736
923
  try {
737
924
  entry = await client.getMCP(serverName);
738
925
  } catch {
739
- console.error(`Unknown MCP server: ${serverName}`);
740
- console.error("Run `ucli mcp list` to see available servers.");
741
- process.exit(1);
926
+ outputError(
927
+ ExitCode.NOT_FOUND,
928
+ `Unknown MCP server: ${serverName}`,
929
+ "Run: ucli mcp list to see available servers"
930
+ );
742
931
  }
743
932
  let tools;
744
933
  try {
745
934
  tools = await listMcpTools(entry);
746
935
  } catch (err) {
747
- console.error("Failed to fetch tools:", err.message);
748
- process.exit(1);
936
+ outputError(ExitCode.GENERAL_ERROR, `Failed to fetch tools: ${err.message}`);
937
+ }
938
+ if (isJsonOutput()) {
939
+ outputSuccess(tools);
940
+ return;
749
941
  }
750
942
  if (tools.length === 0) {
751
943
  console.log(`No tools found on MCP server "${serverName}".`);
@@ -756,6 +948,10 @@ ${"SERVER".padEnd(nameWidth)} TRANSPORT DESCRIPTION`);
756
948
  console.log(JSON.stringify(tools, null, 2));
757
949
  return;
758
950
  }
951
+ if (format === "yaml") {
952
+ console.log(toYaml(tools));
953
+ return;
954
+ }
759
955
  console.log(`
760
956
  Tools on "${serverName}":`);
761
957
  console.log("\u2500".repeat(60));
@@ -772,29 +968,441 @@ Tools on "${serverName}":`);
772
968
  try {
773
969
  entry = await client.getMCP(serverName);
774
970
  } catch {
775
- console.error(`Unknown MCP server: ${serverName}`);
776
- console.error("Run `ucli mcp list` to see available servers.");
777
- process.exit(1);
971
+ outputError(
972
+ ExitCode.NOT_FOUND,
973
+ `Unknown MCP server: ${serverName}`,
974
+ "Run: ucli mcp list to see available servers"
975
+ );
778
976
  }
779
977
  try {
780
978
  await runMcpTool(entry, toolName, args);
781
979
  } catch (err) {
782
- console.error("Tool execution failed:", err.message);
783
- process.exit(1);
980
+ outputError(ExitCode.GENERAL_ERROR, `Tool execution failed: ${err.message}`);
784
981
  }
785
982
  });
786
983
  }
787
984
 
985
+ // src/lib/errors.ts
986
+ var debugEnabled = false;
987
+ function setDebugMode(enabled) {
988
+ debugEnabled = enabled;
989
+ }
990
+ function debug(message) {
991
+ if (debugEnabled) {
992
+ console.error(`[DEBUG] ${message}`);
993
+ }
994
+ }
995
+ var HINT_MAP = {
996
+ [ExitCode.CONFIG_ERROR]: "Run: ucli configure --server <url> --token <jwt>",
997
+ [ExitCode.AUTH_ERROR]: "Your token may be expired or revoked. Run: ucli configure --server <url> --token <jwt>",
998
+ [ExitCode.CONNECTIVITY_ERROR]: "Check that the server URL is correct and the server is running. Run: ucli doctor",
999
+ [ExitCode.NOT_FOUND]: "Check the resource name. Run: ucli services list or ucli mcp list",
1000
+ [ExitCode.SERVER_ERROR]: "The server returned an unexpected error. Try again or run: ucli doctor"
1001
+ };
1002
+
1003
+ // src/commands/doctor.ts
1004
+ import axios2 from "axios";
1005
+ function registerDoctor(program2) {
1006
+ program2.command("doctor").description("Check configuration, server connectivity, and token validity").action(async () => {
1007
+ const results = [];
1008
+ debug("Checking configuration...");
1009
+ if (!isConfigured()) {
1010
+ results.push({
1011
+ name: "Configuration",
1012
+ ok: false,
1013
+ detail: "Not configured. Run: ucli configure --server <url> --token <jwt>"
1014
+ });
1015
+ outputError(
1016
+ ExitCode.CONFIG_ERROR,
1017
+ "Not configured",
1018
+ "Run: ucli configure --server <url> --token <jwt>"
1019
+ );
1020
+ }
1021
+ let cfg;
1022
+ try {
1023
+ cfg = getConfig();
1024
+ results.push({
1025
+ name: "Configuration",
1026
+ ok: true,
1027
+ detail: `Server: ${cfg.serverUrl}`
1028
+ });
1029
+ } catch (err) {
1030
+ results.push({
1031
+ name: "Configuration",
1032
+ ok: false,
1033
+ detail: `Failed to read config: ${err.message}`
1034
+ });
1035
+ outputError(
1036
+ ExitCode.CONFIG_ERROR,
1037
+ `Failed to read config: ${err.message}`,
1038
+ "Run: ucli configure --server <url> --token <jwt>"
1039
+ );
1040
+ }
1041
+ debug(`Checking connectivity to ${cfg.serverUrl}...`);
1042
+ try {
1043
+ const healthUrl = `${cfg.serverUrl}/api/v1/health`;
1044
+ const resp = await axios2.get(healthUrl, { timeout: 1e4 });
1045
+ results.push({
1046
+ name: "Server connectivity",
1047
+ ok: resp.status === 200,
1048
+ detail: resp.status === 200 ? `Health endpoint OK (${cfg.serverUrl})` : `Unexpected status: ${resp.status}`
1049
+ });
1050
+ } catch (err) {
1051
+ const msg = axios2.isAxiosError(err) ? err.code ?? err.message : err.message;
1052
+ results.push({
1053
+ name: "Server connectivity",
1054
+ ok: false,
1055
+ detail: `Cannot reach server: ${msg}`
1056
+ });
1057
+ }
1058
+ debug("Validating JWT token...");
1059
+ try {
1060
+ const client = new ServerClient(cfg);
1061
+ await client.listOAS();
1062
+ results.push({
1063
+ name: "Authentication",
1064
+ ok: true,
1065
+ detail: "Token accepted by server"
1066
+ });
1067
+ } catch (err) {
1068
+ const msg = err.message;
1069
+ results.push({
1070
+ name: "Authentication",
1071
+ ok: false,
1072
+ detail: `Token rejected: ${msg}`
1073
+ });
1074
+ }
1075
+ const allOk = results.every((r) => r.ok);
1076
+ if (isJsonOutput()) {
1077
+ outputSuccess({
1078
+ healthy: allOk,
1079
+ checks: results
1080
+ });
1081
+ process.exit(allOk ? ExitCode.SUCCESS : ExitCode.GENERAL_ERROR);
1082
+ }
1083
+ printResults(results);
1084
+ process.exit(allOk ? ExitCode.SUCCESS : ExitCode.GENERAL_ERROR);
1085
+ });
1086
+ }
1087
+ function printResults(results) {
1088
+ console.log("\nucli doctor\n" + "\u2550".repeat(40));
1089
+ for (const r of results) {
1090
+ const icon = r.ok ? "\u2713" : "\u2716";
1091
+ console.log(` ${icon} ${r.name}: ${r.detail}`);
1092
+ }
1093
+ console.log();
1094
+ }
1095
+
1096
+ // src/commands/completions.ts
1097
+ function registerCompletions(program2) {
1098
+ program2.command("completions <shell>").description("Generate shell completion script (bash | zsh | fish)").action((shell) => {
1099
+ const normalized = shell.toLowerCase().trim();
1100
+ switch (normalized) {
1101
+ case "bash":
1102
+ console.log(bashCompletions());
1103
+ break;
1104
+ case "zsh":
1105
+ console.log(zshCompletions());
1106
+ break;
1107
+ case "fish":
1108
+ console.log(fishCompletions());
1109
+ break;
1110
+ default:
1111
+ console.error(`Unsupported shell: ${shell}. Supported: bash, zsh, fish`);
1112
+ process.exit(ExitCode.USAGE_ERROR);
1113
+ }
1114
+ });
1115
+ }
1116
+ function bashCompletions() {
1117
+ return `# ucli bash completions \u2014 eval "$(ucli completions bash)"
1118
+ _ucli_completions() {
1119
+ local cur prev commands
1120
+ COMPREPLY=()
1121
+ cur="\${COMP_WORDS[COMP_CWORD]}"
1122
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
1123
+ commands="configure services run refresh help mcp doctor completions introspect"
1124
+
1125
+ case "\${COMP_WORDS[1]}" in
1126
+ services)
1127
+ COMPREPLY=( $(compgen -W "list info" -- "$cur") )
1128
+ return 0
1129
+ ;;
1130
+ mcp)
1131
+ COMPREPLY=( $(compgen -W "list tools run" -- "$cur") )
1132
+ return 0
1133
+ ;;
1134
+ completions)
1135
+ COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") )
1136
+ return 0
1137
+ ;;
1138
+ run|help)
1139
+ # dynamic completions would require server calls; skip for now
1140
+ return 0
1141
+ ;;
1142
+ esac
1143
+
1144
+ if [ "$COMP_CWORD" -eq 1 ]; then
1145
+ COMPREPLY=( $(compgen -W "$commands" -- "$cur") )
1146
+ fi
1147
+
1148
+ return 0
1149
+ }
1150
+ complete -F _ucli_completions ucli`;
1151
+ }
1152
+ function zshCompletions() {
1153
+ return `# ucli zsh completions \u2014 eval "$(ucli completions zsh)"
1154
+ #compdef ucli
1155
+
1156
+ _ucli() {
1157
+ local -a commands
1158
+ commands=(
1159
+ 'configure:Configure server URL and authentication token'
1160
+ 'services:Manage and inspect available OAS services'
1161
+ 'run:Execute an operation on a service'
1162
+ 'refresh:Force-refresh the local OAS cache'
1163
+ 'help:Show usage guide'
1164
+ 'mcp:Interact with MCP servers'
1165
+ 'doctor:Check configuration and connectivity'
1166
+ 'completions:Generate shell completion script'
1167
+ 'introspect:Return complete capability manifest for AI agents'
1168
+ )
1169
+
1170
+ _arguments -C \\
1171
+ '1: :->command' \\
1172
+ '*::arg:->args'
1173
+
1174
+ case $state in
1175
+ command)
1176
+ _describe -t commands 'ucli commands' commands
1177
+ ;;
1178
+ args)
1179
+ case $words[1] in
1180
+ services)
1181
+ _values 'subcommand' 'list[List all OAS services]' 'info[Show service details]'
1182
+ ;;
1183
+ mcp)
1184
+ _values 'subcommand' 'list[List MCP servers]' 'tools[List tools]' 'run[Call a tool]'
1185
+ ;;
1186
+ completions)
1187
+ _values 'shell' bash zsh fish
1188
+ ;;
1189
+ esac
1190
+ ;;
1191
+ esac
1192
+ }
1193
+
1194
+ _ucli "$@"`;
1195
+ }
1196
+ function fishCompletions() {
1197
+ return `# ucli fish completions \u2014 ucli completions fish | source
1198
+ complete -c ucli -e
1199
+
1200
+ # Top-level commands
1201
+ complete -c ucli -n __fish_use_subcommand -a configure -d 'Configure server URL and token'
1202
+ complete -c ucli -n __fish_use_subcommand -a services -d 'Manage OAS services'
1203
+ complete -c ucli -n __fish_use_subcommand -a run -d 'Execute a service operation'
1204
+ complete -c ucli -n __fish_use_subcommand -a refresh -d 'Refresh local cache'
1205
+ complete -c ucli -n __fish_use_subcommand -a help -d 'Show usage guide'
1206
+ complete -c ucli -n __fish_use_subcommand -a mcp -d 'Interact with MCP servers'
1207
+ complete -c ucli -n __fish_use_subcommand -a doctor -d 'Check config and connectivity'
1208
+ complete -c ucli -n __fish_use_subcommand -a completions -d 'Generate shell completions'
1209
+
1210
+ complete -c ucli -n __fish_use_subcommand -a introspect -d 'Return capability manifest for AI agents'
1211
+
1212
+ # services subcommands
1213
+ complete -c ucli -n '__fish_seen_subcommand_from services' -a list -d 'List services'
1214
+ complete -c ucli -n '__fish_seen_subcommand_from services' -a info -d 'Show service details'
1215
+
1216
+ # mcp subcommands
1217
+ complete -c ucli -n '__fish_seen_subcommand_from mcp' -a list -d 'List MCP servers'
1218
+ complete -c ucli -n '__fish_seen_subcommand_from mcp' -a tools -d 'List tools'
1219
+ complete -c ucli -n '__fish_seen_subcommand_from mcp' -a run -d 'Call a tool'
1220
+
1221
+ # completions subcommands
1222
+ complete -c ucli -n '__fish_seen_subcommand_from completions' -a 'bash zsh fish' -d 'Shell type'`;
1223
+ }
1224
+
1225
+ // src/commands/introspect.ts
1226
+ function registerIntrospect(program2) {
1227
+ program2.command("introspect").description("Return a complete capability manifest for AI agent discovery (services, MCP servers, commands)").option("--format <fmt>", "Output format: json | yaml", "json").action(async (opts) => {
1228
+ const cfg = getConfig();
1229
+ const client = new ServerClient(cfg);
1230
+ let oasEntries = [];
1231
+ let mcpEntries = [];
1232
+ try {
1233
+ ;
1234
+ [oasEntries, mcpEntries] = await Promise.all([
1235
+ client.listOAS(),
1236
+ client.listMCP()
1237
+ ]);
1238
+ } catch (err) {
1239
+ const message = err.message;
1240
+ outputError(
1241
+ ExitCode.CONNECTIVITY_ERROR,
1242
+ `Failed to fetch capabilities: ${message}`,
1243
+ "Check server connectivity with: ucli doctor"
1244
+ );
1245
+ }
1246
+ const manifest = {
1247
+ version: "1",
1248
+ services: oasEntries.map(toIntrospectService),
1249
+ mcpServers: mcpEntries.map(toIntrospectMcpServer),
1250
+ commands: getCommandReference()
1251
+ };
1252
+ const format = (opts.format ?? "json").toLowerCase();
1253
+ if (isJsonOutput()) {
1254
+ outputSuccess(manifest);
1255
+ return;
1256
+ }
1257
+ if (format === "json") {
1258
+ console.log(JSON.stringify(manifest, null, 2));
1259
+ return;
1260
+ }
1261
+ if (format === "yaml") {
1262
+ console.log(toYaml(manifest));
1263
+ return;
1264
+ }
1265
+ console.log(JSON.stringify(manifest, null, 2));
1266
+ });
1267
+ }
1268
+ function toIntrospectService(e) {
1269
+ return {
1270
+ name: e.name,
1271
+ description: e.description,
1272
+ authType: e.authType,
1273
+ remoteUrl: e.remoteUrl,
1274
+ baseEndpoint: e.baseEndpoint,
1275
+ cacheTtl: e.cacheTtl
1276
+ };
1277
+ }
1278
+ function toIntrospectMcpServer(e) {
1279
+ return {
1280
+ name: e.name,
1281
+ description: e.description,
1282
+ transport: e.transport,
1283
+ enabled: e.enabled
1284
+ };
1285
+ }
1286
+ function getCommandReference() {
1287
+ return [
1288
+ {
1289
+ name: "services list",
1290
+ description: "List all OAS services available in the current group",
1291
+ usage: "ucli services list [--format json|table|yaml] [--refresh]",
1292
+ examples: [
1293
+ "ucli services list",
1294
+ "ucli services list --format json",
1295
+ "ucli services list --refresh"
1296
+ ]
1297
+ },
1298
+ {
1299
+ name: "services info",
1300
+ description: "Show detailed information and available operations for a service",
1301
+ usage: "ucli services info <name> [--format json|table|yaml]",
1302
+ examples: [
1303
+ "ucli services info payments",
1304
+ "ucli services info payments --format json"
1305
+ ]
1306
+ },
1307
+ {
1308
+ name: "run",
1309
+ description: "Execute an operation on an OAS service",
1310
+ usage: "ucli run <service> <operation> [--format json|table|yaml] [--query <jmespath>] [--data <json|@file>] [--params <json>]",
1311
+ examples: [
1312
+ "ucli run payments listTransactions",
1313
+ "ucli run payments getTransaction --transactionId txn_123",
1314
+ `ucli run payments createCharge --data '{"amount": 5000, "currency": "USD"}'`,
1315
+ 'ucli run inventory listProducts --query "items[?stock > `0`].name"'
1316
+ ]
1317
+ },
1318
+ {
1319
+ name: "mcp list",
1320
+ description: "List all MCP servers available in the current group",
1321
+ usage: "ucli mcp list [--format json|table|yaml]",
1322
+ examples: [
1323
+ "ucli mcp list",
1324
+ "ucli mcp list --format json"
1325
+ ]
1326
+ },
1327
+ {
1328
+ name: "mcp tools",
1329
+ description: "List tools available on a MCP server",
1330
+ usage: "ucli mcp tools <server> [--format json|table|yaml]",
1331
+ examples: [
1332
+ "ucli mcp tools weather",
1333
+ "ucli mcp tools weather --format json"
1334
+ ]
1335
+ },
1336
+ {
1337
+ name: "mcp run",
1338
+ description: "Call a tool on a MCP server",
1339
+ usage: "ucli mcp run <server> <tool> [key=value ...]",
1340
+ examples: [
1341
+ 'ucli mcp run weather get_forecast location="New York"',
1342
+ 'ucli mcp run search web_search query="ucli docs" limit=5'
1343
+ ]
1344
+ },
1345
+ {
1346
+ name: "introspect",
1347
+ description: "Return complete capability manifest (this command)",
1348
+ usage: "ucli introspect [--format json|yaml]",
1349
+ examples: [
1350
+ "ucli introspect",
1351
+ "ucli introspect --format yaml"
1352
+ ]
1353
+ },
1354
+ {
1355
+ name: "refresh",
1356
+ description: "Force-refresh the local OAS cache from the server",
1357
+ usage: "ucli refresh [--service <name>]",
1358
+ examples: [
1359
+ "ucli refresh",
1360
+ "ucli refresh --service payments"
1361
+ ]
1362
+ },
1363
+ {
1364
+ name: "doctor",
1365
+ description: "Check configuration, server connectivity, and token validity",
1366
+ usage: "ucli doctor",
1367
+ examples: ["ucli doctor"]
1368
+ },
1369
+ {
1370
+ name: "configure",
1371
+ description: "Configure the server URL and authentication token",
1372
+ usage: "ucli configure --server <url> --token <jwt>",
1373
+ examples: ["ucli configure --server https://oas.example.com --token eyJ..."]
1374
+ }
1375
+ ];
1376
+ }
1377
+
788
1378
  // src/index.ts
789
1379
  var require3 = createRequire2(import.meta.url);
790
1380
  var pkg = require3("../package.json");
791
1381
  var program = new Command();
792
- program.name("ucli").description(pkg.description).version(pkg.version, "-v, --version").addHelpCommand(false);
1382
+ program.name("ucli").description(pkg.description).version(pkg.version, "-v, --version").option("--debug", "Enable verbose debug logging").option("--output <mode>", "Output mode: text | json (json wraps every result in a structured envelope for agent consumption)", "text").addHelpCommand(false).hook("preAction", (_thisCommand, actionCommand) => {
1383
+ let cmd = actionCommand;
1384
+ while (cmd) {
1385
+ const opts = cmd.opts();
1386
+ if (opts.debug) {
1387
+ setDebugMode(true);
1388
+ }
1389
+ if (opts.output && typeof opts.output === "string") {
1390
+ const mode = opts.output.toLowerCase();
1391
+ if (mode === "json" || mode === "text") {
1392
+ setOutputMode(mode);
1393
+ }
1394
+ }
1395
+ cmd = cmd.parent;
1396
+ }
1397
+ });
793
1398
  registerConfigure(program);
794
1399
  registerServices(program);
795
1400
  registerRun(program);
796
1401
  registerRefresh(program);
797
1402
  registerMcp(program);
1403
+ registerDoctor(program);
1404
+ registerCompletions(program);
1405
+ registerIntrospect(program);
798
1406
  registerHelp(program);
799
1407
  program.parse(process.argv);
800
1408
  //# sourceMappingURL=index.js.map