@f5xc-salesdemos/xcsh 14.0.3 → 14.1.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/CHANGELOG.md CHANGED
@@ -2,14 +2,37 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
- ### Added
6
-
5
+ ## [14.0.2] - 2026-04-09
6
+ ### Added
7
+
8
+ - Added `/force` slash command to force the next agent turn to use a specific tool
9
+ - Added `ToolChoiceQueue` for managing tool-choice directives with lifecycle callbacks and requeue semantics
10
+ - Added `setForcedToolChoice()` method to AgentSession to programmatically force tool invocations
11
+ - Added `toolChoiceQueue` property to AgentSession for direct queue access
12
+ - Added `peekQueueInvoker()` method to AgentSession to retrieve in-flight tool invocation handlers
13
+ - Added `queueResolveHandler()` function as the canonical entry point for preview/apply workflows
14
+ - Added `buildToolChoice()` and `steer()` methods to ToolSession for tool-choice queue integration
15
+ - Added `getToolChoiceQueue()` method to ToolSession for accessing the tool-choice queue
16
+ - Added support for embedded URL selectors (`:raw` and `:L#-L#` line ranges) in read command paths
17
+ - Added `parseReadUrlTarget` function to parse and validate URL read targets with line range support
7
18
  - Added `decl` region to chunk selector for targeting declarations without leading trivia
8
19
  - Exported `hooks` subpath for extensibility API access
9
20
  - Added `build` script for compiling binary artifacts
10
21
 
11
22
  ### Changed
12
23
 
24
+ - Refactored pending action handling from `PendingActionStore` to `ToolChoiceQueue` with generator-based directives
25
+ - Changed tool-choice override mechanism from simple override to a queue-based system with callbacks
26
+ - Updated `ResolveTool` to dispatch to in-flight queue invokers instead of popping from a pending action store
27
+ - Updated custom tool loader to accept `pushPendingAction` callback instead of `PendingActionStore` instance
28
+ - Updated `AstEditTool` to use `queueResolveHandler()` for preview/apply semantics
29
+ - Changed eager-todo prelude to use the tool-choice queue instead of simple override
30
+ - Updated todo reminder suppression to check for user-forced directives via `consumeLastServedLabel()`
31
+ - Made model-specific edit mode defaults conditional on `PI_STRICT_EDIT_MODE` environment variable for greater flexibility in edit mode selection
32
+ - Updated slash command handlers to support returning remaining text as prompt input instead of consuming input entirely
33
+ - Enhanced slash command parser to recognize both whitespace and colon (`:`) as command argument separators
34
+ - Updated indentation guidance for chunk edit content to use single leading spaces per indent level instead of tabs
35
+ - Updated read CLI to delegate URL inputs through the read tool pipeline instead of treating them as local file paths
13
36
  - Updated chunk edit documentation to clarify region semantics and emphasize using the narrowest region for edits
14
37
  - Improved chunk selector guidance with visual diagram showing region boundaries
15
38
  - Renamed `build:binary` script to `build`
@@ -17,6 +40,18 @@
17
40
  - Added `check:types`, `lint`, `fmt`, and `fix` scripts for improved developer workflow
18
41
  - Simplified TypeScript configuration by extending workspace-level config
19
42
 
43
+ ### Removed
44
+
45
+ - Removed `PendingActionStore` class and related pending-action module
46
+ - Removed `pendingActionStore` parameter from AgentSession config
47
+ - Removed `pendingActionStore` from ToolSession interface
48
+ - Removed `consumeNextToolChoiceOverride()` method from AgentSession (replaced by `nextToolChoice()`)
49
+
50
+ ### Fixed
51
+
52
+ - Fixed tool-choice queue cleanup on agent loop abort to prevent orphaned in-flight directives
53
+ - Fixed requeue semantics to preserve `onInvoked` and `onRejected` callbacks across multiple abort cycles
54
+
20
55
  ## [14.0.1] - 2026-04-08
21
56
 
22
57
  ### Changed
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "14.0.3",
4
+ "version": "14.1.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",
@@ -46,12 +46,12 @@
46
46
  "dependencies": {
47
47
  "@agentclientprotocol/sdk": "0.16.1",
48
48
  "@mozilla/readability": "^0.6",
49
- "@f5xc-salesdemos/xcsh-stats": "14.0.1",
50
- "@f5xc-salesdemos/pi-agent-core": "14.0.1",
51
- "@f5xc-salesdemos/pi-ai": "14.0.1",
52
- "@f5xc-salesdemos/pi-natives": "14.0.1",
53
- "@f5xc-salesdemos/pi-tui": "14.0.1",
54
- "@f5xc-salesdemos/pi-utils": "14.0.1",
49
+ "@f5xc-salesdemos/xcsh-stats": "14.1.1",
50
+ "@f5xc-salesdemos/pi-agent-core": "14.1.1",
51
+ "@f5xc-salesdemos/pi-ai": "14.1.1",
52
+ "@f5xc-salesdemos/pi-natives": "14.1.1",
53
+ "@f5xc-salesdemos/pi-tui": "14.1.1",
54
+ "@f5xc-salesdemos/pi-utils": "14.1.1",
55
55
  "@sinclair/typebox": "^0.34",
56
56
  "@xterm/headless": "^6.0",
57
57
  "ajv": "^8.18",
@@ -1,21 +1,47 @@
1
1
  /**
2
2
  * Read CLI command handler.
3
3
  *
4
- * Handles `xcsh read` subcommand — emits chunk-mode read output for a file.
4
+ * Handles `xcsh read` subcommand — emits chunk-mode read output for files,
5
+ * and delegates URL reads through the read tool pipeline.
5
6
  */
6
7
  import * as path from "node:path";
7
8
  import chalk from "chalk";
9
+ import { Settings } from "../config/settings";
8
10
  import { formatChunkedRead, resolveAnchorStyle } from "../edit/modes/chunk";
9
11
  import { getLanguageFromPath } from "../modes/theme/theme";
12
+ import type { ToolSession } from "../tools";
13
+ import { parseReadUrlTarget } from "../tools/fetch";
14
+ import { ReadTool } from "../tools/read";
10
15
 
11
16
  export interface ReadCommandArgs {
12
17
  path: string;
13
18
  sel?: string;
14
19
  }
15
20
 
21
+ function createCliReadSession(cwd: string, settings: Settings): ToolSession {
22
+ return {
23
+ cwd,
24
+ hasUI: false,
25
+ hasEditTool: true,
26
+ getSessionFile: () => null,
27
+ getSessionSpawns: () => null,
28
+ settings,
29
+ };
30
+ }
31
+
16
32
  export async function runReadCommand(cmd: ReadCommandArgs): Promise<void> {
17
- const filePath = path.resolve(cmd.path);
33
+ const cwd = process.cwd();
34
+ const parsedUrlTarget = parseReadUrlTarget(cmd.path, cmd.sel);
35
+ if (parsedUrlTarget) {
36
+ const settings = await Settings.init({ cwd });
37
+ const tool = new ReadTool(createCliReadSession(cwd, settings));
38
+ const result = await tool.execute("cli-read", { path: cmd.path, sel: cmd.sel });
39
+ const text = result.content.find((content): content is { type: "text"; text: string } => content.type === "text");
40
+ console.log(text?.text ?? "");
41
+ return;
42
+ }
18
43
 
44
+ const filePath = path.resolve(cmd.path);
19
45
  const file = Bun.file(filePath);
20
46
  if (!(await file.exists())) {
21
47
  console.error(chalk.red(`Error: File not found: ${cmd.path}`));
@@ -24,7 +50,6 @@ export async function runReadCommand(cmd: ReadCommandArgs): Promise<void> {
24
50
 
25
51
  const readPath = cmd.sel ? `${filePath}:${cmd.sel}` : filePath;
26
52
  const language = getLanguageFromPath(filePath);
27
- const cwd = process.cwd();
28
53
 
29
54
  try {
30
55
  const result = await formatChunkedRead({
@@ -238,7 +238,7 @@ export const SETTINGS_SCHEMA = {
238
238
  // Theme
239
239
  "theme.dark": {
240
240
  type: "string",
241
- default: "titanium",
241
+ default: "xcsh-dark",
242
242
  ui: {
243
243
  tab: "appearance",
244
244
  label: "Dark Theme",
@@ -249,7 +249,7 @@ export const SETTINGS_SCHEMA = {
249
249
 
250
250
  "theme.light": {
251
251
  type: "string",
252
- default: "light",
252
+ default: "xcsh-light",
253
253
  ui: {
254
254
  tab: "appearance",
255
255
  label: "Light Theme",
@@ -312,7 +312,7 @@ export const chunkToolEditSchema = Type.Object({
312
312
  "Chunk selector. Format: 'path@region' for insertions, 'path#CRC@region' for replace. Omit @region to target the full chunk. Valid regions: head, body, tail, decl.",
313
313
  }),
314
314
  content: Type.String({
315
- description: "New content. Use \\t for indentation. Do NOT include the chunk's base padding.",
315
+ description: "New content. Use one leading space per indent level; do not include the chunk's base padding.",
316
316
  }),
317
317
  });
318
318
  export const chunkEditParamsSchema = Type.Object(
@@ -5,6 +5,7 @@
5
5
  * to avoid import resolution issues with custom tools loaded from user directories.
6
6
  */
7
7
  import * as path from "node:path";
8
+ import type { AgentToolResult } from "@f5xc-salesdemos/pi-agent-core";
8
9
  import { logger } from "@f5xc-salesdemos/pi-utils";
9
10
  import * as typebox from "@sinclair/typebox";
10
11
  import { toolCapability } from "../../capability/tool";
@@ -13,7 +14,6 @@ import type { ExecOptions } from "../../exec/exec";
13
14
  import { execCommand } from "../../exec/exec";
14
15
  import type { HookUIContext } from "../../extensibility/hooks/types";
15
16
  import { getAllPluginToolPaths } from "../../extensibility/plugins/loader";
16
- import type { PendingActionStore } from "../../tools/pending-action";
17
17
  import { createNoOpUIContext, resolvePath } from "../utils";
18
18
  import type { CustomToolAPI, CustomToolFactory, LoadedCustomTool, ToolLoadError } from "./types";
19
19
 
@@ -88,7 +88,12 @@ export class CustomToolLoader {
88
88
  pi: typeof import("@f5xc-salesdemos/xcsh"),
89
89
  cwd: string,
90
90
  builtInToolNames: string[],
91
- pendingActionStore?: PendingActionStore,
91
+ pushPendingAction?: (action: {
92
+ label: string;
93
+ sourceToolName: string;
94
+ apply(reason: string): Promise<AgentToolResult<unknown>>;
95
+ reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>;
96
+ }) => void,
92
97
  ) {
93
98
  this.#sharedApi = {
94
99
  cwd,
@@ -100,15 +105,14 @@ export class CustomToolLoader {
100
105
  typebox,
101
106
  pi,
102
107
  pushPendingAction: action => {
103
- if (!pendingActionStore) {
108
+ if (!pushPendingAction) {
104
109
  throw new Error("Pending action store unavailable for custom tools in this runtime.");
105
110
  }
106
- pendingActionStore.push({
111
+ pushPendingAction({
107
112
  label: action.label,
108
113
  sourceToolName: action.sourceToolName ?? "custom_tool",
109
114
  apply: action.apply,
110
115
  reject: action.reject,
111
- details: action.details,
112
116
  });
113
117
  },
114
118
  };
@@ -159,14 +163,14 @@ export async function loadCustomTools(
159
163
  pathsWithSources: ToolPathWithSource[],
160
164
  cwd: string,
161
165
  builtInToolNames: string[],
162
- pendingActionStore?: PendingActionStore,
166
+ pushPendingAction?: (action: {
167
+ label: string;
168
+ sourceToolName: string;
169
+ apply(reason: string): Promise<AgentToolResult<unknown>>;
170
+ reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>;
171
+ }) => void,
163
172
  ) {
164
- const loader = new CustomToolLoader(
165
- await import("@f5xc-salesdemos/xcsh"),
166
- cwd,
167
- builtInToolNames,
168
- pendingActionStore,
169
- );
173
+ const loader = new CustomToolLoader(await import("@f5xc-salesdemos/xcsh"), cwd, builtInToolNames, pushPendingAction);
170
174
  await loader.load(pathsWithSources);
171
175
  return {
172
176
  tools: loader.tools,
@@ -191,7 +195,12 @@ export async function discoverAndLoadCustomTools(
191
195
  configuredPaths: string[],
192
196
  cwd: string,
193
197
  builtInToolNames: string[],
194
- pendingActionStore?: PendingActionStore,
198
+ pushPendingAction?: (action: {
199
+ label: string;
200
+ sourceToolName: string;
201
+ apply(reason: string): Promise<AgentToolResult<unknown>>;
202
+ reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>;
203
+ }) => void,
195
204
  ) {
196
205
  const allPathsWithSources: ToolPathWithSource[] = [];
197
206
  const seen = new Set<string>();
@@ -225,5 +234,5 @@ export async function discoverAndLoadCustomTools(
225
234
  addPath(resolvePath(configPath, cwd), { provider: "config", providerName: "Config", level: "project" });
226
235
  }
227
236
 
228
- return loadCustomTools(allPathsWithSources, cwd, builtInToolNames, pendingActionStore);
237
+ return loadCustomTools(allPathsWithSources, cwd, builtInToolNames, pushPendingAction);
229
238
  }
@@ -14,7 +14,7 @@ export interface LspServerInfo {
14
14
  }
15
15
 
16
16
  /**
17
- * Premium welcome screen with block-based OMP logo and two-column layout.
17
+ * Premium welcome screen with F5 XCSH logo and two-column layout.
18
18
  */
19
19
  export class WelcomeComponent implements Component {
20
20
  constructor(
@@ -42,14 +42,14 @@ export class WelcomeComponent implements Component {
42
42
 
43
43
  render(termWidth: number): string[] {
44
44
  // Box dimensions - responsive with max width and small-terminal support
45
- const maxWidth = 100;
45
+ const maxWidth = 120;
46
46
  const boxWidth = Math.min(maxWidth, Math.max(0, termWidth - 2));
47
47
  if (boxWidth < 4) {
48
48
  return [];
49
49
  }
50
50
  const dualContentWidth = boxWidth - 3; // 3 = │ + │ + │
51
- const preferredLeftCol = 26;
52
- const minLeftCol = 14; // logo width
51
+ const preferredLeftCol = 50;
52
+ const minLeftCol = 48; // F5 logo width (46 chars + padding)
53
53
  const minRightCol = 20;
54
54
  const leftMinContentWidth = Math.max(
55
55
  minLeftCol,
@@ -67,23 +67,40 @@ export class WelcomeComponent implements Component {
67
67
  const leftCol = showRightColumn ? dualLeftCol : boxWidth - 2;
68
68
  const rightCol = showRightColumn ? dualRightCol : 0;
69
69
 
70
- // Block-based OMP logo (gradient: magenta → cyan)
70
+ // F5 XCSH globe logo
71
71
  // biome-ignore format: preserve ASCII art layout
72
- const piLogo = ["▀████████████▀", " ╘███ ███ ", " ███ ███ ", " ███ ███ ", " ▄███▄ ▄███▄ "];
72
+ const f5Logo = [
73
+ " ________",
74
+ " (▒▒▒▒▓▓▓▓▓▓▓▓▒▒▒▒)",
75
+ " (▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒)",
76
+ " (▒▒▓▓▓▓██████████▓▓▓▓█████████████)",
77
+ " (▒▓▓▓▓██████▒▒▒▒▒███▓▓██████████████▒)",
78
+ " (▒▓▓▓▓██████▒▓▓▓▓▓▒▒▒▓██▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒)",
79
+ " (▒▓▓▓▓▓██████▓▓▓▓▓▓▓▓▓██▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒)",
80
+ " (▒▓▓███████████████▓▓▓▓█████████████▓▓▓▓▓▓▒)",
81
+ "(▒▓▓▓▒▒▒███████▒▒▒▒▒▓▓▓████████████████▓▓▓▓▓▒)",
82
+ "|▒▓▓▓▓▓▓▒██████▓▓▓▓▓▓▓████████████████████▓▓▒|",
83
+ "|▒▓▓▓▓▓▓▓██████▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒██████████▓▒|",
84
+ "(▒▓▓▓▓▓▓▓██████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒████████▒▒)",
85
+ " (▒▓▓▓▓▓▓██████▓▓▓▓▓▓▓███▓▓▓▓▓▓▓▓▓▓▒▒▒████▒▒)",
86
+ " (▒▓▓▓▓▓██████▓▓▓▓▓▓█████▓▓▓▓▓▓▓▓▓▓▓▓███▒▒)",
87
+ " (▒▒██████████▓▓▓▓▓▒██████▓▓▓▓▓▓▓▓███▒▒▒)",
88
+ " (▒▒▒▒▒██████████▓▓▒▒█████████████▒▒▓▒)",
89
+ " (▒▓▓▒▒▒▒▒▒▒▒▒▒▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒)",
90
+ " (▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒)",
91
+ " (▒▒▒▒▓▓▓▓▓▓▓▓▒▒▒▒)",
92
+ ];
73
93
 
74
- // Apply gradient to logo
75
- const logoColored = piLogo.map(line => this.#gradientLine(line));
94
+ // Apply F5 branding colors to logo
95
+ const logoColored = f5Logo.map(line => this.#f5ColorLine(line));
76
96
 
77
- // Left column - centered content
78
- const leftLines = [
79
- "",
80
- this.#centerText(theme.bold("Welcome back!"), leftCol),
81
- "",
82
- ...logoColored.map(l => this.#centerText(l, leftCol)),
83
- "",
84
- this.#centerText(theme.fg("muted", this.modelName), leftCol),
85
- this.#centerText(theme.fg("borderMuted", this.providerName), leftCol),
86
- ];
97
+ // Center the logo as a block (widest line = 46 chars), preserving internal alignment
98
+ const logoMaxWidth = 46;
99
+ const logoBlockPad = Math.max(0, Math.floor((leftCol - logoMaxWidth) / 2));
100
+ const logoPadStr = padding(logoBlockPad);
101
+
102
+ // Left column - logo only
103
+ const leftLines = [...logoColored.map(l => logoPadStr + l), ""];
87
104
 
88
105
  // Right column separator
89
106
  const separatorWidth = Math.max(0, rightCol - 2); // padding on each side
@@ -135,28 +152,29 @@ export class WelcomeComponent implements Component {
135
152
  "",
136
153
  ];
137
154
 
138
- // Border characters (dim)
155
+ // Border characters (themed)
156
+ const border = (s: string) => theme.fg("borderMuted", s);
139
157
  const hChar = theme.boxRound.horizontal;
140
- const h = theme.fg("dim", hChar);
141
- const v = theme.fg("dim", theme.boxRound.vertical);
142
- const tl = theme.fg("dim", theme.boxRound.topLeft);
143
- const tr = theme.fg("dim", theme.boxRound.topRight);
144
- const bl = theme.fg("dim", theme.boxRound.bottomLeft);
145
- const br = theme.fg("dim", theme.boxRound.bottomRight);
158
+ const h = border(hChar);
159
+ const v = border(theme.boxRound.vertical);
160
+ const tl = border(theme.boxRound.topLeft);
161
+ const tr = border(theme.boxRound.topRight);
162
+ const bl = border(theme.boxRound.bottomLeft);
163
+ const br = border(theme.boxRound.bottomRight);
146
164
 
147
165
  const lines: string[] = [];
148
166
 
149
167
  // Top border with embedded title
150
168
  const title = ` ${APP_NAME} v${this.version} `;
151
169
  const titlePrefixRaw = hChar.repeat(3);
152
- const titleStyled = theme.fg("dim", titlePrefixRaw) + theme.fg("muted", title);
170
+ const titleStyled = border(titlePrefixRaw) + theme.bold(theme.fg("text", title));
153
171
  const titleVisLen = visibleWidth(titlePrefixRaw) + visibleWidth(title);
154
172
  const titleSpace = boxWidth - 2;
155
173
  if (titleVisLen >= titleSpace) {
156
174
  lines.push(tl + truncateToWidth(titleStyled, titleSpace) + tr);
157
175
  } else {
158
176
  const afterTitle = titleSpace - titleVisLen;
159
- lines.push(tl + titleStyled + theme.fg("dim", hChar.repeat(afterTitle)) + tr);
177
+ lines.push(tl + titleStyled + border(hChar.repeat(afterTitle)) + tr);
160
178
  }
161
179
 
162
180
  // Content rows
@@ -172,7 +190,7 @@ export class WelcomeComponent implements Component {
172
190
  }
173
191
  // Bottom border
174
192
  if (showRightColumn) {
175
- lines.push(bl + h.repeat(leftCol) + theme.fg("dim", theme.boxSharp.teeUp) + h.repeat(rightCol) + br);
193
+ lines.push(bl + h.repeat(leftCol) + border(theme.boxSharp.teeUp) + h.repeat(rightCol) + br);
176
194
  } else {
177
195
  lines.push(bl + h.repeat(leftCol) + br);
178
196
  }
@@ -180,40 +198,22 @@ export class WelcomeComponent implements Component {
180
198
  return lines;
181
199
  }
182
200
 
183
- /** Center text within a given width */
184
- #centerText(text: string, width: number): string {
185
- const visLen = visibleWidth(text);
186
- if (visLen >= width) {
187
- return truncateToWidth(text, width);
188
- }
189
- const leftPad = Math.floor((width - visLen) / 2);
190
- const rightPad = width - visLen - leftPad;
191
- return padding(leftPad) + text + padding(rightPad);
192
- }
193
-
194
- /** Apply magenta→cyan gradient to a string */
195
- #gradientLine(line: string): string {
196
- const colors = [
197
- "\x1b[38;5;199m", // bright magenta
198
- "\x1b[38;5;171m", // magenta-purple
199
- "\x1b[38;5;135m", // purple
200
- "\x1b[38;5;99m", // purple-blue
201
- "\x1b[38;5;75m", // cyan-blue
202
- "\x1b[38;5;51m", // bright cyan
203
- ];
201
+ /** Apply F5 branding colors: ▓→red solid, █→bold white, ▒→red, outlines→red */
202
+ #f5ColorLine(line: string): string {
203
+ const red = "\x1b[38;5;160m"; // F5 red (#ca260a)
204
+ const white = "\x1b[1;37m"; // bold white
204
205
  const reset = "\x1b[0m";
205
206
 
206
207
  let result = "";
207
- let colorIdx = 0;
208
- const step = Math.max(1, Math.floor(line.length / colors.length));
209
-
210
- for (let i = 0; i < line.length; i++) {
211
- if (i > 0 && i % step === 0 && colorIdx < colors.length - 1) {
212
- colorIdx++;
213
- }
214
- const char = line[i];
215
- if (char !== " ") {
216
- result += colors[colorIdx] + char + reset;
208
+ for (const char of line) {
209
+ if (char === "▓") {
210
+ result += `${red}\u2588${reset}`; // render as solid block in red
211
+ } else if (char === "█") {
212
+ result += `${white}\u2588${reset}`; // solid block in bold white
213
+ } else if (char === "▒") {
214
+ result += `${red}\u2592${reset}`; // medium shade in red
215
+ } else if ("()|_".includes(char)) {
216
+ result += `${red}${char}${reset}`; // outlines in red
217
217
  } else {
218
218
  result += char;
219
219
  }
@@ -219,14 +219,17 @@ export class InputController {
219
219
  if (!text) return;
220
220
 
221
221
  // Handle built-in slash commands
222
- if (
223
- await executeBuiltinSlashCommand(text, {
224
- ctx: this.ctx,
225
- handleBackgroundCommand: () => this.handleBackgroundCommand(),
226
- })
227
- ) {
222
+ const slashResult = await executeBuiltinSlashCommand(text, {
223
+ ctx: this.ctx,
224
+ handleBackgroundCommand: () => this.handleBackgroundCommand(),
225
+ });
226
+ if (slashResult === true) {
228
227
  return;
229
228
  }
229
+ if (typeof slashResult === "string") {
230
+ // Command handled but returned remaining text to use as prompt
231
+ text = slashResult;
232
+ }
230
233
 
231
234
  // Handle skill commands (/skill:name [args])
232
235
  if (text.startsWith("/skill:")) {
@@ -96,6 +96,8 @@ import porcelain from "./porcelain.json" with { type: "json" };
96
96
  import quartz from "./quartz.json" with { type: "json" };
97
97
  import sandstone from "./sandstone.json" with { type: "json" };
98
98
  import titanium from "./titanium.json" with { type: "json" };
99
+ import xcshDark from "./xcsh-dark.json" with { type: "json" };
100
+ import xcshLight from "./xcsh-light.json" with { type: "json" };
99
101
 
100
102
  export const defaultThemes = {
101
103
  alabaster: alabaster,
@@ -196,4 +198,6 @@ export const defaultThemes = {
196
198
  quartz: quartz,
197
199
  sandstone: sandstone,
198
200
  titanium: titanium,
201
+ "xcsh-dark": xcshDark,
202
+ "xcsh-light": xcshLight,
199
203
  };
@@ -0,0 +1,89 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/f5xc-salesdemos/xcsh/main/packages/coding-agent/theme-schema.json",
3
+ "name": "xcsh-dark",
4
+ "vars": {
5
+ "f5Red": "#ca260a",
6
+ "f5DarkRed": "#8a1a07",
7
+ "charcoal": "#151820",
8
+ "deepCharcoal": "#0f1216",
9
+ "brightWhite": "#e8ecf4",
10
+ "coolGray": "#9ca3b0",
11
+ "warmAmber": "#ffb347",
12
+ "signalGreen": "#00ff88",
13
+ "alertRed": "#ff4757",
14
+ "subtleGray": "#2a3038"
15
+ },
16
+ "colors": {
17
+ "accent": "f5Red",
18
+ "border": "subtleGray",
19
+ "borderAccent": "f5Red",
20
+ "borderMuted": "f5Red",
21
+ "success": "signalGreen",
22
+ "error": "alertRed",
23
+ "warning": "warmAmber",
24
+ "muted": "coolGray",
25
+ "dim": "#6b7280",
26
+ "text": "",
27
+ "thinkingText": "coolGray",
28
+ "selectedBg": "f5DarkRed",
29
+ "userMessageBg": "deepCharcoal",
30
+ "userMessageText": "",
31
+ "customMessageBg": "subtleGray",
32
+ "customMessageText": "",
33
+ "customMessageLabel": "warmAmber",
34
+ "toolPendingBg": "deepCharcoal",
35
+ "toolSuccessBg": "deepCharcoal",
36
+ "toolErrorBg": "#1a0f10",
37
+ "toolTitle": "",
38
+ "toolOutput": "coolGray",
39
+ "mdHeading": "coolGray",
40
+ "mdLink": "coolGray",
41
+ "mdLinkUrl": "#6b7280",
42
+ "mdCode": "signalGreen",
43
+ "mdCodeBlock": "coolGray",
44
+ "mdCodeBlockBorder": "subtleGray",
45
+ "mdQuote": "coolGray",
46
+ "mdQuoteBorder": "subtleGray",
47
+ "mdHr": "subtleGray",
48
+ "mdListBullet": "coolGray",
49
+ "toolDiffAdded": "signalGreen",
50
+ "toolDiffRemoved": "alertRed",
51
+ "toolDiffContext": "coolGray",
52
+ "syntaxComment": "#6b7280",
53
+ "syntaxKeyword": "coolGray",
54
+ "syntaxFunction": "signalGreen",
55
+ "syntaxVariable": "brightWhite",
56
+ "syntaxString": "warmAmber",
57
+ "syntaxNumber": "warmAmber",
58
+ "syntaxType": "coolGray",
59
+ "syntaxOperator": "coolGray",
60
+ "syntaxPunctuation": "coolGray",
61
+ "thinkingOff": "#4a5058",
62
+ "thinkingMinimal": "#5a6068",
63
+ "thinkingLow": "#6a7078",
64
+ "thinkingMedium": "coolGray",
65
+ "thinkingHigh": "coolGray",
66
+ "thinkingXhigh": "warmAmber",
67
+ "bashMode": "signalGreen",
68
+ "statusLineBg": "deepCharcoal",
69
+ "statusLineSep": "subtleGray",
70
+ "statusLineModel": "f5Red",
71
+ "statusLinePath": "brightWhite",
72
+ "statusLineGitClean": "signalGreen",
73
+ "statusLineGitDirty": "warmAmber",
74
+ "statusLineContext": "coolGray",
75
+ "statusLineSpend": "warmAmber",
76
+ "statusLineStaged": "signalGreen",
77
+ "statusLineDirty": "warmAmber",
78
+ "statusLineUntracked": "coolGray",
79
+ "statusLineOutput": "f5DarkRed",
80
+ "statusLineCost": "warmAmber",
81
+ "statusLineSubagents": "f5Red",
82
+ "pythonMode": "#f0c040"
83
+ },
84
+ "export": {
85
+ "pageBg": "charcoal",
86
+ "cardBg": "deepCharcoal",
87
+ "infoBg": "subtleGray"
88
+ }
89
+ }