@f5xc-salesdemos/xcsh 18.28.0 → 18.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5xc-salesdemos/xcsh",
|
|
4
|
-
"version": "18.28.
|
|
4
|
+
"version": "18.28.1",
|
|
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",
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
49
49
|
"@mozilla/readability": "^0.6",
|
|
50
|
-
"@f5xc-salesdemos/xcsh-stats": "18.28.
|
|
51
|
-
"@f5xc-salesdemos/pi-agent-core": "18.28.
|
|
52
|
-
"@f5xc-salesdemos/pi-ai": "18.28.
|
|
53
|
-
"@f5xc-salesdemos/pi-natives": "18.28.
|
|
54
|
-
"@f5xc-salesdemos/pi-tui": "18.28.
|
|
55
|
-
"@f5xc-salesdemos/pi-utils": "18.28.
|
|
50
|
+
"@f5xc-salesdemos/xcsh-stats": "18.28.1",
|
|
51
|
+
"@f5xc-salesdemos/pi-agent-core": "18.28.1",
|
|
52
|
+
"@f5xc-salesdemos/pi-ai": "18.28.1",
|
|
53
|
+
"@f5xc-salesdemos/pi-natives": "18.28.1",
|
|
54
|
+
"@f5xc-salesdemos/pi-tui": "18.28.1",
|
|
55
|
+
"@f5xc-salesdemos/pi-utils": "18.28.1",
|
|
56
56
|
"@sinclair/typebox": "^0.34",
|
|
57
57
|
"@xterm/headless": "^6.0",
|
|
58
58
|
"ajv": "^8.18",
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/f5xc-salesdemos/xcsh/main/packages/coding-agent/src/config/context-schema.json",
|
|
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.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["apiUrl", "apiToken"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"$schema": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Optional schema reference for editor autocomplete and validation."
|
|
13
|
+
},
|
|
14
|
+
"name": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"pattern": "^[a-zA-Z0-9_-]{1,64}$",
|
|
17
|
+
"not": {
|
|
18
|
+
"enum": [
|
|
19
|
+
"list", "show", "status", "create", "delete", "rename",
|
|
20
|
+
"namespace", "env", "set", "unset", "add", "remove",
|
|
21
|
+
"clear", "activate", "validate", "export", "import",
|
|
22
|
+
"wizard", "help"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"description": "Context name. Derived from filename. Must not collide with /context subcommands."
|
|
26
|
+
},
|
|
27
|
+
"apiUrl": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"format": "uri",
|
|
30
|
+
"pattern": "^https://",
|
|
31
|
+
"description": "F5 XC console API URL (must be HTTPS)."
|
|
32
|
+
},
|
|
33
|
+
"apiToken": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"minLength": 1,
|
|
36
|
+
"description": "API token for authentication."
|
|
37
|
+
},
|
|
38
|
+
"defaultNamespace": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"minLength": 1,
|
|
41
|
+
"default": "default",
|
|
42
|
+
"description": "Default namespace for CRUD operations. Projected as F5XC_NAMESPACE in bash environment. Use /context namespace to change."
|
|
43
|
+
},
|
|
44
|
+
"env": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"description": "Additional environment variables injected into bash sessions. Reserved keys that have dedicated top-level fields are forbidden.",
|
|
47
|
+
"additionalProperties": {
|
|
48
|
+
"type": "string"
|
|
49
|
+
},
|
|
50
|
+
"propertyNames": {
|
|
51
|
+
"allOf": [
|
|
52
|
+
{
|
|
53
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$",
|
|
54
|
+
"description": "Keys must be valid environment variable names."
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"not": {
|
|
58
|
+
"enum": ["F5XC_NAMESPACE", "F5XC_API_URL", "F5XC_API_TOKEN", "F5XC_TENANT"]
|
|
59
|
+
},
|
|
60
|
+
"description": "Reserved keys have dedicated top-level fields and must not appear in env."
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"sensitiveKeys": {
|
|
66
|
+
"type": "array",
|
|
67
|
+
"items": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"minLength": 1
|
|
70
|
+
},
|
|
71
|
+
"uniqueItems": true,
|
|
72
|
+
"description": "Env var names from env whose values should be masked in output. Values must reference keys present in env (enforced at runtime)."
|
|
73
|
+
},
|
|
74
|
+
"knowledgeSources": {
|
|
75
|
+
"type": "array",
|
|
76
|
+
"items": {
|
|
77
|
+
"type": "object",
|
|
78
|
+
"required": ["url"],
|
|
79
|
+
"additionalProperties": false,
|
|
80
|
+
"properties": {
|
|
81
|
+
"url": { "type": "string", "format": "uri" },
|
|
82
|
+
"label": { "type": "string" },
|
|
83
|
+
"type": { "type": "string", "enum": ["llms-txt", "skill-dir", "docs-site"] }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"includeSkills": {
|
|
88
|
+
"type": "array",
|
|
89
|
+
"items": { "type": "string", "minLength": 1 },
|
|
90
|
+
"uniqueItems": true,
|
|
91
|
+
"description": "Skill names to force-include when this context is active. Must not overlap with excludeSkills (enforced at runtime)."
|
|
92
|
+
},
|
|
93
|
+
"excludeSkills": {
|
|
94
|
+
"type": "array",
|
|
95
|
+
"items": { "type": "string", "minLength": 1 },
|
|
96
|
+
"uniqueItems": true,
|
|
97
|
+
"description": "Skill names to exclude when this context is active. Must not overlap with includeSkills (enforced at runtime)."
|
|
98
|
+
},
|
|
99
|
+
"version": {
|
|
100
|
+
"type": "integer",
|
|
101
|
+
"minimum": 1,
|
|
102
|
+
"description": "Schema version. Must be <= CURRENT_SCHEMA_VERSION (enforced at runtime since the ceiling is dynamic)."
|
|
103
|
+
},
|
|
104
|
+
"metadata": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"additionalProperties": false,
|
|
107
|
+
"properties": {
|
|
108
|
+
"createdAt": { "type": "string", "format": "date-time" },
|
|
109
|
+
"expiresAt": { "type": "string", "format": "date-time" },
|
|
110
|
+
"lastRotatedAt": { "type": "string", "format": "date-time" },
|
|
111
|
+
"rotateAfterDays": { "type": "integer", "minimum": 1 }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "18.28.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "18.28.1",
|
|
21
|
+
"commit": "42862f837ad12f42e37bc29e47ab6f58be779de0",
|
|
22
|
+
"shortCommit": "42862f8",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v18.28.
|
|
25
|
-
"commitDate": "2026-04-
|
|
26
|
-
"buildDate": "2026-04-
|
|
24
|
+
"tag": "v18.28.1",
|
|
25
|
+
"commitDate": "2026-04-30T11:56:41Z",
|
|
26
|
+
"buildDate": "2026-04-30T12:21:15.369Z",
|
|
27
27
|
"dirty": false,
|
|
28
28
|
"prNumber": "",
|
|
29
29
|
"repoUrl": "https://github.com/f5xc-salesdemos/xcsh",
|
|
30
30
|
"repoSlug": "f5xc-salesdemos/xcsh",
|
|
31
|
-
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/
|
|
32
|
-
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.28.
|
|
31
|
+
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/42862f837ad12f42e37bc29e47ab6f58be779de0",
|
|
32
|
+
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.28.1"
|
|
33
33
|
};
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
F5XC_NAMESPACE,
|
|
12
12
|
F5XC_TENANT,
|
|
13
13
|
F5XC_USERNAME,
|
|
14
|
+
RESERVED_ENV_KEYS,
|
|
14
15
|
} from "./f5xc-env";
|
|
15
16
|
import {
|
|
16
17
|
formatAuthIndicator,
|
|
@@ -256,7 +257,7 @@ async function handleShow(ctx: CommandContext, service: ContextService, name?: s
|
|
|
256
257
|
rows.push({ key: F5XC_NAMESPACE, value: sanitize(context.defaultNamespace) });
|
|
257
258
|
if (context.env) {
|
|
258
259
|
for (const [key, value] of Object.entries(context.env)) {
|
|
259
|
-
if (authKeys.includes(key)) continue;
|
|
260
|
+
if (authKeys.includes(key) || RESERVED_ENV_KEYS.has(key)) continue;
|
|
260
261
|
rows.push({ key: sanitize(key), value: isSensitiveKey(key) ? service.maskToken(value) : sanitize(value) });
|
|
261
262
|
}
|
|
262
263
|
}
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
F5XC_NAMESPACE,
|
|
12
12
|
F5XC_TENANT,
|
|
13
13
|
hasEnvOverride,
|
|
14
|
+
RESERVED_ENV_KEYS,
|
|
15
|
+
RESERVED_ENV_MESSAGES,
|
|
14
16
|
} from "./f5xc-env";
|
|
15
17
|
|
|
16
18
|
export const CURRENT_SCHEMA_VERSION = 1;
|
|
@@ -508,8 +510,13 @@ export class ContextService {
|
|
|
508
510
|
version: CURRENT_SCHEMA_VERSION,
|
|
509
511
|
metadata: { createdAt: new Date().toISOString() },
|
|
510
512
|
};
|
|
513
|
+
const filePayload = {
|
|
514
|
+
$schema:
|
|
515
|
+
"https://raw.githubusercontent.com/f5xc-salesdemos/xcsh/main/packages/coding-agent/src/config/context-schema.json",
|
|
516
|
+
...data,
|
|
517
|
+
} as Record<string, unknown>;
|
|
511
518
|
const tmpPath = `${contextPath}.tmp`;
|
|
512
|
-
fs.writeFileSync(tmpPath, JSON.stringify(
|
|
519
|
+
fs.writeFileSync(tmpPath, JSON.stringify(filePayload, null, 2), { mode: 0o600 });
|
|
513
520
|
fs.renameSync(tmpPath, contextPath);
|
|
514
521
|
this.#contextsCache = [...this.#contextsCache, data].sort((a, b) => a.name.localeCompare(b.name));
|
|
515
522
|
}
|
|
@@ -845,6 +852,12 @@ export class ContextService {
|
|
|
845
852
|
|
|
846
853
|
this.#assertCompatibleVersion(context);
|
|
847
854
|
|
|
855
|
+
const reservedViolations = Object.keys(vars).filter(k => RESERVED_ENV_KEYS.has(k));
|
|
856
|
+
if (reservedViolations.length > 0) {
|
|
857
|
+
const messages = reservedViolations.map(k => RESERVED_ENV_MESSAGES[k]).join("\n");
|
|
858
|
+
throw new ContextError(messages, name);
|
|
859
|
+
}
|
|
860
|
+
|
|
848
861
|
const env = { ...(context.env ?? {}), ...vars };
|
|
849
862
|
const sensitiveSet = new Set(context.sensitiveKeys ?? []);
|
|
850
863
|
const newSensitive: string[] = [];
|
|
@@ -1181,7 +1194,36 @@ export class ContextService {
|
|
|
1181
1194
|
if (parsed.env && typeof parsed.env === "object" && !Array.isArray(parsed.env)) {
|
|
1182
1195
|
env = {};
|
|
1183
1196
|
for (const [k, v] of Object.entries(parsed.env)) {
|
|
1184
|
-
if (typeof v
|
|
1197
|
+
if (typeof v !== "string") continue;
|
|
1198
|
+
if (RESERVED_ENV_KEYS.has(k)) {
|
|
1199
|
+
// Resolve the corresponding top-level field to detect value mismatches
|
|
1200
|
+
let topLevelValue: string | undefined;
|
|
1201
|
+
switch (k) {
|
|
1202
|
+
case F5XC_NAMESPACE:
|
|
1203
|
+
topLevelValue = typeof parsed.defaultNamespace === "string" ? parsed.defaultNamespace : undefined;
|
|
1204
|
+
break;
|
|
1205
|
+
case F5XC_API_URL:
|
|
1206
|
+
topLevelValue = typeof parsed.apiUrl === "string" ? parsed.apiUrl : undefined;
|
|
1207
|
+
break;
|
|
1208
|
+
case F5XC_API_TOKEN:
|
|
1209
|
+
topLevelValue = typeof parsed.apiToken === "string" ? parsed.apiToken : undefined;
|
|
1210
|
+
break;
|
|
1211
|
+
default:
|
|
1212
|
+
topLevelValue = undefined;
|
|
1213
|
+
break; // F5XC_TENANT: derived, no stored top-level field
|
|
1214
|
+
}
|
|
1215
|
+
// Warn on mismatch OR when there is no top-level field to compare (F5XC_TENANT)
|
|
1216
|
+
if (topLevelValue === undefined || v !== topLevelValue) {
|
|
1217
|
+
logger.warn("F5XC context env contains reserved key — stripping", {
|
|
1218
|
+
name: canonicalName,
|
|
1219
|
+
key: k,
|
|
1220
|
+
envValue: SECRET_ENV_PATTERNS.test(k) ? "[redacted]" : v,
|
|
1221
|
+
topLevelValue: SECRET_ENV_PATTERNS.test(k) ? "[redacted]" : (topLevelValue ?? "(derived)"),
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
env[k] = v;
|
|
1185
1227
|
}
|
|
1186
1228
|
if (Object.keys(env).length === 0) env = undefined;
|
|
1187
1229
|
}
|
|
@@ -1284,7 +1326,7 @@ export class ContextService {
|
|
|
1284
1326
|
// Inject all additional env vars from context.env map
|
|
1285
1327
|
if (context.env) {
|
|
1286
1328
|
for (const [key, value] of Object.entries(context.env)) {
|
|
1287
|
-
if (!process.env[key]) merged[key] = value;
|
|
1329
|
+
if (!process.env[key] && !(RESERVED_ENV_KEYS.has(key) && key in merged)) merged[key] = value;
|
|
1288
1330
|
}
|
|
1289
1331
|
}
|
|
1290
1332
|
|
package/src/services/f5xc-env.ts
CHANGED
|
@@ -10,6 +10,20 @@ export const F5XC_TENANT = "F5XC_TENANT" as const;
|
|
|
10
10
|
export const F5XC_USERNAME = "F5XC_USERNAME" as const;
|
|
11
11
|
export const F5XC_CONSOLE_PASSWORD = "F5XC_CONSOLE_PASSWORD" as const;
|
|
12
12
|
|
|
13
|
+
export const RESERVED_ENV_KEYS: ReadonlySet<string> = new Set([
|
|
14
|
+
F5XC_NAMESPACE,
|
|
15
|
+
F5XC_API_URL,
|
|
16
|
+
F5XC_API_TOKEN,
|
|
17
|
+
F5XC_TENANT,
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
export const RESERVED_ENV_MESSAGES: Readonly<Record<string, string>> = {
|
|
21
|
+
[F5XC_NAMESPACE]: `${F5XC_NAMESPACE} is managed by defaultNamespace. Use /context namespace <value> to change it.`,
|
|
22
|
+
[F5XC_API_URL]: `${F5XC_API_URL} is managed by apiUrl. It cannot be overridden via env vars.`,
|
|
23
|
+
[F5XC_API_TOKEN]: `${F5XC_API_TOKEN} is managed by apiToken. It cannot be overridden via env vars.`,
|
|
24
|
+
[F5XC_TENANT]: `${F5XC_TENANT} is read-only (derived from apiUrl). It cannot be set directly.`,
|
|
25
|
+
};
|
|
26
|
+
|
|
13
27
|
/**
|
|
14
28
|
* True iff an env var is overriding a context-provided credential.
|
|
15
29
|
* F5XC_API_URL alone is NOT an override — it is the signal that the user
|