@austinthesing/magic-shell 0.2.8 → 0.2.10

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/dist/cli.js +61 -36
  2. package/dist/tui.js +61 -36
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -22863,7 +22863,7 @@ function createMainUI() {
22863
22863
  paddingLeft: 1,
22864
22864
  paddingRight: 1,
22865
22865
  paddingTop: 0,
22866
- paddingBottom: 0,
22866
+ paddingBottom: 1,
22867
22867
  backgroundColor: theme.colors.backgroundPanel
22868
22868
  });
22869
22869
  mainContainer.add(inputContainer);
@@ -22878,8 +22878,8 @@ function createMainUI() {
22878
22878
  keyBindings: [
22879
22879
  { name: "return", action: "submit" },
22880
22880
  { name: "linefeed", action: "submit" },
22881
- { name: "return", shift: true, action: "newline" },
22882
- { name: "linefeed", shift: true, action: "newline" },
22881
+ { name: "return", shift: true, action: "submit" },
22882
+ { name: "linefeed", shift: true, action: "submit" },
22883
22883
  { name: "return", meta: true, action: "submit" }
22884
22884
  ],
22885
22885
  onSubmit: () => {
@@ -22890,7 +22890,8 @@ function createMainUI() {
22890
22890
  inputContainer.add(inputField);
22891
22891
  inputHintText = new TextRenderable(renderer, {
22892
22892
  id: "input-hint",
22893
- content: getInputHintContent()
22893
+ content: getInputHintContent(),
22894
+ marginTop: 1
22894
22895
  });
22895
22896
  inputContainer.add(inputHintText);
22896
22897
  helpBarText = new TextRenderable(renderer, {
@@ -22914,13 +22915,13 @@ function getStatusBarContent() {
22914
22915
  function getHelpBarContent() {
22915
22916
  const theme = getTheme();
22916
22917
  if (awaitingConfirmation) {
22917
- return t`${fg(theme.colors.warning)(">>> Press Enter to execute command <<<")} ${fg(theme.colors.textMuted)("|")} ${fg(theme.colors.error)("Esc")}${fg(theme.colors.textMuted)(" Cancel")} ${fg(theme.colors.primary)("e")}${fg(theme.colors.textMuted)(" Edit")} ${fg(theme.colors.primary)("c")}${fg(theme.colors.textMuted)(" Copy")}`;
22918
+ return t`${fg(theme.colors.warning)(">>> Cmd+Enter or Enter to execute <<<")} ${fg(theme.colors.textMuted)("|")} ${fg(theme.colors.error)("Esc")}${fg(theme.colors.textMuted)(" Cancel")} ${fg(theme.colors.primary)("e")}${fg(theme.colors.textMuted)(" Edit")} ${fg(theme.colors.primary)("c")}${fg(theme.colors.textMuted)(" Copy")}`;
22918
22919
  }
22919
22920
  return t`${fg(theme.colors.primary)("Ctrl+X P")}${fg(theme.colors.textMuted)(" Commands")} ${fg(theme.colors.primary)("Ctrl+Y")}${fg(theme.colors.textMuted)(" Safety")} ${fg(theme.colors.primary)("Ctrl+Z")}${fg(theme.colors.textMuted)(" Exit")}`;
22920
22921
  }
22921
22922
  function getInputHintContent() {
22922
22923
  const theme = getTheme();
22923
- return t`${fg(theme.colors.textMuted)("Enter")}${fg(theme.colors.border)(" send")} ${fg(theme.colors.textMuted)("Shift+Enter")}${fg(theme.colors.border)(" newline")} ${fg(theme.colors.textMuted)("!")}${fg(theme.colors.border)(" shell")}`;
22924
+ return t`${fg(theme.colors.primary)("Enter")} ${fg(theme.colors.textMuted)("to send")}`;
22924
22925
  }
22925
22926
  function getWelcomeMessage() {
22926
22927
  const providerName = config.provider === "opencode-zen" ? "OpenCode Zen" : "OpenRouter";
@@ -22965,13 +22966,15 @@ function addAssistantMessage(content, command, safety) {
22965
22966
  renderMessage(msg);
22966
22967
  return msg;
22967
22968
  }
22968
- function addResultMessage(content, exitCode) {
22969
+ function addResultMessage(content, exitCode, executionKind, parentMessageId) {
22969
22970
  const msg = {
22970
22971
  id: generateMessageId(),
22971
22972
  type: "result",
22972
22973
  content,
22973
22974
  timestamp: Date.now(),
22974
- exitCode
22975
+ exitCode,
22976
+ executionKind,
22977
+ parentMessageId
22975
22978
  };
22976
22979
  chatMessages.push(msg);
22977
22980
  renderMessage(msg);
@@ -23016,7 +23019,7 @@ function createAssistantMessageRenderable(msg, theme) {
23016
23019
  width: "100%",
23017
23020
  border: true,
23018
23021
  borderColor: isSelected ? theme.colors.primary : theme.colors.border,
23019
- borderStyle: "single",
23022
+ borderStyle: "rounded",
23020
23023
  paddingLeft: 1,
23021
23024
  paddingRight: 1,
23022
23025
  paddingTop: 0,
@@ -23025,34 +23028,32 @@ function createAssistantMessageRenderable(msg, theme) {
23025
23028
  });
23026
23029
  const commandText = new TextRenderable(renderer, {
23027
23030
  id: `msg-${msg.id}-cmd`,
23028
- content: t`${fg(theme.colors.textMuted)("Command:")} ${fg(theme.colors.text)(msg.command || "")}`
23031
+ content: t`${fg(theme.colors.textMuted)("Command:")} ${fg(theme.colors.secondary)(msg.command || "")}`
23029
23032
  });
23030
23033
  card.add(commandText);
23031
23034
  if (msg.safety) {
23032
23035
  const severityColor = getSeverityColor(msg.safety.severity);
23033
- const severityText = msg.safety.isDangerous ? `${msg.safety.severity.toUpperCase()} risk${msg.safety.reason ? ` - ${msg.safety.reason}` : ""}` : "Low risk";
23036
+ const severityLabel = msg.safety.severity === "low" ? "Low risk" : `${msg.safety.severity[0].toUpperCase()}${msg.safety.severity.slice(1)} risk`;
23034
23037
  const safetyText = new TextRenderable(renderer, {
23035
23038
  id: `msg-${msg.id}-safety`,
23036
- content: t`${fg(severityColor)("●")} ${fg(theme.colors.textMuted)(severityText)}`
23039
+ content: t`${fg(severityColor)(severityLabel)}`
23037
23040
  });
23038
23041
  card.add(safetyText);
23042
+ if (msg.safety.isDangerous && msg.safety.reason) {
23043
+ const reasonText = new TextRenderable(renderer, {
23044
+ id: `msg-${msg.id}-safety-reason`,
23045
+ content: t`${fg(theme.colors.textMuted)(msg.safety.reason)}`
23046
+ });
23047
+ card.add(reasonText);
23048
+ }
23039
23049
  }
23040
23050
  if (isSelected && !msg.executed) {
23041
23051
  const actionsText = new TextRenderable(renderer, {
23042
23052
  id: `msg-${msg.id}-actions`,
23043
- content: t`${fg(theme.colors.warning)("Press Enter to run")} ${fg(theme.colors.textMuted)("|")} ${fg(theme.colors.primary)("[c]")} ${fg(theme.colors.textMuted)("Copy")} ${fg(theme.colors.primary)("[e]")} ${fg(theme.colors.textMuted)("Edit")} ${fg(theme.colors.error)("[Esc]")} ${fg(theme.colors.textMuted)("Cancel")}`
23053
+ content: t`${fg(theme.colors.warning)("Cmd+Enter or Enter to run")} ${fg(theme.colors.textMuted)("|")} ${fg(theme.colors.primary)("[c]")} ${fg(theme.colors.textMuted)("Copy")} ${fg(theme.colors.primary)("[e]")} ${fg(theme.colors.textMuted)("Edit")} ${fg(theme.colors.error)("[Esc]")} ${fg(theme.colors.textMuted)("Cancel")}`
23044
23054
  });
23045
23055
  card.add(actionsText);
23046
23056
  }
23047
- if (msg.executed) {
23048
- const wasAutoRun = !msg.safety?.isDangerous;
23049
- const execLabel = wasAutoRun ? "Auto-executed (safe)" : "Executed";
23050
- const execText = new TextRenderable(renderer, {
23051
- id: `msg-${msg.id}-exec`,
23052
- content: t`${fg(theme.colors.success)("✓")} ${fg(theme.colors.success)(execLabel)}`
23053
- });
23054
- card.add(execText);
23055
- }
23056
23057
  return card;
23057
23058
  }
23058
23059
  function createResultMessageRenderable(msg, theme) {
@@ -23063,28 +23064,30 @@ function createResultMessageRenderable(msg, theme) {
23063
23064
  `) : [];
23064
23065
  const isLongOutput = outputLines.length > 5;
23065
23066
  const PREVIEW_LINES = 3;
23067
+ const executionKind = msg.executionKind || "manual";
23066
23068
  const card = new BoxRenderable(renderer, {
23067
23069
  id: `msg-${msg.id}`,
23068
23070
  flexDirection: "column",
23069
23071
  width: "100%",
23070
23072
  border: true,
23071
- borderColor: isSuccess ? theme.colors.success : theme.colors.error,
23072
- borderStyle: "single",
23073
+ borderColor: theme.colors.border,
23074
+ borderStyle: "rounded",
23073
23075
  paddingLeft: 1,
23074
23076
  paddingRight: 1,
23077
+ paddingTop: 0,
23078
+ paddingBottom: 0,
23075
23079
  backgroundColor: theme.colors.backgroundPanel,
23076
23080
  onMouseDown: isLongOutput ? () => {
23077
23081
  toggleResultExpand(msg.id);
23078
23082
  } : undefined
23079
23083
  });
23080
- const statusIcon = isSuccess ? "✓" : "✗";
23081
23084
  const statusColor = isSuccess ? theme.colors.success : theme.colors.error;
23082
- const statusLabel = isSuccess ? "Executed successfully" : `Exit code: ${msg.exitCode}`;
23085
+ const statusLabel = isSuccess ? executionKind === "auto" ? "Auto-executed (safe command)" : executionKind === "dry-run" ? "Dry run (not executed)" : "Executed (confirmed)" : `Command failed (exit code: ${msg.exitCode})`;
23083
23086
  const expandIcon = isLongOutput ? isExpanded ? "▼" : "▶" : "";
23084
23087
  const lineCount = isLongOutput ? ` (${outputLines.length} lines)` : "";
23085
23088
  const statusText = new TextRenderable(renderer, {
23086
23089
  id: `msg-${msg.id}-status`,
23087
- content: t`${fg(statusColor)(statusIcon)} ${fg(theme.colors.text)(statusLabel)}${fg(theme.colors.textMuted)(lineCount)} ${fg(theme.colors.primary)(expandIcon)}`
23090
+ content: t`${fg(statusColor)(">")} ${fg(statusColor)(statusLabel)}${fg(theme.colors.textMuted)(lineCount)} ${fg(theme.colors.primary)(expandIcon)}`
23088
23091
  });
23089
23092
  card.add(statusText);
23090
23093
  if (hasOutput) {
@@ -23097,11 +23100,26 @@ function createResultMessageRenderable(msg, theme) {
23097
23100
  `) + `
23098
23101
  ... ${outputLines.length - PREVIEW_LINES} more lines`;
23099
23102
  }
23103
+ const outputBox = new BoxRenderable(renderer, {
23104
+ id: `msg-${msg.id}-output-box`,
23105
+ flexDirection: "column",
23106
+ width: "100%",
23107
+ border: true,
23108
+ borderColor: theme.colors.borderSubtle,
23109
+ borderStyle: "single",
23110
+ paddingLeft: 1,
23111
+ paddingRight: 1,
23112
+ paddingTop: 0,
23113
+ paddingBottom: 0,
23114
+ backgroundColor: theme.colors.backgroundElement,
23115
+ marginTop: 1
23116
+ });
23100
23117
  const outputText = new TextRenderable(renderer, {
23101
23118
  id: `msg-${msg.id}-output`,
23102
23119
  content: t`${fg(theme.colors.textMuted)(displayContent)}`
23103
23120
  });
23104
- card.add(outputText);
23121
+ outputBox.add(outputText);
23122
+ card.add(outputBox);
23105
23123
  if (isLongOutput) {
23106
23124
  const hintText = new TextRenderable(renderer, {
23107
23125
  id: `msg-${msg.id}-hint`,
@@ -23275,6 +23293,8 @@ async function processDirectCommand(input, command) {
23275
23293
  }
23276
23294
  }
23277
23295
  async function executeAndShowResult(input, command, assistantMsgId) {
23296
+ const executionKind = getExecutionKind(assistantMsgId, dryRunMode);
23297
+ updateAssistantMessage(assistantMsgId, { executed: true });
23278
23298
  if (command.startsWith("cd ")) {
23279
23299
  const path2 = command.slice(3).trim().replace(/^["']|["']$/g, "");
23280
23300
  try {
@@ -23282,7 +23302,7 @@ async function executeAndShowResult(input, command, assistantMsgId) {
23282
23302
  process.chdir(expandedPath);
23283
23303
  currentCwd = getCwd();
23284
23304
  statusBarText.content = getStatusBarContent();
23285
- addResultMessage(`Changed directory to ${currentCwd}`, 0);
23305
+ addResultMessage(`Changed directory to ${currentCwd}`, 0, executionKind, assistantMsgId);
23286
23306
  addToHistory({
23287
23307
  input,
23288
23308
  command,
@@ -23290,22 +23310,20 @@ async function executeAndShowResult(input, command, assistantMsgId) {
23290
23310
  timestamp: Date.now()
23291
23311
  });
23292
23312
  history = loadHistory();
23293
- updateAssistantMessage(assistantMsgId, { executed: true });
23294
23313
  } catch (err) {
23295
- addResultMessage(`cd: ${err instanceof Error ? err.message : String(err)}`, 1);
23314
+ addResultMessage(`cd: ${err instanceof Error ? err.message : String(err)}`, 1, executionKind, assistantMsgId);
23296
23315
  }
23297
23316
  clearCommandState();
23298
23317
  return;
23299
23318
  }
23300
23319
  if (dryRunMode) {
23301
- addResultMessage(`[DRY RUN] Would execute: ${command}`, 0);
23302
- updateAssistantMessage(assistantMsgId, { executed: true });
23320
+ addResultMessage(`[DRY RUN] Would execute: ${command}`, 0, executionKind, assistantMsgId);
23303
23321
  clearCommandState();
23304
23322
  return;
23305
23323
  }
23306
23324
  try {
23307
23325
  const { output, exitCode } = await executeCommandWithCode(command);
23308
- addResultMessage(output || "Command completed successfully", exitCode);
23326
+ addResultMessage(output || "Command completed successfully", exitCode, executionKind, assistantMsgId);
23309
23327
  addToHistory({
23310
23328
  input,
23311
23329
  command,
@@ -23313,13 +23331,20 @@ async function executeAndShowResult(input, command, assistantMsgId) {
23313
23331
  timestamp: Date.now()
23314
23332
  });
23315
23333
  history = loadHistory();
23316
- updateAssistantMessage(assistantMsgId, { executed: true });
23317
23334
  } catch (error) {
23318
23335
  const message = error instanceof Error ? error.message : String(error);
23319
- addResultMessage(`Error: ${message}`, 1);
23336
+ addResultMessage(`Error: ${message}`, 1, executionKind, assistantMsgId);
23320
23337
  }
23321
23338
  clearCommandState();
23322
23339
  }
23340
+ function getExecutionKind(assistantMsgId, isDryRun) {
23341
+ if (isDryRun)
23342
+ return "dry-run";
23343
+ const assistantMsg = chatMessages.find((msg) => msg.id === assistantMsgId);
23344
+ if (!assistantMsg || assistantMsg.type !== "assistant")
23345
+ return "manual";
23346
+ return assistantMsg.safety?.isDangerous ? "manual" : "auto";
23347
+ }
23323
23348
  function executeCommandWithCode(command) {
23324
23349
  return new Promise((resolve3, reject) => {
23325
23350
  const child = spawn(command, {
package/dist/tui.js CHANGED
@@ -22863,7 +22863,7 @@ function createMainUI() {
22863
22863
  paddingLeft: 1,
22864
22864
  paddingRight: 1,
22865
22865
  paddingTop: 0,
22866
- paddingBottom: 0,
22866
+ paddingBottom: 1,
22867
22867
  backgroundColor: theme.colors.backgroundPanel
22868
22868
  });
22869
22869
  mainContainer.add(inputContainer);
@@ -22878,8 +22878,8 @@ function createMainUI() {
22878
22878
  keyBindings: [
22879
22879
  { name: "return", action: "submit" },
22880
22880
  { name: "linefeed", action: "submit" },
22881
- { name: "return", shift: true, action: "newline" },
22882
- { name: "linefeed", shift: true, action: "newline" },
22881
+ { name: "return", shift: true, action: "submit" },
22882
+ { name: "linefeed", shift: true, action: "submit" },
22883
22883
  { name: "return", meta: true, action: "submit" }
22884
22884
  ],
22885
22885
  onSubmit: () => {
@@ -22890,7 +22890,8 @@ function createMainUI() {
22890
22890
  inputContainer.add(inputField);
22891
22891
  inputHintText = new TextRenderable(renderer, {
22892
22892
  id: "input-hint",
22893
- content: getInputHintContent()
22893
+ content: getInputHintContent(),
22894
+ marginTop: 1
22894
22895
  });
22895
22896
  inputContainer.add(inputHintText);
22896
22897
  helpBarText = new TextRenderable(renderer, {
@@ -22914,13 +22915,13 @@ function getStatusBarContent() {
22914
22915
  function getHelpBarContent() {
22915
22916
  const theme = getTheme();
22916
22917
  if (awaitingConfirmation) {
22917
- return t`${fg(theme.colors.warning)(">>> Press Enter to execute command <<<")} ${fg(theme.colors.textMuted)("|")} ${fg(theme.colors.error)("Esc")}${fg(theme.colors.textMuted)(" Cancel")} ${fg(theme.colors.primary)("e")}${fg(theme.colors.textMuted)(" Edit")} ${fg(theme.colors.primary)("c")}${fg(theme.colors.textMuted)(" Copy")}`;
22918
+ return t`${fg(theme.colors.warning)(">>> Cmd+Enter or Enter to execute <<<")} ${fg(theme.colors.textMuted)("|")} ${fg(theme.colors.error)("Esc")}${fg(theme.colors.textMuted)(" Cancel")} ${fg(theme.colors.primary)("e")}${fg(theme.colors.textMuted)(" Edit")} ${fg(theme.colors.primary)("c")}${fg(theme.colors.textMuted)(" Copy")}`;
22918
22919
  }
22919
22920
  return t`${fg(theme.colors.primary)("Ctrl+X P")}${fg(theme.colors.textMuted)(" Commands")} ${fg(theme.colors.primary)("Ctrl+Y")}${fg(theme.colors.textMuted)(" Safety")} ${fg(theme.colors.primary)("Ctrl+Z")}${fg(theme.colors.textMuted)(" Exit")}`;
22920
22921
  }
22921
22922
  function getInputHintContent() {
22922
22923
  const theme = getTheme();
22923
- return t`${fg(theme.colors.textMuted)("Enter")}${fg(theme.colors.border)(" send")} ${fg(theme.colors.textMuted)("Shift+Enter")}${fg(theme.colors.border)(" newline")} ${fg(theme.colors.textMuted)("!")}${fg(theme.colors.border)(" shell")}`;
22924
+ return t`${fg(theme.colors.primary)("Enter")} ${fg(theme.colors.textMuted)("to send")}`;
22924
22925
  }
22925
22926
  function getWelcomeMessage() {
22926
22927
  const providerName = config.provider === "opencode-zen" ? "OpenCode Zen" : "OpenRouter";
@@ -22965,13 +22966,15 @@ function addAssistantMessage(content, command, safety) {
22965
22966
  renderMessage(msg);
22966
22967
  return msg;
22967
22968
  }
22968
- function addResultMessage(content, exitCode) {
22969
+ function addResultMessage(content, exitCode, executionKind, parentMessageId) {
22969
22970
  const msg = {
22970
22971
  id: generateMessageId(),
22971
22972
  type: "result",
22972
22973
  content,
22973
22974
  timestamp: Date.now(),
22974
- exitCode
22975
+ exitCode,
22976
+ executionKind,
22977
+ parentMessageId
22975
22978
  };
22976
22979
  chatMessages.push(msg);
22977
22980
  renderMessage(msg);
@@ -23016,7 +23019,7 @@ function createAssistantMessageRenderable(msg, theme) {
23016
23019
  width: "100%",
23017
23020
  border: true,
23018
23021
  borderColor: isSelected ? theme.colors.primary : theme.colors.border,
23019
- borderStyle: "single",
23022
+ borderStyle: "rounded",
23020
23023
  paddingLeft: 1,
23021
23024
  paddingRight: 1,
23022
23025
  paddingTop: 0,
@@ -23025,34 +23028,32 @@ function createAssistantMessageRenderable(msg, theme) {
23025
23028
  });
23026
23029
  const commandText = new TextRenderable(renderer, {
23027
23030
  id: `msg-${msg.id}-cmd`,
23028
- content: t`${fg(theme.colors.textMuted)("Command:")} ${fg(theme.colors.text)(msg.command || "")}`
23031
+ content: t`${fg(theme.colors.textMuted)("Command:")} ${fg(theme.colors.secondary)(msg.command || "")}`
23029
23032
  });
23030
23033
  card.add(commandText);
23031
23034
  if (msg.safety) {
23032
23035
  const severityColor = getSeverityColor(msg.safety.severity);
23033
- const severityText = msg.safety.isDangerous ? `${msg.safety.severity.toUpperCase()} risk${msg.safety.reason ? ` - ${msg.safety.reason}` : ""}` : "Low risk";
23036
+ const severityLabel = msg.safety.severity === "low" ? "Low risk" : `${msg.safety.severity[0].toUpperCase()}${msg.safety.severity.slice(1)} risk`;
23034
23037
  const safetyText = new TextRenderable(renderer, {
23035
23038
  id: `msg-${msg.id}-safety`,
23036
- content: t`${fg(severityColor)("●")} ${fg(theme.colors.textMuted)(severityText)}`
23039
+ content: t`${fg(severityColor)(severityLabel)}`
23037
23040
  });
23038
23041
  card.add(safetyText);
23042
+ if (msg.safety.isDangerous && msg.safety.reason) {
23043
+ const reasonText = new TextRenderable(renderer, {
23044
+ id: `msg-${msg.id}-safety-reason`,
23045
+ content: t`${fg(theme.colors.textMuted)(msg.safety.reason)}`
23046
+ });
23047
+ card.add(reasonText);
23048
+ }
23039
23049
  }
23040
23050
  if (isSelected && !msg.executed) {
23041
23051
  const actionsText = new TextRenderable(renderer, {
23042
23052
  id: `msg-${msg.id}-actions`,
23043
- content: t`${fg(theme.colors.warning)("Press Enter to run")} ${fg(theme.colors.textMuted)("|")} ${fg(theme.colors.primary)("[c]")} ${fg(theme.colors.textMuted)("Copy")} ${fg(theme.colors.primary)("[e]")} ${fg(theme.colors.textMuted)("Edit")} ${fg(theme.colors.error)("[Esc]")} ${fg(theme.colors.textMuted)("Cancel")}`
23053
+ content: t`${fg(theme.colors.warning)("Cmd+Enter or Enter to run")} ${fg(theme.colors.textMuted)("|")} ${fg(theme.colors.primary)("[c]")} ${fg(theme.colors.textMuted)("Copy")} ${fg(theme.colors.primary)("[e]")} ${fg(theme.colors.textMuted)("Edit")} ${fg(theme.colors.error)("[Esc]")} ${fg(theme.colors.textMuted)("Cancel")}`
23044
23054
  });
23045
23055
  card.add(actionsText);
23046
23056
  }
23047
- if (msg.executed) {
23048
- const wasAutoRun = !msg.safety?.isDangerous;
23049
- const execLabel = wasAutoRun ? "Auto-executed (safe)" : "Executed";
23050
- const execText = new TextRenderable(renderer, {
23051
- id: `msg-${msg.id}-exec`,
23052
- content: t`${fg(theme.colors.success)("✓")} ${fg(theme.colors.success)(execLabel)}`
23053
- });
23054
- card.add(execText);
23055
- }
23056
23057
  return card;
23057
23058
  }
23058
23059
  function createResultMessageRenderable(msg, theme) {
@@ -23063,28 +23064,30 @@ function createResultMessageRenderable(msg, theme) {
23063
23064
  `) : [];
23064
23065
  const isLongOutput = outputLines.length > 5;
23065
23066
  const PREVIEW_LINES = 3;
23067
+ const executionKind = msg.executionKind || "manual";
23066
23068
  const card = new BoxRenderable(renderer, {
23067
23069
  id: `msg-${msg.id}`,
23068
23070
  flexDirection: "column",
23069
23071
  width: "100%",
23070
23072
  border: true,
23071
- borderColor: isSuccess ? theme.colors.success : theme.colors.error,
23072
- borderStyle: "single",
23073
+ borderColor: theme.colors.border,
23074
+ borderStyle: "rounded",
23073
23075
  paddingLeft: 1,
23074
23076
  paddingRight: 1,
23077
+ paddingTop: 0,
23078
+ paddingBottom: 0,
23075
23079
  backgroundColor: theme.colors.backgroundPanel,
23076
23080
  onMouseDown: isLongOutput ? () => {
23077
23081
  toggleResultExpand(msg.id);
23078
23082
  } : undefined
23079
23083
  });
23080
- const statusIcon = isSuccess ? "✓" : "✗";
23081
23084
  const statusColor = isSuccess ? theme.colors.success : theme.colors.error;
23082
- const statusLabel = isSuccess ? "Executed successfully" : `Exit code: ${msg.exitCode}`;
23085
+ const statusLabel = isSuccess ? executionKind === "auto" ? "Auto-executed (safe command)" : executionKind === "dry-run" ? "Dry run (not executed)" : "Executed (confirmed)" : `Command failed (exit code: ${msg.exitCode})`;
23083
23086
  const expandIcon = isLongOutput ? isExpanded ? "▼" : "▶" : "";
23084
23087
  const lineCount = isLongOutput ? ` (${outputLines.length} lines)` : "";
23085
23088
  const statusText = new TextRenderable(renderer, {
23086
23089
  id: `msg-${msg.id}-status`,
23087
- content: t`${fg(statusColor)(statusIcon)} ${fg(theme.colors.text)(statusLabel)}${fg(theme.colors.textMuted)(lineCount)} ${fg(theme.colors.primary)(expandIcon)}`
23090
+ content: t`${fg(statusColor)(">")} ${fg(statusColor)(statusLabel)}${fg(theme.colors.textMuted)(lineCount)} ${fg(theme.colors.primary)(expandIcon)}`
23088
23091
  });
23089
23092
  card.add(statusText);
23090
23093
  if (hasOutput) {
@@ -23097,11 +23100,26 @@ function createResultMessageRenderable(msg, theme) {
23097
23100
  `) + `
23098
23101
  ... ${outputLines.length - PREVIEW_LINES} more lines`;
23099
23102
  }
23103
+ const outputBox = new BoxRenderable(renderer, {
23104
+ id: `msg-${msg.id}-output-box`,
23105
+ flexDirection: "column",
23106
+ width: "100%",
23107
+ border: true,
23108
+ borderColor: theme.colors.borderSubtle,
23109
+ borderStyle: "single",
23110
+ paddingLeft: 1,
23111
+ paddingRight: 1,
23112
+ paddingTop: 0,
23113
+ paddingBottom: 0,
23114
+ backgroundColor: theme.colors.backgroundElement,
23115
+ marginTop: 1
23116
+ });
23100
23117
  const outputText = new TextRenderable(renderer, {
23101
23118
  id: `msg-${msg.id}-output`,
23102
23119
  content: t`${fg(theme.colors.textMuted)(displayContent)}`
23103
23120
  });
23104
- card.add(outputText);
23121
+ outputBox.add(outputText);
23122
+ card.add(outputBox);
23105
23123
  if (isLongOutput) {
23106
23124
  const hintText = new TextRenderable(renderer, {
23107
23125
  id: `msg-${msg.id}-hint`,
@@ -23275,6 +23293,8 @@ async function processDirectCommand(input, command) {
23275
23293
  }
23276
23294
  }
23277
23295
  async function executeAndShowResult(input, command, assistantMsgId) {
23296
+ const executionKind = getExecutionKind(assistantMsgId, dryRunMode);
23297
+ updateAssistantMessage(assistantMsgId, { executed: true });
23278
23298
  if (command.startsWith("cd ")) {
23279
23299
  const path2 = command.slice(3).trim().replace(/^["']|["']$/g, "");
23280
23300
  try {
@@ -23282,7 +23302,7 @@ async function executeAndShowResult(input, command, assistantMsgId) {
23282
23302
  process.chdir(expandedPath);
23283
23303
  currentCwd = getCwd();
23284
23304
  statusBarText.content = getStatusBarContent();
23285
- addResultMessage(`Changed directory to ${currentCwd}`, 0);
23305
+ addResultMessage(`Changed directory to ${currentCwd}`, 0, executionKind, assistantMsgId);
23286
23306
  addToHistory({
23287
23307
  input,
23288
23308
  command,
@@ -23290,22 +23310,20 @@ async function executeAndShowResult(input, command, assistantMsgId) {
23290
23310
  timestamp: Date.now()
23291
23311
  });
23292
23312
  history = loadHistory();
23293
- updateAssistantMessage(assistantMsgId, { executed: true });
23294
23313
  } catch (err) {
23295
- addResultMessage(`cd: ${err instanceof Error ? err.message : String(err)}`, 1);
23314
+ addResultMessage(`cd: ${err instanceof Error ? err.message : String(err)}`, 1, executionKind, assistantMsgId);
23296
23315
  }
23297
23316
  clearCommandState();
23298
23317
  return;
23299
23318
  }
23300
23319
  if (dryRunMode) {
23301
- addResultMessage(`[DRY RUN] Would execute: ${command}`, 0);
23302
- updateAssistantMessage(assistantMsgId, { executed: true });
23320
+ addResultMessage(`[DRY RUN] Would execute: ${command}`, 0, executionKind, assistantMsgId);
23303
23321
  clearCommandState();
23304
23322
  return;
23305
23323
  }
23306
23324
  try {
23307
23325
  const { output, exitCode } = await executeCommandWithCode(command);
23308
- addResultMessage(output || "Command completed successfully", exitCode);
23326
+ addResultMessage(output || "Command completed successfully", exitCode, executionKind, assistantMsgId);
23309
23327
  addToHistory({
23310
23328
  input,
23311
23329
  command,
@@ -23313,13 +23331,20 @@ async function executeAndShowResult(input, command, assistantMsgId) {
23313
23331
  timestamp: Date.now()
23314
23332
  });
23315
23333
  history = loadHistory();
23316
- updateAssistantMessage(assistantMsgId, { executed: true });
23317
23334
  } catch (error) {
23318
23335
  const message = error instanceof Error ? error.message : String(error);
23319
- addResultMessage(`Error: ${message}`, 1);
23336
+ addResultMessage(`Error: ${message}`, 1, executionKind, assistantMsgId);
23320
23337
  }
23321
23338
  clearCommandState();
23322
23339
  }
23340
+ function getExecutionKind(assistantMsgId, isDryRun) {
23341
+ if (isDryRun)
23342
+ return "dry-run";
23343
+ const assistantMsg = chatMessages.find((msg) => msg.id === assistantMsgId);
23344
+ if (!assistantMsg || assistantMsg.type !== "assistant")
23345
+ return "manual";
23346
+ return assistantMsg.safety?.isDangerous ? "manual" : "auto";
23347
+ }
23323
23348
  function executeCommandWithCode(command) {
23324
23349
  return new Promise((resolve3, reject) => {
23325
23350
  const child = spawn(command, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@austinthesing/magic-shell",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Natural language to terminal commands with safety features. Supports OpenCode Zen (with free models) and OpenRouter.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",