@supatest/cli 0.0.18 → 0.0.20

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/dist/index.js CHANGED
@@ -3899,18 +3899,187 @@ var init_setup = __esm({
3899
3899
  }
3900
3900
  });
3901
3901
 
3902
+ // src/utils/command-discovery.ts
3903
+ import { existsSync, readdirSync, readFileSync, statSync } from "fs";
3904
+ import { join, relative } from "path";
3905
+ function parseMarkdownFrontmatter(content) {
3906
+ const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
3907
+ const match = content.match(frontmatterRegex);
3908
+ if (!match) {
3909
+ return { frontmatter: {}, body: content };
3910
+ }
3911
+ const [, frontmatterStr, body] = match;
3912
+ const frontmatter = {};
3913
+ for (const line of frontmatterStr.split("\n")) {
3914
+ const colonIndex = line.indexOf(":");
3915
+ if (colonIndex > 0) {
3916
+ const key = line.slice(0, colonIndex).trim();
3917
+ const value = line.slice(colonIndex + 1).trim();
3918
+ frontmatter[key] = value;
3919
+ }
3920
+ }
3921
+ return { frontmatter, body };
3922
+ }
3923
+ function discoverMarkdownFiles(dir, baseDir, files = []) {
3924
+ if (!existsSync(dir)) {
3925
+ return files;
3926
+ }
3927
+ const entries = readdirSync(dir);
3928
+ for (const entry of entries) {
3929
+ const fullPath = join(dir, entry);
3930
+ const stat = statSync(fullPath);
3931
+ if (stat.isDirectory()) {
3932
+ discoverMarkdownFiles(fullPath, baseDir, files);
3933
+ } else if (entry.endsWith(".md")) {
3934
+ files.push(fullPath);
3935
+ }
3936
+ }
3937
+ return files;
3938
+ }
3939
+ function discoverCommands(cwd) {
3940
+ const commandsDir = join(cwd, ".supatest", "commands");
3941
+ if (!existsSync(commandsDir)) {
3942
+ return [];
3943
+ }
3944
+ const files = discoverMarkdownFiles(commandsDir, commandsDir);
3945
+ const commands = [];
3946
+ for (const filePath of files) {
3947
+ try {
3948
+ const content = readFileSync(filePath, "utf-8");
3949
+ const { frontmatter } = parseMarkdownFrontmatter(content);
3950
+ const relativePath = relative(commandsDir, filePath);
3951
+ const name = relativePath.replace(/\.md$/, "").replace(/\//g, ".").replace(/\\/g, ".");
3952
+ commands.push({
3953
+ name,
3954
+ description: frontmatter.description,
3955
+ filePath
3956
+ });
3957
+ } catch {
3958
+ }
3959
+ }
3960
+ return commands.sort((a, b2) => a.name.localeCompare(b2.name));
3961
+ }
3962
+ function expandCommand(cwd, commandName, args) {
3963
+ const commandsDir = join(cwd, ".supatest", "commands");
3964
+ const relativePath = commandName.replace(/\./g, "/") + ".md";
3965
+ const filePath = join(commandsDir, relativePath);
3966
+ if (!existsSync(filePath)) {
3967
+ return null;
3968
+ }
3969
+ try {
3970
+ const content = readFileSync(filePath, "utf-8");
3971
+ const { body } = parseMarkdownFrontmatter(content);
3972
+ let expanded = body;
3973
+ if (args) {
3974
+ expanded = expanded.replace(/\$ARGUMENTS/g, args);
3975
+ const argParts = args.split(/\s+/);
3976
+ for (let i = 0; i < argParts.length; i++) {
3977
+ expanded = expanded.replace(new RegExp(`\\$${i + 1}`, "g"), argParts[i]);
3978
+ }
3979
+ } else {
3980
+ expanded = expanded.replace(/\$ARGUMENTS/g, "");
3981
+ }
3982
+ return expanded.trim();
3983
+ } catch {
3984
+ return null;
3985
+ }
3986
+ }
3987
+ function discoverAgents(cwd) {
3988
+ const agentsDir = join(cwd, ".supatest", "agents");
3989
+ if (!existsSync(agentsDir)) {
3990
+ return [];
3991
+ }
3992
+ const files = discoverMarkdownFiles(agentsDir, agentsDir);
3993
+ const agents = [];
3994
+ for (const filePath of files) {
3995
+ try {
3996
+ const content = readFileSync(filePath, "utf-8");
3997
+ const { frontmatter } = parseMarkdownFrontmatter(content);
3998
+ const relativePath = relative(agentsDir, filePath);
3999
+ const defaultName = relativePath.replace(/\.md$/, "").replace(/\//g, "-").replace(/\\/g, "-");
4000
+ agents.push({
4001
+ name: frontmatter.name || defaultName,
4002
+ description: frontmatter.description,
4003
+ model: frontmatter.model,
4004
+ filePath
4005
+ });
4006
+ } catch {
4007
+ }
4008
+ }
4009
+ return agents.sort((a, b2) => a.name.localeCompare(b2.name));
4010
+ }
4011
+ var init_command_discovery = __esm({
4012
+ "src/utils/command-discovery.ts"() {
4013
+ "use strict";
4014
+ }
4015
+ });
4016
+
4017
+ // src/utils/mcp-loader.ts
4018
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
4019
+ import { join as join2 } from "path";
4020
+ function expandEnvVar(value) {
4021
+ return value.replace(/\$\{([^}]+)\}/g, (_2, expr) => {
4022
+ const [varName, defaultValue] = expr.split(":-");
4023
+ return process.env[varName] ?? defaultValue ?? "";
4024
+ });
4025
+ }
4026
+ function expandServerConfig(config2) {
4027
+ const expanded = {
4028
+ command: expandEnvVar(config2.command)
4029
+ };
4030
+ if (config2.args) {
4031
+ expanded.args = config2.args.map(expandEnvVar);
4032
+ }
4033
+ if (config2.env) {
4034
+ expanded.env = {};
4035
+ for (const [key, value] of Object.entries(config2.env)) {
4036
+ expanded.env[key] = expandEnvVar(value);
4037
+ }
4038
+ }
4039
+ return expanded;
4040
+ }
4041
+ function loadMcpServers(cwd) {
4042
+ const mcpPath = join2(cwd, ".supatest", "mcp.json");
4043
+ if (!existsSync2(mcpPath)) {
4044
+ return {};
4045
+ }
4046
+ try {
4047
+ const content = readFileSync2(mcpPath, "utf-8");
4048
+ const config2 = JSON.parse(content);
4049
+ if (!config2.mcpServers) {
4050
+ return {};
4051
+ }
4052
+ const expanded = {};
4053
+ for (const [name, serverConfig] of Object.entries(config2.mcpServers)) {
4054
+ expanded[name] = expandServerConfig(serverConfig);
4055
+ }
4056
+ return expanded;
4057
+ } catch (error) {
4058
+ console.warn(
4059
+ `Warning: Failed to load MCP servers from ${mcpPath}:`,
4060
+ error instanceof Error ? error.message : String(error)
4061
+ );
4062
+ return {};
4063
+ }
4064
+ }
4065
+ var init_mcp_loader = __esm({
4066
+ "src/utils/mcp-loader.ts"() {
4067
+ "use strict";
4068
+ }
4069
+ });
4070
+
3902
4071
  // src/utils/project-instructions.ts
3903
- import { existsSync, readFileSync } from "fs";
3904
- import { join } from "path";
4072
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
4073
+ import { join as join3 } from "path";
3905
4074
  function loadProjectInstructions(cwd) {
3906
4075
  const paths = [
3907
- join(cwd, "SUPATEST.md"),
3908
- join(cwd, ".supatest", "SUPATEST.md")
4076
+ join3(cwd, "SUPATEST.md"),
4077
+ join3(cwd, ".supatest", "SUPATEST.md")
3909
4078
  ];
3910
4079
  for (const path5 of paths) {
3911
- if (existsSync(path5)) {
4080
+ if (existsSync3(path5)) {
3912
4081
  try {
3913
- return readFileSync(path5, "utf-8");
4082
+ return readFileSync3(path5, "utf-8");
3914
4083
  } catch {
3915
4084
  }
3916
4085
  }
@@ -3926,13 +4095,15 @@ var init_project_instructions = __esm({
3926
4095
  // src/core/agent.ts
3927
4096
  import { createRequire } from "module";
3928
4097
  import { homedir } from "os";
3929
- import { dirname, join as join2 } from "path";
4098
+ import { dirname, join as join4 } from "path";
3930
4099
  import { query } from "@anthropic-ai/claude-agent-sdk";
3931
4100
  var CoreAgent;
3932
4101
  var init_agent = __esm({
3933
4102
  "src/core/agent.ts"() {
3934
4103
  "use strict";
3935
4104
  init_config();
4105
+ init_command_discovery();
4106
+ init_mcp_loader();
3936
4107
  init_project_instructions();
3937
4108
  CoreAgent = class {
3938
4109
  presenter;
@@ -3965,13 +4136,29 @@ ${config2.logs}
3965
4136
  const isPlanMode = config2.mode === "plan";
3966
4137
  const cwd = config2.cwd || process.cwd();
3967
4138
  const projectInstructions = loadProjectInstructions(cwd);
4139
+ const customAgents = discoverAgents(cwd);
4140
+ let customAgentsPrompt;
4141
+ if (customAgents.length > 0) {
4142
+ const agentList = customAgents.map((agent) => {
4143
+ const modelInfo = agent.model ? ` (model: ${agent.model})` : "";
4144
+ return `- **${agent.name}**${modelInfo}: ${agent.description || "No description"}`;
4145
+ }).join("\n");
4146
+ customAgentsPrompt = `
4147
+
4148
+ # Custom Sub-Agents (from .supatest/agents/)
4149
+
4150
+ The following custom sub-agents are available via the Task tool. Use them by setting subagent_type to the agent name:
4151
+
4152
+ ${agentList}`;
4153
+ }
3968
4154
  const systemPromptAppend = [
3969
4155
  config2.systemPromptAppend,
3970
4156
  projectInstructions && `
3971
4157
 
3972
4158
  # Project Instructions (from SUPATEST.md)
3973
4159
 
3974
- ${projectInstructions}`
4160
+ ${projectInstructions}`,
4161
+ customAgentsPrompt
3975
4162
  ].filter(Boolean).join("\n") || void 0;
3976
4163
  const cleanEnv = {};
3977
4164
  const excludeKeys = /* @__PURE__ */ new Set([
@@ -3986,8 +4173,8 @@ ${projectInstructions}`
3986
4173
  cleanEnv[key] = value;
3987
4174
  }
3988
4175
  }
3989
- const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || join2(homedir(), ".supatest", "claude-config");
3990
- cleanEnv.CLAUDE_CONFIG_DIR = claudeConfigDir;
4176
+ const internalConfigDir = join4(homedir(), ".supatest", "claude-internal");
4177
+ cleanEnv.CLAUDE_CONFIG_DIR = internalConfigDir;
3991
4178
  cleanEnv.ANTHROPIC_API_KEY = config2.supatestApiKey || "";
3992
4179
  cleanEnv.ANTHROPIC_BASE_URL = process.env.ANTHROPIC_BASE_URL || "";
3993
4180
  cleanEnv.ANTHROPIC_AUTH_TOKEN = "";
@@ -4005,12 +4192,21 @@ ${projectInstructions}`
4005
4192
  includePartialMessages: true,
4006
4193
  executable: "node",
4007
4194
  // MCP servers for enhanced capabilities
4008
- mcpServers: {
4009
- playwright: {
4010
- command: "npx",
4011
- args: ["-y", "@playwright/mcp@latest"]
4012
- }
4013
- },
4195
+ // User-defined servers from .supatest/mcp.json can override defaults
4196
+ mcpServers: (() => {
4197
+ const userServers = loadMcpServers(cwd);
4198
+ const allServers = {
4199
+ // Default Playwright MCP server for browser automation
4200
+ playwright: {
4201
+ command: "npx",
4202
+ args: ["-y", "@playwright/mcp@latest"]
4203
+ },
4204
+ // User-defined servers override defaults (spread after)
4205
+ ...userServers
4206
+ };
4207
+ this.presenter.onLog(`MCP servers loaded: ${Object.keys(allServers).join(", ")}`);
4208
+ return allServers;
4209
+ })(),
4014
4210
  // Resume from previous session if providerSessionId is provided
4015
4211
  // This allows the agent to continue conversations with full context
4016
4212
  // Note: Sessions expire after ~30 days due to Anthropic's data retention policy
@@ -4198,7 +4394,7 @@ ${projectInstructions}`
4198
4394
  async resolveClaudeCodePath() {
4199
4395
  const fs4 = await import("fs/promises");
4200
4396
  let claudeCodePath;
4201
- const bundledPath = join2(dirname(import.meta.url.replace("file://", "")), "claude-code-cli.js");
4397
+ const bundledPath = join4(dirname(import.meta.url.replace("file://", "")), "claude-code-cli.js");
4202
4398
  try {
4203
4399
  await fs4.access(bundledPath);
4204
4400
  claudeCodePath = bundledPath;
@@ -4206,7 +4402,7 @@ ${projectInstructions}`
4206
4402
  } catch {
4207
4403
  const require2 = createRequire(import.meta.url);
4208
4404
  const sdkPath = require2.resolve("@anthropic-ai/claude-agent-sdk/sdk.mjs");
4209
- claudeCodePath = join2(dirname(sdkPath), "cli.js");
4405
+ claudeCodePath = join4(dirname(sdkPath), "cli.js");
4210
4406
  this.presenter.onLog(`Development mode: ${claudeCodePath}`);
4211
4407
  }
4212
4408
  if (config.claudeCodeExecutablePath) {
@@ -4745,7 +4941,7 @@ var CLI_VERSION;
4745
4941
  var init_version = __esm({
4746
4942
  "src/version.ts"() {
4747
4943
  "use strict";
4748
- CLI_VERSION = "0.0.18";
4944
+ CLI_VERSION = "0.0.20";
4749
4945
  }
4750
4946
  });
4751
4947
 
@@ -4819,21 +5015,21 @@ var init_encryption = __esm({
4819
5015
  });
4820
5016
 
4821
5017
  // src/utils/token-storage.ts
4822
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
5018
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, unlinkSync, writeFileSync } from "fs";
4823
5019
  import { homedir as homedir2 } from "os";
4824
- import { join as join4 } from "path";
5020
+ import { join as join6 } from "path";
4825
5021
  function getTokenFilePath() {
4826
5022
  const apiUrl = process.env.SUPATEST_API_URL || PRODUCTION_API_URL;
4827
5023
  if (apiUrl === PRODUCTION_API_URL) {
4828
- return join4(CONFIG_DIR, "token.json");
5024
+ return join6(CONFIG_DIR, "token.json");
4829
5025
  }
4830
- return join4(CONFIG_DIR, "token.local.json");
5026
+ return join6(CONFIG_DIR, "token.local.json");
4831
5027
  }
4832
5028
  function isV2Format(stored) {
4833
5029
  return "version" in stored && stored.version === 2;
4834
5030
  }
4835
5031
  function ensureConfigDir() {
4836
- if (!existsSync2(CONFIG_DIR)) {
5032
+ if (!existsSync4(CONFIG_DIR)) {
4837
5033
  mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
4838
5034
  }
4839
5035
  }
@@ -4853,11 +5049,11 @@ function saveToken(token, expiresAt) {
4853
5049
  }
4854
5050
  function loadToken() {
4855
5051
  const tokenFile = getTokenFilePath();
4856
- if (!existsSync2(tokenFile)) {
5052
+ if (!existsSync4(tokenFile)) {
4857
5053
  return null;
4858
5054
  }
4859
5055
  try {
4860
- const data = readFileSync2(tokenFile, "utf8");
5056
+ const data = readFileSync4(tokenFile, "utf8");
4861
5057
  const stored = JSON.parse(data);
4862
5058
  let payload;
4863
5059
  if (isV2Format(stored)) {
@@ -4886,7 +5082,7 @@ function loadToken() {
4886
5082
  }
4887
5083
  function removeToken() {
4888
5084
  const tokenFile = getTokenFilePath();
4889
- if (existsSync2(tokenFile)) {
5085
+ if (existsSync4(tokenFile)) {
4890
5086
  unlinkSync(tokenFile);
4891
5087
  }
4892
5088
  }
@@ -4895,10 +5091,10 @@ var init_token_storage = __esm({
4895
5091
  "src/utils/token-storage.ts"() {
4896
5092
  "use strict";
4897
5093
  init_encryption();
4898
- CONFIG_DIR = join4(homedir2(), ".supatest");
5094
+ CONFIG_DIR = join6(homedir2(), ".supatest");
4899
5095
  PRODUCTION_API_URL = "https://code-api.supatest.ai";
4900
5096
  STORAGE_VERSION = 2;
4901
- TOKEN_FILE = join4(CONFIG_DIR, "token.json");
5097
+ TOKEN_FILE = join6(CONFIG_DIR, "token.json");
4902
5098
  }
4903
5099
  });
4904
5100
 
@@ -5056,6 +5252,10 @@ var init_react = __esm({
5056
5252
  todos
5057
5253
  });
5058
5254
  }
5255
+ } else if (tool === "ExitPlanMode") {
5256
+ this.callbacks.onExitPlanMode?.();
5257
+ } else if (tool === "EnterPlanMode") {
5258
+ this.callbacks.onEnterPlanMode?.();
5059
5259
  }
5060
5260
  const toolUseEvent = {
5061
5261
  type: "tool_use",
@@ -6562,13 +6762,20 @@ var init_FeedbackDialog = __esm({
6562
6762
 
6563
6763
  // src/ui/components/HelpMenu.tsx
6564
6764
  import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
6565
- import React5 from "react";
6765
+ import React5, { useEffect as useEffect4, useState as useState3 } from "react";
6566
6766
  var HelpMenu;
6567
6767
  var init_HelpMenu = __esm({
6568
6768
  "src/ui/components/HelpMenu.tsx"() {
6569
6769
  "use strict";
6770
+ init_command_discovery();
6570
6771
  init_theme();
6571
- HelpMenu = ({ isAuthenticated, onClose }) => {
6772
+ HelpMenu = ({ isAuthenticated, onClose, cwd }) => {
6773
+ const [customCommands, setCustomCommands] = useState3([]);
6774
+ useEffect4(() => {
6775
+ const projectDir = cwd || process.cwd();
6776
+ const commands = discoverCommands(projectDir);
6777
+ setCustomCommands(commands);
6778
+ }, [cwd]);
6572
6779
  useInput2((input, key) => {
6573
6780
  if (key.escape || input === "q" || input === "?" || key.ctrl && input === "h") {
6574
6781
  onClose();
@@ -6587,15 +6794,16 @@ var init_HelpMenu = __esm({
6587
6794
  /* @__PURE__ */ React5.createElement(Box4, { marginTop: 1 }),
6588
6795
  /* @__PURE__ */ React5.createElement(Text4, { bold: true, color: theme.text.secondary }, "Slash Commands:"),
6589
6796
  /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column", marginLeft: 2, marginTop: 0 }, /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "/help"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " or "), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "/?"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Toggle this help menu")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "/resume"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Resume a previous session")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "/clear"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Clear message history")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "/model"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Cycle through available models")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "/setup"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Initial setup for Supatest CLI")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "/feedback"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Report an issue or request a feature")), isAuthenticated ? /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "/logout"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Log out of Supatest")) : /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "/login"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Authenticate with Supatest")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "/exit"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Exit the CLI"))),
6797
+ customCommands.length > 0 && /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Box4, { marginTop: 1 }), /* @__PURE__ */ React5.createElement(Text4, { bold: true, color: theme.text.secondary }, "Project Commands:"), /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column", marginLeft: 2, marginTop: 0 }, customCommands.slice(0, 5).map((cmd) => /* @__PURE__ */ React5.createElement(Text4, { key: cmd.name }, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "/", cmd.name), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, cmd.description ? ` - ${cmd.description}` : ""))), customCommands.length > 5 && /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, "...and ", customCommands.length - 5, " more (use Tab to autocomplete)"))),
6590
6798
  /* @__PURE__ */ React5.createElement(Box4, { marginTop: 1 }),
6591
6799
  /* @__PURE__ */ React5.createElement(Text4, { bold: true, color: theme.text.secondary }, "Keyboard Shortcuts:"),
6592
- /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column", marginLeft: 2, marginTop: 0 }, /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "?"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " ", "- Toggle help (when input is empty)")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "Ctrl+H"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Toggle help")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "Ctrl+C"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " ", "- Exit (or clear input if not empty)")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "Ctrl+D"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Exit immediately")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "Ctrl+L"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Clear terminal screen")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "Ctrl+U"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Clear current input line")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "ESC"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Interrupt running agent")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "Shift+Up/Down"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Scroll through messages")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "Shift+Enter"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Add new line in input")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "ctrl+o"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Toggle tool outputs"))),
6800
+ /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column", marginLeft: 2, marginTop: 0 }, /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "?"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " ", "- Toggle help (when input is empty)")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "Ctrl+H"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Toggle help")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "Ctrl+C"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " ", "- Exit (or clear input if not empty)")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "Ctrl+D"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Exit immediately")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "Ctrl+L"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Clear terminal screen")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "Ctrl+U"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Clear current input line")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "ESC"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Interrupt running agent")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "Shift+Enter"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Add new line in input")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "ctrl+o"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Toggle tool outputs")), /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "Ctrl+M"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " - Cycle through models"))),
6593
6801
  /* @__PURE__ */ React5.createElement(Box4, { marginTop: 1 }),
6594
6802
  /* @__PURE__ */ React5.createElement(Text4, { bold: true, color: theme.text.secondary }, "File References:"),
6595
6803
  /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column", marginLeft: 2, marginTop: 0 }, /* @__PURE__ */ React5.createElement(Text4, null, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.accent }, "@filename"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, " ", "- Reference a file (autocomplete with Tab)")), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, 'Example: "Fix the bug in @src/app.ts"')),
6596
6804
  /* @__PURE__ */ React5.createElement(Box4, { marginTop: 1 }),
6597
6805
  /* @__PURE__ */ React5.createElement(Text4, { bold: true, color: theme.text.secondary }, "Tips:"),
6598
- /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column", marginLeft: 2, marginTop: 0 }, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, "\u2022 Press Enter to submit your task"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, "\u2022 Use Shift+Enter to write multi-line prompts"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, "\u2022 Drag and drop files into the terminal to add file paths"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, "\u2022 The agent will automatically run tools and fix issues")),
6806
+ /* @__PURE__ */ React5.createElement(Box4, { flexDirection: "column", marginLeft: 2, marginTop: 0 }, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, "\u2022 Press Enter to submit your task"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, "\u2022 Use Shift+Enter to write multi-line prompts"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, "\u2022 Drag and drop files into the terminal to add file paths"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, "\u2022 The agent will automatically run tools and fix issues"), /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, "\u2022 Use Ctrl+L to clear the terminal screen without clearing the messages history")),
6599
6807
  /* @__PURE__ */ React5.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text4, { color: theme.text.dim }, "Press ", /* @__PURE__ */ React5.createElement(Text4, { bold: true }, "ESC"), " or ", /* @__PURE__ */ React5.createElement(Text4, { bold: true }, "?"), " to close"))
6600
6808
  );
6601
6809
  };
@@ -6603,7 +6811,7 @@ var init_HelpMenu = __esm({
6603
6811
  });
6604
6812
 
6605
6813
  // src/ui/contexts/SessionContext.tsx
6606
- import React6, { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useState as useState3 } from "react";
6814
+ import React6, { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useState as useState4 } from "react";
6607
6815
  var SessionContext, SessionProvider, useSession;
6608
6816
  var init_SessionContext = __esm({
6609
6817
  "src/ui/contexts/SessionContext.tsx"() {
@@ -6614,24 +6822,24 @@ var init_SessionContext = __esm({
6614
6822
  children,
6615
6823
  initialModel
6616
6824
  }) => {
6617
- const [messages, setMessages] = useState3([]);
6618
- const [todos, setTodos] = useState3([]);
6619
- const [stats, setStats] = useState3({
6825
+ const [messages, setMessages] = useState4([]);
6826
+ const [todos, setTodos] = useState4([]);
6827
+ const [stats, setStats] = useState4({
6620
6828
  filesModified: /* @__PURE__ */ new Set(),
6621
6829
  commandsRun: [],
6622
6830
  iterations: 0,
6623
6831
  startTime: Date.now()
6624
6832
  });
6625
- const [isAgentRunning, setIsAgentRunning] = useState3(false);
6626
- const [shouldInterruptAgent, setShouldInterruptAgent] = useState3(false);
6627
- const [usageStats, setUsageStats] = useState3(null);
6628
- const [sessionId, setSessionId] = useState3();
6629
- const [webUrl, setWebUrl] = useState3();
6630
- const [agentMode, setAgentMode] = useState3("build");
6631
- const [planFilePath, setPlanFilePath] = useState3();
6632
- const [selectedModel, setSelectedModel] = useState3(initialModel || Mt);
6633
- const [allToolsExpanded, setAllToolsExpanded] = useState3(true);
6634
- const [staticRemountKey, setStaticRemountKey] = useState3(0);
6833
+ const [isAgentRunning, setIsAgentRunning] = useState4(false);
6834
+ const [shouldInterruptAgent, setShouldInterruptAgent] = useState4(false);
6835
+ const [usageStats, setUsageStats] = useState4(null);
6836
+ const [sessionId, setSessionId] = useState4();
6837
+ const [webUrl, setWebUrl] = useState4();
6838
+ const [agentMode, setAgentMode] = useState4("build");
6839
+ const [planFilePath, setPlanFilePath] = useState4();
6840
+ const [selectedModel, setSelectedModel] = useState4(initialModel || Mt);
6841
+ const [allToolsExpanded, setAllToolsExpanded] = useState4(true);
6842
+ const [staticRemountKey, setStaticRemountKey] = useState4(0);
6635
6843
  const addMessage = useCallback2(
6636
6844
  (message) => {
6637
6845
  const expandableTools = ["Bash", "BashOutput", "Command Output"];
@@ -6838,7 +7046,7 @@ var init_file_completion = __esm({
6838
7046
 
6839
7047
  // src/ui/components/ModelSelector.tsx
6840
7048
  import { Box as Box5, Text as Text5, useInput as useInput3 } from "ink";
6841
- import React7, { useState as useState4 } from "react";
7049
+ import React7, { useState as useState5 } from "react";
6842
7050
  function getNextModel(currentModel) {
6843
7051
  const currentIndex = ke.findIndex((m2) => m2.id === currentModel);
6844
7052
  const nextIndex = (currentIndex + 1) % ke.length;
@@ -6861,7 +7069,7 @@ var init_ModelSelector = __esm({
6861
7069
  onCancel
6862
7070
  }) => {
6863
7071
  const currentIndex = ke.findIndex((m2) => m2.id === currentModel);
6864
- const [selectedIndex, setSelectedIndex] = useState4(currentIndex >= 0 ? currentIndex : 0);
7072
+ const [selectedIndex, setSelectedIndex] = useState5(currentIndex >= 0 ? currentIndex : 0);
6865
7073
  useInput3((input, key) => {
6866
7074
  if (key.upArrow) {
6867
7075
  setSelectedIndex((prev) => prev > 0 ? prev - 1 : ke.length - 1);
@@ -6903,12 +7111,13 @@ var init_ModelSelector = __esm({
6903
7111
  import path4 from "path";
6904
7112
  import chalk5 from "chalk";
6905
7113
  import { Box as Box6, Text as Text6 } from "ink";
6906
- import React8, { forwardRef, useEffect as useEffect4, useImperativeHandle, useState as useState5 } from "react";
7114
+ import React8, { forwardRef, useEffect as useEffect5, useImperativeHandle, useState as useState6 } from "react";
6907
7115
  var InputPrompt;
6908
7116
  var init_InputPrompt = __esm({
6909
7117
  "src/ui/components/InputPrompt.tsx"() {
6910
7118
  "use strict";
6911
7119
  init_shared_es();
7120
+ init_command_discovery();
6912
7121
  init_SessionContext();
6913
7122
  init_useKeypress();
6914
7123
  init_file_completion();
@@ -6919,19 +7128,20 @@ var init_InputPrompt = __esm({
6919
7128
  placeholder = "Enter your task (press Enter to submit, Shift+Enter for new line)...",
6920
7129
  disabled = false,
6921
7130
  onHelpToggle,
7131
+ cwd,
6922
7132
  currentFolder,
6923
7133
  gitBranch,
6924
7134
  onInputChange
6925
7135
  }, ref) => {
6926
7136
  const { messages, agentMode, selectedModel, setSelectedModel, isAgentRunning, usageStats } = useSession();
6927
- const [value, setValue] = useState5("");
6928
- const [cursorOffset, setCursorOffset] = useState5(0);
6929
- const [allFiles, setAllFiles] = useState5([]);
6930
- const [suggestions, setSuggestions] = useState5([]);
6931
- const [activeSuggestion, setActiveSuggestion] = useState5(0);
6932
- const [showSuggestions, setShowSuggestions] = useState5(false);
6933
- const [mentionStartIndex, setMentionStartIndex] = useState5(-1);
6934
- const SLASH_COMMANDS = [
7137
+ const [value, setValue] = useState6("");
7138
+ const [cursorOffset, setCursorOffset] = useState6(0);
7139
+ const [allFiles, setAllFiles] = useState6([]);
7140
+ const [suggestions, setSuggestions] = useState6([]);
7141
+ const [activeSuggestion, setActiveSuggestion] = useState6(0);
7142
+ const [showSuggestions, setShowSuggestions] = useState6(false);
7143
+ const [mentionStartIndex, setMentionStartIndex] = useState6(-1);
7144
+ const BUILTIN_SLASH_COMMANDS = [
6935
7145
  { name: "/help", desc: "Show help" },
6936
7146
  { name: "/resume", desc: "Resume session" },
6937
7147
  { name: "/clear", desc: "Clear history" },
@@ -6942,7 +7152,21 @@ var init_InputPrompt = __esm({
6942
7152
  { name: "/logout", desc: "Log out" },
6943
7153
  { name: "/exit", desc: "Exit CLI" }
6944
7154
  ];
6945
- const [isSlashCommand, setIsSlashCommand] = useState5(false);
7155
+ const [customCommands, setCustomCommands] = useState6([]);
7156
+ const [isSlashCommand, setIsSlashCommand] = useState6(false);
7157
+ useEffect5(() => {
7158
+ try {
7159
+ const projectDir = cwd || process.cwd();
7160
+ const discovered = discoverCommands(projectDir);
7161
+ const formatted = discovered.map((cmd) => ({
7162
+ name: `/${cmd.name}`,
7163
+ desc: cmd.description || ""
7164
+ }));
7165
+ setCustomCommands(formatted);
7166
+ } catch {
7167
+ }
7168
+ }, [cwd]);
7169
+ const allSlashCommands = [...BUILTIN_SLASH_COMMANDS, ...customCommands];
6946
7170
  useImperativeHandle(ref, () => ({
6947
7171
  clear: () => {
6948
7172
  setValue("");
@@ -6951,7 +7175,7 @@ var init_InputPrompt = __esm({
6951
7175
  onInputChange?.("");
6952
7176
  }
6953
7177
  }));
6954
- useEffect4(() => {
7178
+ useEffect5(() => {
6955
7179
  setTimeout(() => {
6956
7180
  try {
6957
7181
  const files = getFiles();
@@ -6969,7 +7193,18 @@ var init_InputPrompt = __esm({
6969
7193
  const checkSuggestions = (text, cursor) => {
6970
7194
  if (text.startsWith("/") && cursor <= text.length && !text.includes(" ", 1)) {
6971
7195
  const query2 = text.slice(1);
6972
- const matches = SLASH_COMMANDS.filter((cmd) => cmd.name.slice(1).startsWith(query2.toLowerCase())).map((cmd) => `${cmd.name} ${cmd.desc}`);
7196
+ const builtinMatches = BUILTIN_SLASH_COMMANDS.filter((cmd) => cmd.name.slice(1).toLowerCase().startsWith(query2.toLowerCase())).map((cmd) => cmd.desc ? `${cmd.name} ${cmd.desc}` : cmd.name);
7197
+ const customMatches = customCommands.filter((cmd) => cmd.name.slice(1).toLowerCase().startsWith(query2.toLowerCase())).map((cmd) => cmd.desc ? `${cmd.name} ${cmd.desc}` : cmd.name);
7198
+ const matches = [];
7199
+ if (builtinMatches.length > 0) {
7200
+ matches.push(...builtinMatches);
7201
+ }
7202
+ if (customMatches.length > 0) {
7203
+ if (builtinMatches.length > 0) {
7204
+ matches.push("\u2500\u2500\u2500\u2500\u2500 custom commands \u2500\u2500\u2500\u2500\u2500");
7205
+ }
7206
+ matches.push(...customMatches);
7207
+ }
6973
7208
  if (matches.length > 0) {
6974
7209
  setSuggestions(matches);
6975
7210
  setShowSuggestions(true);
@@ -7035,8 +7270,8 @@ var init_InputPrompt = __esm({
7035
7270
  cleanPath = cleanPath.replace(/\\ /g, " ");
7036
7271
  if (path4.isAbsolute(cleanPath)) {
7037
7272
  try {
7038
- const cwd = process.cwd();
7039
- const rel = path4.relative(cwd, cleanPath);
7273
+ const cwd2 = process.cwd();
7274
+ const rel = path4.relative(cwd2, cleanPath);
7040
7275
  if (!rel.startsWith("..") && !path4.isAbsolute(rel)) {
7041
7276
  cleanPath = rel;
7042
7277
  }
@@ -7055,20 +7290,31 @@ var init_InputPrompt = __esm({
7055
7290
  return;
7056
7291
  }
7057
7292
  if (showSuggestions && !key.shift) {
7293
+ const isSeparator = (idx) => suggestions[idx]?.startsWith("\u2500\u2500\u2500\u2500\u2500");
7058
7294
  if (key.name === "up") {
7059
- setActiveSuggestion(
7060
- (prev) => prev > 0 ? prev - 1 : suggestions.length - 1
7061
- );
7295
+ setActiveSuggestion((prev) => {
7296
+ let next = prev > 0 ? prev - 1 : suggestions.length - 1;
7297
+ while (isSeparator(next) && next !== prev) {
7298
+ next = next > 0 ? next - 1 : suggestions.length - 1;
7299
+ }
7300
+ return next;
7301
+ });
7062
7302
  return;
7063
7303
  }
7064
7304
  if (key.name === "down") {
7065
- setActiveSuggestion(
7066
- (prev) => prev < suggestions.length - 1 ? prev + 1 : 0
7067
- );
7305
+ setActiveSuggestion((prev) => {
7306
+ let next = prev < suggestions.length - 1 ? prev + 1 : 0;
7307
+ while (isSeparator(next) && next !== prev) {
7308
+ next = next < suggestions.length - 1 ? next + 1 : 0;
7309
+ }
7310
+ return next;
7311
+ });
7068
7312
  return;
7069
7313
  }
7070
7314
  if (key.name === "tab" || key.name === "return") {
7071
- completeSuggestion(key.name === "return");
7315
+ if (!isSeparator(activeSuggestion)) {
7316
+ completeSuggestion(key.name === "return");
7317
+ }
7072
7318
  return;
7073
7319
  }
7074
7320
  if (key.name === "escape") {
@@ -7100,14 +7346,13 @@ var init_InputPrompt = __esm({
7100
7346
  setCursorOffset(Math.min(value.length, cursorOffset + 1));
7101
7347
  } else if (key.ctrl && input === "u") {
7102
7348
  updateValue("", 0);
7103
- } else if (key.name === "tab" && !showSuggestions) {
7104
- if (value.length === 0 && !isAgentRunning) {
7105
- if (key.shift) {
7106
- setSelectedModel(getPreviousModel(selectedModel));
7107
- } else {
7108
- setSelectedModel(getNextModel(selectedModel));
7109
- }
7349
+ } else if (key.ctrl && key.name === "m" && !isAgentRunning) {
7350
+ if (key.shift) {
7351
+ setSelectedModel(getPreviousModel(selectedModel));
7352
+ } else {
7353
+ setSelectedModel(getNextModel(selectedModel));
7110
7354
  }
7355
+ } else if (key.name === "tab" && !showSuggestions) {
7111
7356
  } else if (key.paste) {
7112
7357
  const newValue = value.slice(0, cursorOffset) + input + value.slice(cursorOffset);
7113
7358
  updateValue(newValue, cursorOffset + input.length);
@@ -7141,7 +7386,13 @@ var init_InputPrompt = __esm({
7141
7386
  marginBottom: 0,
7142
7387
  paddingX: 1
7143
7388
  },
7144
- suggestions.map((file, idx) => /* @__PURE__ */ React8.createElement(Text6, { color: idx === activeSuggestion ? theme.text.accent : theme.text.dim, key: file }, idx === activeSuggestion ? "\u276F " : " ", " ", file))
7389
+ suggestions.map((item, idx) => {
7390
+ const isSeparator = item.startsWith("\u2500\u2500\u2500\u2500\u2500");
7391
+ if (isSeparator) {
7392
+ return /* @__PURE__ */ React8.createElement(Text6, { color: theme.text.dim, key: item }, " ", item);
7393
+ }
7394
+ return /* @__PURE__ */ React8.createElement(Text6, { color: idx === activeSuggestion ? theme.text.accent : theme.text.dim, key: item }, idx === activeSuggestion ? "\u276F " : " ", item);
7395
+ })
7145
7396
  ), /* @__PURE__ */ React8.createElement(
7146
7397
  Box6,
7147
7398
  {
@@ -7162,7 +7413,7 @@ var init_InputPrompt = __esm({
7162
7413
  }
7163
7414
  return /* @__PURE__ */ React8.createElement(Text6, { color: theme.text.primary, key: idx }, line);
7164
7415
  })), !hasContent && disabled && /* @__PURE__ */ React8.createElement(Text6, { color: theme.text.dim, italic: true }, "Waiting for agent to complete...")))
7165
- ), /* @__PURE__ */ React8.createElement(Box6, { justifyContent: "space-between", paddingX: 1 }, /* @__PURE__ */ React8.createElement(Box6, { gap: 2 }, /* @__PURE__ */ React8.createElement(Box6, null, /* @__PURE__ */ React8.createElement(Text6, { color: agentMode === "plan" ? theme.status.inProgress : theme.text.dim }, agentMode === "plan" ? "\u23F8 plan" : "\u25B6 build"), /* @__PURE__ */ React8.createElement(Text6, { color: theme.text.dim }, " (shift+tab)")), /* @__PURE__ */ React8.createElement(Box6, null, /* @__PURE__ */ React8.createElement(Text6, { color: theme.text.dim }, "model:"), /* @__PURE__ */ React8.createElement(Text6, { color: theme.text.info }, St(selectedModel)), /* @__PURE__ */ React8.createElement(Text6, { color: theme.text.dim }, " (tab)"))), /* @__PURE__ */ React8.createElement(Box6, null, /* @__PURE__ */ React8.createElement(Text6, { color: usageStats && usageStats.contextPct >= 90 ? theme.text.error : usageStats && usageStats.contextPct >= 75 ? theme.text.warning : theme.text.dim }, usageStats?.contextPct ?? 0, "% context used"), /* @__PURE__ */ React8.createElement(Text6, { color: theme.text.dim }, " ", "(", usageStats ? usageStats.inputTokens >= 1e3 ? `${(usageStats.inputTokens / 1e3).toFixed(1)}K` : usageStats.inputTokens : 0, " / ", usageStats ? usageStats.contextWindow >= 1e3 ? `${(usageStats.contextWindow / 1e3).toFixed(0)}K` : usageStats.contextWindow : "200K", ")"))));
7416
+ ), /* @__PURE__ */ React8.createElement(Box6, { justifyContent: "space-between", paddingX: 1 }, /* @__PURE__ */ React8.createElement(Box6, { gap: 2 }, /* @__PURE__ */ React8.createElement(Box6, null, /* @__PURE__ */ React8.createElement(Text6, { color: agentMode === "plan" ? theme.status.inProgress : theme.text.dim }, agentMode === "plan" ? "\u23F8 plan" : "\u25B6 build"), /* @__PURE__ */ React8.createElement(Text6, { color: theme.text.dim }, " (shift+tab)")), /* @__PURE__ */ React8.createElement(Box6, null, /* @__PURE__ */ React8.createElement(Text6, { color: theme.text.dim }, "model:"), /* @__PURE__ */ React8.createElement(Text6, { color: theme.text.info }, St(selectedModel)), /* @__PURE__ */ React8.createElement(Text6, { color: theme.text.dim }, " (ctrl+m)"))), /* @__PURE__ */ React8.createElement(Box6, null, /* @__PURE__ */ React8.createElement(Text6, { color: usageStats && usageStats.contextPct >= 90 ? theme.text.error : usageStats && usageStats.contextPct >= 75 ? theme.text.warning : theme.text.dim }, usageStats?.contextPct ?? 0, "% context used"), /* @__PURE__ */ React8.createElement(Text6, { color: theme.text.dim }, " ", "(", usageStats ? usageStats.inputTokens >= 1e3 ? `${(usageStats.inputTokens / 1e3).toFixed(1)}K` : usageStats.inputTokens : 0, " / ", usageStats ? usageStats.contextWindow >= 1e3 ? `${(usageStats.contextWindow / 1e3).toFixed(0)}K` : usageStats.contextWindow : "200K", ")"))));
7166
7417
  });
7167
7418
  InputPrompt.displayName = "InputPrompt";
7168
7419
  }
@@ -7212,7 +7463,7 @@ var init_Header = __esm({
7212
7463
  import chalk6 from "chalk";
7213
7464
  import { Box as Box8, Text as Text8 } from "ink";
7214
7465
  import { all, createLowlight } from "lowlight";
7215
- import React10, { useMemo as useMemo2 } from "react";
7466
+ import React10, { useMemo } from "react";
7216
7467
  function parseMarkdownSections(text) {
7217
7468
  const sections = [];
7218
7469
  const lines = text.split(/\r?\n/);
@@ -7372,7 +7623,7 @@ var init_markdown = __esm({
7372
7623
  text,
7373
7624
  isPending = false
7374
7625
  }) => {
7375
- const sections = useMemo2(() => parseMarkdownSections(text), [text]);
7626
+ const sections = useMemo(() => parseMarkdownSections(text), [text]);
7376
7627
  const elements = sections.map((section, index) => {
7377
7628
  if (section.type === "table" && section.tableRows) {
7378
7629
  return /* @__PURE__ */ React10.createElement(Table, { key: `table-${index}`, rows: section.tableRows });
@@ -7622,7 +7873,7 @@ var init_ErrorMessage = __esm({
7622
7873
  // src/ui/components/messages/LoadingMessage.tsx
7623
7874
  import { Box as Box11, Text as Text11 } from "ink";
7624
7875
  import Spinner2 from "ink-spinner";
7625
- import React13, { useEffect as useEffect5, useState as useState6 } from "react";
7876
+ import React13, { useEffect as useEffect6, useState as useState7 } from "react";
7626
7877
  var LOADING_MESSAGES, SHIMMER_INTERVAL_MS, TEXT_ROTATION_INTERVAL_MS, LoadingMessage;
7627
7878
  var init_LoadingMessage = __esm({
7628
7879
  "src/ui/components/messages/LoadingMessage.tsx"() {
@@ -7639,10 +7890,10 @@ var init_LoadingMessage = __esm({
7639
7890
  SHIMMER_INTERVAL_MS = 80;
7640
7891
  TEXT_ROTATION_INTERVAL_MS = 2e3;
7641
7892
  LoadingMessage = () => {
7642
- const [messageIndex, setMessageIndex] = useState6(0);
7643
- const [shimmerPosition, setShimmerPosition] = useState6(0);
7893
+ const [messageIndex, setMessageIndex] = useState7(0);
7894
+ const [shimmerPosition, setShimmerPosition] = useState7(0);
7644
7895
  const message = LOADING_MESSAGES[messageIndex];
7645
- useEffect5(() => {
7896
+ useEffect6(() => {
7646
7897
  const rotationInterval = setInterval(() => {
7647
7898
  setMessageIndex((prev) => (prev + 1) % LOADING_MESSAGES.length);
7648
7899
  setShimmerPosition(0);
@@ -7651,7 +7902,7 @@ var init_LoadingMessage = __esm({
7651
7902
  clearInterval(rotationInterval);
7652
7903
  };
7653
7904
  }, []);
7654
- useEffect5(() => {
7905
+ useEffect6(() => {
7655
7906
  const shimmerInterval = setInterval(() => {
7656
7907
  setShimmerPosition((prev) => (prev + 1) % (message.length + 1));
7657
7908
  }, SHIMMER_INTERVAL_MS);
@@ -7695,16 +7946,38 @@ var init_TodoMessage = __esm({
7695
7946
  "use strict";
7696
7947
  init_theme();
7697
7948
  TodoMessage = ({ todos }) => {
7698
- const completed = todos.filter((t) => t.status === "completed");
7699
- const inProgress = todos.filter((t) => t.status === "in_progress");
7700
- const pending = todos.filter((t) => t.status === "pending");
7949
+ const completedCount = todos.filter((t) => t.status === "completed").length;
7701
7950
  const total = todos.length;
7702
- const completedCount = completed.length;
7703
7951
  const progress = total > 0 ? Math.round(completedCount / total * 100) : 0;
7704
7952
  const barLength = 20;
7705
7953
  const filledLength = Math.round(barLength * completedCount / total);
7706
7954
  const bar = "\u2588".repeat(filledLength) + "\u2591".repeat(barLength - filledLength);
7707
- return /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "column", marginY: 0 }, /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "row" }, /* @__PURE__ */ React15.createElement(Text13, { color: theme.text.info }, "\u{1F4DD} "), /* @__PURE__ */ React15.createElement(Text13, { color: theme.text.dim }, "Todo Progress: "), /* @__PURE__ */ React15.createElement(Text13, { color: theme.text.accent }, bar), /* @__PURE__ */ React15.createElement(Text13, { color: theme.text.primary }, " ", progress, "%"), /* @__PURE__ */ React15.createElement(Text13, { color: theme.text.dim }, " ", "(", completedCount, "/", total, ")")), inProgress.length > 0 && /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "column", marginLeft: 2 }, inProgress.map((todo, idx) => /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "row", key: `in-progress-${idx}` }, /* @__PURE__ */ React15.createElement(Text13, { color: theme.status.inProgress }, "\u2192 "), /* @__PURE__ */ React15.createElement(Text13, { color: theme.text.primary }, todo.activeForm || todo.content)))), completed.length > 0 && completed.length <= 2 && /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "column", marginLeft: 2 }, completed.map((todo, idx) => /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "row", key: `completed-${idx}` }, /* @__PURE__ */ React15.createElement(Text13, { color: theme.status.completed }, "\u2713 "), /* @__PURE__ */ React15.createElement(Text13, { color: theme.text.dim }, todo.content)))), pending.length > 0 && pending.length <= 3 && /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "column", marginLeft: 2 }, pending.map((todo, idx) => /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "row", key: `pending-${idx}` }, /* @__PURE__ */ React15.createElement(Text13, { color: theme.status.pending }, "\u23F3 "), /* @__PURE__ */ React15.createElement(Text13, { color: theme.text.secondary }, todo.content)))));
7955
+ const getStatusIcon = (status) => {
7956
+ switch (status) {
7957
+ case "completed":
7958
+ return { icon: "\u2713", color: theme.status.completed };
7959
+ case "in_progress":
7960
+ return { icon: "\u2192", color: theme.status.inProgress };
7961
+ case "pending":
7962
+ return { icon: "\u25CB", color: theme.status.pending };
7963
+ }
7964
+ };
7965
+ const getTextColor = (status) => {
7966
+ switch (status) {
7967
+ case "completed":
7968
+ return theme.text.dim;
7969
+ case "in_progress":
7970
+ return theme.text.primary;
7971
+ case "pending":
7972
+ return theme.text.secondary;
7973
+ }
7974
+ };
7975
+ return /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "column", marginY: 0 }, /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "row" }, /* @__PURE__ */ React15.createElement(Text13, { color: theme.text.info }, "\u{1F4DD} "), /* @__PURE__ */ React15.createElement(Text13, { color: theme.text.dim }, "Todo Progress: "), /* @__PURE__ */ React15.createElement(Text13, { color: theme.text.accent }, bar), /* @__PURE__ */ React15.createElement(Text13, { color: theme.text.primary }, " ", progress, "%"), /* @__PURE__ */ React15.createElement(Text13, { color: theme.text.dim }, " ", "(", completedCount, "/", total, ")")), /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "column", marginLeft: 2 }, todos.map((todo, idx) => {
7976
+ const { icon, color } = getStatusIcon(todo.status);
7977
+ const textColor = getTextColor(todo.status);
7978
+ const displayText = todo.status === "in_progress" ? todo.activeForm || todo.content : todo.content;
7979
+ return /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "row", key: `todo-${idx}` }, /* @__PURE__ */ React15.createElement(Text13, { color }, icon, " "), /* @__PURE__ */ React15.createElement(Text13, { color: textColor }, displayText));
7980
+ })));
7708
7981
  };
7709
7982
  }
7710
7983
  });
@@ -7886,11 +8159,11 @@ var init_UserMessage = __esm({
7886
8159
  "use strict";
7887
8160
  init_theme();
7888
8161
  UserMessage = ({ text }) => {
7889
- return /* @__PURE__ */ React17.createElement(Box15, { flexDirection: "row", marginTop: 1, alignItems: "center" }, /* @__PURE__ */ React17.createElement(Text15, { color: theme.text.info }, "\u{1F464} "), /* @__PURE__ */ React17.createElement(
8162
+ return /* @__PURE__ */ React17.createElement(Box15, { alignItems: "center", flexDirection: "row", marginTop: 1 }, /* @__PURE__ */ React17.createElement(Text15, { color: theme.text.info }, "\u{1F464} "), /* @__PURE__ */ React17.createElement(
7890
8163
  Box15,
7891
8164
  {
7892
- borderStyle: "round",
7893
8165
  borderColor: theme.text.dim,
8166
+ borderStyle: "round",
7894
8167
  paddingLeft: 1,
7895
8168
  paddingRight: 1
7896
8169
  },
@@ -7902,7 +8175,7 @@ var init_UserMessage = __esm({
7902
8175
 
7903
8176
  // src/ui/components/MessageList.tsx
7904
8177
  import { Box as Box16, Static } from "ink";
7905
- import React18, { useMemo as useMemo4 } from "react";
8178
+ import React18, { useMemo as useMemo3 } from "react";
7906
8179
  var MessageList;
7907
8180
  var init_MessageList = __esm({
7908
8181
  "src/ui/components/MessageList.tsx"() {
@@ -7972,7 +8245,7 @@ var init_MessageList = __esm({
7972
8245
  return null;
7973
8246
  }
7974
8247
  };
7975
- const { completedMessages, pendingMessages } = useMemo4(() => {
8248
+ const { completedMessages, pendingMessages } = useMemo3(() => {
7976
8249
  const completed = [];
7977
8250
  const pending = [];
7978
8251
  for (const msg of messages) {
@@ -7984,7 +8257,7 @@ var init_MessageList = __esm({
7984
8257
  }
7985
8258
  return { completedMessages: completed, pendingMessages: pending };
7986
8259
  }, [messages]);
7987
- const staticItems = useMemo4(() => [
8260
+ const staticItems = useMemo3(() => [
7988
8261
  { id: "header", type: "header" },
7989
8262
  ...completedMessages.map((msg) => ({ ...msg, _isMessage: true }))
7990
8263
  ], [completedMessages]);
@@ -8036,7 +8309,7 @@ var init_QueuedMessageDisplay = __esm({
8036
8309
 
8037
8310
  // src/ui/components/SessionSelector.tsx
8038
8311
  import { Box as Box18, Text as Text17, useInput as useInput4 } from "ink";
8039
- import React20, { useEffect as useEffect6, useState as useState7 } from "react";
8312
+ import React20, { useEffect as useEffect7, useState as useState8 } from "react";
8040
8313
  function getSessionPrefix(authMethod) {
8041
8314
  return authMethod === "api-key" ? "[Team]" : "[Me]";
8042
8315
  }
@@ -8051,13 +8324,13 @@ var init_SessionSelector = __esm({
8051
8324
  onSelect,
8052
8325
  onCancel
8053
8326
  }) => {
8054
- const [allSessions, setAllSessions] = useState7([]);
8055
- const [selectedIndex, setSelectedIndex] = useState7(0);
8056
- const [isLoading, setIsLoading] = useState7(false);
8057
- const [hasMore, setHasMore] = useState7(true);
8058
- const [totalSessions, setTotalSessions] = useState7(0);
8059
- const [error, setError] = useState7(null);
8060
- useEffect6(() => {
8327
+ const [allSessions, setAllSessions] = useState8([]);
8328
+ const [selectedIndex, setSelectedIndex] = useState8(0);
8329
+ const [isLoading, setIsLoading] = useState8(false);
8330
+ const [hasMore, setHasMore] = useState8(true);
8331
+ const [totalSessions, setTotalSessions] = useState8(0);
8332
+ const [error, setError] = useState8(null);
8333
+ useEffect7(() => {
8061
8334
  loadMoreSessions();
8062
8335
  }, []);
8063
8336
  const loadMoreSessions = async () => {
@@ -8164,11 +8437,11 @@ var init_SessionSelector = __esm({
8164
8437
  });
8165
8438
 
8166
8439
  // src/ui/hooks/useModeToggle.ts
8167
- import { useEffect as useEffect7 } from "react";
8440
+ import { useEffect as useEffect8 } from "react";
8168
8441
  function useModeToggle() {
8169
8442
  const { subscribe, unsubscribe } = useKeypressContext();
8170
8443
  const { agentMode, setAgentMode, isAgentRunning } = useSession();
8171
- useEffect7(() => {
8444
+ useEffect8(() => {
8172
8445
  const handleKeypress = (key) => {
8173
8446
  if (key.name === "tab" && key.shift && !isAgentRunning) {
8174
8447
  const newMode = agentMode === "plan" ? "build" : "plan";
@@ -8189,7 +8462,7 @@ var init_useModeToggle = __esm({
8189
8462
  });
8190
8463
 
8191
8464
  // src/ui/hooks/useOverlayEscapeGuard.ts
8192
- import { useCallback as useCallback3, useMemo as useMemo5, useRef as useRef2 } from "react";
8465
+ import { useCallback as useCallback3, useMemo as useMemo4, useRef as useRef2 } from "react";
8193
8466
  var useOverlayEscapeGuard;
8194
8467
  var init_useOverlayEscapeGuard = __esm({
8195
8468
  "src/ui/hooks/useOverlayEscapeGuard.ts"() {
@@ -8200,7 +8473,7 @@ var init_useOverlayEscapeGuard = __esm({
8200
8473
  suppressUntilRef.current = Date.now() + suppressionMs;
8201
8474
  }, [suppressionMs]);
8202
8475
  const isCancelSuppressed = useCallback3(() => Date.now() < suppressUntilRef.current, []);
8203
- const isOverlayOpen = useMemo5(() => overlays.some(Boolean), [overlays]);
8476
+ const isOverlayOpen = useMemo4(() => overlays.some(Boolean), [overlays]);
8204
8477
  return { isOverlayOpen, isCancelSuppressed, markOverlayClosed };
8205
8478
  };
8206
8479
  }
@@ -8211,7 +8484,7 @@ import { execSync as execSync3 } from "child_process";
8211
8484
  import { homedir as homedir3 } from "os";
8212
8485
  import { Box as Box19, Text as Text18, useApp } from "ink";
8213
8486
  import Spinner3 from "ink-spinner";
8214
- import React21, { useEffect as useEffect8, useRef as useRef3, useState as useState8 } from "react";
8487
+ import React21, { useEffect as useEffect9, useRef as useRef3, useState as useState9 } from "react";
8215
8488
  var getGitBranch, getCurrentFolder, AppContent, App;
8216
8489
  var init_App = __esm({
8217
8490
  "src/ui/App.tsx"() {
@@ -8219,6 +8492,7 @@ var init_App = __esm({
8219
8492
  init_shared_es();
8220
8493
  init_login();
8221
8494
  init_setup();
8495
+ init_command_discovery();
8222
8496
  init_stdio();
8223
8497
  init_token_storage();
8224
8498
  init_version();
@@ -8244,43 +8518,43 @@ var init_App = __esm({
8244
8518
  return "";
8245
8519
  }
8246
8520
  };
8247
- getCurrentFolder = () => {
8248
- const cwd = process.cwd();
8521
+ getCurrentFolder = (configCwd) => {
8522
+ const cwd = configCwd || process.cwd();
8249
8523
  const home = homedir3();
8250
8524
  if (cwd.startsWith(home)) {
8251
8525
  return `~${cwd.slice(home.length)}`;
8252
8526
  }
8253
8527
  return cwd;
8254
8528
  };
8255
- AppContent = ({ config: config2, sessionId, webUrl, queuedTasks = [], onExit, onSubmitTask, apiClient, onResumeSession }) => {
8529
+ AppContent = ({ config: config2, sessionId, webUrl, queuedTasks = [], onExit, onSubmitTask, apiClient, onResumeSession, onClearSession }) => {
8256
8530
  const { exit } = useApp();
8257
8531
  const { addMessage, clearMessages, isAgentRunning, messages, setSessionId, setWebUrl, setShouldInterruptAgent, setIsAgentRunning, toggleAllToolOutputs, allToolsExpanded, selectedModel, setSelectedModel } = useSession();
8258
8532
  useModeToggle();
8259
- const [terminalWidth, setTerminalWidth] = useState8(process.stdout.columns || 80);
8260
- const [showHelp, setShowHelp] = useState8(false);
8261
- const [showInput, setShowInput] = useState8(true);
8262
- const [gitBranch] = useState8(() => getGitBranch());
8263
- const [currentFolder] = useState8(() => getCurrentFolder());
8264
- const [hasInputContent, setHasInputContent] = useState8(false);
8265
- const [exitWarning, setExitWarning] = useState8(null);
8533
+ const [terminalWidth, setTerminalWidth] = useState9(process.stdout.columns || 80);
8534
+ const [showHelp, setShowHelp] = useState9(false);
8535
+ const [showInput, setShowInput] = useState9(true);
8536
+ const [gitBranch] = useState9(() => getGitBranch());
8537
+ const [currentFolder] = useState9(() => getCurrentFolder(config2.cwd));
8538
+ const [hasInputContent, setHasInputContent] = useState9(false);
8539
+ const [exitWarning, setExitWarning] = useState9(null);
8266
8540
  const inputPromptRef = useRef3(null);
8267
- const [showSessionSelector, setShowSessionSelector] = useState8(false);
8268
- const [showModelSelector, setShowModelSelector] = useState8(false);
8269
- const [showFeedbackDialog, setShowFeedbackDialog] = useState8(false);
8270
- const [isLoadingSession, setIsLoadingSession] = useState8(false);
8271
- const [authState, setAuthState] = useState8(
8541
+ const [showSessionSelector, setShowSessionSelector] = useState9(false);
8542
+ const [showModelSelector, setShowModelSelector] = useState9(false);
8543
+ const [showFeedbackDialog, setShowFeedbackDialog] = useState9(false);
8544
+ const [isLoadingSession, setIsLoadingSession] = useState9(false);
8545
+ const [authState, setAuthState] = useState9(
8272
8546
  () => config2.supatestApiKey ? "authenticated" /* Authenticated */ : "unauthenticated" /* Unauthenticated */
8273
8547
  );
8274
- const [showAuthDialog, setShowAuthDialog] = useState8(false);
8548
+ const [showAuthDialog, setShowAuthDialog] = useState9(false);
8275
8549
  const { isOverlayOpen, isCancelSuppressed, markOverlayClosed } = useOverlayEscapeGuard({
8276
8550
  overlays: [showHelp, showSessionSelector, showAuthDialog, showModelSelector, showFeedbackDialog]
8277
8551
  });
8278
- useEffect8(() => {
8552
+ useEffect9(() => {
8279
8553
  if (!config2.supatestApiKey) {
8280
8554
  setShowAuthDialog(true);
8281
8555
  }
8282
8556
  }, [config2.supatestApiKey]);
8283
- useEffect8(() => {
8557
+ useEffect9(() => {
8284
8558
  if (sessionId) {
8285
8559
  setSessionId(sessionId);
8286
8560
  }
@@ -8322,6 +8596,7 @@ var init_App = __esm({
8322
8596
  if (command === "/clear") {
8323
8597
  clearTerminalViewportAndScrollback();
8324
8598
  clearMessages();
8599
+ onClearSession?.();
8325
8600
  return;
8326
8601
  }
8327
8602
  if (command === "/exit") {
@@ -8420,6 +8695,25 @@ var init_App = __esm({
8420
8695
  }
8421
8696
  return;
8422
8697
  }
8698
+ const projectDir = config2.cwd || process.cwd();
8699
+ const spaceIndex = trimmedTask.indexOf(" ");
8700
+ const commandName = spaceIndex > 0 ? trimmedTask.slice(1, spaceIndex) : trimmedTask.slice(1);
8701
+ const commandArgs = spaceIndex > 0 ? trimmedTask.slice(spaceIndex + 1) : void 0;
8702
+ const expandedContent = expandCommand(projectDir, commandName, commandArgs);
8703
+ if (expandedContent) {
8704
+ addMessage({
8705
+ type: "user",
8706
+ content: trimmedTask
8707
+ });
8708
+ onSubmitTask?.(expandedContent);
8709
+ return;
8710
+ }
8711
+ addMessage({
8712
+ type: "error",
8713
+ content: `Unknown command: ${trimmedTask}. Type /help for available commands.`,
8714
+ errorType: "warning"
8715
+ });
8716
+ return;
8423
8717
  }
8424
8718
  if (authState !== "authenticated" /* Authenticated */) {
8425
8719
  addMessage({
@@ -8509,7 +8803,7 @@ var init_App = __esm({
8509
8803
  markOverlayClosed();
8510
8804
  setShowHelp(false);
8511
8805
  };
8512
- useEffect8(() => {
8806
+ useEffect9(() => {
8513
8807
  const handleResize = () => {
8514
8808
  setTerminalWidth(process.stdout.columns || 80);
8515
8809
  };
@@ -8569,7 +8863,7 @@ var init_App = __esm({
8569
8863
  },
8570
8864
  { isActive: !isOverlayOpen }
8571
8865
  );
8572
- useEffect8(() => {
8866
+ useEffect9(() => {
8573
8867
  if (config2.task) {
8574
8868
  addMessage({
8575
8869
  type: "user",
@@ -8625,6 +8919,7 @@ var init_App = __esm({
8625
8919
  InputPrompt,
8626
8920
  {
8627
8921
  currentFolder,
8922
+ cwd: config2.cwd,
8628
8923
  gitBranch,
8629
8924
  onHelpToggle: () => setShowHelp((prev) => !prev),
8630
8925
  onInputChange: (val) => setHasInputContent(val.trim().length > 0),
@@ -8642,7 +8937,7 @@ var init_App = __esm({
8642
8937
  });
8643
8938
 
8644
8939
  // src/ui/hooks/useBracketedPaste.ts
8645
- import { useEffect as useEffect9 } from "react";
8940
+ import { useEffect as useEffect10 } from "react";
8646
8941
  var ENABLE_BRACKETED_PASTE, DISABLE_BRACKETED_PASTE, useBracketedPaste;
8647
8942
  var init_useBracketedPaste = __esm({
8648
8943
  "src/ui/hooks/useBracketedPaste.ts"() {
@@ -8651,7 +8946,7 @@ var init_useBracketedPaste = __esm({
8651
8946
  ENABLE_BRACKETED_PASTE = "\x1B[?2004h";
8652
8947
  DISABLE_BRACKETED_PASTE = "\x1B[?2004l";
8653
8948
  useBracketedPaste = () => {
8654
- useEffect9(() => {
8949
+ useEffect10(() => {
8655
8950
  writeToStdout(ENABLE_BRACKETED_PASTE);
8656
8951
  const cleanup = () => {
8657
8952
  writeToStdout(DISABLE_BRACKETED_PASTE);
@@ -8672,7 +8967,7 @@ __export(interactive_exports, {
8672
8967
  runInteractive: () => runInteractive
8673
8968
  });
8674
8969
  import { render } from "ink";
8675
- import React22, { useEffect as useEffect10, useRef as useRef4 } from "react";
8970
+ import React22, { useEffect as useEffect11, useRef as useRef4 } from "react";
8676
8971
  function getToolDescription2(toolName, input) {
8677
8972
  switch (toolName) {
8678
8973
  case "Read":
@@ -8874,17 +9169,18 @@ var init_interactive = __esm({
8874
9169
  shouldInterruptAgent,
8875
9170
  setShouldInterruptAgent,
8876
9171
  agentMode,
9172
+ setAgentMode,
8877
9173
  planFilePath,
8878
9174
  selectedModel
8879
9175
  } = useSession();
8880
9176
  const agentRef = useRef4(null);
8881
- useEffect10(() => {
9177
+ useEffect11(() => {
8882
9178
  if (shouldInterruptAgent && agentRef.current) {
8883
9179
  agentRef.current.abort();
8884
9180
  setShouldInterruptAgent(false);
8885
9181
  }
8886
9182
  }, [shouldInterruptAgent, setShouldInterruptAgent]);
8887
- useEffect10(() => {
9183
+ useEffect11(() => {
8888
9184
  let isMounted = true;
8889
9185
  const runAgent2 = async () => {
8890
9186
  setIsAgentRunning(true);
@@ -8917,6 +9213,12 @@ var init_interactive = __esm({
8917
9213
  // Note: onComplete is now called after agent.run() returns
8918
9214
  // to capture the providerSessionId from the result
8919
9215
  onComplete: () => {
9216
+ },
9217
+ onExitPlanMode: () => {
9218
+ if (isMounted) setAgentMode("build");
9219
+ },
9220
+ onEnterPlanMode: () => {
9221
+ if (isMounted) setAgentMode("plan");
8920
9222
  }
8921
9223
  },
8922
9224
  apiClient,
@@ -9033,11 +9335,18 @@ var init_interactive = __esm({
9033
9335
  setShouldRunAgent(true);
9034
9336
  }
9035
9337
  }, [shouldRunAgent, taskQueue, addMessage]);
9338
+ const handleClearSession = React22.useCallback(() => {
9339
+ setSessionId(void 0);
9340
+ setContextSessionId(void 0);
9341
+ setProviderSessionId(void 0);
9342
+ setTaskQueue([]);
9343
+ }, [setContextSessionId]);
9036
9344
  return /* @__PURE__ */ React22.createElement(React22.Fragment, null, /* @__PURE__ */ React22.createElement(
9037
9345
  App,
9038
9346
  {
9039
9347
  apiClient,
9040
9348
  config: { ...config2, task: currentTask },
9349
+ onClearSession: handleClearSession,
9041
9350
  onExit,
9042
9351
  onResumeSession: async (session) => {
9043
9352
  try {