@f5xc-salesdemos/xcsh 18.27.1 → 18.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5xc-salesdemos/xcsh",
|
|
4
|
-
"version": "18.
|
|
4
|
+
"version": "18.28.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",
|
|
@@ -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.
|
|
51
|
-
"@f5xc-salesdemos/pi-agent-core": "18.
|
|
52
|
-
"@f5xc-salesdemos/pi-ai": "18.
|
|
53
|
-
"@f5xc-salesdemos/pi-natives": "18.
|
|
54
|
-
"@f5xc-salesdemos/pi-tui": "18.
|
|
55
|
-
"@f5xc-salesdemos/pi-utils": "18.
|
|
50
|
+
"@f5xc-salesdemos/xcsh-stats": "18.28.0",
|
|
51
|
+
"@f5xc-salesdemos/pi-agent-core": "18.28.0",
|
|
52
|
+
"@f5xc-salesdemos/pi-ai": "18.28.0",
|
|
53
|
+
"@f5xc-salesdemos/pi-natives": "18.28.0",
|
|
54
|
+
"@f5xc-salesdemos/pi-tui": "18.28.0",
|
|
55
|
+
"@f5xc-salesdemos/pi-utils": "18.28.0",
|
|
56
56
|
"@sinclair/typebox": "^0.34",
|
|
57
57
|
"@xterm/headless": "^6.0",
|
|
58
58
|
"ajv": "^8.18",
|
|
@@ -17,17 +17,17 @@ export interface BuildInfo {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const BUILD_INFO: BuildInfo = {
|
|
20
|
-
"version": "18.
|
|
21
|
-
"commit": "
|
|
22
|
-
"shortCommit": "
|
|
20
|
+
"version": "18.28.0",
|
|
21
|
+
"commit": "aefe3468aa6c23cfe1f6ac0575e011b2416389cf",
|
|
22
|
+
"shortCommit": "aefe346",
|
|
23
23
|
"branch": "main",
|
|
24
|
-
"tag": "v18.
|
|
25
|
-
"commitDate": "2026-04-
|
|
26
|
-
"buildDate": "2026-04-
|
|
24
|
+
"tag": "v18.28.0",
|
|
25
|
+
"commitDate": "2026-04-30T03:44:13Z",
|
|
26
|
+
"buildDate": "2026-04-30T04:02:30.628Z",
|
|
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.
|
|
31
|
+
"commitUrl": "https://github.com/f5xc-salesdemos/xcsh/commit/aefe3468aa6c23cfe1f6ac0575e011b2416389cf",
|
|
32
|
+
"releaseUrl": "https://github.com/f5xc-salesdemos/xcsh/releases/tag/v18.28.0"
|
|
33
33
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { renderContextMessage } from "../../services/f5xc-table";
|
|
1
2
|
import { ContextAddWizard } from "../components/context-add-wizard";
|
|
2
3
|
import type { InteractiveModeContext } from "../types";
|
|
3
4
|
|
|
@@ -31,10 +32,11 @@ export class ContextCommandController {
|
|
|
31
32
|
const { ContextService } = await import("../../services/f5xc-context");
|
|
32
33
|
const service = await ContextService.getOrInit();
|
|
33
34
|
await service.createContext(context);
|
|
34
|
-
this.#ctx.showStatus(`Context '${context.name}' created.`);
|
|
35
35
|
if (shouldActivate) {
|
|
36
36
|
await service.activate(context.name);
|
|
37
|
-
this.#ctx.showStatus(
|
|
37
|
+
this.#ctx.showStatus(renderContextMessage(context.name, "Created and activated."), { dim: false });
|
|
38
|
+
} else {
|
|
39
|
+
this.#ctx.showStatus(renderContextMessage(context.name, "Created."), { dim: false });
|
|
38
40
|
}
|
|
39
41
|
this.#ctx.statusLine?.invalidate();
|
|
40
42
|
this.#ctx.updateEditorTopBorder?.();
|
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import { SECRET_ENV_PATTERNS } from "../secrets/index";
|
|
3
3
|
import { expandTilde } from "../tools/path-utils";
|
|
4
4
|
import { ContextError, ContextService, CURRENT_SCHEMA_VERSION } from "./f5xc-context";
|
|
5
|
+
import { formatStatusIcon } from "./f5xc-context-indicators";
|
|
5
6
|
import {
|
|
6
7
|
deriveTenantFromUrl,
|
|
7
8
|
F5XC_API_TOKEN,
|
|
@@ -16,12 +17,13 @@ import {
|
|
|
16
17
|
formatExpiration,
|
|
17
18
|
formatRelativeTime,
|
|
18
19
|
formatRotation,
|
|
20
|
+
renderContextMessage,
|
|
19
21
|
renderF5XCTable,
|
|
20
22
|
type TableRow,
|
|
21
23
|
} from "./f5xc-table";
|
|
22
24
|
|
|
23
25
|
interface CommandContext {
|
|
24
|
-
showStatus(msg: string): void;
|
|
26
|
+
showStatus(msg: string, options?: { dim?: boolean }): void;
|
|
25
27
|
showError(msg: string): void;
|
|
26
28
|
editor: { setText(text: string): void };
|
|
27
29
|
statusLine?: { invalidate(): void };
|
|
@@ -130,20 +132,30 @@ async function handleList(ctx: CommandContext, service: ContextService): Promise
|
|
|
130
132
|
const status = service.getStatus();
|
|
131
133
|
if (status.credentialSource === "environment" && status.activeContextUrl) {
|
|
132
134
|
const label = deriveTenantFromUrl(status.activeContextUrl) ?? "(environment)";
|
|
133
|
-
ctx.showStatus(
|
|
135
|
+
ctx.showStatus(
|
|
136
|
+
renderContextMessage(
|
|
137
|
+
"contexts",
|
|
138
|
+
` * ${sanitize(label)} ${sanitize(status.activeContextUrl)} (via env vars)`,
|
|
139
|
+
),
|
|
140
|
+
{ dim: false },
|
|
141
|
+
);
|
|
134
142
|
return;
|
|
135
143
|
}
|
|
136
|
-
ctx.showStatus(
|
|
144
|
+
ctx.showStatus(
|
|
145
|
+
renderContextMessage("contexts", "No F5 XC contexts found. Use /context create or ask me to help set one up."),
|
|
146
|
+
{ dim: false },
|
|
147
|
+
);
|
|
137
148
|
return;
|
|
138
149
|
}
|
|
139
150
|
const status = service.getStatus();
|
|
140
|
-
const
|
|
141
|
-
const
|
|
151
|
+
const rows: TableRow[] = contexts.map(p => {
|
|
152
|
+
const isActive = p.name === status.activeContextName;
|
|
153
|
+
const marker = isActive ? `${formatStatusIcon("connected")} ` : " ";
|
|
142
154
|
const versionSuffix =
|
|
143
155
|
p.version !== undefined && p.version > CURRENT_SCHEMA_VERSION ? ` (v${p.version} — upgrade required)` : "";
|
|
144
|
-
return
|
|
156
|
+
return { key: `${marker}${sanitize(p.name)}`, value: `${sanitize(p.apiUrl)}${versionSuffix}` };
|
|
145
157
|
});
|
|
146
|
-
ctx.showStatus(
|
|
158
|
+
ctx.showStatus(renderF5XCTable("contexts", rows), { dim: false });
|
|
147
159
|
}
|
|
148
160
|
|
|
149
161
|
async function handleActivate(ctx: CommandContext, service: ContextService, name: string): Promise<void> {
|
|
@@ -286,7 +298,7 @@ async function handleShow(ctx: CommandContext, service: ContextService, name?: s
|
|
|
286
298
|
rows.push(...metaRows);
|
|
287
299
|
}
|
|
288
300
|
|
|
289
|
-
ctx.showStatus(renderF5XCTable(context.name, rows, { dividers }));
|
|
301
|
+
ctx.showStatus(renderF5XCTable(context.name, rows, { dividers }), { dim: false });
|
|
290
302
|
}
|
|
291
303
|
|
|
292
304
|
async function handleValidate(ctx: CommandContext, service: ContextService, name: string): Promise<void> {
|
|
@@ -305,7 +317,7 @@ async function handleValidate(ctx: CommandContext, service: ContextService, name
|
|
|
305
317
|
{ key: F5XC_API_TOKEN, value: service.maskToken(result.context.apiToken) },
|
|
306
318
|
{ key: "Status", value: formatAuthIndicator(result.status, result.latencyMs, result.errorClass) },
|
|
307
319
|
];
|
|
308
|
-
ctx.showStatus(renderF5XCTable(`${result.context.name} (validation only)`, rows));
|
|
320
|
+
ctx.showStatus(renderF5XCTable(`${result.context.name} (validation only)`, rows), { dim: false });
|
|
309
321
|
} catch (err) {
|
|
310
322
|
ctx.showError(err instanceof ContextError ? err.message : String(err));
|
|
311
323
|
}
|
|
@@ -314,7 +326,10 @@ async function handleValidate(ctx: CommandContext, service: ContextService, name
|
|
|
314
326
|
async function handleStatus(ctx: CommandContext, service: ContextService): Promise<void> {
|
|
315
327
|
const status = service.getStatus();
|
|
316
328
|
if (!status.isConfigured) {
|
|
317
|
-
ctx.showStatus(
|
|
329
|
+
ctx.showStatus(
|
|
330
|
+
renderContextMessage("status", "Not configured. Use /context create or ask me to help set one up."),
|
|
331
|
+
{ dim: false },
|
|
332
|
+
);
|
|
318
333
|
return;
|
|
319
334
|
}
|
|
320
335
|
const auth = await service.validateToken({ timeoutMs: 3000 });
|
|
@@ -325,7 +340,7 @@ async function handleStatus(ctx: CommandContext, service: ContextService): Promi
|
|
|
325
340
|
{ key: "Namespace", value: status.activeContextNamespace ?? "(not set)" },
|
|
326
341
|
{ key: "Status", value: formatAuthIndicator(auth.status, auth.latencyMs, auth.errorClass) },
|
|
327
342
|
];
|
|
328
|
-
ctx.showStatus(renderF5XCTable(status.activeContextName ?? "status", rows));
|
|
343
|
+
ctx.showStatus(renderF5XCTable(status.activeContextName ?? "status", rows), { dim: false });
|
|
329
344
|
}
|
|
330
345
|
|
|
331
346
|
async function handleCreate(ctx: CommandContext, service: ContextService, args: string[]): Promise<void> {
|
|
@@ -355,7 +370,9 @@ async function handleCreate(ctx: CommandContext, service: ContextService, args:
|
|
|
355
370
|
apiToken: token,
|
|
356
371
|
defaultNamespace: namespace ?? "default",
|
|
357
372
|
});
|
|
358
|
-
ctx.showStatus(
|
|
373
|
+
ctx.showStatus(renderContextMessage(name, `Created. Use /context activate ${name} to switch to it.`), {
|
|
374
|
+
dim: false,
|
|
375
|
+
});
|
|
359
376
|
} catch (err) {
|
|
360
377
|
ctx.showError(err instanceof ContextError ? err.message : String(err));
|
|
361
378
|
}
|
|
@@ -369,7 +386,7 @@ async function handleRename(ctx: CommandContext, service: ContextService, args:
|
|
|
369
386
|
}
|
|
370
387
|
try {
|
|
371
388
|
await service.renameContext(oldName, newName);
|
|
372
|
-
ctx.showStatus(`
|
|
389
|
+
ctx.showStatus(renderContextMessage(newName, `Renamed from '${oldName}'.`), { dim: false });
|
|
373
390
|
ctx.statusLine?.invalidate();
|
|
374
391
|
ctx.updateEditorTopBorder?.();
|
|
375
392
|
ctx.ui?.requestRender();
|
|
@@ -452,13 +469,13 @@ async function handleImport(ctx: CommandContext, service: ContextService, rawArg
|
|
|
452
469
|
|
|
453
470
|
try {
|
|
454
471
|
const result = await service.importContexts(parsed, { overwrite });
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
for (const name of result.imported)
|
|
472
|
+
const bodyLines: string[] = [];
|
|
473
|
+
bodyLines.push(`Imported ${result.imported.length} context${result.imported.length === 1 ? "" : "s"}:`);
|
|
474
|
+
for (const name of result.imported) bodyLines.push(` + ${name}`);
|
|
458
475
|
if (result.overwritten.length > 0) {
|
|
459
|
-
|
|
476
|
+
bodyLines.push(`Overwrote ${result.overwritten.length}: ${result.overwritten.join(", ")}`);
|
|
460
477
|
}
|
|
461
|
-
ctx.showStatus(
|
|
478
|
+
ctx.showStatus(renderContextMessage("import", bodyLines.join("\n")), { dim: false });
|
|
462
479
|
// Invalidate TUI chrome IF the active context was overwritten. The
|
|
463
480
|
// service's importContexts re-activates the active context when an
|
|
464
481
|
// overwrite touches it, which means #activeContext, bash.environment,
|
|
@@ -491,13 +508,17 @@ async function handleDelete(ctx: CommandContext, service: ContextService, args:
|
|
|
491
508
|
}
|
|
492
509
|
if (!confirmed) {
|
|
493
510
|
ctx.showStatus(
|
|
494
|
-
|
|
511
|
+
renderContextMessage(
|
|
512
|
+
name,
|
|
513
|
+
`This will permanently delete context '${name}' from ~/.config/f5xc/contexts/.\nRun /context delete ${name} --confirm to proceed.`,
|
|
514
|
+
),
|
|
515
|
+
{ dim: false },
|
|
495
516
|
);
|
|
496
517
|
return;
|
|
497
518
|
}
|
|
498
519
|
try {
|
|
499
520
|
await service.deleteContext(name);
|
|
500
|
-
ctx.showStatus(
|
|
521
|
+
ctx.showStatus(renderContextMessage(name, "Deleted."), { dim: false });
|
|
501
522
|
} catch (err) {
|
|
502
523
|
ctx.showError(err instanceof ContextError ? err.message : String(err));
|
|
503
524
|
}
|
|
@@ -512,7 +533,7 @@ async function handleNamespace(ctx: CommandContext, service: ContextService, nam
|
|
|
512
533
|
}
|
|
513
534
|
try {
|
|
514
535
|
service.setNamespace(namespace);
|
|
515
|
-
ctx.showStatus(`Namespace
|
|
536
|
+
ctx.showStatus(renderContextMessage("namespace", `Namespace → ${namespace}`), { dim: false });
|
|
516
537
|
ctx.statusLine?.invalidate();
|
|
517
538
|
ctx.updateEditorTopBorder?.();
|
|
518
539
|
ctx.ui?.requestRender();
|
|
@@ -597,7 +618,7 @@ async function handleEnvList(ctx: CommandContext, service: ContextService): Prom
|
|
|
597
618
|
const contexts = await service.listContexts();
|
|
598
619
|
const context = contexts.find(p => p.name === contextName);
|
|
599
620
|
if (!context?.env || Object.keys(context.env).length === 0) {
|
|
600
|
-
ctx.showStatus(
|
|
621
|
+
ctx.showStatus(renderContextMessage(`${contextName} env`, "No custom environment variables."), { dim: false });
|
|
601
622
|
return;
|
|
602
623
|
}
|
|
603
624
|
const rows: TableRow[] = [];
|
|
@@ -605,7 +626,7 @@ async function handleEnvList(ctx: CommandContext, service: ContextService): Prom
|
|
|
605
626
|
const sensitive = isSensitiveKey(key) || (context.sensitiveKeys ?? []).includes(key);
|
|
606
627
|
rows.push({ key: sanitize(key), value: sensitive ? service.maskToken(value) : sanitize(value) });
|
|
607
628
|
}
|
|
608
|
-
ctx.showStatus(renderF5XCTable(`${contextName} env`, rows));
|
|
629
|
+
ctx.showStatus(renderF5XCTable(`${contextName} env`, rows), { dim: false });
|
|
609
630
|
}
|
|
610
631
|
|
|
611
632
|
async function handleEnvSet(ctx: CommandContext, service: ContextService, args: string): Promise<void> {
|
|
@@ -623,15 +644,14 @@ async function handleEnvSet(ctx: CommandContext, service: ContextService, args:
|
|
|
623
644
|
}
|
|
624
645
|
try {
|
|
625
646
|
const result = await service.setEnvVars(contextName, vars);
|
|
626
|
-
const
|
|
647
|
+
const bodyLines: string[] = [];
|
|
648
|
+
bodyLines.push(`Set ${keys.length} variable${keys.length > 1 ? "s" : ""} on '${contextName}':`);
|
|
627
649
|
for (const key of keys) {
|
|
628
650
|
const lock = result.sensitive.includes(key) ? " (auto-sensitive)" : "";
|
|
629
651
|
const displayValue = isSensitiveKey(key) ? "***" : vars[key];
|
|
630
|
-
|
|
652
|
+
bodyLines.push(` ${key}=${displayValue}${lock}`);
|
|
631
653
|
}
|
|
632
|
-
ctx.showStatus(
|
|
633
|
-
`Set ${keys.length} variable${keys.length > 1 ? "s" : ""} on '${contextName}':\n${lines.join("\n")}`,
|
|
634
|
-
);
|
|
654
|
+
ctx.showStatus(renderContextMessage(contextName, bodyLines.join("\n")), { dim: false });
|
|
635
655
|
ctx.statusLine?.invalidate();
|
|
636
656
|
} catch (err) {
|
|
637
657
|
ctx.showError(err instanceof ContextError ? err.message : String(err));
|
|
@@ -653,11 +673,15 @@ async function handleEnvUnset(ctx: CommandContext, service: ContextService, args
|
|
|
653
673
|
try {
|
|
654
674
|
const result = await service.unsetEnvVars(contextName, keys);
|
|
655
675
|
if (result.removed.length === 0) {
|
|
656
|
-
ctx.showStatus(
|
|
676
|
+
ctx.showStatus(renderContextMessage(contextName, "No matching variables found."), { dim: false });
|
|
657
677
|
return;
|
|
658
678
|
}
|
|
659
679
|
ctx.showStatus(
|
|
660
|
-
|
|
680
|
+
renderContextMessage(
|
|
681
|
+
contextName,
|
|
682
|
+
`Removed ${result.removed.length} variable${result.removed.length > 1 ? "s" : ""} from '${contextName}':\n${result.removed.map(k => ` ${k}`).join("\n")}`,
|
|
683
|
+
),
|
|
684
|
+
{ dim: false },
|
|
661
685
|
);
|
|
662
686
|
ctx.statusLine?.invalidate();
|
|
663
687
|
} catch (err) {
|
|
@@ -141,3 +141,24 @@ export function renderF5XCTable(title: string, rows: TableRow[], options?: Table
|
|
|
141
141
|
|
|
142
142
|
return lines.join("\n");
|
|
143
143
|
}
|
|
144
|
+
|
|
145
|
+
export function renderContextMessage(title: string, body: string): string {
|
|
146
|
+
const bodyLines = body.split("\n");
|
|
147
|
+
const maxLine = Math.max(...bodyLines.map(l => visibleWidth(l)), 0);
|
|
148
|
+
const innerWidth = Math.max(maxLine + 2, visibleWidth(title) + 3, 40);
|
|
149
|
+
|
|
150
|
+
const lines: string[] = [];
|
|
151
|
+
|
|
152
|
+
const titleText = ` ${title} `;
|
|
153
|
+
const titlePad = innerWidth - visibleWidth(titleText) - 1;
|
|
154
|
+
lines.push(`${r(BOX.tl + BOX.h)}${BOLD}${titleText}${RESET}${r(BOX.h.repeat(Math.max(0, titlePad)) + BOX.tr)}`);
|
|
155
|
+
|
|
156
|
+
for (const bodyLine of bodyLines) {
|
|
157
|
+
const pad = innerWidth - visibleWidth(bodyLine) - 2;
|
|
158
|
+
lines.push(`${r(BOX.v)} ${bodyLine}${" ".repeat(Math.max(0, pad))} ${r(BOX.v)}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
lines.push(r(BOX.bl + BOX.h.repeat(innerWidth) + BOX.br));
|
|
162
|
+
|
|
163
|
+
return lines.join("\n");
|
|
164
|
+
}
|