@f5xc-salesdemos/xcsh 19.44.1 → 19.45.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 (60) hide show
  1. package/package.json +8 -8
  2. package/scripts/capture-ax-fixture.ts +5 -5
  3. package/scripts/capture-login-fixture.ts +1 -1
  4. package/scripts/extension-uat-harness.ts +7 -7
  5. package/scripts/generate-api-spec-index.ts +2 -2
  6. package/scripts/generate-terraform-index.ts +8 -8
  7. package/scripts/uat-matrix-modalities.ts +7 -7
  8. package/scripts/uat-matrix.ts +13 -13
  9. package/src/browser/extension-page-actions.ts +51 -4
  10. package/src/browser/extension-provider.ts +3 -3
  11. package/src/browser/input-commit.ts +24 -2
  12. package/src/cli/chrome-cli.ts +1 -1
  13. package/src/config/context-schema.json +3 -3
  14. package/src/config/settings-schema.ts +5 -5
  15. package/src/internal-urls/api-catalog-resolve.ts +5 -5
  16. package/src/internal-urls/branding-index.generated.ts +8 -8
  17. package/src/internal-urls/build-info-runtime.ts +2 -2
  18. package/src/internal-urls/build-info.generated.ts +8 -8
  19. package/src/internal-urls/console-catalog.generated.ts +766 -10
  20. package/src/internal-urls/console-field-metadata-types.ts +7 -0
  21. package/src/internal-urls/console-field-metadata.generated.ts +358 -11
  22. package/src/internal-urls/console-resolve.ts +16 -3
  23. package/src/internal-urls/docs-index.generated.ts +26 -26
  24. package/src/internal-urls/terraform-index.generated.ts +252 -252
  25. package/src/internal-urls/terraform-resolve.ts +13 -13
  26. package/src/internal-urls/xcsh-protocol.ts +3 -3
  27. package/src/main.ts +3 -3
  28. package/src/modes/components/context-add-wizard.ts +6 -6
  29. package/src/modes/components/plugins/utils.ts +1 -1
  30. package/src/modes/components/status-line/presets.ts +15 -15
  31. package/src/modes/components/status-line/segments.ts +10 -10
  32. package/src/modes/components/status-line-segment-editor.ts +1 -1
  33. package/src/modes/components/welcome-checks.ts +2 -2
  34. package/src/modes/components/welcome.ts +1 -1
  35. package/src/modes/controllers/context-command-controller.ts +3 -3
  36. package/src/modes/interactive-mode.ts +1 -1
  37. package/src/modes/theme/defaults/xcsh-dark.json +2 -2
  38. package/src/modes/theme/defaults/xcsh-light.json +2 -2
  39. package/src/modes/theme/theme-schema.json +4 -4
  40. package/src/modes/theme/theme.ts +6 -6
  41. package/src/prompts/system/system-prompt.md +12 -10
  42. package/src/prompts/tools/xcsh-api.md +16 -16
  43. package/src/sdk.ts +12 -12
  44. package/src/services/context-env.ts +17 -17
  45. package/src/services/{f5xc-api-client.ts → xcsh-api-client.ts} +22 -22
  46. package/src/services/{f5xc-context-command.ts → xcsh-context-command.ts} +36 -36
  47. package/src/services/{f5xc-context-display.ts → xcsh-context-display.ts} +1 -1
  48. package/src/services/{f5xc-context-segment.ts → xcsh-context-segment.ts} +4 -4
  49. package/src/services/{f5xc-context.ts → xcsh-context.ts} +104 -112
  50. package/src/services/{f5xc-env.ts → xcsh-env.ts} +42 -20
  51. package/src/services/{f5xc-knowledge.ts → xcsh-knowledge.ts} +2 -2
  52. package/src/services/{f5xc-table.ts → xcsh-table.ts} +3 -3
  53. package/src/session/session-manager.ts +2 -2
  54. package/src/slash-commands/builtin-registry.ts +1 -1
  55. package/src/slash-commands/export-command.ts +4 -4
  56. package/src/slash-commands/resource-commands.ts +4 -4
  57. package/src/system-prompt.ts +1 -1
  58. package/src/tools/catalog-workflow-runner.ts +6 -6
  59. package/src/tools/xcsh-api.ts +12 -12
  60. /package/src/services/{f5xc-context-indicators.ts → xcsh-context-indicators.ts} +0 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "19.44.1",
4
+ "version": "19.45.0",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/f5xc-salesdemos/xcsh",
7
7
  "author": "Can Boluk",
@@ -54,13 +54,13 @@
54
54
  "dependencies": {
55
55
  "@agentclientprotocol/sdk": "0.16.1",
56
56
  "@mozilla/readability": "^0.6",
57
- "@f5xc-salesdemos/xcsh-stats": "19.44.1",
58
- "@f5xc-salesdemos/pi-agent-core": "19.44.1",
59
- "@f5xc-salesdemos/pi-ai": "19.44.1",
60
- "@f5xc-salesdemos/pi-natives": "19.44.1",
61
- "@f5xc-salesdemos/pi-resource-management": "19.44.1",
62
- "@f5xc-salesdemos/pi-tui": "19.44.1",
63
- "@f5xc-salesdemos/pi-utils": "19.44.1",
57
+ "@f5xc-salesdemos/xcsh-stats": "19.45.0",
58
+ "@f5xc-salesdemos/pi-agent-core": "19.45.0",
59
+ "@f5xc-salesdemos/pi-ai": "19.45.0",
60
+ "@f5xc-salesdemos/pi-natives": "19.45.0",
61
+ "@f5xc-salesdemos/pi-resource-management": "19.45.0",
62
+ "@f5xc-salesdemos/pi-tui": "19.45.0",
63
+ "@f5xc-salesdemos/pi-utils": "19.45.0",
64
64
  "@sinclair/typebox": "^0.34",
65
65
  "@xterm/headless": "^6.0",
66
66
  "ajv": "^8.20",
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env bun
2
2
  // One-shot capture: login + open the HTTP-LB create form + snapshot the accessibility tree.
3
- // Creds via env (F5XC_USERNAME/F5XC_CONSOLE_PASSWORD); never hard-coded. Output is the resolver's test oracle.
3
+ // Creds via env (XCSH_USERNAME/XCSH_CONSOLE_PASSWORD); never hard-coded. Output is the resolver's test oracle.
4
4
  import puppeteer from "puppeteer";
5
5
 
6
6
  const CHROME = process.env.CHROME_PATH;
7
- const BASE = process.env.F5XC_CONSOLE_URL ?? "https://nferreira.staging.volterra.us";
8
- const NS = process.env.F5XC_NS ?? "demo";
7
+ const BASE = process.env.XCSH_CONSOLE_URL ?? "https://nferreira.staging.volterra.us";
8
+ const NS = process.env.XCSH_NS ?? "demo";
9
9
  const OUT = "test/browser/fixtures/xc-http-lb-create.ax.json";
10
10
 
11
11
  const browser = await puppeteer.launch({
@@ -18,8 +18,8 @@ try {
18
18
  await page.goto(BASE, { waitUntil: "networkidle2", timeout: 45000 }).catch(() => {});
19
19
  if (/login-staging\.volterra\.us/.test(page.url())) {
20
20
  await page.waitForSelector("#username", { timeout: 20000 });
21
- await page.type("#username", process.env.F5XC_USERNAME ?? "");
22
- await page.type("#password", process.env.F5XC_CONSOLE_PASSWORD ?? "");
21
+ await page.type("#username", process.env.XCSH_USERNAME ?? "");
22
+ await page.type("#password", process.env.XCSH_CONSOLE_PASSWORD ?? "");
23
23
  await Promise.all([
24
24
  page.waitForNavigation({ waitUntil: "networkidle2", timeout: 45000 }).catch(() => {}),
25
25
  page.click("#kc-login").catch(() => page.keyboard.press("Enter")),
@@ -13,7 +13,7 @@
13
13
  import puppeteer from "puppeteer";
14
14
 
15
15
  const CHROME = process.env.CHROME_PATH;
16
- const BASE = process.env.F5XC_API_URL ?? "https://nferreira.staging.volterra.us";
16
+ const BASE = process.env.XCSH_API_URL ?? "https://nferreira.staging.volterra.us";
17
17
  const PROFILE = "/tmp/xc-login-capture-profile";
18
18
  const LOGIN_OUT = "test/browser/fixtures/xc-login-wall.html";
19
19
 
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import { type BridgeServer, startBridgeServer } from "../src/browser/extension-bridge";
17
17
 
18
- const BASE = process.env.F5XC_API_URL ?? "https://nferreira.staging.volterra.us";
18
+ const BASE = process.env.XCSH_API_URL ?? "https://nferreira.staging.volterra.us";
19
19
  const ROUTE = `${BASE}/web/workspaces/web-app-and-api-protection/namespaces/demo/manage/load_balancers/http_loadbalancers`;
20
20
 
21
21
  let server: BridgeServer;
@@ -69,9 +69,9 @@ async function run() {
69
69
  }
70
70
 
71
71
  console.log("[1b] login (native F5 XC auth)");
72
- const F5XC_USERNAME = process.env.F5XC_USERNAME;
73
- const F5XC_CONSOLE_PASSWORD = process.env.F5XC_CONSOLE_PASSWORD;
74
- if (F5XC_USERNAME && F5XC_CONSOLE_PASSWORD) {
72
+ const XCSH_USERNAME = process.env.XCSH_USERNAME;
73
+ const XCSH_CONSOLE_PASSWORD = process.env.XCSH_CONSOLE_PASSWORD;
74
+ if (XCSH_USERNAME && XCSH_CONSOLE_PASSWORD) {
75
75
  try {
76
76
  // Login directly to the target deep-link — NOT /web. Going to /web triggers
77
77
  // an OIDC flow, then the subsequent navigate to the LB URL interrupts it
@@ -79,7 +79,7 @@ async function run() {
79
79
  // means ONE navigation, one OIDC flow, no interruption.
80
80
  const l = await tool(
81
81
  "login",
82
- { email: F5XC_USERNAME, password: F5XC_CONSOLE_PASSWORD, consoleUrl: ROUTE },
82
+ { email: XCSH_USERNAME, password: XCSH_CONSOLE_PASSWORD, consoleUrl: ROUTE },
83
83
  90000,
84
84
  );
85
85
  (l as any)?.loggedIn
@@ -89,11 +89,11 @@ async function run() {
89
89
  fail("login", (e as Error).message);
90
90
  }
91
91
  } else {
92
- console.log(" ⏭ login skipped (no F5XC_USERNAME/F5XC_CONSOLE_PASSWORD env) — relying on existing session");
92
+ console.log(" ⏭ login skipped (no XCSH_USERNAME/XCSH_CONSOLE_PASSWORD env) — relying on existing session");
93
93
  }
94
94
 
95
95
  console.log("[2] navigate");
96
- if (F5XC_USERNAME && F5XC_CONSOLE_PASSWORD) {
96
+ if (XCSH_USERNAME && XCSH_CONSOLE_PASSWORD) {
97
97
  // Login already navigated to ROUTE — skip the redundant second navigate
98
98
  // (two navigations to the same OIDC-protected URL in succession → CSRF).
99
99
  pass("navigate", "skipped (login already navigated to target)");
@@ -150,7 +150,7 @@ async function downloadFromRelease(): Promise<string> {
150
150
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "api-specs-"));
151
151
  downloadedTmpDir = tmpDir;
152
152
  const tag = process.env.API_SPECS_TAG ?? (await resolveLatestTag());
153
- const zipName = `f5xc-api-specs-${tag}.zip`;
153
+ const zipName = `xcsh-api-specs-${tag}.zip`;
154
154
  const downloadUrl = `https://github.com/${REPO}/releases/download/${tag}/${zipName}`;
155
155
 
156
156
  console.log(`Downloading API specs from ${downloadUrl}...`);
@@ -552,7 +552,7 @@ if (catalog) {
552
552
  `export const API_CATALOG_INDEX: ApiCatalogIndex = {`,
553
553
  `\tversion: ${JSON.stringify(catalog.version ?? "unknown")},`,
554
554
  `\tdisplayName: ${JSON.stringify(catalog.displayName ?? "F5 Distributed Cloud")},`,
555
- `\tservice: ${JSON.stringify(catalog.service ?? "f5xc")},`,
555
+ `\tservice: ${JSON.stringify(catalog.service ?? "xcsh")},`,
556
556
  `\tcategoryCount: ${categories.length},`,
557
557
  `\tauth: ${JSON.stringify(catalog.auth ?? {})},`,
558
558
  `\tdefaults: ${JSON.stringify(catalog.defaults ?? {})},`,
@@ -9,13 +9,13 @@ const LOCAL_JSON_PATH = path.resolve(
9
9
  "..",
10
10
  "..",
11
11
  "..",
12
- "terraform-provider-f5xc",
12
+ "terraform-provider-xcsh",
13
13
  "docs",
14
14
  "terraform-llms-index.json",
15
15
  );
16
16
 
17
17
  const GITHUB_RAW_URL =
18
- "https://raw.githubusercontent.com/f5xc-salesdemos/terraform-provider-f5xc/main/docs/terraform-llms-index.json";
18
+ "https://raw.githubusercontent.com/f5xc-salesdemos/terraform-provider-xcsh/main/docs/terraform-llms-index.json";
19
19
 
20
20
  async function loadTerraformIndex(): Promise<unknown> {
21
21
  const localFile = Bun.file(LOCAL_JSON_PATH);
@@ -41,14 +41,14 @@ async function loadTerraformIndex(): Promise<unknown> {
41
41
  // Backfill provider fields that older terraform-llms-index.json revisions lack, so the
42
42
  // generated index always satisfies TerraformProvider regardless of which provider-repo
43
43
  // revision it was fetched from (the source repo is the authority once it ships them).
44
- const DEFAULT_CONFIG_BLOCK = 'provider "f5xc" {}';
44
+ const DEFAULT_CONFIG_BLOCK = 'provider "xcsh" {}';
45
45
  const DEFAULT_AUTH_METHODS = [
46
- 'REQUIRED: every .tf must contain a `provider "f5xc" {}` block. Without it Terraform errors: "Provider requires explicit configuration. Add a provider block".',
46
+ 'REQUIRED: every .tf must contain a `provider "xcsh" {}` block. Without it Terraform errors: "Provider requires explicit configuration. Add a provider block".',
47
47
  "Configure exactly ONE auth method, via environment variables (preferred) or explicit arguments in the provider block:",
48
- "api_token (env F5XC_API_TOKEN) — API token authentication.",
49
- "api_p12_file + p12_password (env F5XC_P12_FILE + F5XC_P12_PASSWORD) — PKCS#12 certificate authentication.",
50
- "api_cert + api_key (env F5XC_CERT + F5XC_KEY) — PEM certificate authentication.",
51
- "api_url (env F5XC_API_URL) — tenant base URL without /api suffix, e.g. https://your-tenant.console.ves.volterra.io.",
48
+ "api_token (env XCSH_API_TOKEN) — API token authentication.",
49
+ "api_p12_file + p12_password (env XCSH_P12_FILE + XCSH_P12_PASSWORD) — PKCS#12 certificate authentication.",
50
+ "api_cert + api_key (env XCSH_CERT + XCSH_KEY) — PEM certificate authentication.",
51
+ "api_url (env XCSH_API_URL) — tenant base URL without /api suffix, e.g. https://your-tenant.console.ves.volterra.io.",
52
52
  ];
53
53
 
54
54
  function normalizeProvider(data: unknown): unknown {
@@ -108,7 +108,7 @@ export function detectRouted(stdout: string): Cell["routedTo"] {
108
108
  if (usedConsole && !usedApi) return "console";
109
109
  if (usedApi && !usedConsole) return "api";
110
110
  if (usedConsole && usedApi) return "console"; // console drove it even if it also read via API
111
- if (/```(hcl|terraform)|resource\s+"f5xc_/.test(stdout)) return "file";
111
+ if (/```(hcl|terraform)|resource\s+"xcsh_/.test(stdout)) return "file";
112
112
  return "unknown";
113
113
  }
114
114
 
@@ -168,7 +168,7 @@ export async function runConsole(p: ConsolePhrase, cfg: MatrixConfig): Promise<C
168
168
  const run = await runXcsh(args, {
169
169
  timeoutMs: cfg.cellTimeoutMs,
170
170
  bin: cfg.xcshBin,
171
- env: { ...process.env, F5XC_API_URL: cfg.apiUrl },
171
+ env: { ...process.env, XCSH_API_URL: cfg.apiUrl },
172
172
  });
173
173
 
174
174
  const routedTo = detectRouted(run.stdout);
@@ -268,7 +268,7 @@ export async function runJson(p: CrudPhrase, cfg: MatrixConfig): Promise<Cell> {
268
268
  const run = await runXcsh(["--print", "--no-session", p.phrase], {
269
269
  timeoutMs: 120_000,
270
270
  bin: cfg.xcshBin,
271
- env: { ...process.env, F5XC_API_URL: cfg.apiUrl, F5XC_API_TOKEN: cfg.apiToken },
271
+ env: { ...process.env, XCSH_API_URL: cfg.apiUrl, XCSH_API_TOKEN: cfg.apiToken },
272
272
  });
273
273
 
274
274
  let code = await apiGet(verifyPath, cfg);
@@ -318,7 +318,7 @@ function extractHcl(stdout: string, workdir: string): string {
318
318
  let m: RegExpExecArray | null;
319
319
  // biome-ignore lint/suspicious/noAssignInExpressions: standard regex loop
320
320
  while ((m = fence.exec(stdout)) !== null) {
321
- if (/resource\s+"f5xc_|provider\s+"f5xc"|terraform\s*\{/.test(m[1]!)) hcl += `\n${m[1]}`;
321
+ if (/resource\s+"xcsh_|provider\s+"xcsh"|terraform\s*\{/.test(m[1]!)) hcl += `\n${m[1]}`;
322
322
  }
323
323
  return hcl.trim();
324
324
  }
@@ -330,14 +330,14 @@ async function terraformAvailable(): Promise<boolean> {
330
330
 
331
331
  export async function runHcl(p: TfPhrase, cfg: MatrixConfig, tfOk: boolean): Promise<Cell> {
332
332
  const start = performance.now();
333
- const id = `H-${(p.expect_resource ?? p.operation).replace(/^f5xc_/, "")}-${p.operation}`;
333
+ const id = `H-${(p.expect_resource ?? p.operation).replace(/^xcsh_/, "")}-${p.operation}`;
334
334
  const workdir = fs.mkdtempSync(path.join(os.tmpdir(), "uat-hcl-"));
335
335
  try {
336
336
  const run = await runXcsh(["--print", "--no-session", p.phrase], {
337
337
  timeoutMs: 120_000,
338
338
  bin: cfg.xcshBin,
339
339
  cwd: workdir,
340
- env: { ...process.env, F5XC_API_URL: cfg.apiUrl },
340
+ env: { ...process.env, XCSH_API_URL: cfg.apiUrl },
341
341
  });
342
342
 
343
343
  // expect_command phrases (plan/destroy explanations): keyword presence only.
@@ -427,7 +427,7 @@ export async function runI18n(p: I18nPhrase, locale: string, cfg: MatrixConfig):
427
427
  timeoutMs: 120_000,
428
428
  bin: cfg.xcshBin,
429
429
  cwd: workdir,
430
- env: { ...process.env, F5XC_API_URL: cfg.apiUrl },
430
+ env: { ...process.env, XCSH_API_URL: cfg.apiUrl },
431
431
  });
432
432
  const hcl = extractHcl(run.stdout, workdir);
433
433
  const ok = hcl.includes(p.expected_resource_type) && p.expected_fields.every(f => hcl.includes(f));
@@ -13,7 +13,7 @@
13
13
  * [--filter <regex over phrase ids>] [--no-cleanup] [--cleanup-only]
14
14
  * [--dry-run] [--self-test-api] [--strict-nl] [--report-dir <dir>]
15
15
  *
16
- * Env: F5XC_API_URL, F5XC_API_TOKEN, F5XC_USERNAME, F5XC_CONSOLE_PASSWORD, F5XC_NAMESPACE=demo,
16
+ * Env: XCSH_API_URL, XCSH_API_TOKEN, XCSH_USERNAME, XCSH_CONSOLE_PASSWORD, XCSH_NAMESPACE=demo,
17
17
  * XCSH_BIN=xcsh
18
18
  */
19
19
  import * as fs from "node:fs";
@@ -122,10 +122,10 @@ function applyLimitFilter<T>(items: T[], args: Args, idOf: (t: T) => string): T[
122
122
  function cfgFromEnv(args: Args): MatrixConfig {
123
123
  return {
124
124
  xcshBin: process.env.XCSH_BIN ?? "xcsh",
125
- apiUrl: process.env.F5XC_API_URL ?? "https://nferreira.staging.volterra.us",
126
- apiToken: process.env.F5XC_API_TOKEN ?? "",
127
- consoleNamespace: process.env.F5XC_NAMESPACE ?? "demo",
128
- tenant: (process.env.F5XC_API_URL ?? "https://nferreira.staging.volterra.us")
125
+ apiUrl: process.env.XCSH_API_URL ?? "https://nferreira.staging.volterra.us",
126
+ apiToken: process.env.XCSH_API_TOKEN ?? "",
127
+ consoleNamespace: process.env.XCSH_NAMESPACE ?? "demo",
128
+ tenant: (process.env.XCSH_API_URL ?? "https://nferreira.staging.volterra.us")
129
129
  .replace(/^https?:\/\//, "")
130
130
  .split(".")[0]!,
131
131
  observable: args.observable,
@@ -152,21 +152,21 @@ async function ensureExtensionConnectedAndLogin(cfg: MatrixConfig): Promise<void
152
152
  );
153
153
  }
154
154
  console.log("[console] extension connected ✅");
155
- const F5XC_USERNAME = process.env.F5XC_USERNAME;
156
- const F5XC_CONSOLE_PASSWORD = process.env.F5XC_CONSOLE_PASSWORD;
157
- if (F5XC_USERNAME && F5XC_CONSOLE_PASSWORD) {
155
+ const XCSH_USERNAME = process.env.XCSH_USERNAME;
156
+ const XCSH_CONSOLE_PASSWORD = process.env.XCSH_CONSOLE_PASSWORD;
157
+ if (XCSH_USERNAME && XCSH_CONSOLE_PASSWORD) {
158
158
  const route = `${cfg.apiUrl}/web/workspaces/web-app-and-api-protection/namespaces/${cfg.consoleNamespace}/manage/load_balancers/http_loadbalancers`;
159
159
  console.log("[console] establishing session via login tool...");
160
160
  const r = await server.request(
161
161
  "login",
162
- { email: F5XC_USERNAME, password: F5XC_CONSOLE_PASSWORD, consoleUrl: route },
162
+ { email: XCSH_USERNAME, password: XCSH_CONSOLE_PASSWORD, consoleUrl: route },
163
163
  160_000,
164
164
  );
165
165
  if (r.is_error || !(r.content as any)?.loggedIn)
166
166
  throw new Error(`login failed: ${JSON.stringify(r.content).slice(0, 160)}`);
167
167
  console.log("[console] logged in ✅ (session persists across xcsh runs)");
168
168
  } else {
169
- console.log("[console] no F5XC_USERNAME/F5XC_CONSOLE_PASSWORD — relying on an existing Chrome session");
169
+ console.log("[console] no XCSH_USERNAME/XCSH_CONSOLE_PASSWORD — relying on an existing Chrome session");
170
170
  }
171
171
  } finally {
172
172
  // Release the socket so each `xcsh --print` can own it (the extension reconnects via its alarm).
@@ -211,7 +211,7 @@ async function main() {
211
211
  const startedAt = new Date().toISOString();
212
212
 
213
213
  if (args.cleanupOnly) {
214
- if (!cfg.apiToken) throw new Error("F5XC_API_TOKEN required for cleanup");
214
+ if (!cfg.apiToken) throw new Error("XCSH_API_TOKEN required for cleanup");
215
215
  await cleanup(cfg);
216
216
  return;
217
217
  }
@@ -244,7 +244,7 @@ async function main() {
244
244
 
245
245
  // --- API self-test ---
246
246
  if (args.selfTestApi) {
247
- if (!cfg.apiToken) throw new Error("F5XC_API_TOKEN required");
247
+ if (!cfg.apiToken) throw new Error("XCSH_API_TOKEN required");
248
248
  const present = await apiGet(`/api/config/namespaces/${cfg.consoleNamespace}/healthchecks`, cfg);
249
249
  const absent = await apiGet(
250
250
  `/api/config/namespaces/${cfg.consoleNamespace}/healthchecks/uat-definitely-absent-xyz`,
@@ -254,7 +254,7 @@ async function main() {
254
254
  process.exit(present === "200" && absent === "404" ? 0 : 1);
255
255
  }
256
256
 
257
- if (!cfg.apiToken) throw new Error("F5XC_API_TOKEN required for verification");
257
+ if (!cfg.apiToken) throw new Error("XCSH_API_TOKEN required for verification");
258
258
 
259
259
  // --- Console FIRST (human watching) ---
260
260
  if (args.modalities.has("console")) {
@@ -43,6 +43,7 @@ function roleToSelector(role){
43
43
  case'link':return'a[href],[role=link]';
44
44
  case'tab':return'[role=tab]';
45
45
  case'textbox':return'input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]),textarea,[role=textbox]';
46
+ case'textarea':return'textarea';
46
47
  case'spinbutton':return'input[type=number],[role=spinbutton]';
47
48
  case'checkbox':return'input[type=checkbox],[role=checkbox]';
48
49
  case'radio':return'input[type=radio],[role=radio]';
@@ -130,7 +131,8 @@ if(sel.includes('>>')){
130
131
  if(!el)return JSON.stringify({found:false,error:'no match for '+sel});
131
132
  el.scrollIntoView({block:'center',inline:'center'});
132
133
  const r=el.getBoundingClientRect();
133
- return JSON.stringify({found:true,x:Math.round(r.x+r.width/2),y:Math.round(r.y+r.height/2),tag:el.tagName,txt:(el.textContent||'').trim().slice(0,30)});
134
+ const inGrid=!!el.closest&&!!el.closest('ngx-datatable,datatable-body-cell,datatable-body-row,[class*=datatable]');
135
+ return JSON.stringify({found:true,x:Math.round(r.x+r.width/2),y:Math.round(r.y+r.height/2),tag:el.tagName,txt:(el.textContent||'').trim().slice(0,30),inGrid:inGrid,val:(el.value!==undefined&&el.value!==null?String(el.value):'')});
134
136
  })()`;
135
137
  }
136
138
 
@@ -168,7 +170,7 @@ function buildFillScript(selector: string, value: string): string {
168
170
  const val = JSON.stringify(value);
169
171
  return `(()=>{
170
172
  const sel=${sel};
171
- function roleToSel(r){const m={button:'button,input[type=button],input[type=submit],[role=button]',link:'a[href],[role=link]',tab:'[role=tab]',textbox:'input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]),textarea,[role=textbox]',spinbutton:'input[type=number],[role=spinbutton]',checkbox:'input[type=checkbox],[role=checkbox]',combobox:'select,[role=combobox]',listbox:'[role=listbox],select',option:'[role=option],option',heading:'h1,h2,h3,h4,h5,h6,[role=heading]'};return m[r]||'[role='+r+']';}
173
+ function roleToSel(r){const m={button:'button,input[type=button],input[type=submit],[role=button]',link:'a[href],[role=link]',tab:'[role=tab]',textbox:'input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]),textarea,[role=textbox]',textarea:'textarea',spinbutton:'input[type=number],[role=spinbutton]',checkbox:'input[type=checkbox],[role=checkbox]',combobox:'select,[role=combobox]',listbox:'[role=listbox],select',option:'[role=option],option',heading:'h1,h2,h3,h4,h5,h6,[role=heading]'};return m[r]||'[role='+r+']';}
172
174
  function norm(t){return t.replace(/\\u0421/g,'C').replace(/\\s+/g,' ').trim();}
173
175
  function findText(text,rSel){return(rSel?[...document.querySelectorAll(rSel)]:[...document.querySelectorAll('*')]).find(e=>{const n=norm(e.textContent||'');return n===norm(text)||n.includes(norm(text));});}
174
176
  function findRoleName(role,name){const cs=[...document.querySelectorAll(roleToSel(role))];const w=norm(name);return cs.find(e=>norm(e.getAttribute('aria-label')||e.getAttribute('name')||e.getAttribute('placeholder')||e.textContent||'').includes(w))||(role==='textbox'?cs.find(e=>norm((e.closest('[class*=form-group],[class*=field],.row')||{}).textContent||'').includes(w)):null);}
@@ -179,8 +181,20 @@ if(!el)return JSON.stringify({filled:false,error:'selector not found: '+sel});
179
181
  el.scrollIntoView({block:'center',inline:'center'});el.focus();
180
182
  let p=Object.getPrototypeOf(el),d;while(p){d=Object.getOwnPropertyDescriptor(p,'value');if(d&&d.set)break;p=Object.getPrototypeOf(p);}
181
183
  const v=${val};d&&d.set?d.set.call(el,v):el.value=v;
182
- for(const ev of['input','change','blur','focusout'])el.dispatchEvent(new Event(ev,{bubbles:true}));
183
- return JSON.stringify({filled:true,val:el.value});
184
+ // Commit to Angular's form model: input+change first. ngx-datatable inline-edit
185
+ // cells differ in how they persist: http-lb "Domains" (vsui-input) commits on
186
+ // BLUR, while ip-prefix-set "IPv4 Prefix" reverts on a bare blur and only persists
187
+ // on an Enter keydown. So for inputs inside a datatable we dispatch Enter BEFORE
188
+ // blur (Enter commits the row; the subsequent blur then re-renders from a model
189
+ // that already holds the value, instead of reverting). Plain textboxes get only
190
+ // blur/focusout — no synthetic Enter, which would risk a premature form submit.
191
+ el.dispatchEvent(new Event('input',{bubbles:true}));
192
+ el.dispatchEvent(new Event('change',{bubbles:true}));
193
+ const inGrid=!!el.closest&&!!el.closest('ngx-datatable,datatable-body-cell,datatable-body-row,[class*=datatable]');
194
+ if(inGrid){for(const t of['keydown','keypress','keyup'])el.dispatchEvent(new KeyboardEvent(t,{bubbles:true,key:'Enter',code:'Enter',keyCode:13,which:13}));}
195
+ el.dispatchEvent(new Event('blur',{bubbles:true}));
196
+ el.dispatchEvent(new Event('focusout',{bubbles:true}));
197
+ return JSON.stringify({filled:true,val:el.value,inGrid:inGrid});
184
198
  })()`;
185
199
  }
186
200
 
@@ -214,6 +228,39 @@ export class ExtensionPageActions implements PageActions {
214
228
  }
215
229
 
216
230
  async fill(selector: string, value: string, _context?: string): Promise<void> {
231
+ // Probe the element: location, current value, and whether it's an
232
+ // ngx-datatable inline-edit cell. Most console inputs commit reliably via
233
+ // the native-setter path (buildFillScript) — that's what the verified
234
+ // resources use. But ngx-datatable inline cells (e.g. ip-prefix-set's
235
+ // "IPv4 Prefix") do NOT pick up a synthetic value-setter: the cell stays
236
+ // ng-untouched and the reactive form reports the field empty at save. Those
237
+ // cells only commit through REAL keystrokes (each keydown runs inside
238
+ // Angular's NgZone), so for grid inputs we focus the cell and type via CDP.
239
+ const probe = await evalJson(this.#ext, buildResolverScript(selector));
240
+ if (!probe?.found) {
241
+ throw new Error(`fill("${selector}"): ${probe?.error ?? "selector not found"}`);
242
+ }
243
+ // Two element classes need REAL CDP keystrokes, not a synthetic value-setter:
244
+ // - ngx-datatable inline cells (probe.inGrid) — commit on Enter.
245
+ // - <textarea> inside vsui secret/Blindfold sub-forms (container_registry's
246
+ // "Secret to Blindfold") — the native setter leaves them empty/required.
247
+ // For textareas we must NOT press Enter (it inserts a newline); Tab blurs.
248
+ const isTextarea = probe.tag === "TEXTAREA";
249
+ if (probe.inGrid || isTextarea) {
250
+ await this.#ext.clickXy(probe.x as number, probe.y as number);
251
+ await new Promise(r => setTimeout(r, 200));
252
+ // Clear any pre-existing text (Backspace ×N) so re-fills are clean —
253
+ // fresh Add-Item rows are empty, so this is a no-op for create.
254
+ const curLen = typeof probe.val === "string" ? probe.val.length : 0;
255
+ for (let i = 0; i < curLen; i++) await this.#ext.keyPress("Backspace").catch(() => {});
256
+ await this.#ext.typeText(value);
257
+ await new Promise(r => setTimeout(r, 200));
258
+ await this.#ext.keyPress(probe.inGrid && !isTextarea ? "Enter" : "Tab").catch(() => {});
259
+ await new Promise(r => setTimeout(r, 300));
260
+ const after = await evalJson(this.#ext, buildResolverScript(selector));
261
+ if (typeof after?.val === "string" && after.val.includes(value)) return;
262
+ // Real typing didn't stick — fall through to the native-setter backstop.
263
+ }
217
264
  const result = await evalJson(this.#ext, buildFillScript(selector, value));
218
265
  if (!result?.filled) {
219
266
  throw new Error(`fill("${selector}"): ${result?.error ?? "could not set value"}`);
@@ -252,13 +252,13 @@ export class ExtensionBrowserProvider implements BrowserProvider {
252
252
 
253
253
  const page: ExtensionPage = new BridgeExtensionPage(server);
254
254
 
255
- // Auto-login: if F5XC_USERNAME + F5XC_CONSOLE_PASSWORD are available (set by
255
+ // Auto-login: if XCSH_USERNAME + XCSH_CONSOLE_PASSWORD are available (set by
256
256
  // ContextService from the context's env map), login automatically — the login
257
257
  // tool handles "already authenticated" (instant return) so calling it every
258
258
  // time is cheap and guarantees a valid session. Falls back to navigate-only
259
259
  // (co-drive) if credentials aren't available.
260
- const email = process.env.F5XC_USERNAME;
261
- const password = process.env.F5XC_CONSOLE_PASSWORD;
260
+ const email = process.env.XCSH_USERNAME;
261
+ const password = process.env.XCSH_CONSOLE_PASSWORD;
262
262
  if (email && password) {
263
263
  await page.login(email, password, consoleUrl);
264
264
  } else {
@@ -2,7 +2,7 @@
2
2
  * Commit a value into a form control so that a framework which patched the
3
3
  * element's `value` descriptor still observes the change.
4
4
  *
5
- * Why this exists: the F5XC console's `vsui-input` (e.g. over the HTTP-LB
5
+ * Why this exists: the XCSH console's `vsui-input` (e.g. over the HTTP-LB
6
6
  * "Domains" ngx-datatable cell) overrides the native `<input>` `value`
7
7
  * descriptor with its own accessor. Typing real keystrokes — or assigning
8
8
  * `el.value = …` directly — leaves the text in the DOM but never reaches the
@@ -18,10 +18,16 @@
18
18
  * `.toString()` — the same pattern used by dom-context/dt-context.
19
19
  */
20
20
 
21
+ /** Minimal KeyboardEvent constructor shape — typed loosely because this module
22
+ * is type-checked under the webworker lib (no DOM `KeyboardEvent` global) but is
23
+ * injected into and runs in the page, where `view.KeyboardEvent` exists. */
24
+ type KeyboardEventCtor = new (type: string, init?: { bubbles?: boolean; key?: string; code?: string }) => unknown;
25
+
21
26
  interface CommittableElement {
22
27
  value: string;
23
- ownerDocument?: { defaultView?: { Event?: typeof Event } | null } | null;
28
+ ownerDocument?: { defaultView?: { Event?: typeof Event; KeyboardEvent?: KeyboardEventCtor } | null } | null;
24
29
  dispatchEvent(event: unknown): unknown;
30
+ closest?(selector: string): unknown;
25
31
  }
26
32
 
27
33
  export function commitInputValue(el: CommittableElement, value: string): void {
@@ -46,6 +52,22 @@ export function commitInputValue(el: CommittableElement, value: string): void {
46
52
  // console's vsui-input commits to the model on blur, not on keystroke).
47
53
  el.dispatchEvent(new EventCtor("input", { bubbles: true }));
48
54
  el.dispatchEvent(new EventCtor("change", { bubbles: true }));
55
+ // ngx-datatable inline-edit cells that persist on Enter (ip-prefix-set
56
+ // "IPv4 Prefix") revert on a bare blur. For inputs inside a datatable,
57
+ // dispatch Enter BEFORE blur so the row commits first; http-lb "Domains"
58
+ // (commits on blur) is unaffected because blur still fires afterwards.
59
+ // Only the page provides KeyboardEvent (this module type-checks under the
60
+ // webworker lib, which has no DOM KeyboardEvent global) — if absent, skip
61
+ // the best-effort Enter and let the blur below carry the commit.
62
+ const KeyCtor = view?.KeyboardEvent;
63
+ const inGrid =
64
+ typeof el.closest === "function" &&
65
+ !!el.closest("ngx-datatable,datatable-body-cell,datatable-body-row,[class*=datatable]");
66
+ if (inGrid && KeyCtor) {
67
+ for (const t of ["keydown", "keypress", "keyup"] as const) {
68
+ el.dispatchEvent(new KeyCtor(t, { bubbles: true, key: "Enter", code: "Enter" }));
69
+ }
70
+ }
49
71
  el.dispatchEvent(new EventCtor("blur", { bubbles: false }));
50
72
  el.dispatchEvent(new EventCtor("focusout", { bubbles: true }));
51
73
  }
@@ -18,7 +18,7 @@ export type ChromeAction = "status" | "relaunch" | "setup";
18
18
  export const EXTENSION_ID = "klajkjdoehjidngligegnpknogmjjhkc";
19
19
  export const EXTENSION_IDS = [EXTENSION_ID];
20
20
 
21
- const NATIVE_HOST_NAME = "com.f5xc.xcsh.chrome_host";
21
+ const NATIVE_HOST_NAME = "com.xcsh.xcsh.chrome_host";
22
22
 
23
23
  function nativeHostDir(platform: NodeJS.Platform, home: string): string {
24
24
  if (platform === "darwin")
@@ -2,7 +2,7 @@
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "https://raw.githubusercontent.com/f5xc-salesdemos/xcsh/main/packages/coding-agent/src/config/context-schema.json",
4
4
  "title": "F5 XC Context",
5
- "description": "Schema for F5 XC context files stored in ~/.config/f5xc/contexts/<name>.json. Validated at load, create, and import time.",
5
+ "description": "Schema for F5 XC context files stored in ~/.config/xcsh/contexts/<name>.json. Validated at load, create, and import time.",
6
6
  "type": "object",
7
7
  "required": ["apiUrl", "apiToken"],
8
8
  "additionalProperties": false,
@@ -39,7 +39,7 @@
39
39
  "type": "string",
40
40
  "minLength": 1,
41
41
  "default": "default",
42
- "description": "Default namespace for CRUD operations. Projected as F5XC_NAMESPACE in bash environment. Use /context namespace to change."
42
+ "description": "Default namespace for CRUD operations. Projected as XCSH_NAMESPACE in bash environment. Use /context namespace to change."
43
43
  },
44
44
  "env": {
45
45
  "type": "object",
@@ -55,7 +55,7 @@
55
55
  },
56
56
  {
57
57
  "not": {
58
- "enum": ["F5XC_NAMESPACE", "F5XC_API_URL", "F5XC_API_TOKEN", "F5XC_TENANT"]
58
+ "enum": ["XCSH_NAMESPACE", "XCSH_API_URL", "XCSH_API_TOKEN", "XCSH_TENANT"]
59
59
  },
60
60
  "description": "Reserved keys have dedicated top-level fields and must not appear in env."
61
61
  }
@@ -76,7 +76,7 @@ export type StatusLineSegmentId =
76
76
  | "cache_read"
77
77
  | "cache_write"
78
78
  | "session_name"
79
- | "context_f5xc"
79
+ | "context_xcsh"
80
80
  | "os_icon";
81
81
 
82
82
  interface UiMetadata {
@@ -1904,14 +1904,14 @@ export const SETTINGS_SCHEMA = {
1904
1904
  "thinkingBudgets.xhigh": { type: "number", default: 32768 },
1905
1905
 
1906
1906
  // ────────────────────────────────────────────────────────────────────────
1907
- // Fork-specific settings (xcsh / f5xc)
1907
+ // Fork-specific settings (xcsh / xcsh)
1908
1908
  // ────────────────────────────────────────────────────────────────────────
1909
1909
 
1910
- /** Per-session environment variables injected into bash (used by f5xc context system) */
1910
+ /** Per-session environment variables injected into bash (used by xcsh context system) */
1911
1911
  "bash.environment": { type: "record", default: {} as Record<string, string> },
1912
1912
 
1913
- /** Sensitive env var key names from the active f5xc context (populated by ContextService) */
1914
- "f5xc.sensitiveKeys": { type: "array", default: [] as string[] },
1913
+ /** Sensitive env var key names from the active xcsh context (populated by ContextService) */
1914
+ "xcsh.sensitiveKeys": { type: "array", default: [] as string[] },
1915
1915
 
1916
1916
  /** Clear terminal on startup */
1917
1917
  "startup.clearScreen": { type: "boolean", default: false },
@@ -252,7 +252,7 @@ function renderCatalogDetail(cat: ApiCatalogCategory, index: ApiCatalogIndex, op
252
252
  // Universal reference format hint — shown even in compact mode
253
253
  if (op.method.toUpperCase() === "POST" || op.method.toUpperCase() === "PUT") {
254
254
  sections.push(
255
- 'Object references: `{"namespace": "$F5XC_NAMESPACE", "name": "<name>"}`. Pool arrays: `[{"pool": {"namespace": "$F5XC_NAMESPACE", "name": "<name>"}, "weight": 1, "priority": 1}]`.',
255
+ 'Object references: `{"namespace": "$XCSH_NAMESPACE", "name": "<name>"}`. Pool arrays: `[{"pool": {"namespace": "$XCSH_NAMESPACE", "name": "<name>"}, "weight": 1, "priority": 1}]`.',
256
256
  );
257
257
  }
258
258
 
@@ -340,13 +340,13 @@ async function fetchNamespaceInventory(
340
340
  data: Readonly<Record<string, ApiCatalogCategory>>,
341
341
  url: InternalUrl,
342
342
  ): Promise<string> {
343
- const apiBase = (process.env.F5XC_API_URL ?? "").replace(/\/+$/, "");
344
- const apiToken = process.env.F5XC_API_TOKEN ?? "";
343
+ const apiBase = (process.env.XCSH_API_URL ?? "").replace(/\/+$/, "");
344
+ const apiToken = process.env.XCSH_API_TOKEN ?? "";
345
345
  if (!apiBase || !apiToken) {
346
- return "# Namespace Inventory\n\nError: No API credentials configured. Set F5XC_API_URL and F5XC_API_TOKEN, or activate a context.\n";
346
+ return "# Namespace Inventory\n\nError: No API credentials configured. Set XCSH_API_URL and XCSH_API_TOKEN, or activate a context.\n";
347
347
  }
348
348
 
349
- const ns = url.searchParams.get("ns") ?? process.env.F5XC_NAMESPACE ?? "default";
349
+ const ns = url.searchParams.get("ns") ?? process.env.XCSH_NAMESPACE ?? "default";
350
350
  const CONFIG_PREFIX = "/api/config/namespaces/{namespace}/";
351
351
  const APP_KW =
352
352
  /loadbalancer|pool|firewall|_policys|setting|type|mitigation|identification|network|route|host|definition|rate_limiter|prefix_set|cdn|waf|api_/i;
@@ -31,21 +31,21 @@ export const BRANDING_DEPRECATIONS = {
31
31
  note: "Still live on registry with no deprecation notice. High risk of AI model recommendation due to training data prevalence.\n",
32
32
  },
33
33
  canonical: {
34
- registry: "registry.terraform.io/providers/f5xc-salesdemos/f5xc",
35
- source: "f5xc-salesdemos/f5xc",
36
- github: "github.com/f5xc-salesdemos/terraform-provider-f5xc",
37
- docs: "https://f5xc-salesdemos.github.io/terraform-provider-f5xc/",
38
- llms_txt: "https://f5xc-salesdemos.github.io/terraform-provider-f5xc/llms.txt",
34
+ registry: "registry.terraform.io/providers/f5xc-salesdemos/xcsh",
35
+ source: "f5xc-salesdemos/xcsh",
36
+ github: "github.com/f5xc-salesdemos/terraform-provider-xcsh",
37
+ docs: "https://f5xc-salesdemos.github.io/terraform-provider-xcsh/",
38
+ llms_txt: "https://f5xc-salesdemos.github.io/terraform-provider-xcsh/llms.txt",
39
39
  },
40
40
  required_providers_block:
41
- 'terraform {\n required_providers {\n f5xc = {\n source = "f5xc-salesdemos/f5xc"\n }\n }\n}\n',
41
+ 'terraform {\n required_providers {\n xcsh = {\n source = "f5xc-salesdemos/xcsh"\n }\n }\n}\n',
42
42
  },
43
43
  api_endpoint: {
44
44
  deprecated: {
45
45
  url: "console.ves.volterra.io",
46
46
  },
47
47
  canonical: {
48
- note: "Tenant-specific. No hardcoded default. Require F5XC_API_URL env var.",
48
+ note: "Tenant-specific. No hardcoded default. Require XCSH_API_URL env var.",
49
49
  },
50
50
  },
51
51
  documentation: {
@@ -53,7 +53,7 @@ export const BRANDING_DEPRECATIONS = {
53
53
  note: "docs.cloud.f5.com references to Volterra provider point to the deprecated volterraedge/volterra registry.\n",
54
54
  },
55
55
  canonical: {
56
- url: "https://f5xc-salesdemos.github.io/terraform-provider-f5xc/",
56
+ url: "https://f5xc-salesdemos.github.io/terraform-provider-xcsh/",
57
57
  },
58
58
  },
59
59
  } as const;
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { $ } from "bun";
4
- import type { ContextStatus } from "../services/f5xc-context";
4
+ import type { ContextStatus } from "../services/xcsh-context";
5
5
  import { BUILD_INFO, type BuildInfo } from "./build-info.generated";
6
6
 
7
7
  export type BuildInfoSource = "compiled" | "live-git" | "embedded-fallback";
@@ -114,7 +114,7 @@ function renderAuthStatusLine(context: ContextStatus, nowMs: number): string {
114
114
  }
115
115
 
116
116
  function renderPlatformContext(context: ContextStatus | null, nowMs: number): string {
117
- // xcsh can be connected via a named context OR via F5XC_API_URL / F5XC_API_TOKEN env vars.
117
+ // xcsh can be connected via a named context OR via XCSH_API_URL / XCSH_API_TOKEN env vars.
118
118
  // In the env-only case, activeContextName is null but activeContextTenant (derived from the
119
119
  // env URL) and credentialSource ("environment") are still populated. Guard on tenant, not
120
120
  // name, so env-backed deployments see the configured state instead of the unconfigured copy.