@tronsfey/ucli 0.5.0 → 0.5.2

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
@@ -166,23 +166,59 @@ var ExitCode = {
166
166
  SERVER_ERROR: 7
167
167
  };
168
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
+
169
197
  // src/commands/configure.ts
170
198
  function registerConfigure(program2) {
171
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) => {
172
200
  const serverUrl = opts.server.replace(/\/$/, "");
173
201
  const token = opts.token;
174
- console.log(`Connecting to ${serverUrl}...`);
202
+ if (!isJsonOutput()) {
203
+ console.log(`Connecting to ${serverUrl}...`);
204
+ }
175
205
  const client = new ServerClient({ serverUrl, token });
176
206
  try {
177
207
  await client.listOAS();
178
208
  saveConfig({ serverUrl, token });
209
+ if (isJsonOutput()) {
210
+ outputSuccess({ serverUrl, configured: true });
211
+ return;
212
+ }
179
213
  console.log("\u2713 Configuration saved successfully.");
180
214
  console.log(` Server: ${serverUrl}`);
181
215
  console.log(` Token: ${token.slice(0, 20)}...`);
182
216
  } catch (err) {
183
- console.error("Connection failed:", err.message);
184
- console.error("Please check the server URL and token.");
185
- process.exit(ExitCode.CONNECTIVITY_ERROR);
217
+ outputError(
218
+ ExitCode.CONNECTIVITY_ERROR,
219
+ `Connection failed: ${err.message}`,
220
+ "Check the server URL and token"
221
+ );
186
222
  }
187
223
  });
188
224
  }
@@ -335,7 +371,7 @@ function buildSafeEnv(authEnv) {
335
371
  }
336
372
  async function runOperation(opts) {
337
373
  const bin = resolveOpenapi2CliBin();
338
- const { entry, operationArgs, format, query } = opts;
374
+ const { entry, operationArgs, format, query, machine, dryRun } = opts;
339
375
  const args = [
340
376
  "run",
341
377
  "--oas",
@@ -343,6 +379,8 @@ async function runOperation(opts) {
343
379
  "--cache-ttl",
344
380
  String(entry.cacheTtl),
345
381
  ...entry.baseEndpoint ? ["--endpoint", entry.baseEndpoint] : [],
382
+ ...machine ? ["--machine"] : [],
383
+ ...dryRun ? ["--dry-run"] : [],
346
384
  ...format ? ["--format", format] : [],
347
385
  ...query ? ["--query", query] : [],
348
386
  ...operationArgs
@@ -466,24 +504,24 @@ function registerServices(program2) {
466
504
  await writeOASListCache(entries, maxTtl);
467
505
  }
468
506
  }
507
+ const safe = entries.map(({ authConfig, ...rest }) => ({
508
+ ...rest,
509
+ authConfig: { type: authConfig["type"] ?? rest.authType }
510
+ }));
511
+ if (isJsonOutput()) {
512
+ outputSuccess(safe);
513
+ return;
514
+ }
469
515
  const format = (opts.format ?? "table").toLowerCase();
470
516
  if (entries.length === 0) {
471
517
  console.log("No services registered in this group.");
472
518
  return;
473
519
  }
474
520
  if (format === "json") {
475
- const safe = entries.map(({ authConfig, ...rest }) => ({
476
- ...rest,
477
- authConfig: { type: authConfig["type"] ?? rest.authType }
478
- }));
479
521
  console.log(JSON.stringify(safe, null, 2));
480
522
  return;
481
523
  }
482
524
  if (format === "yaml") {
483
- const safe = entries.map(({ authConfig, ...rest }) => ({
484
- ...rest,
485
- authConfig: { type: authConfig["type"] ?? rest.authType }
486
- }));
487
525
  console.log(toYaml(safe));
488
526
  return;
489
527
  }
@@ -504,30 +542,30 @@ ${"SERVICE".padEnd(nameWidth)} AUTH DESCRIPTION`);
504
542
  let entry;
505
543
  try {
506
544
  entry = await client.getOAS(name);
507
- } catch (err) {
508
- console.error(`Service not found: ${name}`);
509
- console.error("Run `ucli services list` to see available services.");
510
- process.exit(ExitCode.NOT_FOUND);
545
+ } catch {
546
+ outputError(
547
+ ExitCode.NOT_FOUND,
548
+ `Service not found: ${name}`,
549
+ "Run: ucli services list to see available services"
550
+ );
511
551
  }
512
552
  const help = await getServiceHelp(entry);
553
+ const { authConfig, ...rest } = entry;
554
+ const safe = {
555
+ ...rest,
556
+ authConfig: { type: authConfig["type"] ?? rest.authType },
557
+ operationsHelp: help
558
+ };
559
+ if (isJsonOutput()) {
560
+ outputSuccess(safe);
561
+ return;
562
+ }
513
563
  const format = (opts.format ?? "table").toLowerCase();
514
564
  if (format === "json") {
515
- const { authConfig, ...rest } = entry;
516
- const safe = {
517
- ...rest,
518
- authConfig: { type: authConfig["type"] ?? rest.authType },
519
- operationsHelp: help
520
- };
521
565
  console.log(JSON.stringify(safe, null, 2));
522
566
  return;
523
567
  }
524
568
  if (format === "yaml") {
525
- const { authConfig, ...rest } = entry;
526
- const safe = {
527
- ...rest,
528
- authConfig: { type: authConfig["type"] ?? rest.authType },
529
- operationsHelp: help
530
- };
531
569
  console.log(toYaml(safe));
532
570
  return;
533
571
  }
@@ -546,15 +584,20 @@ Service: ${entry.name}`);
546
584
 
547
585
  // src/commands/run.ts
548
586
  function registerRun(program2) {
549
- 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) => {
587
+ 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)").option("--machine", "Agent-friendly mode: structured JSON envelope output").option("--dry-run", "Preview the HTTP request without executing (implies --machine)").allowUnknownOption(true).action(async (serviceArg, args, opts) => {
550
588
  if (serviceArg && opts.service && serviceArg !== opts.service) {
551
- console.error(`Conflicting service values: positional "${serviceArg}" and --service "${opts.service}". Use either the positional argument or --service flag, not both.`);
552
- process.exit(ExitCode.USAGE_ERROR);
589
+ outputError(
590
+ ExitCode.USAGE_ERROR,
591
+ `Conflicting service values: positional "${serviceArg}" and --service "${opts.service}". Use either the positional argument or --service flag, not both.`
592
+ );
553
593
  }
554
594
  const service = opts.service ?? serviceArg;
555
595
  if (!service) {
556
- console.error("Missing service name. Use positional <service> or --service <name>.");
557
- process.exit(ExitCode.USAGE_ERROR);
596
+ outputError(
597
+ ExitCode.USAGE_ERROR,
598
+ "Missing service name. Use positional <service> or --service <name>.",
599
+ "Run: ucli services list to see available services"
600
+ );
558
601
  }
559
602
  const cfg = getConfig();
560
603
  const client = new ServerClient(cfg);
@@ -562,9 +605,11 @@ function registerRun(program2) {
562
605
  try {
563
606
  entry = await client.getOAS(service);
564
607
  } catch {
565
- console.error(`Unknown service: ${service}`);
566
- console.error("Run `ucli services list` to see available services.");
567
- process.exit(ExitCode.NOT_FOUND);
608
+ outputError(
609
+ ExitCode.NOT_FOUND,
610
+ `Unknown service: ${service}`,
611
+ "Run: ucli services list to see available services"
612
+ );
568
613
  }
569
614
  const extraArgs = opts.args ?? [];
570
615
  const operationArgs = [...args, ...extraArgs];
@@ -576,8 +621,11 @@ function registerRun(program2) {
576
621
  try {
577
622
  parsed = JSON.parse(opts.params);
578
623
  } catch {
579
- console.error(`Invalid --params JSON. Example: --params '{"petId": 1}'`);
580
- process.exit(ExitCode.USAGE_ERROR);
624
+ outputError(
625
+ ExitCode.USAGE_ERROR,
626
+ "Invalid --params JSON.",
627
+ `Example: --params '{"petId": 1}'`
628
+ );
581
629
  }
582
630
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
583
631
  for (const [k, v] of Object.entries(parsed)) {
@@ -592,16 +640,22 @@ function registerRun(program2) {
592
640
  }
593
641
  const format = opts.format;
594
642
  const query = opts.query;
643
+ const machine = opts.machine ?? false;
644
+ const dryRun = opts.dryRun ?? false;
595
645
  try {
596
646
  await runOperation({
597
647
  entry,
598
648
  operationArgs,
599
649
  ...format !== void 0 ? { format } : {},
600
- ...query !== void 0 ? { query } : {}
650
+ ...query !== void 0 ? { query } : {},
651
+ ...machine ? { machine } : {},
652
+ ...dryRun ? { dryRun } : {}
601
653
  });
602
654
  } catch (err) {
603
- console.error("Operation failed:", err.message);
604
- process.exit(ExitCode.GENERAL_ERROR);
655
+ outputError(
656
+ ExitCode.GENERAL_ERROR,
657
+ `Operation failed: ${err.message}`
658
+ );
605
659
  }
606
660
  });
607
661
  }
@@ -611,7 +665,9 @@ function registerRefresh(program2) {
611
665
  program2.command("refresh").description("Force-refresh the local OAS cache from the server").option("--service <name>", "Refresh only a specific service").action(async (opts) => {
612
666
  const cfg = getConfig();
613
667
  const client = new ServerClient(cfg);
614
- console.log("Refreshing OAS list from server...");
668
+ if (!isJsonOutput()) {
669
+ console.log("Refreshing OAS list from server...");
670
+ }
615
671
  if (opts.service) {
616
672
  await clearOASCache(opts.service);
617
673
  } else {
@@ -622,6 +678,10 @@ function registerRefresh(program2) {
622
678
  const maxTtl = Math.min(...entries.map((e) => e.cacheTtl));
623
679
  await writeOASListCache(entries, maxTtl);
624
680
  }
681
+ if (isJsonOutput()) {
682
+ outputSuccess({ refreshed: entries.length });
683
+ return;
684
+ }
625
685
  console.log(`\u2713 Refreshed ${entries.length} service(s).`);
626
686
  });
627
687
  }
@@ -696,6 +756,10 @@ DISCOVERY
696
756
  ucli services info <service>
697
757
  Show detailed service info and all available operations.
698
758
 
759
+ ucli introspect
760
+ Return complete capability manifest in a single call (JSON).
761
+ Ideal for AI agents: includes services, MCP servers, and command reference.
762
+
699
763
  ucli help [service]
700
764
  Show this guide, or service-specific operations.
701
765
 
@@ -708,6 +772,16 @@ EXECUTION
708
772
  --query <jmespath> Filter response with JMESPath
709
773
  --data <json|@file> Request body for POST/PUT/PATCH
710
774
 
775
+ MCP SERVERS
776
+ ucli mcp list
777
+ List all MCP servers in your group.
778
+
779
+ ucli mcp tools <server>
780
+ List tools available on a MCP server.
781
+
782
+ ucli mcp run <server> <tool> [key=value ...]
783
+ Call a tool on a MCP server.
784
+
711
785
  MAINTENANCE
712
786
  ucli refresh
713
787
  Force-refresh the local OAS cache from the server.
@@ -722,6 +796,8 @@ SHELL COMPLETIONS
722
796
 
723
797
  GLOBAL FLAGS
724
798
  --debug Enable verbose debug logging
799
+ --output json Wrap ALL output in structured JSON envelopes
800
+ (for agent/automation consumption)
725
801
  -v, --version Show version number
726
802
 
727
803
  ERRORS
@@ -729,6 +805,11 @@ ERRORS
729
805
  404 Not Found \u2192 Check service name: ucli services list
730
806
  4xx Client Error \u2192 Check operation args: ucli services info <service>
731
807
  5xx Server Error \u2192 Retry or run: ucli refresh
808
+
809
+ AI AGENT QUICK START
810
+ 1. ucli introspect # discover everything in one call
811
+ 2. ucli run <svc> <op> [args] # execute operations
812
+ 3. Use --output json globally # get structured { success, data/error } envelopes
732
813
  `);
733
814
  }
734
815
 
@@ -739,12 +820,14 @@ function resolve(mod, name) {
739
820
  throw new Error(`Cannot resolve export "${name}" from module`);
740
821
  }
741
822
  async function getMcp2cli() {
742
- const clientMod = await import("./client-3I7XBDJU.js");
743
- const runnerMod = await import("./runner-GVYIJNHN.js");
823
+ const clientMod = await import("./client-Y6NONDCI.js");
824
+ const runnerMod = await import("./runner-ROLDMGH3.js");
744
825
  return {
745
826
  createMcpClient: resolve(clientMod, "createMcpClient"),
746
827
  getTools: resolve(runnerMod, "getTools"),
747
- runTool: resolve(runnerMod, "runTool")
828
+ runTool: resolve(runnerMod, "runTool"),
829
+ describeTool: resolve(runnerMod, "describeTool"),
830
+ describeToolJson: resolve(runnerMod, "describeToolJson")
748
831
  };
749
832
  }
750
833
  async function closeClient(client) {
@@ -778,7 +861,24 @@ async function listMcpTools(entry) {
778
861
  await closeClient(client);
779
862
  }
780
863
  }
781
- async function runMcpTool(entry, toolName, rawArgs) {
864
+ async function describeMcpTool(entry, toolName, opts) {
865
+ const { createMcpClient, getTools, describeTool, describeToolJson } = await getMcp2cli();
866
+ const config = buildMcpConfig(entry);
867
+ const client = await createMcpClient(config);
868
+ try {
869
+ const tools = await getTools(client, config, { noCache: false, cacheTtl: 3600 });
870
+ const tool = tools.find((t) => t.name === toolName);
871
+ if (!tool) throw new Error(`Tool "${toolName}" not found on MCP server "${entry.name}"`);
872
+ if (opts?.json) {
873
+ describeToolJson(tool);
874
+ } else {
875
+ describeTool(tool);
876
+ }
877
+ } finally {
878
+ await closeClient(client);
879
+ }
880
+ }
881
+ async function runMcpTool(entry, toolName, rawArgs, opts) {
782
882
  const { createMcpClient, getTools, runTool } = await getMcp2cli();
783
883
  const config = buildMcpConfig(entry);
784
884
  const client = await createMcpClient(config);
@@ -797,7 +897,10 @@ async function runMcpTool(entry, toolName, rawArgs) {
797
897
  normalizedArgs.push(arg);
798
898
  }
799
899
  }
800
- await runTool(client, tool, normalizedArgs, {});
900
+ await runTool(client, tool, normalizedArgs, {
901
+ ...opts?.json ? { json: true } : {},
902
+ ...opts?.inputJson ? { inputJson: opts.inputJson } : {}
903
+ });
801
904
  } finally {
802
905
  await closeClient(client);
803
906
  }
@@ -810,24 +913,24 @@ function registerMcp(program2) {
810
913
  const cfg = getConfig();
811
914
  const client = new ServerClient(cfg);
812
915
  const entries = await client.listMCP();
916
+ const safe = entries.map(({ authConfig, ...rest }) => ({
917
+ ...rest,
918
+ authConfig: { type: authConfig.type }
919
+ }));
920
+ if (isJsonOutput()) {
921
+ outputSuccess(safe);
922
+ return;
923
+ }
813
924
  if (entries.length === 0) {
814
925
  console.log("No MCP servers registered in this group.");
815
926
  return;
816
927
  }
817
928
  const format = (opts.format ?? "table").toLowerCase();
818
929
  if (format === "json") {
819
- const safe = entries.map(({ authConfig, ...rest }) => ({
820
- ...rest,
821
- authConfig: { type: authConfig.type }
822
- }));
823
930
  console.log(JSON.stringify(safe, null, 2));
824
931
  return;
825
932
  }
826
933
  if (format === "yaml") {
827
- const safe = entries.map(({ authConfig, ...rest }) => ({
828
- ...rest,
829
- authConfig: { type: authConfig.type }
830
- }));
831
934
  console.log(toYaml(safe));
832
935
  return;
833
936
  }
@@ -848,16 +951,21 @@ ${"SERVER".padEnd(nameWidth)} TRANSPORT DESCRIPTION`);
848
951
  try {
849
952
  entry = await client.getMCP(serverName);
850
953
  } catch {
851
- console.error(`Unknown MCP server: ${serverName}`);
852
- console.error("Run `ucli mcp list` to see available servers.");
853
- process.exit(ExitCode.NOT_FOUND);
954
+ outputError(
955
+ ExitCode.NOT_FOUND,
956
+ `Unknown MCP server: ${serverName}`,
957
+ "Run: ucli mcp list to see available servers"
958
+ );
854
959
  }
855
960
  let tools;
856
961
  try {
857
962
  tools = await listMcpTools(entry);
858
963
  } catch (err) {
859
- console.error("Failed to fetch tools:", err.message);
860
- process.exit(ExitCode.GENERAL_ERROR);
964
+ outputError(ExitCode.GENERAL_ERROR, `Failed to fetch tools: ${err.message}`);
965
+ }
966
+ if (isJsonOutput()) {
967
+ outputSuccess(tools);
968
+ return;
861
969
  }
862
970
  if (tools.length === 0) {
863
971
  console.log(`No tools found on MCP server "${serverName}".`);
@@ -881,22 +989,45 @@ Tools on "${serverName}":`);
881
989
  }
882
990
  console.log();
883
991
  });
884
- mcp.command("run <server> <tool> [args...]").description("Call a tool on a MCP server").action(async (serverName, toolName, args) => {
992
+ mcp.command("describe <server> <tool>").description("Show detailed schema for a tool on a MCP server").option("--json", "Output full schema as JSON (for agent consumption)").action(async (serverName, toolName, opts) => {
885
993
  const cfg = getConfig();
886
994
  const client = new ServerClient(cfg);
887
995
  let entry;
888
996
  try {
889
997
  entry = await client.getMCP(serverName);
890
998
  } catch {
891
- console.error(`Unknown MCP server: ${serverName}`);
892
- console.error("Run `ucli mcp list` to see available servers.");
893
- process.exit(ExitCode.NOT_FOUND);
999
+ outputError(
1000
+ ExitCode.NOT_FOUND,
1001
+ `Unknown MCP server: ${serverName}`,
1002
+ "Run: ucli mcp list to see available servers"
1003
+ );
1004
+ }
1005
+ try {
1006
+ await describeMcpTool(entry, toolName, { json: opts.json });
1007
+ } catch (err) {
1008
+ outputError(ExitCode.GENERAL_ERROR, `Failed to describe tool: ${err.message}`);
1009
+ }
1010
+ });
1011
+ mcp.command("run <server> <tool> [args...]").description("Call a tool on a MCP server").option("--json", "Machine-readable JSON output").option("--input-json <json>", "Pass tool arguments as a JSON object").action(async (serverName, toolName, args, opts) => {
1012
+ const cfg = getConfig();
1013
+ const client = new ServerClient(cfg);
1014
+ let entry;
1015
+ try {
1016
+ entry = await client.getMCP(serverName);
1017
+ } catch {
1018
+ outputError(
1019
+ ExitCode.NOT_FOUND,
1020
+ `Unknown MCP server: ${serverName}`,
1021
+ "Run: ucli mcp list to see available servers"
1022
+ );
894
1023
  }
895
1024
  try {
896
- await runMcpTool(entry, toolName, args);
1025
+ await runMcpTool(entry, toolName, args, {
1026
+ json: opts.json,
1027
+ inputJson: opts.inputJson
1028
+ });
897
1029
  } catch (err) {
898
- console.error("Tool execution failed:", err.message);
899
- process.exit(ExitCode.GENERAL_ERROR);
1030
+ outputError(ExitCode.GENERAL_ERROR, `Tool execution failed: ${err.message}`);
900
1031
  }
901
1032
  });
902
1033
  }
@@ -931,8 +1062,11 @@ function registerDoctor(program2) {
931
1062
  ok: false,
932
1063
  detail: "Not configured. Run: ucli configure --server <url> --token <jwt>"
933
1064
  });
934
- printResults(results);
935
- process.exit(ExitCode.CONFIG_ERROR);
1065
+ outputError(
1066
+ ExitCode.CONFIG_ERROR,
1067
+ "Not configured",
1068
+ "Run: ucli configure --server <url> --token <jwt>"
1069
+ );
936
1070
  }
937
1071
  let cfg;
938
1072
  try {
@@ -948,8 +1082,11 @@ function registerDoctor(program2) {
948
1082
  ok: false,
949
1083
  detail: `Failed to read config: ${err.message}`
950
1084
  });
951
- printResults(results);
952
- process.exit(ExitCode.CONFIG_ERROR);
1085
+ outputError(
1086
+ ExitCode.CONFIG_ERROR,
1087
+ `Failed to read config: ${err.message}`,
1088
+ "Run: ucli configure --server <url> --token <jwt>"
1089
+ );
953
1090
  }
954
1091
  debug(`Checking connectivity to ${cfg.serverUrl}...`);
955
1092
  try {
@@ -985,8 +1122,15 @@ function registerDoctor(program2) {
985
1122
  detail: `Token rejected: ${msg}`
986
1123
  });
987
1124
  }
988
- printResults(results);
989
1125
  const allOk = results.every((r) => r.ok);
1126
+ if (isJsonOutput()) {
1127
+ outputSuccess({
1128
+ healthy: allOk,
1129
+ checks: results
1130
+ });
1131
+ process.exit(allOk ? ExitCode.SUCCESS : ExitCode.GENERAL_ERROR);
1132
+ }
1133
+ printResults(results);
990
1134
  process.exit(allOk ? ExitCode.SUCCESS : ExitCode.GENERAL_ERROR);
991
1135
  });
992
1136
  }
@@ -1026,7 +1170,7 @@ _ucli_completions() {
1026
1170
  COMPREPLY=()
1027
1171
  cur="\${COMP_WORDS[COMP_CWORD]}"
1028
1172
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
1029
- commands="configure services run refresh help mcp doctor completions"
1173
+ commands="configure services run refresh help mcp doctor completions introspect"
1030
1174
 
1031
1175
  case "\${COMP_WORDS[1]}" in
1032
1176
  services)
@@ -1070,6 +1214,7 @@ _ucli() {
1070
1214
  'mcp:Interact with MCP servers'
1071
1215
  'doctor:Check configuration and connectivity'
1072
1216
  'completions:Generate shell completion script'
1217
+ 'introspect:Return complete capability manifest for AI agents'
1073
1218
  )
1074
1219
 
1075
1220
  _arguments -C \\
@@ -1112,6 +1257,8 @@ complete -c ucli -n __fish_use_subcommand -a mcp -d 'Interact with MCP servers'
1112
1257
  complete -c ucli -n __fish_use_subcommand -a doctor -d 'Check config and connectivity'
1113
1258
  complete -c ucli -n __fish_use_subcommand -a completions -d 'Generate shell completions'
1114
1259
 
1260
+ complete -c ucli -n __fish_use_subcommand -a introspect -d 'Return capability manifest for AI agents'
1261
+
1115
1262
  # services subcommands
1116
1263
  complete -c ucli -n '__fish_seen_subcommand_from services' -a list -d 'List services'
1117
1264
  complete -c ucli -n '__fish_seen_subcommand_from services' -a info -d 'Show service details'
@@ -1125,16 +1272,188 @@ complete -c ucli -n '__fish_seen_subcommand_from mcp' -a run -d 'Call a tool'
1125
1272
  complete -c ucli -n '__fish_seen_subcommand_from completions' -a 'bash zsh fish' -d 'Shell type'`;
1126
1273
  }
1127
1274
 
1275
+ // src/commands/introspect.ts
1276
+ function registerIntrospect(program2) {
1277
+ 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) => {
1278
+ const cfg = getConfig();
1279
+ const client = new ServerClient(cfg);
1280
+ let oasEntries = [];
1281
+ let mcpEntries = [];
1282
+ try {
1283
+ ;
1284
+ [oasEntries, mcpEntries] = await Promise.all([
1285
+ client.listOAS(),
1286
+ client.listMCP()
1287
+ ]);
1288
+ } catch (err) {
1289
+ const message = err.message;
1290
+ outputError(
1291
+ ExitCode.CONNECTIVITY_ERROR,
1292
+ `Failed to fetch capabilities: ${message}`,
1293
+ "Check server connectivity with: ucli doctor"
1294
+ );
1295
+ }
1296
+ const manifest = {
1297
+ version: "1",
1298
+ services: oasEntries.map(toIntrospectService),
1299
+ mcpServers: mcpEntries.map(toIntrospectMcpServer),
1300
+ commands: getCommandReference()
1301
+ };
1302
+ const format = (opts.format ?? "json").toLowerCase();
1303
+ if (isJsonOutput()) {
1304
+ outputSuccess(manifest);
1305
+ return;
1306
+ }
1307
+ if (format === "json") {
1308
+ console.log(JSON.stringify(manifest, null, 2));
1309
+ return;
1310
+ }
1311
+ if (format === "yaml") {
1312
+ console.log(toYaml(manifest));
1313
+ return;
1314
+ }
1315
+ console.log(JSON.stringify(manifest, null, 2));
1316
+ });
1317
+ }
1318
+ function toIntrospectService(e) {
1319
+ return {
1320
+ name: e.name,
1321
+ description: e.description,
1322
+ authType: e.authType,
1323
+ remoteUrl: e.remoteUrl,
1324
+ baseEndpoint: e.baseEndpoint,
1325
+ cacheTtl: e.cacheTtl
1326
+ };
1327
+ }
1328
+ function toIntrospectMcpServer(e) {
1329
+ return {
1330
+ name: e.name,
1331
+ description: e.description,
1332
+ transport: e.transport,
1333
+ enabled: e.enabled
1334
+ };
1335
+ }
1336
+ function getCommandReference() {
1337
+ return [
1338
+ {
1339
+ name: "services list",
1340
+ description: "List all OAS services available in the current group",
1341
+ usage: "ucli services list [--format json|table|yaml] [--refresh]",
1342
+ examples: [
1343
+ "ucli services list",
1344
+ "ucli services list --format json",
1345
+ "ucli services list --refresh"
1346
+ ]
1347
+ },
1348
+ {
1349
+ name: "services info",
1350
+ description: "Show detailed information and available operations for a service",
1351
+ usage: "ucli services info <name> [--format json|table|yaml]",
1352
+ examples: [
1353
+ "ucli services info payments",
1354
+ "ucli services info payments --format json"
1355
+ ]
1356
+ },
1357
+ {
1358
+ name: "run",
1359
+ description: "Execute an operation on an OAS service",
1360
+ usage: "ucli run <service> <operation> [--format json|table|yaml] [--query <jmespath>] [--data <json|@file>] [--params <json>] [--machine] [--dry-run]",
1361
+ examples: [
1362
+ "ucli run payments listTransactions",
1363
+ "ucli run payments getTransaction --transactionId txn_123",
1364
+ `ucli run payments createCharge --data '{"amount": 5000, "currency": "USD"}'`,
1365
+ 'ucli run inventory listProducts --query "items[?stock > `0`].name"',
1366
+ "ucli run payments listTransactions --machine",
1367
+ `ucli run payments createCharge --dry-run --data '{"amount": 5000}'`
1368
+ ]
1369
+ },
1370
+ {
1371
+ name: "mcp list",
1372
+ description: "List all MCP servers available in the current group",
1373
+ usage: "ucli mcp list [--format json|table|yaml]",
1374
+ examples: [
1375
+ "ucli mcp list",
1376
+ "ucli mcp list --format json"
1377
+ ]
1378
+ },
1379
+ {
1380
+ name: "mcp tools",
1381
+ description: "List tools available on a MCP server",
1382
+ usage: "ucli mcp tools <server> [--format json|table|yaml]",
1383
+ examples: [
1384
+ "ucli mcp tools weather",
1385
+ "ucli mcp tools weather --format json"
1386
+ ]
1387
+ },
1388
+ {
1389
+ name: "mcp describe",
1390
+ description: "Show detailed schema for a tool on a MCP server",
1391
+ usage: "ucli mcp describe <server> <tool> [--json]",
1392
+ examples: [
1393
+ "ucli mcp describe weather get_forecast",
1394
+ "ucli mcp describe weather get_forecast --json"
1395
+ ]
1396
+ },
1397
+ {
1398
+ name: "mcp run",
1399
+ description: "Call a tool on a MCP server",
1400
+ usage: "ucli mcp run <server> <tool> [key=value ...] [--json] [--input-json <json>]",
1401
+ examples: [
1402
+ 'ucli mcp run weather get_forecast location="New York"',
1403
+ 'ucli mcp run search web_search query="ucli docs" limit=5',
1404
+ `ucli mcp run weather get_forecast --input-json '{"location": "New York"}'`,
1405
+ 'ucli mcp run weather get_forecast --json location="New York"'
1406
+ ]
1407
+ },
1408
+ {
1409
+ name: "introspect",
1410
+ description: "Return complete capability manifest (this command)",
1411
+ usage: "ucli introspect [--format json|yaml]",
1412
+ examples: [
1413
+ "ucli introspect",
1414
+ "ucli introspect --format yaml"
1415
+ ]
1416
+ },
1417
+ {
1418
+ name: "refresh",
1419
+ description: "Force-refresh the local OAS cache from the server",
1420
+ usage: "ucli refresh [--service <name>]",
1421
+ examples: [
1422
+ "ucli refresh",
1423
+ "ucli refresh --service payments"
1424
+ ]
1425
+ },
1426
+ {
1427
+ name: "doctor",
1428
+ description: "Check configuration, server connectivity, and token validity",
1429
+ usage: "ucli doctor",
1430
+ examples: ["ucli doctor"]
1431
+ },
1432
+ {
1433
+ name: "configure",
1434
+ description: "Configure the server URL and authentication token",
1435
+ usage: "ucli configure --server <url> --token <jwt>",
1436
+ examples: ["ucli configure --server https://oas.example.com --token eyJ..."]
1437
+ }
1438
+ ];
1439
+ }
1440
+
1128
1441
  // src/index.ts
1129
1442
  var require3 = createRequire2(import.meta.url);
1130
1443
  var pkg = require3("../package.json");
1131
1444
  var program = new Command();
1132
- program.name("ucli").description(pkg.description).version(pkg.version, "-v, --version").option("--debug", "Enable verbose debug logging").addHelpCommand(false).hook("preAction", (_thisCommand, actionCommand) => {
1445
+ 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) => {
1133
1446
  let cmd = actionCommand;
1134
1447
  while (cmd) {
1135
- if (cmd.opts().debug) {
1448
+ const opts = cmd.opts();
1449
+ if (opts.debug) {
1136
1450
  setDebugMode(true);
1137
- break;
1451
+ }
1452
+ if (opts.output && typeof opts.output === "string") {
1453
+ const mode = opts.output.toLowerCase();
1454
+ if (mode === "json" || mode === "text") {
1455
+ setOutputMode(mode);
1456
+ }
1138
1457
  }
1139
1458
  cmd = cmd.parent;
1140
1459
  }
@@ -1146,6 +1465,7 @@ registerRefresh(program);
1146
1465
  registerMcp(program);
1147
1466
  registerDoctor(program);
1148
1467
  registerCompletions(program);
1468
+ registerIntrospect(program);
1149
1469
  registerHelp(program);
1150
1470
  program.parse(process.argv);
1151
1471
  //# sourceMappingURL=index.js.map