steward-cli 0.14.0 → 0.16.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.
Files changed (3) hide show
  1. package/README.md +1 -14
  2. package/dist/cli.js +897 -63
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Steward
2
2
 
3
- [![Version](https://img.shields.io/badge/version-v0.14.0-D77757.svg)](https://github.com/sapirrior/steward/releases)
3
+ [![Version](https://img.shields.io/badge/version-v0.16.0-D77757.svg)](https://github.com/sapirrior/steward/releases)
4
4
  [![npm](https://img.shields.io/npm/v/steward-cli.svg?color=373737)](https://www.npmjs.com/package/steward-cli)
5
5
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
6
  [![Runtime](https://img.shields.io/badge/runtime-Bun-fbf0df.svg?logo=bun)](https://bun.sh)
@@ -10,19 +10,6 @@ Steward is an interactive AI engineering assistant for the terminal. Built with
10
10
 
11
11
  ---
12
12
 
13
- ```text
14
- ▄▄▄▄▄ Steward
15
- ▀▙███▟▀ AI engineering assistant for your terminal
16
- ▘▘ ▝▝
17
-
18
- ────────────────────────────────────────────────────────────────────────────────
19
- > Refactor the authentication service to use JWT refresh token rotation
20
- ────────────────────────────────────────────────────────────────────────────────
21
- ? for shortcuts claude-3-7-sonnet
22
- ```
23
-
24
- ---
25
-
26
13
  ## Get Started
27
14
 
28
15
  ### Installation
package/dist/cli.js CHANGED
@@ -42838,12 +42838,290 @@ var darkTheme = {
42838
42838
  dividerRule: "rgb(129,136,165)",
42839
42839
  dashedRule: "rgb(51,51,51)"
42840
42840
  };
42841
+ var lightTheme = {
42842
+ brand: "rgb(215,119,87)",
42843
+ brandShimmer: "rgb(245,149,117)",
42844
+ permission: "rgb(87,105,247)",
42845
+ permissionDim: "rgb(140,150,200)",
42846
+ permissionShimmer: "rgb(137,155,255)",
42847
+ lavenderHeader: "rgb(100,110,145)",
42848
+ lavenderLight: "rgb(120,130,190)",
42849
+ bashPink: "rgb(219,39,119)",
42850
+ success: "rgb(44,122,57)",
42851
+ error: "rgb(171,43,63)",
42852
+ warning: "rgb(150,108,30)",
42853
+ info: "rgb(40,100,180)",
42854
+ bulletSuccess: "rgb(44,122,57)",
42855
+ bulletError: "rgb(171,43,63)",
42856
+ bulletRunning: "rgb(20,20,20)",
42857
+ text: "rgb(0,0,0)",
42858
+ textMuted: "rgb(120,120,120)",
42859
+ subtle: "rgb(175,175,175)",
42860
+ inactive: "rgb(102,102,102)",
42861
+ promptBorder: "rgb(153,153,153)",
42862
+ userCardBg: "rgb(240,240,240)",
42863
+ userChevron: "rgb(160,160,160)",
42864
+ toolHeaderBg: "rgb(230,230,235)",
42865
+ diffAddBG: "rgb(220,245,220)",
42866
+ diffAddFG: "rgb(30,120,40)",
42867
+ diffDeleteBG: "rgb(255,230,230)",
42868
+ diffDeleteFG: "rgb(180,40,40)",
42869
+ dividerRule: "rgb(120,130,160)",
42870
+ dashedRule: "rgb(200,200,200)"
42871
+ };
42872
+ var draculaTheme = {
42873
+ brand: "rgb(255,184,108)",
42874
+ brandShimmer: "rgb(255,200,140)",
42875
+ permission: "rgb(189,147,249)",
42876
+ permissionDim: "rgb(98,114,164)",
42877
+ permissionShimmer: "rgb(255,121,198)",
42878
+ lavenderHeader: "rgb(189,147,249)",
42879
+ lavenderLight: "rgb(215,190,252)",
42880
+ bashPink: "rgb(255,121,198)",
42881
+ success: "rgb(80,250,123)",
42882
+ error: "rgb(255,85,85)",
42883
+ warning: "rgb(255,184,108)",
42884
+ info: "rgb(139,233,253)",
42885
+ bulletSuccess: "rgb(80,250,123)",
42886
+ bulletError: "rgb(255,85,85)",
42887
+ bulletRunning: "rgb(248,248,242)",
42888
+ text: "rgb(248,248,242)",
42889
+ textMuted: "rgb(98,114,164)",
42890
+ subtle: "rgb(68,71,90)",
42891
+ inactive: "rgb(98,114,164)",
42892
+ promptBorder: "rgb(98,114,164)",
42893
+ userCardBg: "rgb(68,71,90)",
42894
+ userChevron: "rgb(98,114,164)",
42895
+ toolHeaderBg: "rgb(40,42,54)",
42896
+ diffAddBG: "rgb(12,38,18)",
42897
+ diffAddFG: "rgb(80,250,123)",
42898
+ diffDeleteBG: "rgb(45,15,15)",
42899
+ diffDeleteFG: "rgb(255,85,85)",
42900
+ dividerRule: "rgb(189,147,249)",
42901
+ dashedRule: "rgb(68,71,90)"
42902
+ };
42903
+ var darkAnsiTheme = {
42904
+ brand: "ansi(yellow)",
42905
+ brandShimmer: "ansi(yellowBright)",
42906
+ permission: "ansi(blue)",
42907
+ permissionDim: "ansi(gray)",
42908
+ permissionShimmer: "ansi(blueBright)",
42909
+ lavenderHeader: "ansi(magenta)",
42910
+ lavenderLight: "ansi(magentaBright)",
42911
+ bashPink: "ansi(magenta)",
42912
+ success: "ansi(green)",
42913
+ error: "ansi(red)",
42914
+ warning: "ansi(yellow)",
42915
+ info: "ansi(cyan)",
42916
+ bulletSuccess: "ansi(green)",
42917
+ bulletError: "ansi(red)",
42918
+ bulletRunning: "ansi(white)",
42919
+ text: "ansi(white)",
42920
+ textMuted: "ansi(gray)",
42921
+ subtle: "ansi(gray)",
42922
+ inactive: "ansi(gray)",
42923
+ promptBorder: "ansi(gray)",
42924
+ userCardBg: "ansi(bgBlackBright)",
42925
+ userChevron: "ansi(gray)",
42926
+ toolHeaderBg: "ansi(bgBlack)",
42927
+ diffAddBG: "ansi(reset)",
42928
+ diffAddFG: "ansi(green)",
42929
+ diffDeleteBG: "ansi(reset)",
42930
+ diffDeleteFG: "ansi(red)",
42931
+ dividerRule: "ansi(magenta)",
42932
+ dashedRule: "ansi(gray)"
42933
+ };
42934
+ var lightAnsiTheme = {
42935
+ brand: "ansi(yellow)",
42936
+ brandShimmer: "ansi(yellowBright)",
42937
+ permission: "ansi(blue)",
42938
+ permissionDim: "ansi(gray)",
42939
+ permissionShimmer: "ansi(blueBright)",
42940
+ lavenderHeader: "ansi(magenta)",
42941
+ lavenderLight: "ansi(magentaBright)",
42942
+ bashPink: "ansi(magenta)",
42943
+ success: "ansi(green)",
42944
+ error: "ansi(red)",
42945
+ warning: "ansi(yellow)",
42946
+ info: "ansi(blue)",
42947
+ bulletSuccess: "ansi(green)",
42948
+ bulletError: "ansi(red)",
42949
+ bulletRunning: "ansi(black)",
42950
+ text: "ansi(black)",
42951
+ textMuted: "ansi(gray)",
42952
+ subtle: "ansi(gray)",
42953
+ inactive: "ansi(gray)",
42954
+ promptBorder: "ansi(gray)",
42955
+ userCardBg: "ansi(bgWhiteBright)",
42956
+ userChevron: "ansi(gray)",
42957
+ toolHeaderBg: "ansi(bgWhite)",
42958
+ diffAddBG: "ansi(reset)",
42959
+ diffAddFG: "ansi(green)",
42960
+ diffDeleteBG: "ansi(reset)",
42961
+ diffDeleteFG: "ansi(red)",
42962
+ dividerRule: "ansi(magenta)",
42963
+ dashedRule: "ansi(gray)"
42964
+ };
42965
+ var darkColorblindTheme = {
42966
+ brand: "rgb(86,180,233)",
42967
+ brandShimmer: "rgb(136,200,245)",
42968
+ permission: "rgb(0,114,178)",
42969
+ permissionDim: "rgb(93,100,128)",
42970
+ permissionShimmer: "rgb(86,180,233)",
42971
+ lavenderHeader: "rgb(204,121,167)",
42972
+ lavenderLight: "rgb(220,150,190)",
42973
+ bashPink: "rgb(204,121,167)",
42974
+ success: "rgb(0,158,115)",
42975
+ error: "rgb(213,94,0)",
42976
+ warning: "rgb(230,159,0)",
42977
+ info: "rgb(0,114,178)",
42978
+ bulletSuccess: "rgb(0,158,115)",
42979
+ bulletError: "rgb(213,94,0)",
42980
+ bulletRunning: "rgb(255,255,255)",
42981
+ text: "rgb(255,255,255)",
42982
+ textMuted: "rgb(110,110,110)",
42983
+ subtle: "rgb(80,80,80)",
42984
+ inactive: "rgb(153,153,153)",
42985
+ promptBorder: "rgb(136,136,136)",
42986
+ userCardBg: "rgb(55,55,55)",
42987
+ userChevron: "rgb(82,82,82)",
42988
+ toolHeaderBg: "rgb(35,35,40)",
42989
+ diffAddBG: "rgb(0,35,25)",
42990
+ diffAddFG: "rgb(0,158,115)",
42991
+ diffDeleteBG: "rgb(45,20,0)",
42992
+ diffDeleteFG: "rgb(213,94,0)",
42993
+ dividerRule: "rgb(204,121,167)",
42994
+ dashedRule: "rgb(51,51,51)"
42995
+ };
42996
+ var lightColorblindTheme = {
42997
+ brand: "rgb(0,114,178)",
42998
+ brandShimmer: "rgb(86,180,233)",
42999
+ permission: "rgb(0,114,178)",
43000
+ permissionDim: "rgb(140,150,200)",
43001
+ permissionShimmer: "rgb(86,180,233)",
43002
+ lavenderHeader: "rgb(204,121,167)",
43003
+ lavenderLight: "rgb(220,150,190)",
43004
+ bashPink: "rgb(204,121,167)",
43005
+ success: "rgb(0,158,115)",
43006
+ error: "rgb(213,94,0)",
43007
+ warning: "rgb(230,159,0)",
43008
+ info: "rgb(0,114,178)",
43009
+ bulletSuccess: "rgb(0,158,115)",
43010
+ bulletError: "rgb(213,94,0)",
43011
+ bulletRunning: "rgb(20,20,20)",
43012
+ text: "rgb(0,0,0)",
43013
+ textMuted: "rgb(120,120,120)",
43014
+ subtle: "rgb(175,175,175)",
43015
+ inactive: "rgb(102,102,102)",
43016
+ promptBorder: "rgb(153,153,153)",
43017
+ userCardBg: "rgb(240,240,240)",
43018
+ userChevron: "rgb(160,160,160)",
43019
+ toolHeaderBg: "rgb(230,230,235)",
43020
+ diffAddBG: "rgb(215,245,235)",
43021
+ diffAddFG: "rgb(0,158,115)",
43022
+ diffDeleteBG: "rgb(255,235,225)",
43023
+ diffDeleteFG: "rgb(213,94,0)",
43024
+ dividerRule: "rgb(204,121,167)",
43025
+ dashedRule: "rgb(200,200,200)"
43026
+ };
43027
+ var THEMES = {
43028
+ dark: {
43029
+ name: "dark",
43030
+ label: "Dark (Default)",
43031
+ description: "Default dark theme with terracotta and purple accents",
43032
+ theme: darkTheme
43033
+ },
43034
+ light: {
43035
+ name: "light",
43036
+ label: "Light",
43037
+ description: "Clean light palette optimized for light backgrounds",
43038
+ theme: lightTheme
43039
+ },
43040
+ "dark-ansi": {
43041
+ name: "dark-ansi",
43042
+ label: "Dark ANSI",
43043
+ description: "16-color ANSI palette respecting your terminal theme",
43044
+ theme: darkAnsiTheme
43045
+ },
43046
+ "light-ansi": {
43047
+ name: "light-ansi",
43048
+ label: "Light ANSI",
43049
+ description: "16-color ANSI light palette respecting terminal colors",
43050
+ theme: lightAnsiTheme
43051
+ },
43052
+ "dark-colorblind": {
43053
+ name: "dark-colorblind",
43054
+ label: "Dark Colorblind",
43055
+ description: "Okabe–Ito colorblind-accessible dark theme",
43056
+ theme: darkColorblindTheme
43057
+ },
43058
+ "light-colorblind": {
43059
+ name: "light-colorblind",
43060
+ label: "Light Colorblind",
43061
+ description: "Okabe–Ito colorblind-accessible light theme",
43062
+ theme: lightColorblindTheme
43063
+ },
43064
+ dracula: {
43065
+ name: "dracula",
43066
+ label: "Dracula",
43067
+ description: "Official gothic-inspired vampire color scheme",
43068
+ theme: draculaTheme
43069
+ }
43070
+ };
43071
+ var activeThemeName = "dark";
43072
+ function getActiveThemeName() {
43073
+ return activeThemeName;
43074
+ }
43075
+ function setActiveTheme(name) {
43076
+ if (name in THEMES) {
43077
+ activeThemeName = name;
43078
+ }
43079
+ }
42841
43080
  function getTheme() {
42842
- return darkTheme;
43081
+ return THEMES[activeThemeName]?.theme ?? darkTheme;
43082
+ }
43083
+ function listThemes() {
43084
+ return Object.values(THEMES);
43085
+ }
43086
+ function findTheme2(query) {
43087
+ const q = query.trim().toLowerCase().replace(/^["']|["']$/g, "");
43088
+ if (!q)
43089
+ return;
43090
+ return listThemes().find((t) => t.name.toLowerCase() === q || t.label.toLowerCase() === q || t.label.toLowerCase().startsWith(q));
42843
43091
  }
42844
43092
  // src/theme/logo.ts
42845
- var LOGO_LINES = [" ▄▄▄▄▄ ", "▀▙███▟▀", " ▘▘ ▝▝ "];
43093
+ var LOGO_LINES = [" ▄▄▄▄▄ ", "▀▙███▟▀", " ▝ ▘ "];
42846
43094
  var LOGO_WIDTH = Math.max(...LOGO_LINES.map((line) => stringWidth(line)));
43095
+ // src/theme/apply.ts
43096
+ function resolveThemeColor(color, isBg = false) {
43097
+ if (!color || color === "none" || color === "transparent" || color === "ansi(reset)") {
43098
+ return source_default.visible;
43099
+ }
43100
+ if (color.startsWith("ansi(") && color.endsWith(")")) {
43101
+ const name = color.slice(5, -1).trim();
43102
+ if (name === "reset" || name === "none" || name === "transparent" || name === "default") {
43103
+ return source_default.visible;
43104
+ }
43105
+ if (isBg && !name.startsWith("bg")) {
43106
+ const bgName = `bg${name.charAt(0).toUpperCase()}${name.slice(1)}`;
43107
+ const fn = source_default[bgName];
43108
+ if (typeof fn === "function")
43109
+ return fn;
43110
+ }
43111
+ const fn = source_default[name];
43112
+ if (typeof fn === "function")
43113
+ return fn;
43114
+ return source_default.visible;
43115
+ }
43116
+ if (color.startsWith("rgb(")) {
43117
+ const match = color.match(/\d+/g);
43118
+ if (match && match.length >= 3) {
43119
+ const [r, g, b] = [Number(match[0]), Number(match[1]), Number(match[2])];
43120
+ return isBg ? source_default.bgRgb(r, g, b) : source_default.rgb(r, g, b);
43121
+ }
43122
+ }
43123
+ return isBg ? source_default.bgHex(color) : source_default.hex(color);
43124
+ }
42847
43125
  // node_modules/marked/lib/marked.esm.js
42848
43126
  function A() {
42849
43127
  return { async: false, breaks: false, extensions: null, gfm: true, hooks: null, pedantic: false, renderer: null, silent: false, tokenizer: null, walkTokens: null };
@@ -44221,18 +44499,193 @@ var an = f.parseInline;
44221
44499
  var un = b.parse;
44222
44500
  var pn = x.lex;
44223
44501
 
44224
- // src/utils/markdown.ts
44502
+ // src/utils/highlight.ts
44225
44503
  var import_cli_highlight = __toESM(require_dist(), 1);
44504
+ import { extname, basename } from "node:path";
44505
+ var githubDarkHighlightTheme = {
44506
+ keyword: source_default.hex("#ff7b72"),
44507
+ built_in: source_default.hex("#ffa657"),
44508
+ type: source_default.hex("#ffa657"),
44509
+ literal: source_default.hex("#79c0ff"),
44510
+ number: source_default.hex("#79c0ff"),
44511
+ regexp: source_default.hex("#7ee787"),
44512
+ string: source_default.hex("#a5d6ff"),
44513
+ subst: source_default.hex("#c9d1d9"),
44514
+ symbol: source_default.hex("#7ee787"),
44515
+ class: source_default.hex("#ffa657"),
44516
+ function: source_default.hex("#d2a8ff"),
44517
+ title: source_default.hex("#d2a8ff"),
44518
+ params: source_default.hex("#c9d1d9"),
44519
+ comment: (str) => source_default.hex("#8b949e")(source_default.italic(str)),
44520
+ doctag: source_default.hex("#ff7b72"),
44521
+ meta: source_default.hex("#79c0ff"),
44522
+ "meta-keyword": source_default.hex("#ff7b72"),
44523
+ "meta-string": source_default.hex("#a5d6ff"),
44524
+ section: source_default.hex("#79c0ff").bold,
44525
+ tag: source_default.hex("#7ee787"),
44526
+ name: source_default.hex("#7ee787"),
44527
+ "builtin-name": source_default.hex("#ffa657"),
44528
+ attr: source_default.hex("#79c0ff"),
44529
+ attribute: source_default.hex("#79c0ff"),
44530
+ variable: source_default.hex("#ffa657"),
44531
+ bullet: source_default.hex("#ffa657"),
44532
+ code: source_default.hex("#c9d1d9"),
44533
+ emphasis: (str) => source_default.italic(str),
44534
+ strong: (str) => source_default.bold(str),
44535
+ formula: source_default.hex("#7ee787"),
44536
+ link: source_default.hex("#a5d6ff").underline,
44537
+ quote: source_default.hex("#8b949e"),
44538
+ "selector-tag": source_default.hex("#7ee787"),
44539
+ "selector-id": source_default.hex("#79c0ff").bold,
44540
+ "selector-class": source_default.hex("#79c0ff"),
44541
+ "selector-attr": source_default.hex("#79c0ff"),
44542
+ "selector-pseudo": source_default.hex("#79c0ff"),
44543
+ "template-tag": source_default.hex("#7ee787"),
44544
+ "template-variable": source_default.hex("#ffa657"),
44545
+ addition: source_default.hex("#7ee787"),
44546
+ deletion: source_default.hex("#ffa198"),
44547
+ default: source_default.hex("#c9d1d9")
44548
+ };
44549
+ var ansiHighlightTheme = {
44550
+ keyword: source_default.red,
44551
+ built_in: source_default.yellow,
44552
+ type: source_default.yellow,
44553
+ literal: source_default.cyan,
44554
+ number: source_default.cyan,
44555
+ regexp: source_default.green,
44556
+ string: source_default.cyan,
44557
+ subst: source_default.white,
44558
+ symbol: source_default.green,
44559
+ class: source_default.yellow,
44560
+ function: source_default.magenta,
44561
+ title: source_default.magenta,
44562
+ params: source_default.white,
44563
+ comment: (str) => source_default.gray(source_default.italic(str)),
44564
+ doctag: source_default.red,
44565
+ meta: source_default.blue,
44566
+ "meta-keyword": source_default.red,
44567
+ "meta-string": source_default.cyan,
44568
+ section: source_default.blue.bold,
44569
+ tag: source_default.green,
44570
+ name: source_default.green,
44571
+ "builtin-name": source_default.yellow,
44572
+ attr: source_default.cyan,
44573
+ attribute: source_default.cyan,
44574
+ variable: source_default.yellow,
44575
+ bullet: source_default.yellow,
44576
+ code: source_default.white,
44577
+ emphasis: (str) => source_default.italic(str),
44578
+ strong: (str) => source_default.bold(str),
44579
+ formula: source_default.green,
44580
+ link: source_default.cyan.underline,
44581
+ quote: source_default.gray,
44582
+ "selector-tag": source_default.green,
44583
+ "selector-id": source_default.blue.bold,
44584
+ "selector-class": source_default.cyan,
44585
+ "selector-attr": source_default.cyan,
44586
+ "selector-pseudo": source_default.cyan,
44587
+ "template-tag": source_default.green,
44588
+ "template-variable": source_default.yellow,
44589
+ addition: source_default.green,
44590
+ deletion: source_default.red,
44591
+ default: source_default.white
44592
+ };
44593
+ var EXTENSION_MAP = {
44594
+ ".ts": "typescript",
44595
+ ".tsx": "typescript",
44596
+ ".js": "javascript",
44597
+ ".jsx": "javascript",
44598
+ ".mjs": "javascript",
44599
+ ".cjs": "javascript",
44600
+ ".json": "json",
44601
+ ".py": "python",
44602
+ ".rb": "ruby",
44603
+ ".go": "go",
44604
+ ".rs": "rust",
44605
+ ".sh": "bash",
44606
+ ".bash": "bash",
44607
+ ".zsh": "bash",
44608
+ ".html": "html",
44609
+ ".htm": "html",
44610
+ ".css": "css",
44611
+ ".scss": "scss",
44612
+ ".less": "less",
44613
+ ".md": "markdown",
44614
+ ".markdown": "markdown",
44615
+ ".yaml": "yaml",
44616
+ ".yml": "yaml",
44617
+ ".toml": "ini",
44618
+ ".sql": "sql",
44619
+ ".c": "c",
44620
+ ".h": "c",
44621
+ ".cpp": "cpp",
44622
+ ".hpp": "cpp",
44623
+ ".cc": "cpp",
44624
+ ".java": "java",
44625
+ ".php": "php",
44626
+ ".xml": "xml",
44627
+ ".svg": "xml"
44628
+ };
44629
+ var FILENAME_MAP = {
44630
+ dockerfile: "dockerfile",
44631
+ makefile: "makefile",
44632
+ cmakelists: "cmake"
44633
+ };
44634
+ function detectLanguageFromPath(filePath) {
44635
+ if (!filePath)
44636
+ return;
44637
+ const name = basename(filePath).toLowerCase();
44638
+ if (FILENAME_MAP[name])
44639
+ return FILENAME_MAP[name];
44640
+ const ext = extname(filePath).toLowerCase();
44641
+ if (ext && EXTENSION_MAP[ext]) {
44642
+ return EXTENSION_MAP[ext];
44643
+ }
44644
+ if (ext && import_cli_highlight.supportsLanguage(ext.slice(1))) {
44645
+ return ext.slice(1);
44646
+ }
44647
+ return;
44648
+ }
44649
+ function getHighlightTheme() {
44650
+ if (source_default.level === 0) {
44651
+ return {
44652
+ default: (str) => str
44653
+ };
44654
+ }
44655
+ const activeTheme = getActiveThemeName();
44656
+ if (activeTheme.includes("ansi") || source_default.level === 1) {
44657
+ return ansiHighlightTheme;
44658
+ }
44659
+ return githubDarkHighlightTheme;
44660
+ }
44661
+ function highlightCode(code, options) {
44662
+ if (!code)
44663
+ return "";
44664
+ if (source_default.level === 0)
44665
+ return code;
44666
+ const lang = options?.language ?? detectLanguageFromPath(options?.filePath);
44667
+ try {
44668
+ if (lang && import_cli_highlight.supportsLanguage(lang)) {
44669
+ return import_cli_highlight.highlight(code, {
44670
+ language: lang,
44671
+ ignoreIllegals: true,
44672
+ theme: getHighlightTheme()
44673
+ });
44674
+ }
44675
+ return import_cli_highlight.highlight(code, {
44676
+ ignoreIllegals: true,
44677
+ theme: getHighlightTheme()
44678
+ });
44679
+ } catch {
44680
+ return code;
44681
+ }
44682
+ }
44683
+
44684
+ // src/utils/markdown.ts
44226
44685
  var EOL = `
44227
44686
  `;
44228
44687
  function themeColor(color) {
44229
- if (color.startsWith("rgb(")) {
44230
- const match = color.match(/\d+/g);
44231
- if (match && match.length >= 3) {
44232
- return source_default.rgb(Number(match[0]), Number(match[1]), Number(match[2]));
44233
- }
44234
- }
44235
- return source_default.hex(color);
44688
+ return resolveThemeColor(color, false);
44236
44689
  }
44237
44690
  var markedConfigured = false;
44238
44691
  function configureMarked() {
@@ -44269,12 +44722,7 @@ function formatToken(token, listDepth = 0, orderedListNumber = null, parent = nu
44269
44722
  }
44270
44723
  case "code": {
44271
44724
  const code = token;
44272
- let highlighted = code.text;
44273
- if (code.lang && import_cli_highlight.supportsLanguage(code.lang)) {
44274
- try {
44275
- highlighted = import_cli_highlight.highlight(code.text, { language: code.lang, ignoreIllegals: true });
44276
- } catch {}
44277
- }
44725
+ const highlighted = highlightCode(code.text, { language: code.lang });
44278
44726
  const gutter = themeColor(theme.subtle)("│");
44279
44727
  const lines = highlighted.split(EOL);
44280
44728
  const formatted = lines.map((line) => ` ${gutter} ${line}`).join(EOL);
@@ -44391,22 +44839,13 @@ function applyMarkdown(content) {
44391
44839
 
44392
44840
  // src/tui/utils/format.ts
44393
44841
  function themeColor2(color) {
44394
- if (color.startsWith("rgb(")) {
44395
- const match = color.match(/\d+/g);
44396
- if (match && match.length >= 3) {
44397
- return source_default.rgb(Number(match[0]), Number(match[1]), Number(match[2]));
44398
- }
44399
- }
44400
- return source_default.hex(color);
44842
+ return resolveThemeColor(color, false);
44401
44843
  }
44402
44844
  function themeBgColor(color) {
44403
- if (color.startsWith("rgb(")) {
44404
- const match = color.match(/\d+/g);
44405
- if (match && match.length >= 3) {
44406
- return source_default.bgRgb(Number(match[0]), Number(match[1]), Number(match[2]));
44407
- }
44408
- }
44409
- return source_default.bgHex(color);
44845
+ return resolveThemeColor(color, true);
44846
+ }
44847
+ function visibleWidth(text) {
44848
+ return stringWidth(stripAnsi(text));
44410
44849
  }
44411
44850
  function formatMarkdown(md) {
44412
44851
  return applyMarkdown(md);
@@ -52333,7 +52772,7 @@ function formatUserMessage(content, targetWidth) {
52333
52772
  for (const segment of wrappedSegments) {
52334
52773
  const p = isFirstRow ? prefix : " ";
52335
52774
  isFirstRow = false;
52336
- const visibleLen = stringWidth(p) + stringWidth(segment);
52775
+ const visibleLen = visibleWidth(p) + visibleWidth(segment);
52337
52776
  const padLen = Math.max(0, termCols - visibleLen);
52338
52777
  const pStyled = themeColor2(theme.userChevron)(p);
52339
52778
  const textStyled = source_default.white(segment);
@@ -53347,6 +53786,26 @@ function saveVoiceLanguage(languageTag) {
53347
53786
  voiceLanguage: languageTag.trim()
53348
53787
  });
53349
53788
  }
53789
+ function getSavedTheme() {
53790
+ const settings = loadSettings();
53791
+ if (settings.theme && typeof settings.theme === "string" && [
53792
+ "dark",
53793
+ "light",
53794
+ "dark-ansi",
53795
+ "light-ansi",
53796
+ "dark-colorblind",
53797
+ "light-colorblind",
53798
+ "dracula"
53799
+ ].includes(settings.theme)) {
53800
+ return settings.theme;
53801
+ }
53802
+ return;
53803
+ }
53804
+ function saveThemeSelection(name) {
53805
+ saveSettings({
53806
+ theme: name
53807
+ });
53808
+ }
53350
53809
  function normalizeFolderPath(inputPath) {
53351
53810
  let absolute = resolve(inputPath);
53352
53811
  try {
@@ -53387,6 +53846,18 @@ function trustFolder(absolutePath) {
53387
53846
  }
53388
53847
  });
53389
53848
  }
53849
+ function getSavedMode() {
53850
+ const settings = loadSettings();
53851
+ if (settings.mode && typeof settings.mode === "string" && ["normal", "chat", "review", "build"].includes(settings.mode)) {
53852
+ return settings.mode;
53853
+ }
53854
+ return;
53855
+ }
53856
+ function saveModeSelection(name) {
53857
+ saveSettings({
53858
+ mode: name
53859
+ });
53860
+ }
53390
53861
 
53391
53862
  // src/services/checkpoint/path.ts
53392
53863
  function computeWorkspaceHash(workspaceRoot) {
@@ -71082,6 +71553,82 @@ var readFileTool = {
71082
71553
  }
71083
71554
  };
71084
71555
 
71556
+ // src/engine/chat-mode.ts
71557
+ var CHAT_MODES = {
71558
+ normal: {
71559
+ name: "normal",
71560
+ label: "Normal",
71561
+ description: "All tools with file and bash permission prompts",
71562
+ themeColorKey: "text"
71563
+ },
71564
+ chat: {
71565
+ name: "chat",
71566
+ label: "Chat",
71567
+ description: "Conversation only — no tool access",
71568
+ themeColorKey: "info"
71569
+ },
71570
+ review: {
71571
+ name: "review",
71572
+ label: "Review",
71573
+ description: "Read-only tools only — cannot modify files or run commands",
71574
+ themeColorKey: "warning"
71575
+ },
71576
+ build: {
71577
+ name: "build",
71578
+ label: "Build",
71579
+ description: "All tools, file mutations auto-approved, bash still prompts",
71580
+ themeColorKey: "success"
71581
+ }
71582
+ };
71583
+ var MODE_ORDER = ["normal", "chat", "review", "build"];
71584
+ var activeMode = "normal";
71585
+ function getActiveMode() {
71586
+ return activeMode;
71587
+ }
71588
+ function setActiveMode(name) {
71589
+ if (name in CHAT_MODES)
71590
+ activeMode = name;
71591
+ }
71592
+ function cycleMode() {
71593
+ const idx = MODE_ORDER.indexOf(activeMode);
71594
+ activeMode = MODE_ORDER[(idx + 1) % MODE_ORDER.length];
71595
+ return activeMode;
71596
+ }
71597
+ function listModes() {
71598
+ return Object.values(CHAT_MODES);
71599
+ }
71600
+ function findMode(query) {
71601
+ const q = query.trim().toLowerCase();
71602
+ if (!q)
71603
+ return;
71604
+ return listModes().find((m) => m.name === q || m.label.toLowerCase() === q || m.label.toLowerCase().startsWith(q));
71605
+ }
71606
+ var REVIEW_ALLOWED_TOOLS = new Set([
71607
+ "read_file",
71608
+ "glob",
71609
+ "grep",
71610
+ "list_dir",
71611
+ "web_fetch",
71612
+ "web_search",
71613
+ "todo_read",
71614
+ "skill_list",
71615
+ "skill_read",
71616
+ "task_list",
71617
+ "task_read",
71618
+ "sleep"
71619
+ ]);
71620
+ function assertToolAllowed(toolName) {
71621
+ const mode = activeMode;
71622
+ if (mode === "normal" || mode === "build")
71623
+ return;
71624
+ if (mode === "chat") {
71625
+ throw new Error(`Tool "${toolName}" is not available in Chat mode. Chat mode is for conversation only — no tool access. Switch to Normal or Build mode with /mode or Ctrl+B.`);
71626
+ }
71627
+ if (mode === "review" && !REVIEW_ALLOWED_TOOLS.has(toolName)) {
71628
+ throw new Error(`Tool "${toolName}" is not available in Review mode. Review mode only allows read-only operations. Switch to Normal or Build mode with /mode or Ctrl+B.`);
71629
+ }
71630
+ }
71631
+
71085
71632
  // src/tools/write-file/index.ts
71086
71633
  import { existsSync as existsSync11, readFileSync as readFileSync9, statSync as statSync3 } from "node:fs";
71087
71634
 
@@ -71149,14 +71696,15 @@ var writeFileTool = {
71149
71696
  const filePath = args.file_path;
71150
71697
  const isNew = result?.isNew ?? true;
71151
71698
  const summary = isNew ? `Created ${filePath} (${lines} line${lines === 1 ? "" : "s"})` : `Wrote ${lines} line${lines === 1 ? "" : "s"} to ${filePath}`;
71152
- const contentLines = args.content.split(/\r?\n/);
71699
+ const highlighted = highlightCode(args.content, { filePath: args.file_path });
71700
+ const contentLines = highlighted.split(/\r?\n/);
71153
71701
  if (contentLines.length === 0 || contentLines.length === 1 && !contentLines[0]) {
71154
71702
  return summary;
71155
71703
  }
71156
71704
  const cap = 50;
71157
71705
  const shown = contentLines.slice(0, cap);
71158
71706
  const padWidth = String(contentLines.length).length;
71159
- const detail = shown.map((l, i) => `${source_default.dim(String(i + 1).padStart(padWidth))} ${source_default.white(l)}`).join(`
71707
+ const detail = shown.map((l, i) => `${source_default.dim(String(i + 1).padStart(padWidth))} ${l}`).join(`
71160
71708
  `);
71161
71709
  const overflow = contentLines.length > cap ? `
71162
71710
  … (${contentLines.length - cap} more lines)` : "";
@@ -71164,6 +71712,7 @@ var writeFileTool = {
71164
71712
  ${detail}${overflow}`;
71165
71713
  },
71166
71714
  execute: async (args, context) => {
71715
+ assertToolAllowed("write_file");
71167
71716
  const { absolutePath: targetPath, relativePath } = resolveDirectMutationPath(context.cwd, args.file_path);
71168
71717
  const isNew = !existsSync11(targetPath);
71169
71718
  const beforeContent = isNew ? null : readFileSync9(targetPath, "utf-8");
@@ -71537,7 +72086,8 @@ var editFileTool = {
71537
72086
  diffLines.push(addStyle(`${numStr} ${body}`));
71538
72087
  } else {
71539
72088
  const numStr = String(line.newLineNumber ?? line.oldLineNumber ?? "").padStart(padWidth, " ");
71540
- diffLines.push(contextStyle(`${numStr} ${line.text}`));
72089
+ const highlightedContext = highlightCode(line.text, { filePath: args.file_path });
72090
+ diffLines.push(`${contextStyle(`${numStr} `)} ${highlightedContext}`);
71541
72091
  }
71542
72092
  }
71543
72093
  }
@@ -71549,6 +72099,7 @@ ${diffLines.join(`
71549
72099
  `)}` : summary;
71550
72100
  },
71551
72101
  execute: async (args, context) => {
72102
+ assertToolAllowed("edit_file");
71552
72103
  const { absolutePath: targetPath, relativePath } = resolveDirectMutationPath(context.cwd, args.file_path);
71553
72104
  if (!existsSync12(targetPath)) {
71554
72105
  throw new Error(`Cannot edit file because it does not exist: "${args.file_path}". Use write_file to create new files.`);
@@ -72569,6 +73120,7 @@ var bashTool = {
72569
73120
  return `Ran successfully · exit code: ${code}`;
72570
73121
  },
72571
73122
  execute: async (args, context) => {
73123
+ assertToolAllowed("bash");
72572
73124
  const command = typeof args?.command === "string" ? args.command.trim() : "";
72573
73125
  if (!command) {
72574
73126
  throw new Error("Command is required and cannot be empty.");
@@ -72668,6 +73220,7 @@ var taskSendInputTool = {
72668
73220
  return `Sent input to ${args.task_id} (${args.input.length} chars)`;
72669
73221
  },
72670
73222
  execute: async (args, context) => {
73223
+ assertToolAllowed("task_send_input");
72671
73224
  if (!context.shellTasks) {
72672
73225
  throw new Error("No shell tasks manager available in execution context.");
72673
73226
  }
@@ -72691,6 +73244,7 @@ var taskKillTool = {
72691
73244
  return `Stopped task ${args.task_id} · status: ${result?.status ?? "killed"}`;
72692
73245
  },
72693
73246
  execute: async (args, context) => {
73247
+ assertToolAllowed("task_kill");
72694
73248
  if (!context.shellTasks) {
72695
73249
  throw new Error("No shell tasks manager available in execution context.");
72696
73250
  }
@@ -72724,7 +73278,7 @@ var taskListTool = {
72724
73278
  // package.json
72725
73279
  var package_default = {
72726
73280
  name: "steward-cli",
72727
- version: "0.14.0",
73281
+ version: "0.16.0",
72728
73282
  description: "Interactive AI engineering assistant for the terminal with human-in-the-loop control across local and frontier models.",
72729
73283
  author: "sapirrior",
72730
73284
  license: "MIT",
@@ -89031,7 +89585,7 @@ function inferDocMediaType(uriOrName) {
89031
89585
  }
89032
89586
  return "application/octet-stream";
89033
89587
  }
89034
- function basename(uriOrName) {
89588
+ function basename2(uriOrName) {
89035
89589
  const parts = uriOrName.split("/");
89036
89590
  const last = parts[parts.length - 1];
89037
89591
  return last && last.length > 0 ? last : undefined;
@@ -89069,7 +89623,7 @@ function annotationToSource({
89069
89623
  ...fileCitation.file_name != null ? { title: fileCitation.file_name } : {}
89070
89624
  };
89071
89625
  }
89072
- const filename = (_c = fileCitation.file_name) != null ? _c : basename(uri);
89626
+ const filename = (_c = fileCitation.file_name) != null ? _c : basename2(uri);
89073
89627
  const mediaType = inferDocMediaType(uri);
89074
89628
  return {
89075
89629
  type: "source",
@@ -89172,7 +89726,7 @@ function builtinToolResultToSources({
89172
89726
  });
89173
89727
  continue;
89174
89728
  }
89175
- const filename = (_h = entry.file_name) != null ? _h : basename(uri);
89729
+ const filename = (_h = entry.file_name) != null ? _h : basename2(uri);
89176
89730
  const mediaType = inferDocMediaType(uri);
89177
89731
  sources.push({
89178
89732
  type: "source",
@@ -117933,6 +118487,23 @@ ${options.userRules.map((rule) => `- ${rule}`).join(`
117933
118487
  ${options.extraInstructions}
117934
118488
  </additional_instructions>`;
117935
118489
  }
118490
+ const modeBlocks = {
118491
+ chat: `
118492
+ <operating_mode>
118493
+ Mode: CHAT — You have no tool access. Respond conversationally only. Do not attempt to call any tools.
118494
+ </operating_mode>`,
118495
+ review: `
118496
+ <operating_mode>
118497
+ Mode: REVIEW — You may only use read-only tools: read_file, glob, grep, list_dir, web_fetch, web_search, todo_read, skill_list, skill_read, task_list, task_read. Any attempt to write files, run commands, or mutate state will be blocked by the tool itself.
118498
+ </operating_mode>`,
118499
+ build: `
118500
+ <operating_mode>
118501
+ Mode: BUILD — You have full tool access. File writes and edits are automatically approved without user confirmation — act decisively and make changes directly. Bash commands still require user approval.
118502
+ </operating_mode>`
118503
+ };
118504
+ if (options.chatMode && modeBlocks[options.chatMode]) {
118505
+ prompt += modeBlocks[options.chatMode];
118506
+ }
117936
118507
  return prompt;
117937
118508
  }
117938
118509
 
@@ -118083,6 +118654,8 @@ class AgentSession {
118083
118654
  lockManager: globalMutationLockManager
118084
118655
  });
118085
118656
  await tracker.beginTurn(turnId, this.sessionData.turns.length + 1);
118657
+ const mode = getActiveMode();
118658
+ const effectiveFilePermission = mode === "build" ? async (_req) => ({ allowed: true }) : options.requestFilePermission;
118086
118659
  const toolContext = {
118087
118660
  cwd,
118088
118661
  sessionId: this.sessionData.id,
@@ -118090,7 +118663,7 @@ class AgentSession {
118090
118663
  checkpointTracker: tracker,
118091
118664
  mutationLocks: globalMutationLockManager,
118092
118665
  requestBashPermission: options.requestBashPermission,
118093
- requestFilePermission: options.requestFilePermission,
118666
+ requestFilePermission: effectiveFilePermission,
118094
118667
  shellTasks: this.shellTasks
118095
118668
  };
118096
118669
  let activeTools;
@@ -118103,7 +118676,8 @@ class AgentSession {
118103
118676
  }
118104
118677
  const instructions = buildSystemPrompt({
118105
118678
  cwd,
118106
- extraInstructions: options.extraInstructions
118679
+ extraInstructions: options.extraInstructions,
118680
+ chatMode: mode
118107
118681
  });
118108
118682
  const wrappedOnEvent = (event) => {
118109
118683
  if (event.type === "tool-call") {
@@ -118852,6 +119426,84 @@ var skillsCommand = {
118852
119426
  }
118853
119427
  };
118854
119428
 
119429
+ // src/commands/theme/index.ts
119430
+ var themeCommand = {
119431
+ name: "theme",
119432
+ description: "View or switch the active UI color theme with interactive theme picker",
119433
+ usage: "/theme [theme_name]",
119434
+ async execute(args, _context) {
119435
+ if (args.length === 0) {
119436
+ return {
119437
+ handled: true,
119438
+ data: {
119439
+ showThemePicker: true,
119440
+ themes: listThemes(),
119441
+ current: getActiveThemeName()
119442
+ }
119443
+ };
119444
+ }
119445
+ const matched = findTheme2(args.join(" "));
119446
+ if (!matched) {
119447
+ const validNames = listThemes().map((t) => t.name).join(", ");
119448
+ return {
119449
+ handled: true,
119450
+ message: `Theme "${args.join(" ")}" was not found.
119451
+ Available themes: ${validNames}.
119452
+ Type "/theme" to open the interactive theme picker.`
119453
+ };
119454
+ }
119455
+ setActiveTheme(matched.name);
119456
+ saveThemeSelection(matched.name);
119457
+ return {
119458
+ handled: true,
119459
+ message: `Active theme switched to ${matched.label} and saved to ~/.steward/settings.json.`,
119460
+ data: { selected: matched, themeSwitched: true }
119461
+ };
119462
+ }
119463
+ };
119464
+
119465
+ // src/commands/mode/index.ts
119466
+ var modeCommand = {
119467
+ name: "mode",
119468
+ aliases: [],
119469
+ description: "Switch or view the current chat mode",
119470
+ execute: async (args) => {
119471
+ if (args.length === 0) {
119472
+ const current = getActiveMode();
119473
+ const currentMeta = listModes().find((m) => m.name === current);
119474
+ const modeList = listModes().map((m) => ` - ${m.label} (${m.name}): ${m.description}`).join(`
119475
+ `);
119476
+ return {
119477
+ handled: true,
119478
+ message: `Current mode: ${currentMeta?.label ?? current}
119479
+
119480
+ Available modes:
119481
+ ${modeList}
119482
+
119483
+ Use "/mode <name>" or press Ctrl+B to cycle modes.`
119484
+ };
119485
+ }
119486
+ const query = args.join(" ");
119487
+ const matched = findMode(query);
119488
+ if (matched) {
119489
+ setActiveMode(matched.name);
119490
+ saveModeSelection(matched.name);
119491
+ return {
119492
+ handled: true,
119493
+ message: `Switched to ${matched.label} mode. ${matched.description}.`,
119494
+ data: {
119495
+ modeSwitched: true,
119496
+ selectedMode: matched
119497
+ }
119498
+ };
119499
+ }
119500
+ return {
119501
+ handled: true,
119502
+ message: `Unknown mode "${query}". Valid modes are: ${listModes().map((m) => m.label).join(", ")}.`
119503
+ };
119504
+ }
119505
+ };
119506
+
118855
119507
  // src/commands/registry.ts
118856
119508
  class CommandRegistry {
118857
119509
  commands = new Map;
@@ -118894,6 +119546,8 @@ var builtInCommands = [
118894
119546
  rewindCommand,
118895
119547
  modelCommand,
118896
119548
  effortCommand,
119549
+ themeCommand,
119550
+ modeCommand,
118897
119551
  clearCommand,
118898
119552
  exitCommand,
118899
119553
  quitCommand,
@@ -119367,6 +120021,8 @@ function parseKeyInput(chunk) {
119367
120021
  const str = typeof chunk === "string" ? chunk : chunk.toString();
119368
120022
  if (str === "\x03")
119369
120023
  return { type: "ctrl-c", raw: str };
120024
+ if (str === "\x02")
120025
+ return { type: "ctrl-b", raw: str };
119370
120026
  if (str === "\x14")
119371
120027
  return { type: "ctrl-t", raw: str };
119372
120028
  if (str === "\x1B[5~")
@@ -119532,17 +120188,6 @@ class Header extends Component {
119532
120188
  }
119533
120189
 
119534
120190
  // src/tui/components/StatusBar.tsx
119535
- function formatTokens(n) {
119536
- if (n < 0)
119537
- return "0";
119538
- if (n >= 1e9)
119539
- return `${(n / 1e9).toFixed(1)}B`;
119540
- if (n >= 1e6)
119541
- return `${(n / 1e6).toFixed(1)}M`;
119542
- if (n >= 1000)
119543
- return `${(n / 1000).toFixed(1)}K`;
119544
- return `${n}`;
119545
- }
119546
120191
  function formatVoiceDuration(sec) {
119547
120192
  const m = Math.floor(sec / 60).toString().padStart(2, "0");
119548
120193
  const s = (sec % 60).toString().padStart(2, "0");
@@ -119558,6 +120203,7 @@ class StatusBar extends Component {
119558
120203
  constructor(props) {
119559
120204
  super(props);
119560
120205
  this.state = {
120206
+ chatMode: props.chatMode ?? getActiveMode(),
119561
120207
  model: props.model,
119562
120208
  usage: props.usage,
119563
120209
  isBusy: props.isBusy,
@@ -119568,6 +120214,9 @@ class StatusBar extends Component {
119568
120214
  updateStatus: props.updateStatus
119569
120215
  };
119570
120216
  }
120217
+ setMode(mode) {
120218
+ this.setState({ chatMode: mode });
120219
+ }
119571
120220
  update(partial) {
119572
120221
  this.setState(partial);
119573
120222
  }
@@ -119642,7 +120291,8 @@ class StatusBar extends Component {
119642
120291
  warning,
119643
120292
  voiceState,
119644
120293
  voiceDurationSec,
119645
- updateStatus
120294
+ updateStatus,
120295
+ chatMode
119646
120296
  } = this.state;
119647
120297
  let left = "";
119648
120298
  if (warning) {
@@ -119678,14 +120328,16 @@ class StatusBar extends Component {
119678
120328
  } else {
119679
120329
  left = source_default.dim("? for shortcuts");
119680
120330
  }
120331
+ const mode = chatMode ?? "normal";
120332
+ const modeMeta = CHAT_MODES[mode];
120333
+ const modeColorKey = modeMeta?.themeColorKey ?? "text";
120334
+ const modeLabel = modeMeta?.label ?? mode;
119681
120335
  const parts = [];
119682
- if (usage && usage.totalTokens > 0) {
119683
- parts.push(`${formatTokens(usage.totalTokens)} tokens`);
119684
- }
120336
+ parts.push(themeColor2(theme[modeColorKey])(modeLabel));
119685
120337
  const effort = model?.effort ?? "provider-default";
119686
120338
  const effortDisplay = effort === "provider-default" ? "default" : effort;
119687
- parts.push(effortDisplay);
119688
- const right = parts.length > 0 ? themeColor2(theme.textMuted)(parts.join(` ${figures.bullet} `)) : "";
120339
+ parts.push(source_default.dim(effortDisplay));
120340
+ const right = parts.length > 0 ? parts.join(` ${figures.bullet} `) : "";
119689
120341
  const element = /* @__PURE__ */ jsxs(Box, {
119690
120342
  direction: "row",
119691
120343
  justify: "space-between",
@@ -120639,6 +121291,63 @@ class ModelPicker extends SelectList {
120639
121291
  }
120640
121292
  }
120641
121293
 
121294
+ // src/tui/components/docks/ThemePicker.tsx
121295
+ class ThemePicker extends SelectList {
121296
+ wrap = false;
121297
+ clip = true;
121298
+ ellipsis = false;
121299
+ constructor(props) {
121300
+ const currentMeta = props.themes.find((t) => t.name === props.currentTheme);
121301
+ const subtitle = currentMeta ? `Current: ${currentMeta.label}` : `Current: ${props.currentTheme}`;
121302
+ super({
121303
+ items: props.themes,
121304
+ title: "Select Theme",
121305
+ subtitle,
121306
+ placeholder: "Type to filter themes…",
121307
+ emptyMessage: " No themes matching query.",
121308
+ maxVisible: 4,
121309
+ searchFilter: (t, q) => t.name.toLowerCase().includes(q) || t.label.toLowerCase().includes(q) || t.description.toLowerCase().includes(q),
121310
+ onSelect: props.onSelect,
121311
+ onCancel: props.onCancel,
121312
+ renderItem: (t, isSelected, maxCols) => {
121313
+ const theme = getTheme();
121314
+ const selColor = themeColor2(theme.permission);
121315
+ const yellowColor = themeColor2(theme.warning);
121316
+ const isCurrent = t.name === props.currentTheme;
121317
+ const pointer = isSelected ? selColor(`${figures.pointer} `) : " ";
121318
+ const activeBadge = isCurrent ? yellowColor.bold(" (active)") : "";
121319
+ const tBrand = resolveThemeColor(t.theme.brand, false)(figures.blackCircle);
121320
+ const tSuccess = resolveThemeColor(t.theme.success, false)(figures.blackCircle);
121321
+ const tWarning = resolveThemeColor(t.theme.warning, false)(figures.blackCircle);
121322
+ const tError = resolveThemeColor(t.theme.error, false)(figures.blackCircle);
121323
+ const tInfo = resolveThemeColor(t.theme.info, false)(figures.blackCircle);
121324
+ const tPerm = resolveThemeColor(t.theme.permission, false)(figures.blackCircle);
121325
+ const swatches = ` ${tBrand} ${tSuccess} ${tWarning} ${tError} ${tInfo} ${tPerm} ${t.description}`;
121326
+ return /* @__PURE__ */ jsxs(Box, {
121327
+ direction: "column",
121328
+ width: maxCols,
121329
+ children: [
121330
+ /* @__PURE__ */ jsx(Text, {
121331
+ color: isSelected ? selColor : isCurrent ? yellowColor : source_default.white,
121332
+ wrap: false,
121333
+ clip: true,
121334
+ ellipsis: true,
121335
+ children: `${pointer}${t.label}${activeBadge}`
121336
+ }),
121337
+ /* @__PURE__ */ jsx(Text, {
121338
+ dim: true,
121339
+ wrap: false,
121340
+ clip: true,
121341
+ ellipsis: true,
121342
+ children: swatches
121343
+ })
121344
+ ]
121345
+ });
121346
+ }
121347
+ });
121348
+ }
121349
+ }
121350
+
120642
121351
  // src/tui/components/docks/SessionMenu.tsx
120643
121352
  class SessionMenu extends SelectList {
120644
121353
  wrap = false;
@@ -120731,13 +121440,15 @@ class ShortcutsMenu extends Component {
120731
121440
  { key: "/", desc: "Commands" },
120732
121441
  { key: "@", desc: "Mention files" },
120733
121442
  { key: "?", desc: "Shortcuts menu" },
121443
+ { key: "Ctrl+B", desc: "Switch mode" },
120734
121444
  { key: "Esc Esc", desc: "Clear input" }
120735
121445
  ];
120736
121446
  const rightCol = [
120737
121447
  { key: "Shift+Enter", desc: "Newline" },
120738
121448
  { key: "Ctrl+C", desc: "Abort / exit" },
120739
121449
  { key: "PgUp/PgDn", desc: "Scroll history" },
120740
- { key: "↑ / ↓", desc: "History navigation" }
121450
+ { key: "↑ / ↓", desc: "History navigation" },
121451
+ { key: "Ctrl+T", desc: "Voice dictation" }
120741
121452
  ];
120742
121453
  const rows = leftCol.map((left, i) => {
120743
121454
  const right = rightCol[i];
@@ -121414,7 +122125,8 @@ class FilePermissionDock extends Component {
121414
122125
  const diffDelete = themeColor2(theme.diffDeleteFG);
121415
122126
  const diffDeleteBg = themeBgColor(theme.diffDeleteBG);
121416
122127
  if (kind === "create" || kind === "overwrite") {
121417
- const rawLines = after.split(/\r?\n/);
122128
+ const highlighted = highlightCode(after, { filePath: request.filePath });
122129
+ const rawLines = highlighted.split(/\r?\n/);
121418
122130
  const lines = after === "" ? [] : rawLines;
121419
122131
  const maxLineNum = Math.max(1, lines.length);
121420
122132
  const gutterWidth = Math.max(2, String(maxLineNum).length);
@@ -121424,7 +122136,7 @@ class FilePermissionDock extends Component {
121424
122136
  const lineNumStr = String(lineNum).padStart(gutterWidth, " ");
121425
122137
  const firstPrefix = ` ${source_default.dim(lineNumStr)} `;
121426
122138
  const contPrefix = ` ${" ".repeat(gutterWidth)} `;
121427
- const wrapped = prefixedBlock(firstPrefix, source_default.white(lines[i]), {
122139
+ const wrapped = prefixedBlock(firstPrefix, lines[i] ?? "", {
121428
122140
  continuationPrefix: contPrefix,
121429
122141
  width: maxCols
121430
122142
  });
@@ -121473,7 +122185,8 @@ class FilePermissionDock extends Component {
121473
122185
  const numStr = String(lineNum).padStart(gutterWidth, " ");
121474
122186
  const firstPrefix = ` ${source_default.dim(numStr)} `;
121475
122187
  const contPrefix = ` ${" ".repeat(gutterWidth)} `;
121476
- const wrapped = prefixedBlock(firstPrefix, source_default.white(line.text), {
122188
+ const highlightedLine = highlightCode(line.text, { filePath: request.filePath });
122189
+ const wrapped = prefixedBlock(firstPrefix, highlightedLine, {
121477
122190
  continuationPrefix: contPrefix,
121478
122191
  width: maxCols
121479
122192
  });
@@ -123020,6 +123733,18 @@ class TUIApp {
123020
123733
  }, 1500);
123021
123734
  return true;
123022
123735
  }
123736
+ if (action.type === "ctrl-b") {
123737
+ if (this.activeModal)
123738
+ return true;
123739
+ if (this.session.isBusy || this.isBusy) {
123740
+ this.statusBar.showWarning("Cannot change mode while Steward is generating");
123741
+ return true;
123742
+ }
123743
+ const newMode = cycleMode();
123744
+ saveModeSelection(newMode);
123745
+ this.statusBar.setMode(newMode);
123746
+ return true;
123747
+ }
123023
123748
  if (action.type === "ctrl-t") {
123024
123749
  if (this.activeModal) {
123025
123750
  return true;
@@ -123095,6 +123820,30 @@ class TUIApp {
123095
123820
  this.engine.mount(picker, { kind: "dock" });
123096
123821
  this.engine.mount(this.statusBar);
123097
123822
  }
123823
+ openThemePicker(themes) {
123824
+ if (this.activeModal)
123825
+ this.closeModal();
123826
+ this.engine.unmount(this.promptInput);
123827
+ this.engine.unmount(this.statusBar);
123828
+ const currentTheme = getActiveThemeName();
123829
+ const picker = new ThemePicker({
123830
+ themes,
123831
+ currentTheme,
123832
+ onSelect: (selected) => {
123833
+ setActiveTheme(selected.name);
123834
+ saveThemeSelection(selected.name);
123835
+ for (const comp of this.engine.components)
123836
+ comp.markDirty();
123837
+ this.engine.requestFrame(true);
123838
+ this.engine.commit("system", formatSystemMessage(`Theme switched to ${selected.label}.`));
123839
+ this.closeModal();
123840
+ },
123841
+ onCancel: () => this.closeModal()
123842
+ });
123843
+ this.activeModal = picker;
123844
+ this.engine.mount(picker, { kind: "dock" });
123845
+ this.engine.mount(this.statusBar);
123846
+ }
123098
123847
  switchToSession(selected) {
123099
123848
  this.permissionQueue.clear();
123100
123849
  this.session.shutdown().catch(() => {});
@@ -123273,6 +124022,18 @@ class TUIApp {
123273
124022
  this.openModelPicker(cmdResult.data.models ?? []);
123274
124023
  return;
123275
124024
  }
124025
+ if (cmdResult.data?.showThemePicker) {
124026
+ this.openThemePicker(cmdResult.data.themes ?? listThemes());
124027
+ return;
124028
+ }
124029
+ if (cmdResult.data?.themeSwitched) {
124030
+ for (const comp of this.engine.components)
124031
+ comp.markDirty();
124032
+ this.engine.requestFrame(true);
124033
+ }
124034
+ if (cmdResult.data?.modeSwitched && cmdResult.data?.selectedMode) {
124035
+ this.statusBar.setMode(cmdResult.data.selectedMode.name);
124036
+ }
123276
124037
  if (cmdResult.data?.showEffortPicker) {
123277
124038
  this.openEffortPicker();
123278
124039
  return;
@@ -123469,10 +124230,35 @@ function parseCliArgs(args) {
123469
124230
  rawArgs: args
123470
124231
  };
123471
124232
  }
124233
+ if (target === "theme" || target === "ui.theme" || target === "ui-theme" || target === "color") {
124234
+ const value = subArgs.slice(1).join(" ").trim() || undefined;
124235
+ return {
124236
+ command: "config",
124237
+ configArgs: { target: "theme", value },
124238
+ rawArgs: args
124239
+ };
124240
+ }
124241
+ if (target === "model" || target === "ai.model" || target === "ai-model") {
124242
+ const value = subArgs.slice(1).join(" ").trim() || undefined;
124243
+ return {
124244
+ command: "config",
124245
+ configArgs: { target: "model", value },
124246
+ rawArgs: args
124247
+ };
124248
+ }
124249
+ if (target === "mode" || target === "ui.mode" || target === "ui-mode") {
124250
+ const value = subArgs.slice(1).join(" ").trim() || undefined;
124251
+ return {
124252
+ command: "config",
124253
+ configArgs: { target: "mode", value },
124254
+ rawArgs: args
124255
+ };
124256
+ }
123472
124257
  console.error(`Error: Unknown configuration target "${target}".
123473
124258
  ` + `Usage:
123474
- ` + ` steward --config View all current configurations
123475
- ` + ` steward --config voice <lang> Configure preferred voice language`);
124259
+ ` + ` steward --config View all current configurations
124260
+ ` + ` steward --config voice <lang> Configure preferred voice language
124261
+ ` + ` steward --config theme <name> Configure UI color theme`);
123476
124262
  process.exit(1);
123477
124263
  }
123478
124264
  const unrecognized = args[0];
@@ -123566,9 +124352,13 @@ function handleConfigCommand(configArgs) {
123566
124352
  const voiceDisplay = voiceLang ? `${getLanguageDisplayName(voiceLang)} (${voiceLang})` : "auto-detect (default)";
123567
124353
  const savedModel = getSavedModel();
123568
124354
  const modelDisplay = savedModel ? `${savedModel.provider}:${savedModel.modelId} (effort: ${savedModel.effort ?? "provider-default"})` : "not configured (using environment priority)";
124355
+ const savedTheme = getSavedTheme() ?? "dark (default)";
124356
+ const savedMode = getSavedMode() ?? "normal (default)";
123569
124357
  console.log(`Steward Configuration (${settingsPath}):`);
124358
+ console.log(` UI Theme: ${savedTheme}`);
123570
124359
  console.log(` Voice Language: ${voiceDisplay}`);
123571
124360
  console.log(` Saved Model: ${modelDisplay}`);
124361
+ console.log(` Chat Mode: ${savedMode}`);
123572
124362
  process.exit(0);
123573
124363
  }
123574
124364
  if (configArgs.target === "voice") {
@@ -123601,6 +124391,48 @@ function handleConfigCommand(configArgs) {
123601
124391
  console.log(`✓ Voice language configured: ${resolved.name} (${resolved.tag})`);
123602
124392
  process.exit(0);
123603
124393
  }
124394
+ if (configArgs.target === "mode") {
124395
+ const rawMode = configArgs.value?.trim();
124396
+ const allModes = listModes();
124397
+ if (!rawMode) {
124398
+ const current = getSavedMode() ?? "normal";
124399
+ const currentMeta = findMode(current);
124400
+ console.log(`Current chat mode: ${currentMeta?.label ?? current} (${current})`);
124401
+ process.exit(0);
124402
+ }
124403
+ const matched = findMode(rawMode);
124404
+ if (!matched) {
124405
+ console.error(`Error: Unknown mode "${rawMode}".
124406
+ ` + `Available modes:
124407
+ ` + allModes.map((m) => ` steward --config mode ${m.name.padEnd(10)} (${m.label})`).join(`
124408
+ `));
124409
+ process.exit(1);
124410
+ }
124411
+ saveModeSelection(matched.name);
124412
+ console.log(`✓ Chat mode configured: ${matched.label} (${matched.name})`);
124413
+ process.exit(0);
124414
+ }
124415
+ if (configArgs.target === "theme") {
124416
+ const rawTheme = configArgs.value?.trim();
124417
+ const allThemes = listThemes();
124418
+ if (!rawTheme) {
124419
+ const current = getSavedTheme() ?? "dark";
124420
+ const currentMeta = findTheme(current);
124421
+ console.log(`Current UI theme: ${currentMeta?.label ?? current} (${current})`);
124422
+ process.exit(0);
124423
+ }
124424
+ const matched = findTheme(rawTheme);
124425
+ if (!matched) {
124426
+ console.error(`Error: Unknown theme "${rawTheme}".
124427
+ ` + `Available themes:
124428
+ ` + allThemes.map((t) => ` steward --config theme ${t.name.padEnd(18)} (${t.label})`).join(`
124429
+ `));
124430
+ process.exit(1);
124431
+ }
124432
+ saveThemeSelection(matched.name);
124433
+ console.log(`✓ UI theme configured: ${matched.label} (${matched.name})`);
124434
+ process.exit(0);
124435
+ }
123604
124436
  }
123605
124437
 
123606
124438
  // src/cli/commands/version/index.ts
@@ -123654,6 +124486,8 @@ async function runCli(argv = process.argv.slice(2)) {
123654
124486
  return;
123655
124487
  }
123656
124488
  try {
124489
+ setActiveTheme(getSavedTheme() ?? "dark");
124490
+ setActiveMode(getSavedMode() ?? "normal");
123657
124491
  const session = new AgentSession;
123658
124492
  const app = new TUIApp({
123659
124493
  version: VERSION16,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "steward-cli",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Interactive AI engineering assistant for the terminal with human-in-the-loop control across local and frontier models.",
5
5
  "author": "sapirrior",
6
6
  "license": "MIT",