@robinmordasiewicz/f5xc-xcsh 6.46.0 → 6.48.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.
Files changed (2) hide show
  1. package/dist/index.js +436 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -45354,8 +45354,8 @@ function getLogoModeFromEnv(envPrefix) {
45354
45354
  var CLI_NAME = "xcsh";
45355
45355
  var CLI_FULL_NAME = "F5 Distributed Cloud Shell";
45356
45356
  function getVersion() {
45357
- if ("6.46.0") {
45358
- return "6.46.0";
45357
+ if ("6.48.0") {
45358
+ return "6.48.0";
45359
45359
  }
45360
45360
  if (process.env.XCSH_VERSION) {
45361
45361
  return process.env.XCSH_VERSION;
@@ -47744,6 +47744,24 @@ var DebugProtocolImpl = class {
47744
47744
  stack: error instanceof Error ? error.stack : void 0
47745
47745
  });
47746
47746
  }
47747
+ /**
47748
+ * Emit headless mode event
47749
+ */
47750
+ headless(event, data = {}) {
47751
+ this.emit("headless", event, data);
47752
+ }
47753
+ /**
47754
+ * Emit command execution event
47755
+ */
47756
+ command(event, data = {}) {
47757
+ this.emit("command", event, data);
47758
+ }
47759
+ /**
47760
+ * Emit completion event
47761
+ */
47762
+ completion(event, data = {}) {
47763
+ this.emit("completion", event, data);
47764
+ }
47747
47765
  /**
47748
47766
  * Get all captured events
47749
47767
  */
@@ -51207,6 +51225,9 @@ function isCustomDomain(name) {
51207
51225
  const canonical = resolveDomainAlias(name);
51208
51226
  return customDomains.has(canonical);
51209
51227
  }
51228
+ function getDomainAliases() {
51229
+ return new Map(domainAliases);
51230
+ }
51210
51231
 
51211
51232
  // src/extensions/types.ts
51212
51233
  var RESERVED_API_ACTIONS = /* @__PURE__ */ new Set([
@@ -53104,6 +53125,109 @@ async function executeAPICommand(session, ctx, cmd) {
53104
53125
  };
53105
53126
  }
53106
53127
  }
53128
+ function getCommandSuggestions(input, session) {
53129
+ const ctx = session.getContextPath();
53130
+ const suggestions = [];
53131
+ if (ctx.isRoot()) {
53132
+ for (const domain of customDomains.all()) {
53133
+ if (!input || domain.name.toLowerCase().startsWith(input.toLowerCase())) {
53134
+ suggestions.push({
53135
+ text: domain.name,
53136
+ description: domain.description,
53137
+ category: "domain"
53138
+ });
53139
+ }
53140
+ }
53141
+ for (const [alias, canonical] of getDomainAliases()) {
53142
+ if (!input || alias.toLowerCase().startsWith(input.toLowerCase())) {
53143
+ const domain = customDomains.get(canonical);
53144
+ suggestions.push({
53145
+ text: alias,
53146
+ description: domain ? `${domain.description} (alias)` : `Alias for ${canonical}`,
53147
+ category: "domain"
53148
+ });
53149
+ }
53150
+ }
53151
+ for (const extDomain of extensionRegistry.getExtendedDomains()) {
53152
+ if (isCustomDomain(extDomain) || isValidDomain(extDomain)) continue;
53153
+ if (!input || extDomain.toLowerCase().startsWith(input.toLowerCase())) {
53154
+ const merged = extensionRegistry.getMergedDomain(extDomain);
53155
+ suggestions.push({
53156
+ text: extDomain,
53157
+ description: merged?.description ?? `${extDomain} commands`,
53158
+ category: "domain"
53159
+ });
53160
+ }
53161
+ }
53162
+ allDomains().forEach((domain) => {
53163
+ if (isCustomDomain(domain)) return;
53164
+ if (!input || domain.toLowerCase().startsWith(input.toLowerCase())) {
53165
+ const merged = extensionRegistry.getMergedDomain(domain);
53166
+ const hasExt = merged?.hasExtension ? " (+ext)" : "";
53167
+ suggestions.push({
53168
+ text: domain,
53169
+ description: `Navigate to ${domain} domain${hasExt}`,
53170
+ category: "domain"
53171
+ });
53172
+ }
53173
+ });
53174
+ BUILTIN_COMMANDS.forEach((cmd) => {
53175
+ if (!input || cmd.toLowerCase().startsWith(input.toLowerCase())) {
53176
+ suggestions.push({
53177
+ text: cmd,
53178
+ description: getBuiltinDescription(cmd),
53179
+ category: "builtin"
53180
+ });
53181
+ }
53182
+ });
53183
+ }
53184
+ if (ctx.isDomain() && !ctx.isAction()) {
53185
+ const domain = ctx.domain ?? "";
53186
+ const extCmds = extensionRegistry.getExtensionCommandNames(domain);
53187
+ for (const cmd of extCmds) {
53188
+ if (!input || cmd.toLowerCase().startsWith(input.toLowerCase())) {
53189
+ const cmdDef = extensionRegistry.getExtensionCommand(
53190
+ domain,
53191
+ cmd
53192
+ );
53193
+ suggestions.push({
53194
+ text: cmd,
53195
+ description: cmdDef?.description ?? `${cmd} command`,
53196
+ category: "extension"
53197
+ });
53198
+ }
53199
+ }
53200
+ const commonActions = ["list", "get", "create", "delete", "update"];
53201
+ commonActions.forEach((action) => {
53202
+ if (!input || action.toLowerCase().startsWith(input.toLowerCase())) {
53203
+ suggestions.push({
53204
+ text: action,
53205
+ description: `${action} ${domain} resources`,
53206
+ category: "action"
53207
+ });
53208
+ }
53209
+ });
53210
+ }
53211
+ return suggestions;
53212
+ }
53213
+ function getBuiltinDescription(cmd) {
53214
+ const descriptions = /* @__PURE__ */ new Map([
53215
+ ["help", "Show help information"],
53216
+ ["clear", "Clear the screen"],
53217
+ ["quit", "Exit the shell"],
53218
+ ["exit", "Navigate up or exit"],
53219
+ ["back", "Navigate up one level"],
53220
+ ["..", "Navigate up one level"],
53221
+ ["root", "Navigate to root"],
53222
+ ["/", "Navigate to root"],
53223
+ ["context", "Show current context"],
53224
+ ["ctx", "Show current context"],
53225
+ ["history", "Show command history"],
53226
+ ["version", "Show version info"],
53227
+ ["domains", "List available domains"]
53228
+ ]);
53229
+ return descriptions.get(cmd) ?? "Built-in command";
53230
+ }
53107
53231
 
53108
53232
  // src/repl/App.tsx
53109
53233
  var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
@@ -53457,13 +53581,314 @@ function App2({ initialSession } = {}) {
53457
53581
  ] });
53458
53582
  }
53459
53583
 
53584
+ // src/headless/controller.ts
53585
+ import * as readline from "readline";
53586
+
53587
+ // src/headless/protocol.ts
53588
+ function parseInput2(line) {
53589
+ try {
53590
+ const parsed = JSON.parse(line);
53591
+ if (typeof parsed !== "object" || parsed === null) {
53592
+ return null;
53593
+ }
53594
+ const obj = parsed;
53595
+ if (typeof obj.type !== "string") {
53596
+ return null;
53597
+ }
53598
+ const validTypes = [
53599
+ "command",
53600
+ "completion_request",
53601
+ "interrupt",
53602
+ "exit"
53603
+ ];
53604
+ if (!validTypes.includes(obj.type)) {
53605
+ return null;
53606
+ }
53607
+ const result = {
53608
+ type: obj.type
53609
+ };
53610
+ if (typeof obj.value === "string") {
53611
+ result.value = obj.value;
53612
+ }
53613
+ if (typeof obj.partial === "string") {
53614
+ result.partial = obj.partial;
53615
+ }
53616
+ return result;
53617
+ } catch {
53618
+ return null;
53619
+ }
53620
+ }
53621
+ function formatOutput2(output) {
53622
+ return JSON.stringify({
53623
+ ...output,
53624
+ timestamp: output.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
53625
+ });
53626
+ }
53627
+ function createOutputMessage(content, format = "text") {
53628
+ return {
53629
+ type: "output",
53630
+ content,
53631
+ format,
53632
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
53633
+ };
53634
+ }
53635
+ function createPromptMessage(prompt) {
53636
+ return {
53637
+ type: "prompt",
53638
+ prompt,
53639
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
53640
+ };
53641
+ }
53642
+ function createCompletionResponse(suggestions) {
53643
+ return {
53644
+ type: "completion_response",
53645
+ suggestions,
53646
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
53647
+ };
53648
+ }
53649
+ function createErrorMessage(message, code = 1) {
53650
+ return {
53651
+ type: "error",
53652
+ message,
53653
+ code,
53654
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
53655
+ };
53656
+ }
53657
+ function createEventMessage(event, data = {}) {
53658
+ return {
53659
+ type: "event",
53660
+ event,
53661
+ data,
53662
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
53663
+ };
53664
+ }
53665
+ function createExitMessage(code = 0) {
53666
+ return {
53667
+ type: "exit",
53668
+ code,
53669
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
53670
+ };
53671
+ }
53672
+
53673
+ // src/headless/controller.ts
53674
+ var HeadlessController = class {
53675
+ session;
53676
+ rl = null;
53677
+ running = false;
53678
+ constructor() {
53679
+ this.session = new REPLSession();
53680
+ }
53681
+ /**
53682
+ * Initialize the headless session
53683
+ */
53684
+ async initialize() {
53685
+ await this.session.initialize();
53686
+ debugProtocol.session("init", { mode: "headless" });
53687
+ emitSessionState(this.session);
53688
+ this.emitEvent(
53689
+ "session_initialized",
53690
+ this.getSessionState()
53691
+ );
53692
+ }
53693
+ /**
53694
+ * Get current session state for output
53695
+ */
53696
+ getSessionState() {
53697
+ const ctx = this.session.getContextPath();
53698
+ return {
53699
+ authenticated: this.session.isAuthenticated(),
53700
+ tokenValidated: this.session.isTokenValidated(),
53701
+ namespace: this.session.getNamespace(),
53702
+ serverUrl: this.session.getServerUrl(),
53703
+ activeProfile: this.session.getActiveProfileName(),
53704
+ context: {
53705
+ domain: ctx.domain,
53706
+ action: ctx.action
53707
+ }
53708
+ };
53709
+ }
53710
+ /**
53711
+ * Build prompt string based on current context
53712
+ */
53713
+ buildPrompt() {
53714
+ const ctx = this.session.getContextPath();
53715
+ if (ctx.isRoot()) {
53716
+ return `${CLI_NAME}> `;
53717
+ }
53718
+ if (ctx.isAction()) {
53719
+ return `${CLI_NAME}:${ctx.domain}/${ctx.action}> `;
53720
+ }
53721
+ return `${CLI_NAME}:${ctx.domain}> `;
53722
+ }
53723
+ /**
53724
+ * Write output to stdout
53725
+ */
53726
+ write(output) {
53727
+ console.log(formatOutput2(output));
53728
+ }
53729
+ /**
53730
+ * Emit an event
53731
+ */
53732
+ emitEvent(event, data = {}) {
53733
+ this.write(createEventMessage(event, data));
53734
+ }
53735
+ /**
53736
+ * Send prompt
53737
+ */
53738
+ sendPrompt() {
53739
+ this.write(createPromptMessage(this.buildPrompt()));
53740
+ }
53741
+ /**
53742
+ * Handle a command input
53743
+ */
53744
+ async handleCommand(value) {
53745
+ debugProtocol.session("command_start", { command: value });
53746
+ try {
53747
+ const result = await executeCommand(value, this.session);
53748
+ if (result.output.length > 0) {
53749
+ const content = result.output.join("\n");
53750
+ let format = "text";
53751
+ if (content.startsWith("{") || content.startsWith("[")) {
53752
+ format = "json";
53753
+ } else if (content.includes(": ") && content.includes("\n")) {
53754
+ format = "yaml";
53755
+ }
53756
+ this.write(createOutputMessage(content, format));
53757
+ }
53758
+ if (result.shouldExit) {
53759
+ this.running = false;
53760
+ this.write(createExitMessage(0));
53761
+ return;
53762
+ }
53763
+ if (result.contextChanged) {
53764
+ this.emitEvent("context_changed", {
53765
+ context: {
53766
+ domain: this.session.getContextPath().domain,
53767
+ action: this.session.getContextPath().action
53768
+ }
53769
+ });
53770
+ }
53771
+ if (result.error) {
53772
+ this.write(createErrorMessage(result.error, 1));
53773
+ }
53774
+ debugProtocol.session("command_complete", {
53775
+ command: value,
53776
+ success: !result.error
53777
+ });
53778
+ } catch (error) {
53779
+ const message = error instanceof Error ? error.message : String(error);
53780
+ this.write(createErrorMessage(message, 1));
53781
+ debugProtocol.error("command_failed", error, { command: value });
53782
+ }
53783
+ this.sendPrompt();
53784
+ }
53785
+ /**
53786
+ * Handle completion request
53787
+ */
53788
+ handleCompletionRequest(partial) {
53789
+ debugProtocol.session("completion_request", { partial });
53790
+ const rawSuggestions = getCommandSuggestions(partial, this.session);
53791
+ const suggestions = rawSuggestions.map((s) => ({
53792
+ text: s.text,
53793
+ description: s.description,
53794
+ category: s.category
53795
+ }));
53796
+ this.write(createCompletionResponse(suggestions));
53797
+ }
53798
+ /**
53799
+ * Handle interrupt signal
53800
+ */
53801
+ handleInterrupt() {
53802
+ debugProtocol.session("interrupt", {});
53803
+ this.emitEvent("interrupted", {});
53804
+ this.sendPrompt();
53805
+ }
53806
+ /**
53807
+ * Handle exit request
53808
+ */
53809
+ handleExit() {
53810
+ this.running = false;
53811
+ this.write(createExitMessage(0));
53812
+ }
53813
+ /**
53814
+ * Process a single input message
53815
+ */
53816
+ async processInput(input) {
53817
+ switch (input.type) {
53818
+ case "command":
53819
+ if (input.value !== void 0) {
53820
+ await this.handleCommand(input.value);
53821
+ } else {
53822
+ this.write(
53823
+ createErrorMessage(
53824
+ 'Missing "value" for command input',
53825
+ 1
53826
+ )
53827
+ );
53828
+ this.sendPrompt();
53829
+ }
53830
+ break;
53831
+ case "completion_request":
53832
+ this.handleCompletionRequest(input.partial ?? "");
53833
+ break;
53834
+ case "interrupt":
53835
+ this.handleInterrupt();
53836
+ break;
53837
+ case "exit":
53838
+ this.handleExit();
53839
+ break;
53840
+ default:
53841
+ this.write(
53842
+ createErrorMessage(`Unknown input type: ${input.type}`, 1)
53843
+ );
53844
+ this.sendPrompt();
53845
+ }
53846
+ }
53847
+ /**
53848
+ * Run the headless controller
53849
+ * Reads JSON messages from stdin, processes them, and writes responses to stdout
53850
+ */
53851
+ async run() {
53852
+ await this.initialize();
53853
+ this.rl = readline.createInterface({
53854
+ input: process.stdin,
53855
+ output: process.stdout,
53856
+ terminal: false
53857
+ });
53858
+ this.running = true;
53859
+ this.sendPrompt();
53860
+ for await (const line of this.rl) {
53861
+ if (!this.running) break;
53862
+ const trimmed = line.trim();
53863
+ if (!trimmed) continue;
53864
+ const input = parseInput2(trimmed);
53865
+ if (!input) {
53866
+ this.write(
53867
+ createErrorMessage(`Invalid JSON input: ${trimmed}`, 1)
53868
+ );
53869
+ this.sendPrompt();
53870
+ continue;
53871
+ }
53872
+ await this.processInput(input);
53873
+ }
53874
+ this.rl?.close();
53875
+ }
53876
+ /**
53877
+ * Stop the controller
53878
+ */
53879
+ stop() {
53880
+ this.running = false;
53881
+ this.rl?.close();
53882
+ }
53883
+ };
53884
+
53460
53885
  // src/index.tsx
53461
53886
  var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
53462
53887
  var program2 = new Command();
53463
53888
  program2.configureHelp({
53464
53889
  formatHelp: () => formatRootHelp().join("\n")
53465
53890
  });
53466
- program2.name(CLI_NAME).description("F5 Distributed Cloud Shell - Interactive CLI for F5 XC").version(CLI_VERSION, "-v, --version", "Show version number").option("--no-color", "Disable color output").option("--logo <mode>", "Logo display mode: image, ascii, none").option("-o, --output <format>", "Output format (json, yaml, table)").option("--spec", "Output command specification as JSON (for AI)").option("-h, --help", "Show help").argument("[command...]", "Command to execute non-interactively").allowUnknownOption(true).helpOption(false).action(
53891
+ program2.name(CLI_NAME).description("F5 Distributed Cloud Shell - Interactive CLI for F5 XC").version(CLI_VERSION, "-v, --version", "Show version number").option("--no-color", "Disable color output").option("--logo <mode>", "Logo display mode: image, ascii, none").option("-o, --output <format>", "Output format (json, yaml, table)").option("--spec", "Output command specification as JSON (for AI)").option("--headless", "Run in headless JSON protocol mode (for AI agents)").option("-h, --help", "Show help").argument("[command...]", "Command to execute non-interactively").allowUnknownOption(true).helpOption(false).action(
53467
53892
  async (commandArgs, options) => {
53468
53893
  if (options.help && commandArgs.length === 0) {
53469
53894
  formatRootHelp().forEach((line) => console.log(line));
@@ -53485,6 +53910,11 @@ program2.name(CLI_NAME).description("F5 Distributed Cloud Shell - Interactive CL
53485
53910
  if (options.spec && commandArgs.length > 0) {
53486
53911
  commandArgs.push("--spec");
53487
53912
  }
53913
+ if (options.headless) {
53914
+ const controller = new HeadlessController();
53915
+ await controller.run();
53916
+ return;
53917
+ }
53488
53918
  if (commandArgs.length === 0) {
53489
53919
  if (!process.stdin.isTTY) {
53490
53920
  console.error(
@@ -53493,6 +53923,9 @@ program2.name(CLI_NAME).description("F5 Distributed Cloud Shell - Interactive CL
53493
53923
  console.error(
53494
53924
  "Use: xcsh <command> for non-interactive execution."
53495
53925
  );
53926
+ console.error(
53927
+ "Or use: xcsh --headless for AI agent JSON protocol mode."
53928
+ );
53496
53929
  process.exit(1);
53497
53930
  }
53498
53931
  const cliLogoMode = options.logo && isValidLogoMode(options.logo) ? options.logo : void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robinmordasiewicz/f5xc-xcsh",
3
- "version": "6.46.0",
3
+ "version": "6.48.0",
4
4
  "description": "F5 Distributed Cloud Shell - Interactive CLI for F5 XC",
5
5
  "type": "module",
6
6
  "bin": {