steward-cli 0.20.0 → 0.21.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Steward
2
2
 
3
- [![Version](https://img.shields.io/badge/version-v0.20.0-D77757.svg)](https://github.com/sapirrior/steward/releases)
3
+ [![Version](https://img.shields.io/badge/version-v0.21.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)
package/dist/cli.js CHANGED
@@ -41959,6 +41959,9 @@ var require_dist2 = __commonJS(function(exports) {
41959
41959
  __exportStar(require_theme(), exports);
41960
41960
  });
41961
41961
 
41962
+ // src/app/app.ts
41963
+ import { existsSync as existsSync16 } from "node:fs";
41964
+
41962
41965
  // src/packages/tui/src/engine/HistoryStore.ts
41963
41966
  class HistoryStore {
41964
41967
  entries = [];
@@ -68876,6 +68879,28 @@ function rehydrateSessionHistory(sessionData, projection) {
68876
68879
  }
68877
68880
  }
68878
68881
  }
68882
+ if (projection) {
68883
+ for (const [turnId, turnPres] of projection.turns) {
68884
+ if (turnId.startsWith("direct-")) {
68885
+ for (const [toolCallId, toolLog] of turnPres.tools) {
68886
+ restoredItems.push({
68887
+ id: `direct-bash-${toolCallId}`,
68888
+ turnId,
68889
+ type: "tool",
68890
+ content: "",
68891
+ toolData: {
68892
+ toolName: toolLog.toolName,
68893
+ displayName: toolLog.displayName,
68894
+ status: toolLog.status,
68895
+ durationMs: toolLog.durationMs,
68896
+ error: toolLog.errorMessage,
68897
+ toolOutput: toolLog.outputSummary
68898
+ }
68899
+ });
68900
+ }
68901
+ }
68902
+ }
68903
+ }
68879
68904
  return restoredItems;
68880
68905
  }
68881
68906
  // src/packages/services/src/session/logs/types.ts
@@ -71055,7 +71080,21 @@ var bashTool = {
71055
71080
  return `Moved to background · task id: ${result.taskId}`;
71056
71081
  }
71057
71082
  const code = result && "exitCode" in result ? result.exitCode : 0;
71058
- return `Ran successfully · exit code: ${code}`;
71083
+ const headline = `Ran successfully · exit code: ${code}`;
71084
+ const stdout = result && "stdout" in result && typeof result.stdout === "string" ? result.stdout : "";
71085
+ const stderr = result && "stderr" in result && typeof result.stderr === "string" ? result.stderr : "";
71086
+ const output = (stdout + (stdout && stderr ? `
71087
+ ` : "") + stderr).trim();
71088
+ if (output) {
71089
+ return {
71090
+ headline,
71091
+ detail: {
71092
+ kind: "text",
71093
+ text: output
71094
+ }
71095
+ };
71096
+ }
71097
+ return headline;
71059
71098
  },
71060
71099
  execute: async (args, context) => {
71061
71100
  const command = typeof args?.command === "string" ? args.command.trim() : "";
@@ -71221,7 +71260,7 @@ import { isIP } from "node:net";
71221
71260
  // package.json
71222
71261
  var package_default = {
71223
71262
  name: "steward-cli",
71224
- version: "0.20.0",
71263
+ version: "0.21.0",
71225
71264
  description: "Interactive AI engineering assistant for the terminal with human-in-the-loop control across local and frontier models.",
71226
71265
  author: "sapirrior",
71227
71266
  license: "MIT",
@@ -117135,6 +117174,9 @@ class Component {
117135
117174
  this.engine.requestFrame();
117136
117175
  }
117137
117176
  }
117177
+ forceUpdate() {
117178
+ this.markDirty();
117179
+ }
117138
117180
  _cachedCursor = null;
117139
117181
  _getLines(width, forceRedraw = false) {
117140
117182
  if (this._dirty || forceRedraw || width !== undefined && width !== this._lastWidth) {
@@ -117736,10 +117778,16 @@ class StatusBar extends Component_default {
117736
117778
  model: props.model,
117737
117779
  isBusy: props.isBusy,
117738
117780
  exitPending: props.exitPending,
117781
+ isBashMode: props.isBashMode,
117739
117782
  warning: props.warning,
117740
117783
  updateStatus: props.updateStatus
117741
117784
  };
117742
117785
  }
117786
+ setBashMode(isBashMode) {
117787
+ if (this.state.isBashMode !== isBashMode) {
117788
+ this.setState({ isBashMode });
117789
+ }
117790
+ }
117743
117791
  setMode(mode) {
117744
117792
  this.setState({ chatMode: mode });
117745
117793
  }
@@ -117787,12 +117835,16 @@ class StatusBar extends Component_default {
117787
117835
  render(width) {
117788
117836
  const termWidth = width ?? process.stdout.columns ?? 80;
117789
117837
  const maxCols = Math.max(1, termWidth);
117790
- const { model, isBusy, exitPending, warning, updateStatus, chatMode } = this.state;
117838
+ const { model, isBusy, exitPending, isBashMode, warning, updateStatus, chatMode } = this.state;
117791
117839
  let left = "";
117792
117840
  if (warning) {
117793
117841
  left = c.warning(warning);
117794
117842
  } else if (exitPending) {
117795
117843
  left = `${c.error("▸ ")}${c.muted("Press ")}${c.error("Ctrl+C")}${c.muted(" again to exit")}`;
117844
+ } else if (isBusy) {
117845
+ left = c.muted("esc to interrupt");
117846
+ } else if (isBashMode) {
117847
+ left = c.permission("! for bash mode");
117796
117848
  } else if (updateStatus && updateStatus.state !== "idle") {
117797
117849
  if (updateStatus.state === "checking") {
117798
117850
  left = c.text(updateStatus.message ?? "Checking for updates...");
@@ -117802,13 +117854,9 @@ class StatusBar extends Component_default {
117802
117854
  left = c.text(updateStatus.message ?? "Steward is up to date");
117803
117855
  } else if (updateStatus.state === "error" && updateStatus.message) {
117804
117856
  left = c.error(updateStatus.message);
117805
- } else if (isBusy) {
117806
- left = c.muted("esc to interrupt");
117807
117857
  } else {
117808
117858
  left = c.muted("? for shortcuts");
117809
117859
  }
117810
- } else if (isBusy) {
117811
- left = c.muted("esc to interrupt");
117812
117860
  } else {
117813
117861
  left = c.muted("? for shortcuts");
117814
117862
  }
@@ -119575,6 +119623,7 @@ class StreamingView extends Component_default {
119575
119623
  isStreaming: false,
119576
119624
  isThinking: false,
119577
119625
  activeTool: null,
119626
+ activeCommand: null,
119578
119627
  pulseFrame: 0
119579
119628
  };
119580
119629
  }
@@ -119615,8 +119664,25 @@ class StreamingView extends Component_default {
119615
119664
  });
119616
119665
  }
119617
119666
  }
119667
+ setActiveCommand(command) {
119668
+ this.setState({
119669
+ activeCommand: command ? { command, recentLines: [] } : null,
119670
+ isThinking: false
119671
+ });
119672
+ this.ensurePulse();
119673
+ }
119674
+ updateCommandOutput(recentLines) {
119675
+ if (this.state.activeCommand) {
119676
+ this.setState({
119677
+ activeCommand: {
119678
+ ...this.state.activeCommand,
119679
+ recentLines
119680
+ }
119681
+ });
119682
+ }
119683
+ }
119618
119684
  ensurePulse() {
119619
- const isBusy = this.state.isStreaming || this.state.activeTool || this.state.isThinking;
119685
+ const isBusy = this.state.isStreaming || this.state.activeTool || this.state.activeCommand || this.state.isThinking;
119620
119686
  if (isBusy && !this.pulseTimer) {
119621
119687
  this.pulseTimer = setInterval(() => {
119622
119688
  this.setState({ pulseFrame: this.state.pulseFrame + 1 });
@@ -119637,6 +119703,7 @@ class StreamingView extends Component_default {
119637
119703
  isThinking: false,
119638
119704
  thinkingStartTime: undefined,
119639
119705
  activeTool: null,
119706
+ activeCommand: null,
119640
119707
  pulseFrame: 0
119641
119708
  });
119642
119709
  }
@@ -119647,13 +119714,37 @@ class StreamingView extends Component_default {
119647
119714
  }
119648
119715
  }
119649
119716
  render(width) {
119650
- const { text, isStreaming, isThinking, thinkingStartTime, activeTool, pulseFrame } = this.state;
119651
- if (!isStreaming && !text && !activeTool && !isThinking)
119717
+ const {
119718
+ text,
119719
+ isStreaming,
119720
+ isThinking,
119721
+ thinkingStartTime,
119722
+ activeTool,
119723
+ activeCommand,
119724
+ pulseFrame
119725
+ } = this.state;
119726
+ if (!isStreaming && !text && !activeTool && !activeCommand && !isThinking)
119652
119727
  return [];
119653
119728
  const termWidth = width ?? process.stdout.columns ?? 80;
119654
119729
  const maxCols = Math.max(1, termWidth);
119655
119730
  const textWidth = Math.max(1, maxCols - 2);
119656
119731
  const lines = [];
119732
+ if (activeCommand) {
119733
+ lines.push(`${c.permission("!")} ${c.text(activeCommand.command)}`);
119734
+ if (activeCommand.recentLines && activeCommand.recentLines.length > 0) {
119735
+ const toShow = activeCommand.recentLines.slice(-10);
119736
+ const maxLogLen = Math.max(10, maxCols - 6);
119737
+ for (let i = 0;i < toShow.length; i++) {
119738
+ const l = toShow[i] ?? "";
119739
+ const p = i === 0 ? ` ${c.muted("└ ")}` : " ";
119740
+ const truncatedLog = l.length > maxLogLen ? `${l.slice(0, maxLogLen - 1)}…` : l;
119741
+ lines.push(`${p}${c.text(truncatedLog)}`);
119742
+ }
119743
+ } else {
119744
+ lines.push(` ${c.muted("└ ")}${c.muted("Running...")}`);
119745
+ }
119746
+ return lines;
119747
+ }
119657
119748
  if (activeTool) {
119658
119749
  const bullet = getStatusBullet("running", pulseFrame % 2 === 0);
119659
119750
  const toolName = activeTool.displayName ?? (activeTool.name ? activeTool.name.charAt(0).toUpperCase() + activeTool.name.slice(1) : "Tool");
@@ -120101,7 +120192,7 @@ class PromptInput extends Component_default {
120101
120192
  return true;
120102
120193
  }
120103
120194
  if (this.commandPalette.dismiss()) {
120104
- this.forceUpdate();
120195
+ this.markDirty();
120105
120196
  return true;
120106
120197
  }
120107
120198
  if (this.state.value.length > 0) {
@@ -120161,6 +120252,9 @@ ${after}`, this.state.cursorPos);
120161
120252
  }
120162
120253
  const trimmed = this.state.value.trim();
120163
120254
  if (trimmed) {
120255
+ if (trimmed.startsWith("!")) {
120256
+ this.props.onBashModeChange?.(false);
120257
+ }
120164
120258
  this.historyController.add(trimmed);
120165
120259
  this.setState({ value: "", cursorPos: 0, fileMatches: [] });
120166
120260
  this.autocomplete.clear();
@@ -120312,6 +120406,11 @@ ${after}`, this.state.cursorPos);
120312
120406
  }
120313
120407
  updateValueAndCheckCompletions(nextVal, nextPos) {
120314
120408
  const clampedPos = Math.max(0, Math.min(nextVal.length, nextPos));
120409
+ const prevIsBash = this.state.value.startsWith("!");
120410
+ const nextIsBash = nextVal.startsWith("!");
120411
+ if (prevIsBash !== nextIsBash) {
120412
+ this.props.onBashModeChange?.(nextIsBash);
120413
+ }
120315
120414
  this.commandPalette.resetDismissed();
120316
120415
  this.setState({ value: nextVal, cursorPos: clampedPos });
120317
120416
  this.autocomplete.updateQuery(nextVal, clampedPos, () => {
@@ -120348,7 +120447,8 @@ ${after}`, this.state.cursorPos);
120348
120447
  if (escPending) {
120349
120448
  lines.push(c.permission("Press Esc again to clear"));
120350
120449
  }
120351
- const borderColor = disabled ? c.subtle : c.promptBorder;
120450
+ const isBashMode = value.startsWith("!");
120451
+ const borderColor = isBashMode ? c.permission : disabled ? c.subtle : c.promptBorder;
120352
120452
  if (disabled) {
120353
120453
  lines.push(borderColor(figures.horizontalLine.repeat(dividerWidth)));
120354
120454
  lines.push(truncateToWidth2(` ${c.muted("Esc to stop")}`, maxCols));
@@ -120694,13 +120794,20 @@ class PermissionQueue {
120694
120794
  }
120695
120795
 
120696
120796
  // src/app/ui/utils/tool-detail.ts
120697
- function renderToolDetail(detail, targetWidth) {
120797
+ function renderToolDetail(detail, targetWidth, expanded = false) {
120698
120798
  if (detail.kind === "pre-styled") {
120699
120799
  return detail.text;
120700
120800
  }
120701
120801
  if (detail.kind === "text") {
120702
- return detail.text.split(`
120703
- `).map((l) => c.muted(l)).join(`
120802
+ const rawLines = detail.text.split(`
120803
+ `);
120804
+ const cap = expanded ? Infinity : 15;
120805
+ const shown = rawLines.slice(0, cap);
120806
+ const lines = shown.map((l) => c.muted(l));
120807
+ if (rawLines.length > cap) {
120808
+ lines.push(c.text(` … (${rawLines.length - cap} more lines)`));
120809
+ }
120810
+ return lines.join(`
120704
120811
  `);
120705
120812
  }
120706
120813
  if (detail.kind === "code") {
@@ -120709,12 +120816,12 @@ function renderToolDetail(detail, targetWidth) {
120709
120816
  if (contentLines.length === 0 || contentLines.length === 1 && !contentLines[0]) {
120710
120817
  return "";
120711
120818
  }
120712
- const cap = 50;
120819
+ const cap = expanded ? Infinity : 50;
120713
120820
  const shown = contentLines.slice(0, cap);
120714
120821
  const padWidth = String(contentLines.length).length;
120715
120822
  const lines = shown.map((l, i) => `${c.muted(String(i + 1).padStart(padWidth))} ${l}`);
120716
120823
  if (contentLines.length > cap) {
120717
- lines.push(c.muted(` … (${contentLines.length - cap} more lines)`));
120824
+ lines.push(c.text(` … (${contentLines.length - cap} more lines)`));
120718
120825
  }
120719
120826
  return lines.join(`
120720
120827
  `);
@@ -120732,7 +120839,7 @@ function renderToolDetail(detail, targetWidth) {
120732
120839
  const padWidth = Math.max(1, String(maxLineNum).length);
120733
120840
  const blockWidth = targetWidth ? Math.max(30, targetWidth) : undefined;
120734
120841
  const diffLines = [];
120735
- const cap = 30;
120842
+ const cap = expanded ? Infinity : 30;
120736
120843
  let count = 0;
120737
120844
  let overflow = 0;
120738
120845
  for (const hunk of detail.hunks) {
@@ -120758,7 +120865,7 @@ function renderToolDetail(detail, targetWidth) {
120758
120865
  }
120759
120866
  }
120760
120867
  if (overflow > 0) {
120761
- diffLines.push(c.muted(` … (${overflow} more lines)`));
120868
+ diffLines.push(c.text(` … (${overflow} more lines)`));
120762
120869
  }
120763
120870
  return diffLines.join(`
120764
120871
  `);
@@ -120833,7 +120940,7 @@ function formatToolStatus(options) {
120833
120940
  } else {
120834
120941
  headline = summaryVal.headline;
120835
120942
  if (summaryVal.detail) {
120836
- detailText = renderToolDetail(summaryVal.detail);
120943
+ detailText = renderToolDetail(summaryVal.detail, fullTermWidth, options.expanded);
120837
120944
  }
120838
120945
  }
120839
120946
  if (headline) {
@@ -120854,11 +120961,20 @@ function formatToolStatus(options) {
120854
120961
  }
120855
120962
  if (status === "failed" || error) {
120856
120963
  const rawError = error || "Operation failed";
120857
- const firstLineErr = rawError.split(`
120858
- `)[0]?.trim() || rawError;
120964
+ const errLines = rawError.split(`
120965
+ `);
120966
+ const firstLineErr = errLines[0]?.trim() || rawError;
120859
120967
  const maxErrLen = Math.max(10, fullTermWidth - 6);
120860
120968
  const truncatedErr = firstLineErr.length > maxErrLen ? `${firstLineErr.slice(0, maxErrLen - 1)}…` : firstLineErr;
120861
120969
  lines.push(` ${c.muted("└ ")}${c.error(truncatedErr)}`);
120970
+ if (options.expanded && errLines.length > 1) {
120971
+ for (let i = 1;i < errLines.length; i++) {
120972
+ const line = errLines[i];
120973
+ if (line.trim()) {
120974
+ lines.push(` ${c.error(line)}`);
120975
+ }
120976
+ }
120977
+ }
120862
120978
  }
120863
120979
  return lines;
120864
120980
  }
@@ -120902,7 +121018,7 @@ function formatTurnFooter(engine, turnPresentationEnd) {
120902
121018
  engine.commit("system", formatErrorBadge(structured));
120903
121019
  }
120904
121020
  }
120905
- function renderTranscript(engine, sessionData, header) {
121021
+ function renderTranscript(engine, sessionData, header, options) {
120906
121022
  engine.clearAll();
120907
121023
  engine.commit("header", header.render());
120908
121024
  const log = loadSessionLog(sessionData.date, sessionData.id);
@@ -120923,6 +121039,28 @@ function renderTranscript(engine, sessionData, header) {
120923
121039
  engine.commit("system", formatSystemMessage(item.content));
120924
121040
  } else if (item.type === "tool" && item.toolData) {
120925
121041
  const toolData = item.toolData;
121042
+ if (toolData.toolName === "direct-bash") {
121043
+ const cmd = toolData.displayName || "";
121044
+ const lines = [`${c.permission("!")} ${c.text(cmd)}`];
121045
+ if (toolData.status === "completed") {
121046
+ if (toolData.toolOutput) {
121047
+ const outLines = toolData.toolOutput.split(/\r?\n/);
121048
+ lines.push(` ${c.muted("└ ")}${c.text(outLines[0] ?? "")}`);
121049
+ for (let i = 1;i < outLines.length; i++) {
121050
+ lines.push(` ${c.text(outLines[i] ?? "")}`);
121051
+ }
121052
+ }
121053
+ } else {
121054
+ const errMsg = toolData.error || (toolData.toolOutput ? `Command failed: ${toolData.toolOutput}` : "Command failed");
121055
+ const errLines = errMsg.split(/\r?\n/);
121056
+ lines.push(` ${c.muted("└ ")}${c.error(errLines[0] ?? "")}`);
121057
+ for (let i = 1;i < errLines.length; i++) {
121058
+ lines.push(` ${c.error(errLines[i] ?? "")}`);
121059
+ }
121060
+ }
121061
+ engine.commit("raw", lines);
121062
+ continue;
121063
+ }
120926
121064
  const toolDef = defaultToolCatalog.get(toolData.toolName);
120927
121065
  const displayName = toolData.displayName ?? toolDef?.displayName;
120928
121066
  const icon = toolData.icon ?? toolDef?.icon;
@@ -120937,7 +121075,8 @@ function renderTranscript(engine, sessionData, header) {
120937
121075
  durationMs: toolData.durationMs,
120938
121076
  error: toolData.error,
120939
121077
  summary,
120940
- targetWidth: w
121078
+ targetWidth: w,
121079
+ expanded: options?.expanded
120941
121080
  }), { hangingIndent: 2 });
120942
121081
  } else if (item.type === "assistant") {
120943
121082
  engine.commit("assistant-message", formatAssistantMessage(item.content), {
@@ -122582,6 +122721,91 @@ async function applyCommandResult(text, cmdResult, ctx) {
122582
122721
  return true;
122583
122722
  }
122584
122723
 
122724
+ // src/app/utils/bash.ts
122725
+ import { spawn as spawn2 } from "node:child_process";
122726
+ async function executeDirectBash(options) {
122727
+ const { command, cwd = process.cwd(), abortSignal, onChunk } = options;
122728
+ const { shell, args } = getPlatformShell();
122729
+ const childArgs = [...args, command];
122730
+ const startTime = performance.now();
122731
+ return new Promise((resolve, reject) => {
122732
+ let child = null;
122733
+ let stdout = "";
122734
+ let stderr = "";
122735
+ let output = "";
122736
+ let isSettled = false;
122737
+ try {
122738
+ child = spawn2(shell, childArgs, {
122739
+ cwd,
122740
+ env: process.env,
122741
+ stdio: ["pipe", "pipe", "pipe"],
122742
+ detached: process.platform !== "win32"
122743
+ });
122744
+ } catch (err) {
122745
+ return reject(err);
122746
+ }
122747
+ const cleanup = () => {
122748
+ if (abortSignal) {
122749
+ abortSignal.removeEventListener("abort", onAbort);
122750
+ }
122751
+ };
122752
+ const finish = (exitCode) => {
122753
+ if (isSettled)
122754
+ return;
122755
+ isSettled = true;
122756
+ cleanup();
122757
+ const durationMs = Math.max(0, Math.round(performance.now() - startTime));
122758
+ resolve({
122759
+ command,
122760
+ exitCode,
122761
+ stdout,
122762
+ stderr,
122763
+ output: output.trim(),
122764
+ durationMs
122765
+ });
122766
+ };
122767
+ const onAbort = () => {
122768
+ if (child && !isSettled) {
122769
+ try {
122770
+ if (process.platform !== "win32" && child.pid) {
122771
+ process.kill(-child.pid, "SIGKILL");
122772
+ } else {
122773
+ child.kill("SIGKILL");
122774
+ }
122775
+ } catch {}
122776
+ }
122777
+ };
122778
+ if (abortSignal) {
122779
+ if (abortSignal.aborted) {
122780
+ onAbort();
122781
+ } else {
122782
+ abortSignal.addEventListener("abort", onAbort, { once: true });
122783
+ }
122784
+ }
122785
+ child.stdout?.on("data", (chunk) => {
122786
+ const text = chunk.toString("utf-8");
122787
+ stdout += text;
122788
+ output += text;
122789
+ onChunk?.(text, output);
122790
+ });
122791
+ child.stderr?.on("data", (chunk) => {
122792
+ const text = chunk.toString("utf-8");
122793
+ stderr += text;
122794
+ output += text;
122795
+ onChunk?.(text, output);
122796
+ });
122797
+ child.on("error", (err) => {
122798
+ if (isSettled)
122799
+ return;
122800
+ isSettled = true;
122801
+ cleanup();
122802
+ reject(err);
122803
+ });
122804
+ child.on("close", (code) => finish(code));
122805
+ child.on("exit", (code) => finish(code));
122806
+ });
122807
+ }
122808
+
122585
122809
  // src/app/app.ts
122586
122810
  class TUIApp {
122587
122811
  engine;
@@ -122599,6 +122823,7 @@ class TUIApp {
122599
122823
  isScrollViewMode = false;
122600
122824
  updateChecker;
122601
122825
  permissionQueue;
122826
+ directBashAbortController = null;
122602
122827
  constructor(options = {}) {
122603
122828
  this.cwd = options.cwd ?? process.cwd();
122604
122829
  this.session = options.initialSession ?? new AgentSession;
@@ -122615,6 +122840,7 @@ class TUIApp {
122615
122840
  onSubmit: (text) => this.handleSubmit(text),
122616
122841
  onAbort: () => this.handleAbort(),
122617
122842
  onToggleHelp: () => this.modals.toggleHelp(),
122843
+ onBashModeChange: (isBash) => this.statusBar.setBashMode(isBash),
122618
122844
  cwd: this.cwd,
122619
122845
  initialHistory: this.session.session.turns.map((t) => {
122620
122846
  const userMsg = t.messages.find((m) => m.role === "user");
@@ -122718,7 +122944,8 @@ class TUIApp {
122718
122944
  }
122719
122945
  proceedStart() {
122720
122946
  this.engine.ensureAlternateScreen();
122721
- if (this.session.session.turns.length > 0) {
122947
+ const logPath = getSessionLogPath(this.session.session.date, this.session.session.id);
122948
+ if (this.session.session.turns.length > 0 || existsSync16(logPath)) {
122722
122949
  renderTranscript(this.engine, this.session.session, this.header);
122723
122950
  } else {
122724
122951
  this.engine.commit("header", this.header.render());
@@ -122797,19 +123024,25 @@ class TUIApp {
122797
123024
  toggleScrollViewMode() {
122798
123025
  if (this.isScrollViewMode) {
122799
123026
  this.isScrollViewMode = false;
123027
+ renderTranscript(this.engine, this.session.session, this.header, { expanded: false });
123028
+ this.engine.mount(this.streamingView, { kind: "custom" });
122800
123029
  this.engine.mount(this.promptInput, { keepCursorVisible: true, kind: "input" });
122801
123030
  this.engine.mount(this.statusBar, { kind: "custom" });
122802
123031
  this.engine.scrollToBottom();
122803
123032
  } else {
122804
123033
  this.isScrollViewMode = true;
123034
+ this.engine.unmount(this.streamingView);
122805
123035
  this.engine.unmount(this.promptInput);
122806
123036
  this.engine.unmount(this.statusBar);
122807
123037
  this.engine.hideCursor();
122808
- this.engine.requestFrame();
123038
+ renderTranscript(this.engine, this.session.session, this.header, { expanded: true });
122809
123039
  }
122810
123040
  }
122811
123041
  handleAbort() {
122812
123042
  if (this.isBusy) {
123043
+ if (this.directBashAbortController) {
123044
+ this.directBashAbortController.abort();
123045
+ }
122813
123046
  this.permissionQueue.clear();
122814
123047
  this.session.abort();
122815
123048
  }
@@ -122833,6 +123066,98 @@ class TUIApp {
122833
123066
  });
122834
123067
  return;
122835
123068
  }
123069
+ if (text.startsWith("!")) {
123070
+ const command = text.slice(1).trim();
123071
+ if (!command)
123072
+ return;
123073
+ this.setBusy(true);
123074
+ this.directBashAbortController = new AbortController;
123075
+ const callId = `direct-bash-${Date.now()}`;
123076
+ const startedAt = new Date().toISOString();
123077
+ const logWriter = new SessionLogWriter(this.session.session.date, this.session.session.id);
123078
+ logWriter.append({
123079
+ schemaVersion: 1,
123080
+ sessionId: this.session.session.id,
123081
+ turnId: `direct-${Date.now()}`,
123082
+ type: "tool-start",
123083
+ timestamp: startedAt,
123084
+ toolCallId: callId,
123085
+ toolName: "bash",
123086
+ startedAt
123087
+ });
123088
+ this.streamingView.setActiveCommand(command);
123089
+ try {
123090
+ const result = await executeDirectBash({
123091
+ command,
123092
+ cwd: this.cwd,
123093
+ abortSignal: this.directBashAbortController.signal,
123094
+ onChunk: (_chunk, fullOutput) => {
123095
+ const recent = fullOutput.split(/\r?\n/).filter(Boolean);
123096
+ this.streamingView.updateCommandOutput(recent);
123097
+ }
123098
+ });
123099
+ this.streamingView.setActiveCommand(null);
123100
+ this.streamingView.reset();
123101
+ logWriter.append({
123102
+ schemaVersion: 1,
123103
+ sessionId: this.session.session.id,
123104
+ turnId: `direct-${Date.now()}`,
123105
+ type: "tool-end",
123106
+ timestamp: new Date().toISOString(),
123107
+ toolCallId: callId,
123108
+ toolName: "direct-bash",
123109
+ finishedAt: new Date().toISOString(),
123110
+ durationMs: result.durationMs,
123111
+ status: result.exitCode === 0 ? "completed" : "failed",
123112
+ displayName: command,
123113
+ outputSummary: result.output,
123114
+ errorMessage: result.exitCode !== 0 ? `Command failed with exit code ${result.exitCode ?? 1}` : undefined
123115
+ });
123116
+ const lines = [`${c.permission("!")} ${c.text(command)}`];
123117
+ if (result.exitCode === 0) {
123118
+ if (result.output) {
123119
+ const outLines = result.output.split(/\r?\n/);
123120
+ lines.push(` ${c.muted("└ ")}${c.text(outLines[0] ?? "")}`);
123121
+ for (let i = 1;i < outLines.length; i++) {
123122
+ lines.push(` ${c.text(outLines[i] ?? "")}`);
123123
+ }
123124
+ }
123125
+ } else {
123126
+ const errMsg = `Command failed with exit code ${result.exitCode ?? 1}${result.output ? ": " + result.output : ""}`;
123127
+ const errLines = errMsg.split(/\r?\n/);
123128
+ lines.push(` ${c.muted("└ ")}${c.error(errLines[0] ?? "")}`);
123129
+ for (let i = 1;i < errLines.length; i++) {
123130
+ lines.push(` ${c.error(errLines[i] ?? "")}`);
123131
+ }
123132
+ }
123133
+ this.engine.commit("raw", lines);
123134
+ } catch (err) {
123135
+ this.streamingView.setActiveCommand(null);
123136
+ this.streamingView.reset();
123137
+ const msg = err instanceof Error ? err.message : String(err);
123138
+ logWriter.append({
123139
+ schemaVersion: 1,
123140
+ sessionId: this.session.session.id,
123141
+ turnId: `direct-${Date.now()}`,
123142
+ type: "tool-end",
123143
+ timestamp: new Date().toISOString(),
123144
+ toolCallId: callId,
123145
+ toolName: "direct-bash",
123146
+ finishedAt: new Date().toISOString(),
123147
+ status: "failed",
123148
+ displayName: command,
123149
+ errorMessage: msg
123150
+ });
123151
+ this.engine.commit("raw", [
123152
+ `${c.permission("!")} ${c.text(command)}`,
123153
+ ` ${c.muted("└ ")}${c.error(msg)}`
123154
+ ]);
123155
+ } finally {
123156
+ this.directBashAbortController = null;
123157
+ this.setBusy(false);
123158
+ }
123159
+ return;
123160
+ }
122836
123161
  this.engine.commitPrompt(text);
122837
123162
  this.setBusy(true);
122838
123163
  this.streamingView.setThinking(true);
package/dist/steward CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/dist/steward
3
3
  size: 91158272 bytes
4
- sha256: 611baa959e792c7611786ca62cd1e31cf932fd91a6e4066a5b419fe3c60a5e52
4
+ sha256: e620e677a26c92e73c2fecc8975817796de77f0159832e02f23d0a351de614cd
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "steward-cli",
3
- "version": "0.20.0",
3
+ "version": "0.21.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",