@f5-sales-demo/xcsh 19.64.2 → 19.65.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5-sales-demo/xcsh",
4
- "version": "19.64.2",
4
+ "version": "19.65.0",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/f5-sales-demo/xcsh",
7
7
  "author": "Can Boluk",
@@ -56,13 +56,13 @@
56
56
  "dependencies": {
57
57
  "@agentclientprotocol/sdk": "0.16.1",
58
58
  "@mozilla/readability": "^0.6",
59
- "@f5-sales-demo/xcsh-stats": "19.64.2",
60
- "@f5-sales-demo/pi-agent-core": "19.64.2",
61
- "@f5-sales-demo/pi-ai": "19.64.2",
62
- "@f5-sales-demo/pi-natives": "19.64.2",
63
- "@f5-sales-demo/pi-resource-management": "19.64.2",
64
- "@f5-sales-demo/pi-tui": "19.64.2",
65
- "@f5-sales-demo/pi-utils": "19.64.2",
59
+ "@f5-sales-demo/xcsh-stats": "19.65.0",
60
+ "@f5-sales-demo/pi-agent-core": "19.65.0",
61
+ "@f5-sales-demo/pi-ai": "19.65.0",
62
+ "@f5-sales-demo/pi-natives": "19.65.0",
63
+ "@f5-sales-demo/pi-resource-management": "19.65.0",
64
+ "@f5-sales-demo/pi-tui": "19.65.0",
65
+ "@f5-sales-demo/pi-utils": "19.65.0",
66
66
  "@sinclair/typebox": "^0.34",
67
67
  "@xterm/headless": "^6.0",
68
68
  "ajv": "^8.20",
@@ -26,7 +26,7 @@ export interface ExtensionCapabilities {
26
26
 
27
27
  export const EXTENSION_CAPABILITIES: ExtensionCapabilities = {
28
28
  "version": "0.1.0",
29
- "contractVersion": "1.8.0",
29
+ "contractVersion": "1.9.0",
30
30
  "multiPortDiscovery": true,
31
31
  "protocol": "tool_request/result",
32
32
  "tools": [
@@ -832,7 +832,10 @@ export const EXTENSION_CAPABILITIES: ExtensionCapabilities = {
832
832
  "host_tool_call",
833
833
  "host_tool_update",
834
834
  "host_tool_result",
835
- "host_tool_cancel"
835
+ "host_tool_cancel",
836
+ "configure",
837
+ "configure_ack",
838
+ "configure_error"
836
839
  ],
837
840
  "description": "User ↔ xcsh chat over the bridge. The extension side panel sends chat_request (with mode and page-context snapshot); xcsh streams chat_delta tokens then a terminal chat_done (with reference links) or chat_error. Chat ids are prefixed \"c-\". Tool calls during a turn use the normal tool_request flow. chat_stop halts a streaming response. chat_tool_notice is emitted by the EXTENSION (the service worker) to the panel as a best-effort UI signal when a tool runs during a turn — it is NOT sent by xcsh; xcsh must not produce it to avoid double-rendering in the panel.",
838
841
  "promptHints": {
@@ -852,6 +855,6 @@ export const EXTENSION_CAPABILITIES: ExtensionCapabilities = {
852
855
  }
853
856
  };
854
857
 
855
- export const EXTENSION_CONTRACT_VERSION = "1.8.0";
858
+ export const EXTENSION_CONTRACT_VERSION = "1.9.0";
856
859
 
857
860
  export const EXTENSION_TOOL_NAMES: readonly string[] = ["ping","capabilities","reload","debug_exec","detach","set_bridge_port","navigate","login","scroll_to","resize_window","tabs_list","tabs_create","tabs_close","click","click_element","click_xy","type_text","form_input","key_press","select_option","label_select","file_upload","read_ax","get_page_text","query_dom","find","wait_for","assert_text","screenshot","read_console","read_network","diag_suspension","diag_bridges","diag_activation","diag_ttft","capture_login_flow","wait_for_api_response","get_page_context","javascript_tool","browser_batch","set_explain_mode","annotate"];
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.1.0",
3
- "contractVersion": "1.8.0",
3
+ "contractVersion": "1.9.0",
4
4
  "multiPortDiscovery": true,
5
5
  "protocol": "tool_request/result",
6
6
  "tools": [
@@ -740,7 +740,10 @@
740
740
  "host_tool_call",
741
741
  "host_tool_update",
742
742
  "host_tool_result",
743
- "host_tool_cancel"
743
+ "host_tool_cancel",
744
+ "configure",
745
+ "configure_ack",
746
+ "configure_error"
744
747
  ],
745
748
  "description": "User ↔ xcsh chat over the bridge. The extension side panel sends chat_request (with mode and page-context snapshot); xcsh streams chat_delta tokens then a terminal chat_done (with reference links) or chat_error. Chat ids are prefixed \"c-\". Tool calls during a turn use the normal tool_request flow. chat_stop halts a streaming response. chat_tool_notice is emitted by the EXTENSION (the service worker) to the panel as a best-effort UI signal when a tool runs during a turn — it is NOT sent by xcsh; xcsh must not produce it to avoid double-rendering in the panel.",
746
749
  "promptHints": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "contractVersion": "1.8.0",
2
+ "contractVersion": "1.9.0",
3
3
  "schemas": {
4
4
  "chat_request": {
5
5
  "type": "object",
@@ -356,6 +356,52 @@
356
356
  }
357
357
  }
358
358
  },
359
+ "configure": {
360
+ "type": "object",
361
+ "required": ["type", "token"],
362
+ "properties": {
363
+ "type": {
364
+ "const": "configure",
365
+ "type": "string"
366
+ },
367
+ "baseUrl": {
368
+ "type": "string"
369
+ },
370
+ "token": {
371
+ "type": "string",
372
+ "minLength": 1
373
+ },
374
+ "model": {
375
+ "type": "string"
376
+ }
377
+ }
378
+ },
379
+ "configure_ack": {
380
+ "type": "object",
381
+ "required": ["type", "model"],
382
+ "properties": {
383
+ "type": {
384
+ "const": "configure_ack",
385
+ "type": "string"
386
+ },
387
+ "model": {
388
+ "type": "string"
389
+ }
390
+ }
391
+ },
392
+ "configure_error": {
393
+ "type": "object",
394
+ "required": ["type", "error"],
395
+ "properties": {
396
+ "type": {
397
+ "const": "configure_error",
398
+ "type": "string"
399
+ },
400
+ "error": {
401
+ "type": "string"
402
+ }
403
+ }
404
+ },
359
405
  "host_tool_call": {
360
406
  "type": "object",
361
407
  "required": ["type", "id", "toolCallId", "toolName", "arguments"],
@@ -713,6 +759,24 @@
713
759
  "type": "set_host_tools_error",
714
760
  "error": "Host tool \"office_read_range\" must provide a non-empty description"
715
761
  },
762
+ "configure": {
763
+ "type": "configure",
764
+ "baseUrl": "https://f5ai.pd.f5net.com/anthropic",
765
+ "token": "sk-example-token-123",
766
+ "model": "claude-opus-4-8"
767
+ },
768
+ "configure_no_baseUrl": {
769
+ "type": "configure",
770
+ "token": "sk-example-token-123"
771
+ },
772
+ "configure_ack": {
773
+ "type": "configure_ack",
774
+ "model": "claude-opus-4-8"
775
+ },
776
+ "configure_error": {
777
+ "type": "configure_error",
778
+ "error": "No model anthropic/claude-opus-4-8 available"
779
+ },
716
780
  "host_tool_call": {
717
781
  "type": "host_tool_call",
718
782
  "id": "7295551234567890",
@@ -933,6 +997,23 @@
933
997
  "content": "nope"
934
998
  }
935
999
  }
1000
+ },
1001
+ {
1002
+ "schema": "configure",
1003
+ "why": "token missing",
1004
+ "value": {
1005
+ "type": "configure",
1006
+ "baseUrl": "https://f5ai.pd.f5net.com/anthropic",
1007
+ "model": "claude-opus-4-8"
1008
+ }
1009
+ },
1010
+ {
1011
+ "schema": "configure",
1012
+ "why": "token empty",
1013
+ "value": {
1014
+ "type": "configure",
1015
+ "token": ""
1016
+ }
936
1017
  }
937
1018
  ]
938
1019
  }
@@ -1,4 +1,5 @@
1
1
  import type { AssistantMessage } from "@f5-sales-demo/pi-ai";
2
+ import { DEFAULT_MODEL_ROLE } from "../config/settings-schema";
2
3
  import {
3
4
  isRpcHostToolResult,
4
5
  isRpcHostToolUpdate,
@@ -14,11 +15,15 @@ import {
14
15
  type ChatKeepalive,
15
16
  type ChatReference,
16
17
  type ChatRequest,
18
+ type Configure,
19
+ type ConfigureAck,
20
+ type ConfigureError,
17
21
  type HostToolResult,
18
22
  type HostToolUpdate,
19
23
  type InteractionMode,
20
24
  isChatRequest,
21
25
  isChatStop,
26
+ isConfigure,
22
27
  isSetHostTools,
23
28
  type PageContextSnapshot,
24
29
  type SetHostTools,
@@ -92,6 +97,9 @@ export class ChatHandler {
92
97
  // Host-tool channel (#2046): register client tools, then route the client's
93
98
  // result/update frames back to the correlated pending call in the bridge.
94
99
  else if (isSetHostTools(msg)) this.#handleSetHostTools(msg as unknown as SetHostTools);
100
+ // Provider configuration channel (#2095): swap the LLM provider/model in
101
+ // session memory at runtime (never persisted), then ack or nack.
102
+ else if (isConfigure(msg)) this.#handleConfigure(msg as unknown as Configure);
95
103
  else if (isRpcHostToolResult(msg)) this.#hostToolBridge.handleResult(msg as unknown as HostToolResult);
96
104
  else if (isRpcHostToolUpdate(msg)) this.#hostToolBridge.handleUpdate(msg as unknown as HostToolUpdate);
97
105
  });
@@ -287,6 +295,70 @@ export class ChatHandler {
287
295
  }
288
296
  }
289
297
 
298
+ /** Configure the LLM provider at runtime from a bridge client (#2095), then ack
299
+ * with the selected model so the client can await it before its first prompt.
300
+ * SESSION/RUNTIME MEMORY ONLY — the token is never written to models.yml or the
301
+ * SQLite credential store. Mirrors #handleSetHostTools's try/ack-or-nack shape;
302
+ * never throws out of the handler (a nack keeps a waiting client from hanging).
303
+ *
304
+ * The baked F5 gateway registers its models under the "anthropic" provider
305
+ * (DEFAULT_MODEL_ROLE = "anthropic/claude-opus-4-8"), so that is the provider we
306
+ * (re)configure here. */
307
+ async #handleConfigure(msg: Configure): Promise<void> {
308
+ try {
309
+ const registry = this.#session.modelRegistry;
310
+ const [provider, defaultModelId] = DEFAULT_MODEL_ROLE.split("/");
311
+
312
+ if (msg.baseUrl) {
313
+ // SSRF guard: only an `https:` gateway URL may be dialed. Validate BEFORE
314
+ // registerProvider so a bad URL becomes a configure_error nack (never a
315
+ // silently-ignored frame that hangs the client). We deliberately do NOT
316
+ // block loopback/RFC-1918 targets: an operator-chosen INTERNAL gateway is
317
+ // the whole point (the F5 LiteLLM gateway, and the claude-office CORS proxy
318
+ // at https://127-0-0-1.local-ip.sh:8443 are legitimate targets).
319
+ //
320
+ // Accepted residual (user-requested tradeoff): the operator points xcsh at
321
+ // THEIR OWN gateway with THEIR OWN token over a loopback-only, TLS,
322
+ // Origin-checked bridge (extension-bridge `isAllowedBridgeOrigin`), https is
323
+ // enforced here, and the token is session-only (never persisted to disk).
324
+ const baseUrl = requireHttpsUrl(msg.baseUrl);
325
+
326
+ // baseUrl + apiKey, no models[] → sets the in-memory runtime API key AND
327
+ // overrides the existing provider models' baseUrl/headers (reusing their
328
+ // metadata). Nothing is persisted to disk.
329
+ registry.registerProvider(
330
+ provider,
331
+ {
332
+ baseUrl,
333
+ apiKey: msg.token,
334
+ headers: { "anthropic-beta": "context-1m-2025-08-07" },
335
+ },
336
+ "office-configure",
337
+ );
338
+ } else {
339
+ // Key-only: reuse the baked F5 gateway; set just the non-persistent runtime key.
340
+ registry.authStorage.setRuntimeApiKey(provider, msg.token);
341
+ }
342
+
343
+ const modelId = msg.model ?? this.#session.model?.id ?? defaultModelId;
344
+ const model = registry.find(provider, modelId);
345
+ if (!model) {
346
+ throw new Error(`No model ${provider}/${modelId} available`);
347
+ }
348
+ // setModel validates the API key and throws if missing → becomes configure_error.
349
+ await this.#session.setModel(model);
350
+
351
+ this.#server.send({ type: "configure_ack", model: model.id } satisfies ConfigureAck);
352
+ } catch (err) {
353
+ // Nack instead of throwing (set_host_tools parity): a client awaiting
354
+ // configure_ack would otherwise hang on a bad frame or missing key.
355
+ this.#server.send({
356
+ type: "configure_error",
357
+ error: err instanceof Error ? err.message : String(err),
358
+ } satisfies ConfigureError);
359
+ }
360
+ }
361
+
290
362
  #handleChatStop(stop: { id: string }): void {
291
363
  const chat = this.#activeChats.get(stop.id);
292
364
  if (!chat) return;
@@ -322,6 +394,24 @@ export class ChatHandler {
322
394
  }
323
395
  }
324
396
 
397
+ /** SSRF guard for the `configure` frame's optional gateway `baseUrl`: the URL must
398
+ * parse and use `https:`. Returns the ORIGINAL string unchanged on success (no
399
+ * normalization — the operator's exact gateway path is preserved); throws (→ becomes
400
+ * a `configure_error` nack) for a malformed or non-https URL. Loopback/private hosts
401
+ * are intentionally NOT blocked — the target is an operator-chosen internal gateway. */
402
+ export function requireHttpsUrl(raw: string): string {
403
+ let parsed: URL;
404
+ try {
405
+ parsed = new URL(raw);
406
+ } catch {
407
+ throw new Error(`configure baseUrl is not a valid URL: ${raw}`);
408
+ }
409
+ if (parsed.protocol !== "https:") {
410
+ throw new Error(`configure baseUrl must use https (got "${parsed.protocol}")`);
411
+ }
412
+ return raw;
413
+ }
414
+
325
415
  /** Best-effort classification of an upstream/provider error message into a
326
416
  * ChatErrorReason so the panel can pick a distinct, actionable message. Returns
327
417
  * undefined for an unclassified error (the panel then shows the raw error text).
@@ -3,15 +3,17 @@
3
3
  * Contract source of truth: capabilities.json v1.2.0.
4
4
  */
5
5
 
6
- import {
7
- isRpcHostToolResult,
8
- isRpcHostToolUpdate,
9
- type RpcHostToolCallRequest,
10
- type RpcHostToolCancelRequest,
11
- type RpcHostToolDefinition,
12
- type RpcHostToolResult,
13
- type RpcHostToolUpdate,
14
- } from "../host-tools";
6
+ // Import the guards + wire types from the PURE leaf modules (not the `../host-tools`
7
+ // barrel), so this browser-safe contract can be consumed by a lib.dom (React)
8
+ // TypeScript program without pulling the RpcHostToolBridge's theme/tool-proxy graph.
9
+ import { isRpcHostToolResult, isRpcHostToolUpdate } from "../host-tools/guards";
10
+ import type {
11
+ RpcHostToolCallRequest,
12
+ RpcHostToolCancelRequest,
13
+ RpcHostToolDefinition,
14
+ RpcHostToolResult,
15
+ RpcHostToolUpdate,
16
+ } from "../host-tools/types";
15
17
 
16
18
  // ---------------------------------------------------------------------------
17
19
  // Page context snapshot (auto-attached by extension to every chat_request)
@@ -176,6 +178,43 @@ export interface SetHostToolsError {
176
178
  error: string;
177
179
  }
178
180
 
181
+ // ---------------------------------------------------------------------------
182
+ // Provider configuration channel (contract 1.9.0)
183
+ //
184
+ // Lets a bridge client (the Chrome extension, the office-xcsh add-in) configure
185
+ // xcsh's LLM provider at runtime — after the socket is connected — without
186
+ // restarting the worker and WITHOUT persisting the token to disk. xcsh stays the
187
+ // intelligence engine; this only swaps the provider credentials/model in session
188
+ // memory. Single config in-flight, so — like `set_host_tools` — there is no `id`
189
+ // correlation field. Mirrors the set_host_tools ack/nack shape exactly.
190
+ // ---------------------------------------------------------------------------
191
+
192
+ /** Inbound: the client configures the LLM provider. `token` is required and
193
+ * non-empty. `baseUrl` (optional) is an Anthropic-compatible gateway base; when
194
+ * omitted, the baked F5 gateway is reused and only the runtime API key is set.
195
+ * `model` (optional) selects the model id; when omitted, the session default is
196
+ * kept. The token lives in session/runtime memory only — never written to disk. */
197
+ export interface Configure {
198
+ type: "configure";
199
+ baseUrl?: string;
200
+ token: string;
201
+ model?: string;
202
+ }
203
+
204
+ /** Outbound: acks a `configure` with the model id actually selected, so the
205
+ * client can await configuration before its first prompt. */
206
+ export interface ConfigureAck {
207
+ type: "configure_ack";
208
+ model: string;
209
+ }
210
+
211
+ /** Outbound: nacks a `configure` that failed (bad frame, unknown model, missing
212
+ * API key). Emitted instead of the ack so a client awaiting it never hangs. */
213
+ export interface ConfigureError {
214
+ type: "configure_error";
215
+ error: string;
216
+ }
217
+
179
218
  // ---------------------------------------------------------------------------
180
219
  // Validators
181
220
  // ---------------------------------------------------------------------------
@@ -202,6 +241,18 @@ export function isSetHostTools(msg: Record<string, unknown>): boolean {
202
241
  return msg.type === "set_host_tools" && Array.isArray(msg.tools);
203
242
  }
204
243
 
244
+ /** True for a well-formed `configure` frame: a non-empty string `token` is required;
245
+ * `baseUrl`/`model`, when present, must be strings. */
246
+ export function isConfigure(msg: Record<string, unknown>): boolean {
247
+ return (
248
+ msg.type === "configure" &&
249
+ typeof msg.token === "string" &&
250
+ msg.token.length > 0 &&
251
+ (msg.baseUrl === undefined || typeof msg.baseUrl === "string") &&
252
+ (msg.model === undefined || typeof msg.model === "string")
253
+ );
254
+ }
255
+
205
256
  /** Delegates to the neutral guard, which requires `result.content` to be an array. */
206
257
  export function isHostToolResult(msg: Record<string, unknown>): boolean {
207
258
  return isRpcHostToolResult(msg);
@@ -398,6 +398,7 @@ export class BridgeServer {
398
398
  contextBound: info.contextBound,
399
399
  pid: process.pid,
400
400
  wssPort: this.wssPort,
401
+ canConfigureProvider: true,
401
402
  }),
402
403
  );
403
404
  } else {
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Transport-neutral host-tool frame guards.
3
+ *
4
+ * Kept in a leaf module — depending ONLY on the wire types (`./types`) and
5
+ * pi-agent-core's `AgentToolResult` *type* — so browser-safe consumers (e.g.
6
+ * `../browser/chat-protocol`) can import the guards WITHOUT loading the
7
+ * `RpcHostToolBridge` in `./host-tools`, which pulls the theme + tool-proxy
8
+ * runtime graph. That graph is fine for the agent runtime but drags node-coupled
9
+ * modules into a browser (lib.dom) TypeScript program.
10
+ */
11
+ import type { AgentToolResult } from "@f5-sales-demo/pi-agent-core/types";
12
+ import type { RpcHostToolResult, RpcHostToolUpdate } from "./types";
13
+
14
+ function isAgentToolResult(value: unknown): value is AgentToolResult<unknown> {
15
+ if (!value || typeof value !== "object") return false;
16
+ const content = (value as { content?: unknown }).content;
17
+ return Array.isArray(content);
18
+ }
19
+
20
+ export function isRpcHostToolResult(value: unknown): value is RpcHostToolResult {
21
+ if (!value || typeof value !== "object") return false;
22
+ const frame = value as { type?: unknown; id?: unknown; result?: unknown };
23
+ return frame.type === "host_tool_result" && typeof frame.id === "string" && isAgentToolResult(frame.result);
24
+ }
25
+
26
+ export function isRpcHostToolUpdate(value: unknown): value is RpcHostToolUpdate {
27
+ if (!value || typeof value !== "object") return false;
28
+ const frame = value as { type?: unknown; id?: unknown; partialResult?: unknown };
29
+ return frame.type === "host_tool_update" && typeof frame.id === "string" && isAgentToolResult(frame.partialResult);
30
+ }
@@ -19,24 +19,6 @@ type PendingHostToolCall = {
19
19
  onUpdate?: AgentToolUpdateCallback<unknown>;
20
20
  };
21
21
 
22
- function isAgentToolResult(value: unknown): value is AgentToolResult<unknown> {
23
- if (!value || typeof value !== "object") return false;
24
- const content = (value as { content?: unknown }).content;
25
- return Array.isArray(content);
26
- }
27
-
28
- export function isRpcHostToolResult(value: unknown): value is RpcHostToolResult {
29
- if (!value || typeof value !== "object") return false;
30
- const frame = value as { type?: unknown; id?: unknown; result?: unknown };
31
- return frame.type === "host_tool_result" && typeof frame.id === "string" && isAgentToolResult(frame.result);
32
- }
33
-
34
- export function isRpcHostToolUpdate(value: unknown): value is RpcHostToolUpdate {
35
- if (!value || typeof value !== "object") return false;
36
- const frame = value as { type?: unknown; id?: unknown; partialResult?: unknown };
37
- return frame.type === "host_tool_update" && typeof frame.id === "string" && isAgentToolResult(frame.partialResult);
38
- }
39
-
40
22
  /**
41
23
  * Validate + normalize incoming host-tool definitions (trim names/labels/descriptions,
42
24
  * enforce non-empty name/description and a JSON-Schema `parameters` object). Shared by
@@ -7,12 +7,8 @@
7
7
  * transport specifics — a driver supplies only an `output: (frame) => void`
8
8
  * sink — so every transport reuses them verbatim.
9
9
  */
10
- export {
11
- isRpcHostToolResult,
12
- isRpcHostToolUpdate,
13
- normalizeHostToolDefinitions,
14
- RpcHostToolBridge,
15
- } from "./host-tools";
10
+ export { isRpcHostToolResult, isRpcHostToolUpdate } from "./guards";
11
+ export { normalizeHostToolDefinitions, RpcHostToolBridge } from "./host-tools";
16
12
  export type {
17
13
  RpcHostToolCallRequest,
18
14
  RpcHostToolCancelRequest,
@@ -6,7 +6,10 @@
6
6
  * back. They are shared verbatim across transports (stdio RPC and the WS chat
7
7
  * bridge), so they live outside of any single transport driver.
8
8
  */
9
- import type { AgentToolResult } from "@f5-sales-demo/pi-agent-core";
9
+ // Narrow subpath (not the barrel) so browser-safe consumers of these wire types
10
+ // don't transitively pull pi-agent-core's runtime graph (agent → pi-utils), which
11
+ // is not lib.dom-safe. AgentToolResult is a pure type.
12
+ import type { AgentToolResult } from "@f5-sales-demo/pi-agent-core/types";
10
13
 
11
14
  export interface RpcHostToolDefinition {
12
15
  name: string;
@@ -17,17 +17,17 @@ export interface BuildInfo {
17
17
  }
18
18
 
19
19
  export const BUILD_INFO: BuildInfo = {
20
- "version": "19.64.2",
21
- "commit": "2f997ccc03d56bf0345a7cb56fefb94414ff3d8b",
22
- "shortCommit": "2f997cc",
20
+ "version": "19.65.0",
21
+ "commit": "342e21815d71783c1c2b1ffe233bc0078fbcc44a",
22
+ "shortCommit": "342e218",
23
23
  "branch": "main",
24
- "tag": "v19.64.2",
25
- "commitDate": "2026-07-20T14:05:44Z",
26
- "buildDate": "2026-07-20T14:31:59.136Z",
24
+ "tag": "v19.65.0",
25
+ "commitDate": "2026-07-20T18:44:32Z",
26
+ "buildDate": "2026-07-20T19:07:31.313Z",
27
27
  "dirty": true,
28
28
  "prNumber": "",
29
29
  "repoUrl": "https://github.com/f5-sales-demo/xcsh",
30
30
  "repoSlug": "f5-sales-demo/xcsh",
31
- "commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/2f997ccc03d56bf0345a7cb56fefb94414ff3d8b",
32
- "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.64.2"
31
+ "commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/342e21815d71783c1c2b1ffe233bc0078fbcc44a",
32
+ "releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.65.0"
33
33
  };