@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.
- package/package.json +8 -8
- package/scripts/capture-ax-fixture.ts +5 -5
- package/scripts/capture-login-fixture.ts +1 -1
- package/scripts/extension-uat-harness.ts +7 -7
- package/scripts/generate-api-spec-index.ts +2 -2
- package/scripts/generate-terraform-index.ts +8 -8
- package/scripts/uat-matrix-modalities.ts +7 -7
- package/scripts/uat-matrix.ts +13 -13
- package/src/browser/extension-page-actions.ts +51 -4
- package/src/browser/extension-provider.ts +3 -3
- package/src/browser/input-commit.ts +24 -2
- package/src/cli/chrome-cli.ts +1 -1
- package/src/config/context-schema.json +3 -3
- package/src/config/settings-schema.ts +5 -5
- package/src/internal-urls/api-catalog-resolve.ts +5 -5
- package/src/internal-urls/branding-index.generated.ts +8 -8
- package/src/internal-urls/build-info-runtime.ts +2 -2
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/internal-urls/console-catalog.generated.ts +766 -10
- package/src/internal-urls/console-field-metadata-types.ts +7 -0
- package/src/internal-urls/console-field-metadata.generated.ts +358 -11
- package/src/internal-urls/console-resolve.ts +16 -3
- package/src/internal-urls/docs-index.generated.ts +26 -26
- package/src/internal-urls/terraform-index.generated.ts +252 -252
- package/src/internal-urls/terraform-resolve.ts +13 -13
- package/src/internal-urls/xcsh-protocol.ts +3 -3
- package/src/main.ts +3 -3
- package/src/modes/components/context-add-wizard.ts +6 -6
- package/src/modes/components/plugins/utils.ts +1 -1
- package/src/modes/components/status-line/presets.ts +15 -15
- package/src/modes/components/status-line/segments.ts +10 -10
- package/src/modes/components/status-line-segment-editor.ts +1 -1
- package/src/modes/components/welcome-checks.ts +2 -2
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/context-command-controller.ts +3 -3
- package/src/modes/interactive-mode.ts +1 -1
- package/src/modes/theme/defaults/xcsh-dark.json +2 -2
- package/src/modes/theme/defaults/xcsh-light.json +2 -2
- package/src/modes/theme/theme-schema.json +4 -4
- package/src/modes/theme/theme.ts +6 -6
- package/src/prompts/system/system-prompt.md +12 -10
- package/src/prompts/tools/xcsh-api.md +16 -16
- package/src/sdk.ts +12 -12
- package/src/services/context-env.ts +17 -17
- package/src/services/{f5xc-api-client.ts → xcsh-api-client.ts} +22 -22
- package/src/services/{f5xc-context-command.ts → xcsh-context-command.ts} +36 -36
- package/src/services/{f5xc-context-display.ts → xcsh-context-display.ts} +1 -1
- package/src/services/{f5xc-context-segment.ts → xcsh-context-segment.ts} +4 -4
- package/src/services/{f5xc-context.ts → xcsh-context.ts} +104 -112
- package/src/services/{f5xc-env.ts → xcsh-env.ts} +42 -20
- package/src/services/{f5xc-knowledge.ts → xcsh-knowledge.ts} +2 -2
- package/src/services/{f5xc-table.ts → xcsh-table.ts} +3 -3
- package/src/session/session-manager.ts +2 -2
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/slash-commands/export-command.ts +4 -4
- package/src/slash-commands/resource-commands.ts +4 -4
- package/src/system-prompt.ts +1 -1
- package/src/tools/catalog-workflow-runner.ts +6 -6
- package/src/tools/xcsh-api.ts +12 -12
- /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.
|
|
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.
|
|
58
|
-
"@f5xc-salesdemos/pi-agent-core": "19.
|
|
59
|
-
"@f5xc-salesdemos/pi-ai": "19.
|
|
60
|
-
"@f5xc-salesdemos/pi-natives": "19.
|
|
61
|
-
"@f5xc-salesdemos/pi-resource-management": "19.
|
|
62
|
-
"@f5xc-salesdemos/pi-tui": "19.
|
|
63
|
-
"@f5xc-salesdemos/pi-utils": "19.
|
|
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 (
|
|
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.
|
|
8
|
-
const NS = process.env.
|
|
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.
|
|
22
|
-
await page.type("#password", process.env.
|
|
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.
|
|
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.
|
|
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
|
|
73
|
-
const
|
|
74
|
-
if (
|
|
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:
|
|
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
|
|
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 (
|
|
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 = `
|
|
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 ?? "
|
|
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-
|
|
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-
|
|
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 "
|
|
44
|
+
const DEFAULT_CONFIG_BLOCK = 'provider "xcsh" {}';
|
|
45
45
|
const DEFAULT_AUTH_METHODS = [
|
|
46
|
-
'REQUIRED: every .tf must contain a `provider "
|
|
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
|
|
49
|
-
"api_p12_file + p12_password (env
|
|
50
|
-
"api_cert + api_key (env
|
|
51
|
-
"api_url (env
|
|
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+"
|
|
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,
|
|
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,
|
|
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+"
|
|
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(/^
|
|
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,
|
|
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,
|
|
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));
|
package/scripts/uat-matrix.ts
CHANGED
|
@@ -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:
|
|
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.
|
|
126
|
-
apiToken: process.env.
|
|
127
|
-
consoleNamespace: process.env.
|
|
128
|
-
tenant: (process.env.
|
|
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
|
|
156
|
-
const
|
|
157
|
-
if (
|
|
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:
|
|
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
|
|
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("
|
|
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("
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
183
|
-
|
|
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
|
|
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.
|
|
261
|
-
const password = process.env.
|
|
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
|
|
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
|
}
|
package/src/cli/chrome-cli.ts
CHANGED
|
@@ -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.
|
|
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/
|
|
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
|
|
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": ["
|
|
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
|
-
| "
|
|
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 /
|
|
1907
|
+
// Fork-specific settings (xcsh / xcsh)
|
|
1908
1908
|
// ────────────────────────────────────────────────────────────────────────
|
|
1909
1909
|
|
|
1910
|
-
/** Per-session environment variables injected into bash (used by
|
|
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
|
|
1914
|
-
"
|
|
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": "$
|
|
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.
|
|
344
|
-
const apiToken = process.env.
|
|
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
|
|
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.
|
|
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/
|
|
35
|
-
source: "f5xc-salesdemos/
|
|
36
|
-
github: "github.com/f5xc-salesdemos/terraform-provider-
|
|
37
|
-
docs: "https://f5xc-salesdemos.github.io/terraform-provider-
|
|
38
|
-
llms_txt: "https://f5xc-salesdemos.github.io/terraform-provider-
|
|
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
|
|
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
|
|
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-
|
|
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/
|
|
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
|
|
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.
|