@f5-sales-demo/xcsh 19.94.2 → 19.95.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 +11 -10
- package/scripts/generate-sitecli-index.ts +104 -0
- package/src/internal-urls/build-info.generated.ts +8 -8
- package/src/internal-urls/sitecli-index.generated.ts +237 -0
- package/src/internal-urls/terraform-index.generated.ts +1 -1
- package/src/internal-urls/xcsh-protocol.ts +94 -0
- package/src/prompts/tools/ce-sitecli.md +83 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/xcsh",
|
|
4
|
-
"version": "19.
|
|
4
|
+
"version": "19.95.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",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"xcsh": "src/cli.ts"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
|
-
"build": "bun run generate-build-info && bun run generate-extension-capabilities && bun run generate-api-spec-index && bun run generate-branding-index && bun run generate-terraform-index && bun run generate-console-catalog && bun run generate-console-field-metadata && test -f src/internal-urls/api-spec-index.generated.ts && bun --cwd=../stats scripts/generate-client-bundle.ts --generate && bun --cwd=../office-pane scripts/generate-client-bundle.ts --generate && bun --cwd=../natives run embed:native && bun build --compile --define PI_COMPILED=true --external mupdf --root ../.. ./src/cli.ts --outfile dist/xcsh && bun --cwd=../natives run embed:native --reset && bun --cwd=../stats scripts/generate-client-bundle.ts --reset && bun --cwd=../office-pane scripts/generate-client-bundle.ts --reset",
|
|
34
|
+
"build": "bun run generate-build-info && bun run generate-extension-capabilities && bun run generate-api-spec-index && bun run generate-branding-index && bun run generate-sitecli-index && bun run generate-terraform-index && bun run generate-console-catalog && bun run generate-console-field-metadata && test -f src/internal-urls/api-spec-index.generated.ts && bun --cwd=../stats scripts/generate-client-bundle.ts --generate && bun --cwd=../office-pane scripts/generate-client-bundle.ts --generate && bun --cwd=../natives run embed:native && bun build --compile --define PI_COMPILED=true --external mupdf --root ../.. ./src/cli.ts --outfile dist/xcsh && bun --cwd=../natives run embed:native --reset && bun --cwd=../stats scripts/generate-client-bundle.ts --reset && bun --cwd=../office-pane scripts/generate-client-bundle.ts --reset",
|
|
35
35
|
"check": "biome check . && bun run format-prompts -- --check && bun run check:types",
|
|
36
|
-
"check:types": "bun run generate-build-info && bun run generate-extension-capabilities && bun run generate-api-spec-index && bun run generate-branding-index && bun run generate-terraform-index && tsgo -p tsconfig.json --noEmit",
|
|
36
|
+
"check:types": "bun run generate-build-info && bun run generate-extension-capabilities && bun run generate-api-spec-index && bun run generate-branding-index && bun run generate-sitecli-index && bun run generate-terraform-index && tsgo -p tsconfig.json --noEmit",
|
|
37
37
|
"lint": "biome lint .",
|
|
38
38
|
"check:bundle": "bun scripts/check-bundle.ts",
|
|
39
39
|
"test": "bun run generate-build-info && bun run generate-extension-capabilities && bun run generate-api-spec-index && bun run generate-console-catalog && bun run generate-console-field-metadata && bun scripts/bun-test-guarded.ts --max-concurrency 4",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"generate-docs-index": "bun scripts/generate-docs-index.ts",
|
|
45
45
|
"generate-api-spec-index": "bun scripts/generate-api-spec-index.ts",
|
|
46
46
|
"generate-branding-index": "bun scripts/generate-branding-index.ts",
|
|
47
|
+
"generate-sitecli-index": "bun scripts/generate-sitecli-index.ts",
|
|
47
48
|
"generate-build-info": "bun scripts/generate-build-info.ts",
|
|
48
49
|
"generate-extension-capabilities": "bun scripts/generate-extension-capabilities.ts",
|
|
49
50
|
"generate-terraform-index": "bun scripts/generate-terraform-index.ts",
|
|
@@ -56,13 +57,13 @@
|
|
|
56
57
|
"dependencies": {
|
|
57
58
|
"@agentclientprotocol/sdk": "1.3.0",
|
|
58
59
|
"@mozilla/readability": "^0.6",
|
|
59
|
-
"@f5-sales-demo/xcsh-stats": "19.
|
|
60
|
-
"@f5-sales-demo/pi-agent-core": "19.
|
|
61
|
-
"@f5-sales-demo/pi-ai": "19.
|
|
62
|
-
"@f5-sales-demo/pi-natives": "19.
|
|
63
|
-
"@f5-sales-demo/pi-resource-management": "19.
|
|
64
|
-
"@f5-sales-demo/pi-tui": "19.
|
|
65
|
-
"@f5-sales-demo/pi-utils": "19.
|
|
60
|
+
"@f5-sales-demo/xcsh-stats": "19.95.0",
|
|
61
|
+
"@f5-sales-demo/pi-agent-core": "19.95.0",
|
|
62
|
+
"@f5-sales-demo/pi-ai": "19.95.0",
|
|
63
|
+
"@f5-sales-demo/pi-natives": "19.95.0",
|
|
64
|
+
"@f5-sales-demo/pi-resource-management": "19.95.0",
|
|
65
|
+
"@f5-sales-demo/pi-tui": "19.95.0",
|
|
66
|
+
"@f5-sales-demo/pi-utils": "19.95.0",
|
|
66
67
|
"@sinclair/typebox": "^0.34",
|
|
67
68
|
"@xterm/headless": "^6.0",
|
|
68
69
|
"ajv": "^8.20",
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Embeds the Customer Edge Site CLI command surface, so the agent knows it without a
|
|
2
|
+
// network round trip.
|
|
3
|
+
//
|
|
4
|
+
// The source of truth is sitecli/catalog.json in f5-sales-demo/mcn, which is produced
|
|
5
|
+
// by capturing the node's own self-describing catalog rather than transcribed from any
|
|
6
|
+
// document. Resolution order matches generate-terraform-index.ts and
|
|
7
|
+
// generate-branding-index.ts: a sibling checkout first, then raw.githubusercontent.
|
|
8
|
+
import * as fs from "node:fs/promises";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
|
|
11
|
+
const OUTPUT_FILE = path.join(import.meta.dir, "..", "src", "internal-urls", "sitecli-index.generated.ts");
|
|
12
|
+
|
|
13
|
+
const LOCAL_CATALOG_PATH = path.resolve(import.meta.dir, "..", "..", "..", "..", "mcn", "sitecli", "catalog.json");
|
|
14
|
+
|
|
15
|
+
const GITHUB_RAW_URL = "https://raw.githubusercontent.com/f5-sales-demo/mcn/main/sitecli/catalog.json";
|
|
16
|
+
|
|
17
|
+
/** Shape of sitecli/catalog.json as committed by mcn. */
|
|
18
|
+
interface SiteCliCatalog {
|
|
19
|
+
build: string;
|
|
20
|
+
source?: { site?: string; node?: string };
|
|
21
|
+
commands: Record<
|
|
22
|
+
string,
|
|
23
|
+
{
|
|
24
|
+
category: string;
|
|
25
|
+
/** "ExecUser" (read-only) or "Exec" (privileged, mutates or reads a state marker). */
|
|
26
|
+
tier: string;
|
|
27
|
+
/** Illustrative only — often a placeholder such as " container-id". */
|
|
28
|
+
example?: string;
|
|
29
|
+
/** "GLOBAL" means a dedicated GET endpoint, NOT reachable via exec-user. */
|
|
30
|
+
scope?: string;
|
|
31
|
+
}
|
|
32
|
+
>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function loadCatalog(): Promise<SiteCliCatalog> {
|
|
36
|
+
const localFile = Bun.file(LOCAL_CATALOG_PATH);
|
|
37
|
+
if (await localFile.exists()) {
|
|
38
|
+
console.log(`Reading from local checkout: ${LOCAL_CATALOG_PATH}`);
|
|
39
|
+
return localFile.json();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(`Local not found, fetching from ${GITHUB_RAW_URL}`);
|
|
43
|
+
const headers: Record<string, string> = {};
|
|
44
|
+
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
|
45
|
+
if (token) {
|
|
46
|
+
headers.Authorization = `token ${token}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const response = await fetch(GITHUB_RAW_URL, { headers });
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
throw new Error(`Failed to fetch sitecli catalog.json: ${response.status} ${response.statusText}`);
|
|
52
|
+
}
|
|
53
|
+
return (await response.json()) as SiteCliCatalog;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Which endpoint a command must be sent to. This is derived here rather than left to
|
|
58
|
+
* the caller because getting it wrong returns "command not supported" — the same
|
|
59
|
+
* message as a command that does not exist — so the mistake reads as a missing
|
|
60
|
+
* command rather than a wrong transport.
|
|
61
|
+
*/
|
|
62
|
+
function transportFor(entry: { tier: string; scope?: string }): "global-get" | "exec-user" | "exec" {
|
|
63
|
+
if (entry.scope === "GLOBAL") return "global-get";
|
|
64
|
+
return entry.tier === "Exec" ? "exec" : "exec-user";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function generateTypeScript(catalog: SiteCliCatalog): string {
|
|
68
|
+
const commands = Object.fromEntries(
|
|
69
|
+
Object.entries(catalog.commands)
|
|
70
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
71
|
+
.map(([name, entry]) => [
|
|
72
|
+
name,
|
|
73
|
+
{
|
|
74
|
+
category: entry.category,
|
|
75
|
+
tier: entry.tier,
|
|
76
|
+
transport: transportFor(entry),
|
|
77
|
+
mutating: entry.tier === "Exec",
|
|
78
|
+
...(entry.example ? { example: entry.example.trim() } : {}),
|
|
79
|
+
...(entry.scope ? { scope: entry.scope } : {}),
|
|
80
|
+
},
|
|
81
|
+
]),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return `${[
|
|
85
|
+
"// AUTO-GENERATED — do not edit. Run `bun generate-sitecli-index` to regenerate.",
|
|
86
|
+
"//",
|
|
87
|
+
"// Source: f5-sales-demo/mcn sitecli/catalog.json, captured from a live Customer Edge.",
|
|
88
|
+
"// The command surface depends on the node software build, so SITECLI_BUILD records",
|
|
89
|
+
"// which build this describes.",
|
|
90
|
+
"",
|
|
91
|
+
`export const SITECLI_BUILD = ${JSON.stringify(catalog.build)};`,
|
|
92
|
+
"",
|
|
93
|
+
`export const SITECLI_SOURCE = ${JSON.stringify(catalog.source ?? {}, null, 2)} as const;`,
|
|
94
|
+
"",
|
|
95
|
+
`export const SITECLI_COMMANDS = ${JSON.stringify(commands, null, 2)} as const;`,
|
|
96
|
+
"",
|
|
97
|
+
].join("\n")}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const catalog = await loadCatalog();
|
|
101
|
+
const output = generateTypeScript(catalog);
|
|
102
|
+
await fs.writeFile(OUTPUT_FILE, output, "utf-8");
|
|
103
|
+
await Bun.$`bunx biome format --write ${OUTPUT_FILE}`.quiet();
|
|
104
|
+
console.log(`Generated ${OUTPUT_FILE} (${Object.keys(catalog.commands).length} commands, build ${catalog.build})`);
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "19.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "19.95.0",
|
|
21
|
+
"commit": "6239a5a701200eab5295f16c6b7698898480d4b0",
|
|
22
|
+
"shortCommit": "6239a5a",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v19.
|
|
25
|
-
"commitDate": "2026-07-
|
|
26
|
-
"buildDate": "2026-07-
|
|
24
|
+
"tag": "v19.95.0",
|
|
25
|
+
"commitDate": "2026-07-26T17:07:54Z",
|
|
26
|
+
"buildDate": "2026-07-26T17:33:27.485Z",
|
|
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/
|
|
32
|
-
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.
|
|
31
|
+
"commitUrl": "https://github.com/f5-sales-demo/xcsh/commit/6239a5a701200eab5295f16c6b7698898480d4b0",
|
|
32
|
+
"releaseUrl": "https://github.com/f5-sales-demo/xcsh/releases/tag/v19.95.0"
|
|
33
33
|
};
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// AUTO-GENERATED — do not edit. Run `bun generate-sitecli-index` to regenerate.
|
|
2
|
+
//
|
|
3
|
+
// Source: f5-sales-demo/mcn sitecli/catalog.json, captured from a live Customer Edge.
|
|
4
|
+
// The command surface depends on the node software build, so SITECLI_BUILD records
|
|
5
|
+
// which build this describes.
|
|
6
|
+
|
|
7
|
+
export const SITECLI_BUILD = "crt-20250613-3382";
|
|
8
|
+
|
|
9
|
+
export const SITECLI_SOURCE = {
|
|
10
|
+
node: "f5-xc-ce-vm-01",
|
|
11
|
+
site: "ar-bgp-eastus01",
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export const SITECLI_COMMANDS = {
|
|
15
|
+
"chronyc-sources": {
|
|
16
|
+
category: "Network Troubleshooting",
|
|
17
|
+
tier: "ExecUser",
|
|
18
|
+
transport: "exec-user",
|
|
19
|
+
mutating: false,
|
|
20
|
+
},
|
|
21
|
+
"crictl-images": {
|
|
22
|
+
category: "System Troubleshooting",
|
|
23
|
+
tier: "ExecUser",
|
|
24
|
+
transport: "exec-user",
|
|
25
|
+
mutating: false,
|
|
26
|
+
},
|
|
27
|
+
"crictl-inspect": {
|
|
28
|
+
category: "System Troubleshooting",
|
|
29
|
+
tier: "ExecUser",
|
|
30
|
+
transport: "exec-user",
|
|
31
|
+
mutating: false,
|
|
32
|
+
example: "container-id",
|
|
33
|
+
},
|
|
34
|
+
"crictl-logs": {
|
|
35
|
+
category: "System Troubleshooting",
|
|
36
|
+
tier: "ExecUser",
|
|
37
|
+
transport: "exec-user",
|
|
38
|
+
mutating: false,
|
|
39
|
+
example: "container-id",
|
|
40
|
+
},
|
|
41
|
+
"crictl-ps": {
|
|
42
|
+
category: "System Troubleshooting",
|
|
43
|
+
tier: "ExecUser",
|
|
44
|
+
transport: "exec-user",
|
|
45
|
+
mutating: false,
|
|
46
|
+
},
|
|
47
|
+
"crictl-ps-a": {
|
|
48
|
+
category: "System Troubleshooting",
|
|
49
|
+
tier: "ExecUser",
|
|
50
|
+
transport: "exec-user",
|
|
51
|
+
mutating: false,
|
|
52
|
+
},
|
|
53
|
+
"curl-host": {
|
|
54
|
+
category: "Network Troubleshooting",
|
|
55
|
+
tier: "ExecUser",
|
|
56
|
+
transport: "exec-user",
|
|
57
|
+
mutating: false,
|
|
58
|
+
example: "-v cloud.f5.com",
|
|
59
|
+
},
|
|
60
|
+
"curl-vega": {
|
|
61
|
+
category: "Network Troubleshooting",
|
|
62
|
+
tier: "ExecUser",
|
|
63
|
+
transport: "exec-user",
|
|
64
|
+
mutating: false,
|
|
65
|
+
example: "-v cloud.f5.com",
|
|
66
|
+
},
|
|
67
|
+
diagnosis: {
|
|
68
|
+
category: "System Troubleshooting",
|
|
69
|
+
tier: "ExecUser",
|
|
70
|
+
transport: "global-get",
|
|
71
|
+
mutating: false,
|
|
72
|
+
example: "no argument needed",
|
|
73
|
+
scope: "GLOBAL",
|
|
74
|
+
},
|
|
75
|
+
dig: {
|
|
76
|
+
category: "Network Troubleshooting",
|
|
77
|
+
tier: "ExecUser",
|
|
78
|
+
transport: "exec-user",
|
|
79
|
+
mutating: false,
|
|
80
|
+
example: "@192.168.0.2 http://volterra.azurecr.io",
|
|
81
|
+
},
|
|
82
|
+
"docker-images": {
|
|
83
|
+
category: "System Troubleshooting",
|
|
84
|
+
tier: "ExecUser",
|
|
85
|
+
transport: "exec-user",
|
|
86
|
+
mutating: false,
|
|
87
|
+
},
|
|
88
|
+
"docker-inspect": {
|
|
89
|
+
category: "System Troubleshooting",
|
|
90
|
+
tier: "ExecUser",
|
|
91
|
+
transport: "exec-user",
|
|
92
|
+
mutating: false,
|
|
93
|
+
example: "container-id OR name",
|
|
94
|
+
},
|
|
95
|
+
"docker-logs": {
|
|
96
|
+
category: "System Troubleshooting",
|
|
97
|
+
tier: "ExecUser",
|
|
98
|
+
transport: "exec-user",
|
|
99
|
+
mutating: false,
|
|
100
|
+
example: "container-id OR name",
|
|
101
|
+
},
|
|
102
|
+
"docker-ps": {
|
|
103
|
+
category: "System Troubleshooting",
|
|
104
|
+
tier: "ExecUser",
|
|
105
|
+
transport: "exec-user",
|
|
106
|
+
mutating: false,
|
|
107
|
+
},
|
|
108
|
+
"docker-ps-a": {
|
|
109
|
+
category: "System Troubleshooting",
|
|
110
|
+
tier: "ExecUser",
|
|
111
|
+
transport: "exec-user",
|
|
112
|
+
mutating: false,
|
|
113
|
+
},
|
|
114
|
+
dropstats: {
|
|
115
|
+
category: "Network Troubleshooting",
|
|
116
|
+
tier: "ExecUser",
|
|
117
|
+
transport: "exec-user",
|
|
118
|
+
mutating: false,
|
|
119
|
+
},
|
|
120
|
+
"dropstats-non-zero": {
|
|
121
|
+
category: "Network Troubleshooting",
|
|
122
|
+
tier: "ExecUser",
|
|
123
|
+
transport: "exec-user",
|
|
124
|
+
mutating: false,
|
|
125
|
+
},
|
|
126
|
+
"flow-l": {
|
|
127
|
+
category: "Network Troubleshooting",
|
|
128
|
+
tier: "ExecUser",
|
|
129
|
+
transport: "exec-user",
|
|
130
|
+
mutating: false,
|
|
131
|
+
},
|
|
132
|
+
"flow-l-match": {
|
|
133
|
+
category: "Network Troubleshooting",
|
|
134
|
+
tier: "ExecUser",
|
|
135
|
+
transport: "exec-user",
|
|
136
|
+
mutating: false,
|
|
137
|
+
example: "100.127.192.10:53",
|
|
138
|
+
},
|
|
139
|
+
health: {
|
|
140
|
+
category: "System Troubleshooting",
|
|
141
|
+
tier: "ExecUser",
|
|
142
|
+
transport: "global-get",
|
|
143
|
+
mutating: false,
|
|
144
|
+
example: "no argument needed",
|
|
145
|
+
scope: "GLOBAL",
|
|
146
|
+
},
|
|
147
|
+
ip: {
|
|
148
|
+
category: "Network Troubleshooting",
|
|
149
|
+
tier: "ExecUser",
|
|
150
|
+
transport: "exec-user",
|
|
151
|
+
mutating: false,
|
|
152
|
+
example: "help OR addr",
|
|
153
|
+
},
|
|
154
|
+
"ip-link-set": {
|
|
155
|
+
category: "Network Troubleshooting",
|
|
156
|
+
tier: "Exec",
|
|
157
|
+
transport: "exec",
|
|
158
|
+
mutating: true,
|
|
159
|
+
example: "(<device>||<group>) (up||down)",
|
|
160
|
+
},
|
|
161
|
+
"ip-link-show": {
|
|
162
|
+
category: "Network Troubleshooting",
|
|
163
|
+
tier: "ExecUser",
|
|
164
|
+
transport: "exec-user",
|
|
165
|
+
mutating: false,
|
|
166
|
+
},
|
|
167
|
+
"ipsec-status": {
|
|
168
|
+
category: "Network Troubleshooting",
|
|
169
|
+
tier: "ExecUser",
|
|
170
|
+
transport: "exec-user",
|
|
171
|
+
mutating: false,
|
|
172
|
+
},
|
|
173
|
+
"ipsec-statusall": {
|
|
174
|
+
category: "Network Troubleshooting",
|
|
175
|
+
tier: "ExecUser",
|
|
176
|
+
transport: "exec-user",
|
|
177
|
+
mutating: false,
|
|
178
|
+
},
|
|
179
|
+
journalctl: {
|
|
180
|
+
category: "System Troubleshooting",
|
|
181
|
+
tier: "ExecUser",
|
|
182
|
+
transport: "exec-user",
|
|
183
|
+
mutating: false,
|
|
184
|
+
example: "-u vpm -n 200",
|
|
185
|
+
},
|
|
186
|
+
netstat: {
|
|
187
|
+
category: "Network Troubleshooting",
|
|
188
|
+
tier: "ExecUser",
|
|
189
|
+
transport: "exec-user",
|
|
190
|
+
mutating: false,
|
|
191
|
+
},
|
|
192
|
+
nh: {
|
|
193
|
+
category: "Network Troubleshooting",
|
|
194
|
+
tier: "ExecUser",
|
|
195
|
+
transport: "exec-user",
|
|
196
|
+
mutating: false,
|
|
197
|
+
example: "--help OR --list",
|
|
198
|
+
},
|
|
199
|
+
rt: {
|
|
200
|
+
category: "Network Troubleshooting",
|
|
201
|
+
tier: "ExecUser",
|
|
202
|
+
transport: "exec-user",
|
|
203
|
+
mutating: false,
|
|
204
|
+
example: "--help OR --dump $vrf-id OR --get $ipv4 --vrf $vrf-id",
|
|
205
|
+
},
|
|
206
|
+
"show-ip-bgp": {
|
|
207
|
+
category: "Network Troubleshooting",
|
|
208
|
+
tier: "ExecUser",
|
|
209
|
+
transport: "exec-user",
|
|
210
|
+
mutating: false,
|
|
211
|
+
},
|
|
212
|
+
"show-ip-bgp-neighbors": {
|
|
213
|
+
category: "Network Troubleshooting",
|
|
214
|
+
tier: "ExecUser",
|
|
215
|
+
transport: "exec-user",
|
|
216
|
+
mutating: false,
|
|
217
|
+
},
|
|
218
|
+
"show-ip-bgp-neighbors-advertised-route": {
|
|
219
|
+
category: "Network Troubleshooting",
|
|
220
|
+
tier: "ExecUser",
|
|
221
|
+
transport: "exec-user",
|
|
222
|
+
mutating: false,
|
|
223
|
+
},
|
|
224
|
+
"show-ip-bgp-summary": {
|
|
225
|
+
category: "Network Troubleshooting",
|
|
226
|
+
tier: "ExecUser",
|
|
227
|
+
transport: "exec-user",
|
|
228
|
+
mutating: false,
|
|
229
|
+
},
|
|
230
|
+
vif: {
|
|
231
|
+
category: "Network Troubleshooting",
|
|
232
|
+
tier: "ExecUser",
|
|
233
|
+
transport: "exec-user",
|
|
234
|
+
mutating: false,
|
|
235
|
+
example: "--list",
|
|
236
|
+
},
|
|
237
|
+
} as const;
|
|
@@ -210,7 +210,7 @@ export const TERRAFORM_INDEX: TerraformIndex = {
|
|
|
210
210
|
{
|
|
211
211
|
name: "Monitoring",
|
|
212
212
|
slug: "monitoring",
|
|
213
|
-
description: "Log receivers, alert policies,
|
|
213
|
+
description: "Log receivers, alert policies, alert templates, and global logging configuration",
|
|
214
214
|
resource_count: 4,
|
|
215
215
|
resources: ["alert_receiver", "alert_template", "global_log_receiver", "log_receiver"],
|
|
216
216
|
},
|
|
@@ -68,6 +68,7 @@ const PLUGIN_HOST = "plugin";
|
|
|
68
68
|
const CHANGES_HOST = "changes";
|
|
69
69
|
const SOURCE_HOST = "source";
|
|
70
70
|
const FLEET_HOST = "fleet";
|
|
71
|
+
const SITECLI_HOST = "sitecli";
|
|
71
72
|
const EMPTY_INDEX: ApiSpecIndex = { version: "unavailable", timestamp: "", domains: [] };
|
|
72
73
|
const EMPTY_CATALOG_INDEX: ApiCatalogIndex = {
|
|
73
74
|
version: "unavailable",
|
|
@@ -202,6 +203,39 @@ interface BrandingDeprecationEntry {
|
|
|
202
203
|
required_providers_block?: string;
|
|
203
204
|
}
|
|
204
205
|
|
|
206
|
+
interface SiteCliCommand {
|
|
207
|
+
category: string;
|
|
208
|
+
tier: string;
|
|
209
|
+
transport: string;
|
|
210
|
+
mutating: boolean;
|
|
211
|
+
example?: string;
|
|
212
|
+
scope?: string;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let _siteCliCache: { build: string; commands: Record<string, SiteCliCommand> } | null = null;
|
|
216
|
+
|
|
217
|
+
/** Mirrors loadBranding(): lazily required, cached, and degrading to an empty index
|
|
218
|
+
* with a warning rather than throwing when the generated file is absent. */
|
|
219
|
+
function loadSiteCli(): { build: string; commands: Record<string, SiteCliCommand> } {
|
|
220
|
+
if (_siteCliCache) return _siteCliCache;
|
|
221
|
+
try {
|
|
222
|
+
const mod = require("./sitecli-index.generated") as {
|
|
223
|
+
SITECLI_BUILD?: string;
|
|
224
|
+
SITECLI_COMMANDS?: Record<string, SiteCliCommand>;
|
|
225
|
+
};
|
|
226
|
+
_siteCliCache = {
|
|
227
|
+
build: mod.SITECLI_BUILD ?? "unknown",
|
|
228
|
+
commands: (mod.SITECLI_COMMANDS ?? {}) as Record<string, SiteCliCommand>,
|
|
229
|
+
};
|
|
230
|
+
} catch (err) {
|
|
231
|
+
logger.warn("sitecli index unavailable, sitecli protocol disabled", {
|
|
232
|
+
error: err instanceof Error ? err.message : String(err),
|
|
233
|
+
});
|
|
234
|
+
_siteCliCache = { build: "unavailable", commands: {} };
|
|
235
|
+
}
|
|
236
|
+
return _siteCliCache;
|
|
237
|
+
}
|
|
238
|
+
|
|
205
239
|
let _brandingCache: {
|
|
206
240
|
version: string;
|
|
207
241
|
canonical: Record<string, BrandingCanonicalEntry>;
|
|
@@ -412,6 +446,10 @@ export class InternalDocsProtocolHandler implements ProtocolHandler {
|
|
|
412
446
|
return this.#resolveBranding(url);
|
|
413
447
|
}
|
|
414
448
|
|
|
449
|
+
if (host === SITECLI_HOST) {
|
|
450
|
+
return this.#resolveSiteCli(url);
|
|
451
|
+
}
|
|
452
|
+
|
|
415
453
|
if (host === USER_ROUTE) {
|
|
416
454
|
return this.#resolveUserProfile(url);
|
|
417
455
|
}
|
|
@@ -574,6 +612,62 @@ export class InternalDocsProtocolHandler implements ProtocolHandler {
|
|
|
574
612
|
};
|
|
575
613
|
}
|
|
576
614
|
|
|
615
|
+
/** Customer Edge Site CLI command surface. The transport field is the reason this
|
|
616
|
+
* is embedded rather than looked up: sending a command to the wrong endpoint
|
|
617
|
+
* returns "command not supported", which is indistinguishable from the command not
|
|
618
|
+
* existing, so guessing produces a confidently wrong answer. */
|
|
619
|
+
#resolveSiteCli(url: InternalUrl): InternalResource {
|
|
620
|
+
const subpath = (url.rawPathname ?? url.pathname).replace(/^\/+/, "").replace(/\/+$/, "");
|
|
621
|
+
const { build, commands } = loadSiteCli();
|
|
622
|
+
|
|
623
|
+
let content: string;
|
|
624
|
+
if (!subpath) {
|
|
625
|
+
const lines = [
|
|
626
|
+
`# Customer Edge Site CLI (build ${build})`,
|
|
627
|
+
"",
|
|
628
|
+
"Transport is decided by the entry, not by preference. Sending a command to the",
|
|
629
|
+
"wrong endpoint returns `command not supported` — the same message as a command",
|
|
630
|
+
"that does not exist.",
|
|
631
|
+
"",
|
|
632
|
+
"| Command | Category | Tier | Transport |",
|
|
633
|
+
"| --- | --- | --- | --- |",
|
|
634
|
+
];
|
|
635
|
+
for (const [name, c] of Object.entries(commands)) {
|
|
636
|
+
lines.push(`| ${name} | ${c.category} | ${c.tier} | ${c.transport}${c.mutating ? " (MUTATES)" : ""} |`);
|
|
637
|
+
}
|
|
638
|
+
lines.push("", `Read \`xcsh://sitecli/<command>\` for one entry.`);
|
|
639
|
+
content = lines.join("\n");
|
|
640
|
+
} else {
|
|
641
|
+
const entry = commands[subpath];
|
|
642
|
+
content = entry
|
|
643
|
+
? [
|
|
644
|
+
`# ${subpath}`,
|
|
645
|
+
"",
|
|
646
|
+
`- category: ${entry.category}`,
|
|
647
|
+
`- tier: ${entry.tier}`,
|
|
648
|
+
`- transport: ${entry.transport}`,
|
|
649
|
+
`- mutating: ${entry.mutating}`,
|
|
650
|
+
...(entry.scope ? [`- scope: ${entry.scope}`] : []),
|
|
651
|
+
...(entry.example ? [`- example argument: \`${entry.example}\``] : []),
|
|
652
|
+
"",
|
|
653
|
+
entry.transport === "global-get"
|
|
654
|
+
? "GLOBAL scope: GET .../vpm/debug/global/" + subpath + " — NOT reachable via exec-user."
|
|
655
|
+
: entry.transport === "exec"
|
|
656
|
+
? "Exec tier: privileged and mutating. Do not run speculatively."
|
|
657
|
+
: 'POST .../vpm/debug/{node}/exec-user with {"command":["' + subpath + '", ...]}.',
|
|
658
|
+
].join("\n")
|
|
659
|
+
: `Unknown Site CLI command: ${subpath}\n\nRead xcsh://sitecli for the full list (build ${build}).`;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
return {
|
|
663
|
+
url: url.href,
|
|
664
|
+
content,
|
|
665
|
+
contentType: "text/markdown",
|
|
666
|
+
size: Buffer.byteLength(content, "utf-8"),
|
|
667
|
+
sourcePath: `xcsh://sitecli${subpath ? `/${subpath}` : ""}`,
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
577
671
|
#brandingOverview(): string {
|
|
578
672
|
const { version, canonical, deprecations } = loadBranding();
|
|
579
673
|
const lines = [`# F5 Distributed Cloud Branding (v${version})`, "", "## Product Names (Current API)", ""];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Run diagnostic commands on an F5 Distributed Cloud Customer Edge node.
|
|
2
|
+
|
|
3
|
+
The Site CLI runs on the CE itself and is reached over the `vpm/debug` API. The command
|
|
4
|
+
surface is embedded at `xcsh://sitecli` — read it rather than guessing, because command
|
|
5
|
+
availability depends on the node software build.
|
|
6
|
+
|
|
7
|
+
<critical>
|
|
8
|
+
The transport is decided by the command's catalog entry, NOT by preference. Sending a
|
|
9
|
+
command to the wrong endpoint returns `command not supported`, which is the SAME message
|
|
10
|
+
returned for a command that does not exist. Reporting "that command is unavailable" when
|
|
11
|
+
the transport was wrong is a confidently wrong answer, and it is the most common failure
|
|
12
|
+
here.
|
|
13
|
+
</critical>
|
|
14
|
+
|
|
15
|
+
## Three transports
|
|
16
|
+
|
|
17
|
+
|Entry says|Send to|Returns|
|
|
18
|
+
|---|---|---|
|
|
19
|
+
|`transport: global-get`|`GET …/vpm/debug/global/<cmd>`|JSON|
|
|
20
|
+
|`transport: exec-user`|`POST …/vpm/debug/<node>/exec-user`|text|
|
|
21
|
+
|`transport: exec`|`POST …/vpm/debug/<node>/exec`|text|
|
|
22
|
+
|
|
23
|
+
`exec-user` and `exec` take a body of the form:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{ "namespace": "system", "site": "<site>", "node": "<node>", "command": ["<cmd>", "<arg>", "..."] }
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Arguments are separate array elements, not one string:
|
|
30
|
+
`["journalctl", "-u", "vpm", "-n", "200"]`.
|
|
31
|
+
|
|
32
|
+
`global-get` takes no body and no arguments. Only `health` and `diagnosis` use it.
|
|
33
|
+
|
|
34
|
+
<prohibited>
|
|
35
|
+
Never run a command whose entry has `mutating: true` unless the user has asked for that
|
|
36
|
+
specific change and understands the node is live. On the current build that is
|
|
37
|
+
`ip-link-set`, which takes an interface down — including, on a remote CE, the interface
|
|
38
|
+
you are managing it through. Newer builds add `systemctl-restart-NetworkManager`,
|
|
39
|
+
`systemctl-restart-crio`, `systemctl-restart-kubelet` and `systemctl-start-crio-prune`,
|
|
40
|
+
which restart services under a live data plane.
|
|
41
|
+
</prohibited>
|
|
42
|
+
|
|
43
|
+
## Discovering the surface on an unknown node
|
|
44
|
+
|
|
45
|
+
POST to `exec-user` with the `command` key **omitted**. That returns the node's own
|
|
46
|
+
catalog. Sending an unknown command value instead returns an error, not the catalog.
|
|
47
|
+
|
|
48
|
+
## Before running anything, check the node is reachable
|
|
49
|
+
|
|
50
|
+
The `vpm/debug` API is served through the F5 Distributed Cloud control plane over the
|
|
51
|
+
tunnel that registration establishes. It answers only when the site is `ONLINE`:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
GET /api/config/namespaces/system/sites/<site> -> .spec.site_state
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
If the site is not `ONLINE`, no command on this page will work, and the node also
|
|
58
|
+
refuses SSH as shipped. The only remaining route is the Azure Serial Console, which
|
|
59
|
+
needs a human at a terminal.
|
|
60
|
+
|
|
61
|
+
## Reading results
|
|
62
|
+
- `health` first. It is cheap and structured, and `state: PROVISIONED` confirms the node
|
|
63
|
+
registered and is configured.
|
|
64
|
+
- Long output is genuinely long: `nh --list` runs to thousands of lines and the flow
|
|
65
|
+
table holds hundreds of thousands of entries. Prefer `flow-l-match <ip>[:port]` over
|
|
66
|
+
`flow-l`, and `dropstats-non-zero` over `dropstats`.
|
|
67
|
+
- Drop counters are lifetime totals, not rates. A non-zero counter means nothing on its
|
|
68
|
+
own; run the command twice and compare while reproducing the problem.
|
|
69
|
+
|
|
70
|
+
<instruction>
|
|
71
|
+
Two traps that produce plausible but wrong conclusions:
|
|
72
|
+
1. `rt --dump` takes the vRouter table id, which is NOT the `vrf-id` that
|
|
73
|
+
`show-ip-bgp-summary` prints. Passing the BGP number returns
|
|
74
|
+
`No such file or directory`, which reads as a broken node rather than a wrong
|
|
75
|
+
argument.
|
|
76
|
+
2. The node runs Docker AND CRI-O simultaneously. `vpm`, `argo_watch` and
|
|
77
|
+
`site-console` are Docker containers; Kubernetes workloads are CRI-O. A three-entry
|
|
78
|
+
`docker-ps` is the complete list, not a symptom, and `crictl-*` will not show `vpm`.
|
|
79
|
+
</instruction>
|
|
80
|
+
|
|
81
|
+
For anything beyond the command surface — what output means, and the diagnostic
|
|
82
|
+
sequences that chain commands — read the Customer Edge documentation in the `mcn`
|
|
83
|
+
documentation set rather than inferring it.
|