@supatest/cli 0.0.18 → 0.0.19

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
  }
@@ -3925,14 +4094,15 @@ var init_project_instructions = __esm({
3925
4094
 
3926
4095
  // src/core/agent.ts
3927
4096
  import { createRequire } from "module";
3928
- import { homedir } from "os";
3929
- import { dirname, join as join2 } from "path";
4097
+ import { dirname, join as join4 } from "path";
3930
4098
  import { query } from "@anthropic-ai/claude-agent-sdk";
3931
4099
  var CoreAgent;
3932
4100
  var init_agent = __esm({
3933
4101
  "src/core/agent.ts"() {
3934
4102
  "use strict";
3935
4103
  init_config();
4104
+ init_command_discovery();
4105
+ init_mcp_loader();
3936
4106
  init_project_instructions();
3937
4107
  CoreAgent = class {
3938
4108
  presenter;
@@ -3965,13 +4135,29 @@ ${config2.logs}
3965
4135
  const isPlanMode = config2.mode === "plan";
3966
4136
  const cwd = config2.cwd || process.cwd();
3967
4137
  const projectInstructions = loadProjectInstructions(cwd);
4138
+ const customAgents = discoverAgents(cwd);
4139
+ let customAgentsPrompt;
4140
+ if (customAgents.length > 0) {
4141
+ const agentList = customAgents.map((agent) => {
4142
+ const modelInfo = agent.model ? ` (model: ${agent.model})` : "";
4143
+ return `- **${agent.name}**${modelInfo}: ${agent.description || "No description"}`;
4144
+ }).join("\n");
4145
+ customAgentsPrompt = `
4146
+
4147
+ # Custom Sub-Agents (from .supatest/agents/)
4148
+
4149
+ The following custom sub-agents are available via the Task tool. Use them by setting subagent_type to the agent name:
4150
+
4151
+ ${agentList}`;
4152
+ }
3968
4153
  const systemPromptAppend = [
3969
4154
  config2.systemPromptAppend,
3970
4155
  projectInstructions && `
3971
4156
 
3972
4157
  # Project Instructions (from SUPATEST.md)
3973
4158
 
3974
- ${projectInstructions}`
4159
+ ${projectInstructions}`,
4160
+ customAgentsPrompt
3975
4161
  ].filter(Boolean).join("\n") || void 0;
3976
4162
  const cleanEnv = {};
3977
4163
  const excludeKeys = /* @__PURE__ */ new Set([
@@ -3986,8 +4172,8 @@ ${projectInstructions}`
3986
4172
  cleanEnv[key] = value;
3987
4173
  }
3988
4174
  }
3989
- const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || join2(homedir(), ".supatest", "claude-config");
3990
- cleanEnv.CLAUDE_CONFIG_DIR = claudeConfigDir;
4175
+ const projectConfigDir = join4(cwd, ".supatest");
4176
+ cleanEnv.CLAUDE_CONFIG_DIR = projectConfigDir;
3991
4177
  cleanEnv.ANTHROPIC_API_KEY = config2.supatestApiKey || "";
3992
4178
  cleanEnv.ANTHROPIC_BASE_URL = process.env.ANTHROPIC_BASE_URL || "";
3993
4179
  cleanEnv.ANTHROPIC_AUTH_TOKEN = "";
@@ -4005,12 +4191,21 @@ ${projectInstructions}`
4005
4191
  includePartialMessages: true,
4006
4192
  executable: "node",
4007
4193
  // MCP servers for enhanced capabilities
4008
- mcpServers: {
4009
- playwright: {
4010
- command: "npx",
4011
- args: ["-y", "@playwright/mcp@latest"]
4012
- }
4013
- },
4194
+ // User-defined servers from .supatest/mcp.json can override defaults
4195
+ mcpServers: (() => {
4196
+ const userServers = loadMcpServers(cwd);
4197
+ const allServers = {
4198
+ // Default Playwright MCP server for browser automation
4199
+ playwright: {
4200
+ command: "npx",
4201
+ args: ["-y", "@playwright/mcp@latest"]
4202
+ },
4203
+ // User-defined servers override defaults (spread after)
4204
+ ...userServers
4205
+ };
4206
+ this.presenter.onLog(`MCP servers loaded: ${Object.keys(allServers).join(", ")}`);
4207
+ return allServers;
4208
+ })(),
4014
4209
  // Resume from previous session if providerSessionId is provided
4015
4210
  // This allows the agent to continue conversations with full context
4016
4211
  // Note: Sessions expire after ~30 days due to Anthropic's data retention policy
@@ -4198,7 +4393,7 @@ ${projectInstructions}`
4198
4393
  async resolveClaudeCodePath() {
4199
4394
  const fs4 = await import("fs/promises");
4200
4395
  let claudeCodePath;
4201
- const bundledPath = join2(dirname(import.meta.url.replace("file://", "")), "claude-code-cli.js");
4396
+ const bundledPath = join4(dirname(import.meta.url.replace("file://", "")), "claude-code-cli.js");
4202
4397
  try {
4203
4398
  await fs4.access(bundledPath);
4204
4399
  claudeCodePath = bundledPath;
@@ -4206,7 +4401,7 @@ ${projectInstructions}`
4206
4401
  } catch {
4207
4402
  const require2 = createRequire(import.meta.url);
4208
4403
  const sdkPath = require2.resolve("@anthropic-ai/claude-agent-sdk/sdk.mjs");
4209
- claudeCodePath = join2(dirname(sdkPath), "cli.js");
4404
+ claudeCodePath = join4(dirname(sdkPath), "cli.js");
4210
4405
  this.presenter.onLog(`Development mode: ${claudeCodePath}`);
4211
4406
  }
4212
4407
  if (config.claudeCodeExecutablePath) {
@@ -4745,7 +4940,7 @@ var CLI_VERSION;
4745
4940
  var init_version = __esm({
4746
4941
  "src/version.ts"() {
4747
4942
  "use strict";
4748
- CLI_VERSION = "0.0.18";
4943
+ CLI_VERSION = "0.0.19";
4749
4944
  }
4750
4945
  });
4751
4946
 
@@ -4819,21 +5014,21 @@ var init_encryption = __esm({
4819
5014
  });
4820
5015
 
4821
5016
  // src/utils/token-storage.ts
4822
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
4823
- import { homedir as homedir2 } from "os";
4824
- import { join as join4 } from "path";
5017
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, unlinkSync, writeFileSync } from "fs";
5018
+ import { homedir } from "os";
5019
+ import { join as join6 } from "path";
4825
5020
  function getTokenFilePath() {
4826
5021
  const apiUrl = process.env.SUPATEST_API_URL || PRODUCTION_API_URL;
4827
5022
  if (apiUrl === PRODUCTION_API_URL) {
4828
- return join4(CONFIG_DIR, "token.json");
5023
+ return join6(CONFIG_DIR, "token.json");
4829
5024
  }
4830
- return join4(CONFIG_DIR, "token.local.json");
5025
+ return join6(CONFIG_DIR, "token.local.json");
4831
5026
  }
4832
5027
  function isV2Format(stored) {
4833
5028
  return "version" in stored && stored.version === 2;
4834
5029
  }
4835
5030
  function ensureConfigDir() {
4836
- if (!existsSync2(CONFIG_DIR)) {
5031
+ if (!existsSync4(CONFIG_DIR)) {
4837
5032
  mkdirSync(CONFIG_DIR, { recursive: true, mode: 448 });
4838
5033
  }
4839
5034
  }
@@ -4853,11 +5048,11 @@ function saveToken(token, expiresAt) {
4853
5048
  }
4854
5049
  function loadToken() {
4855
5050
  const tokenFile = getTokenFilePath();
4856
- if (!existsSync2(tokenFile)) {
5051
+ if (!existsSync4(tokenFile)) {
4857
5052
  return null;
4858
5053
  }
4859
5054
  try {
4860
- const data = readFileSync2(tokenFile, "utf8");
5055
+ const data = readFileSync4(tokenFile, "utf8");
4861
5056
  const stored = JSON.parse(data);
4862
5057
  let payload;
4863
5058
  if (isV2Format(stored)) {
@@ -4886,7 +5081,7 @@ function loadToken() {
4886
5081
  }
4887
5082
  function removeToken() {
4888
5083
  const tokenFile = getTokenFilePath();
4889
- if (existsSync2(tokenFile)) {
5084
+ if (existsSync4(tokenFile)) {
4890
5085
  unlinkSync(tokenFile);
4891
5086
  }
4892
5087
  }
@@ -4895,10 +5090,10 @@ var init_token_storage = __esm({
4895
5090
  "src/utils/token-storage.ts"() {
4896
5091
  "use strict";
4897
5092
  init_encryption();
4898
- CONFIG_DIR = join4(homedir2(), ".supatest");
5093
+ CONFIG_DIR = join6(homedir(), ".supatest");
4899
5094
  PRODUCTION_API_URL = "https://code-api.supatest.ai";
4900
5095
  STORAGE_VERSION = 2;
4901
- TOKEN_FILE = join4(CONFIG_DIR, "token.json");
5096
+ TOKEN_FILE = join6(CONFIG_DIR, "token.json");
4902
5097
  }
4903
5098
  });
4904
5099
 
@@ -5056,6 +5251,10 @@ var init_react = __esm({
5056
5251
  todos
5057
5252
  });
5058
5253
  }
5254
+ } else if (tool === "ExitPlanMode") {
5255
+ this.callbacks.onExitPlanMode?.();
5256
+ } else if (tool === "EnterPlanMode") {
5257
+ this.callbacks.onEnterPlanMode?.();
5059
5258
  }
5060
5259
  const toolUseEvent = {
5061
5260
  type: "tool_use",
@@ -6562,13 +6761,20 @@ var init_FeedbackDialog = __esm({
6562
6761
 
6563
6762
  // src/ui/components/HelpMenu.tsx
6564
6763
  import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
6565
- import React5 from "react";
6764
+ import React5, { useEffect as useEffect4, useState as useState3 } from "react";
6566
6765
  var HelpMenu;
6567
6766
  var init_HelpMenu = __esm({
6568
6767
  "src/ui/components/HelpMenu.tsx"() {
6569
6768
  "use strict";
6769
+ init_command_discovery();
6570
6770
  init_theme();
6571
- HelpMenu = ({ isAuthenticated, onClose }) => {
6771
+ HelpMenu = ({ isAuthenticated, onClose, cwd }) => {
6772
+ const [customCommands, setCustomCommands] = useState3([]);
6773
+ useEffect4(() => {
6774
+ const projectDir = cwd || process.cwd();
6775
+ const commands = discoverCommands(projectDir);
6776
+ setCustomCommands(commands);
6777
+ }, [cwd]);
6572
6778
  useInput2((input, key) => {
6573
6779
  if (key.escape || input === "q" || input === "?" || key.ctrl && input === "h") {
6574
6780
  onClose();
@@ -6587,15 +6793,16 @@ var init_HelpMenu = __esm({
6587
6793
  /* @__PURE__ */ React5.createElement(Box4, { marginTop: 1 }),
6588
6794
  /* @__PURE__ */ React5.createElement(Text4, { bold: true, color: theme.text.secondary }, "Slash Commands:"),
6589
6795
  /* @__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"))),
6796
+ 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
6797
  /* @__PURE__ */ React5.createElement(Box4, { marginTop: 1 }),
6591
6798
  /* @__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"))),
6799
+ /* @__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
6800
  /* @__PURE__ */ React5.createElement(Box4, { marginTop: 1 }),
6594
6801
  /* @__PURE__ */ React5.createElement(Text4, { bold: true, color: theme.text.secondary }, "File References:"),
6595
6802
  /* @__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
6803
  /* @__PURE__ */ React5.createElement(Box4, { marginTop: 1 }),
6597
6804
  /* @__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")),
6805
+ /* @__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
6806
  /* @__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
6807
  );
6601
6808
  };
@@ -6603,7 +6810,7 @@ var init_HelpMenu = __esm({
6603
6810
  });
6604
6811
 
6605
6812
  // src/ui/contexts/SessionContext.tsx
6606
- import React6, { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useState as useState3 } from "react";
6813
+ import React6, { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useState as useState4 } from "react";
6607
6814
  var SessionContext, SessionProvider, useSession;
6608
6815
  var init_SessionContext = __esm({
6609
6816
  "src/ui/contexts/SessionContext.tsx"() {
@@ -6614,24 +6821,24 @@ var init_SessionContext = __esm({
6614
6821
  children,
6615
6822
  initialModel
6616
6823
  }) => {
6617
- const [messages, setMessages] = useState3([]);
6618
- const [todos, setTodos] = useState3([]);
6619
- const [stats, setStats] = useState3({
6824
+ const [messages, setMessages] = useState4([]);
6825
+ const [todos, setTodos] = useState4([]);
6826
+ const [stats, setStats] = useState4({
6620
6827
  filesModified: /* @__PURE__ */ new Set(),
6621
6828
  commandsRun: [],
6622
6829
  iterations: 0,
6623
6830
  startTime: Date.now()
6624
6831
  });
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);
6832
+ const [isAgentRunning, setIsAgentRunning] = useState4(false);
6833
+ const [shouldInterruptAgent, setShouldInterruptAgent] = useState4(false);
6834
+ const [usageStats, setUsageStats] = useState4(null);
6835
+ const [sessionId, setSessionId] = useState4();
6836
+ const [webUrl, setWebUrl] = useState4();
6837
+ const [agentMode, setAgentMode] = useState4("build");
6838
+ const [planFilePath, setPlanFilePath] = useState4();
6839
+ const [selectedModel, setSelectedModel] = useState4(initialModel || Mt);
6840
+ const [allToolsExpanded, setAllToolsExpanded] = useState4(true);
6841
+ const [staticRemountKey, setStaticRemountKey] = useState4(0);
6635
6842
  const addMessage = useCallback2(
6636
6843
  (message) => {
6637
6844
  const expandableTools = ["Bash", "BashOutput", "Command Output"];
@@ -6838,7 +7045,7 @@ var init_file_completion = __esm({
6838
7045
 
6839
7046
  // src/ui/components/ModelSelector.tsx
6840
7047
  import { Box as Box5, Text as Text5, useInput as useInput3 } from "ink";
6841
- import React7, { useState as useState4 } from "react";
7048
+ import React7, { useState as useState5 } from "react";
6842
7049
  function getNextModel(currentModel) {
6843
7050
  const currentIndex = ke.findIndex((m2) => m2.id === currentModel);
6844
7051
  const nextIndex = (currentIndex + 1) % ke.length;
@@ -6861,7 +7068,7 @@ var init_ModelSelector = __esm({
6861
7068
  onCancel
6862
7069
  }) => {
6863
7070
  const currentIndex = ke.findIndex((m2) => m2.id === currentModel);
6864
- const [selectedIndex, setSelectedIndex] = useState4(currentIndex >= 0 ? currentIndex : 0);
7071
+ const [selectedIndex, setSelectedIndex] = useState5(currentIndex >= 0 ? currentIndex : 0);
6865
7072
  useInput3((input, key) => {
6866
7073
  if (key.upArrow) {
6867
7074
  setSelectedIndex((prev) => prev > 0 ? prev - 1 : ke.length - 1);
@@ -6903,12 +7110,13 @@ var init_ModelSelector = __esm({
6903
7110
  import path4 from "path";
6904
7111
  import chalk5 from "chalk";
6905
7112
  import { Box as Box6, Text as Text6 } from "ink";
6906
- import React8, { forwardRef, useEffect as useEffect4, useImperativeHandle, useState as useState5 } from "react";
7113
+ import React8, { forwardRef, useEffect as useEffect5, useImperativeHandle, useState as useState6 } from "react";
6907
7114
  var InputPrompt;
6908
7115
  var init_InputPrompt = __esm({
6909
7116
  "src/ui/components/InputPrompt.tsx"() {
6910
7117
  "use strict";
6911
7118
  init_shared_es();
7119
+ init_command_discovery();
6912
7120
  init_SessionContext();
6913
7121
  init_useKeypress();
6914
7122
  init_file_completion();
@@ -6919,19 +7127,20 @@ var init_InputPrompt = __esm({
6919
7127
  placeholder = "Enter your task (press Enter to submit, Shift+Enter for new line)...",
6920
7128
  disabled = false,
6921
7129
  onHelpToggle,
7130
+ cwd,
6922
7131
  currentFolder,
6923
7132
  gitBranch,
6924
7133
  onInputChange
6925
7134
  }, ref) => {
6926
7135
  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 = [
7136
+ const [value, setValue] = useState6("");
7137
+ const [cursorOffset, setCursorOffset] = useState6(0);
7138
+ const [allFiles, setAllFiles] = useState6([]);
7139
+ const [suggestions, setSuggestions] = useState6([]);
7140
+ const [activeSuggestion, setActiveSuggestion] = useState6(0);
7141
+ const [showSuggestions, setShowSuggestions] = useState6(false);
7142
+ const [mentionStartIndex, setMentionStartIndex] = useState6(-1);
7143
+ const BUILTIN_SLASH_COMMANDS = [
6935
7144
  { name: "/help", desc: "Show help" },
6936
7145
  { name: "/resume", desc: "Resume session" },
6937
7146
  { name: "/clear", desc: "Clear history" },
@@ -6942,7 +7151,21 @@ var init_InputPrompt = __esm({
6942
7151
  { name: "/logout", desc: "Log out" },
6943
7152
  { name: "/exit", desc: "Exit CLI" }
6944
7153
  ];
6945
- const [isSlashCommand, setIsSlashCommand] = useState5(false);
7154
+ const [customCommands, setCustomCommands] = useState6([]);
7155
+ const [isSlashCommand, setIsSlashCommand] = useState6(false);
7156
+ useEffect5(() => {
7157
+ try {
7158
+ const projectDir = cwd || process.cwd();
7159
+ const discovered = discoverCommands(projectDir);
7160
+ const formatted = discovered.map((cmd) => ({
7161
+ name: `/${cmd.name}`,
7162
+ desc: cmd.description || ""
7163
+ }));
7164
+ setCustomCommands(formatted);
7165
+ } catch {
7166
+ }
7167
+ }, [cwd]);
7168
+ const allSlashCommands = [...BUILTIN_SLASH_COMMANDS, ...customCommands];
6946
7169
  useImperativeHandle(ref, () => ({
6947
7170
  clear: () => {
6948
7171
  setValue("");
@@ -6951,7 +7174,7 @@ var init_InputPrompt = __esm({
6951
7174
  onInputChange?.("");
6952
7175
  }
6953
7176
  }));
6954
- useEffect4(() => {
7177
+ useEffect5(() => {
6955
7178
  setTimeout(() => {
6956
7179
  try {
6957
7180
  const files = getFiles();
@@ -6969,7 +7192,18 @@ var init_InputPrompt = __esm({
6969
7192
  const checkSuggestions = (text, cursor) => {
6970
7193
  if (text.startsWith("/") && cursor <= text.length && !text.includes(" ", 1)) {
6971
7194
  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}`);
7195
+ 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);
7196
+ const customMatches = customCommands.filter((cmd) => cmd.name.slice(1).toLowerCase().startsWith(query2.toLowerCase())).map((cmd) => cmd.desc ? `${cmd.name} ${cmd.desc}` : cmd.name);
7197
+ const matches = [];
7198
+ if (builtinMatches.length > 0) {
7199
+ matches.push(...builtinMatches);
7200
+ }
7201
+ if (customMatches.length > 0) {
7202
+ if (builtinMatches.length > 0) {
7203
+ matches.push("\u2500\u2500\u2500\u2500\u2500 custom commands \u2500\u2500\u2500\u2500\u2500");
7204
+ }
7205
+ matches.push(...customMatches);
7206
+ }
6973
7207
  if (matches.length > 0) {
6974
7208
  setSuggestions(matches);
6975
7209
  setShowSuggestions(true);
@@ -7035,8 +7269,8 @@ var init_InputPrompt = __esm({
7035
7269
  cleanPath = cleanPath.replace(/\\ /g, " ");
7036
7270
  if (path4.isAbsolute(cleanPath)) {
7037
7271
  try {
7038
- const cwd = process.cwd();
7039
- const rel = path4.relative(cwd, cleanPath);
7272
+ const cwd2 = process.cwd();
7273
+ const rel = path4.relative(cwd2, cleanPath);
7040
7274
  if (!rel.startsWith("..") && !path4.isAbsolute(rel)) {
7041
7275
  cleanPath = rel;
7042
7276
  }
@@ -7055,20 +7289,31 @@ var init_InputPrompt = __esm({
7055
7289
  return;
7056
7290
  }
7057
7291
  if (showSuggestions && !key.shift) {
7292
+ const isSeparator = (idx) => suggestions[idx]?.startsWith("\u2500\u2500\u2500\u2500\u2500");
7058
7293
  if (key.name === "up") {
7059
- setActiveSuggestion(
7060
- (prev) => prev > 0 ? prev - 1 : suggestions.length - 1
7061
- );
7294
+ setActiveSuggestion((prev) => {
7295
+ let next = prev > 0 ? prev - 1 : suggestions.length - 1;
7296
+ while (isSeparator(next) && next !== prev) {
7297
+ next = next > 0 ? next - 1 : suggestions.length - 1;
7298
+ }
7299
+ return next;
7300
+ });
7062
7301
  return;
7063
7302
  }
7064
7303
  if (key.name === "down") {
7065
- setActiveSuggestion(
7066
- (prev) => prev < suggestions.length - 1 ? prev + 1 : 0
7067
- );
7304
+ setActiveSuggestion((prev) => {
7305
+ let next = prev < suggestions.length - 1 ? prev + 1 : 0;
7306
+ while (isSeparator(next) && next !== prev) {
7307
+ next = next < suggestions.length - 1 ? next + 1 : 0;
7308
+ }
7309
+ return next;
7310
+ });
7068
7311
  return;
7069
7312
  }
7070
7313
  if (key.name === "tab" || key.name === "return") {
7071
- completeSuggestion(key.name === "return");
7314
+ if (!isSeparator(activeSuggestion)) {
7315
+ completeSuggestion(key.name === "return");
7316
+ }
7072
7317
  return;
7073
7318
  }
7074
7319
  if (key.name === "escape") {
@@ -7100,14 +7345,13 @@ var init_InputPrompt = __esm({
7100
7345
  setCursorOffset(Math.min(value.length, cursorOffset + 1));
7101
7346
  } else if (key.ctrl && input === "u") {
7102
7347
  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
- }
7348
+ } else if (key.ctrl && key.name === "m" && !isAgentRunning) {
7349
+ if (key.shift) {
7350
+ setSelectedModel(getPreviousModel(selectedModel));
7351
+ } else {
7352
+ setSelectedModel(getNextModel(selectedModel));
7110
7353
  }
7354
+ } else if (key.name === "tab" && !showSuggestions) {
7111
7355
  } else if (key.paste) {
7112
7356
  const newValue = value.slice(0, cursorOffset) + input + value.slice(cursorOffset);
7113
7357
  updateValue(newValue, cursorOffset + input.length);
@@ -7141,7 +7385,13 @@ var init_InputPrompt = __esm({
7141
7385
  marginBottom: 0,
7142
7386
  paddingX: 1
7143
7387
  },
7144
- suggestions.map((file, idx) => /* @__PURE__ */ React8.createElement(Text6, { color: idx === activeSuggestion ? theme.text.accent : theme.text.dim, key: file }, idx === activeSuggestion ? "\u276F " : " ", " ", file))
7388
+ suggestions.map((item, idx) => {
7389
+ const isSeparator = item.startsWith("\u2500\u2500\u2500\u2500\u2500");
7390
+ if (isSeparator) {
7391
+ return /* @__PURE__ */ React8.createElement(Text6, { color: theme.text.dim, key: item }, " ", item);
7392
+ }
7393
+ return /* @__PURE__ */ React8.createElement(Text6, { color: idx === activeSuggestion ? theme.text.accent : theme.text.dim, key: item }, idx === activeSuggestion ? "\u276F " : " ", item);
7394
+ })
7145
7395
  ), /* @__PURE__ */ React8.createElement(
7146
7396
  Box6,
7147
7397
  {
@@ -7162,7 +7412,7 @@ var init_InputPrompt = __esm({
7162
7412
  }
7163
7413
  return /* @__PURE__ */ React8.createElement(Text6, { color: theme.text.primary, key: idx }, line);
7164
7414
  })), !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", ")"))));
7415
+ ), /* @__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
7416
  });
7167
7417
  InputPrompt.displayName = "InputPrompt";
7168
7418
  }
@@ -7212,7 +7462,7 @@ var init_Header = __esm({
7212
7462
  import chalk6 from "chalk";
7213
7463
  import { Box as Box8, Text as Text8 } from "ink";
7214
7464
  import { all, createLowlight } from "lowlight";
7215
- import React10, { useMemo as useMemo2 } from "react";
7465
+ import React10, { useMemo } from "react";
7216
7466
  function parseMarkdownSections(text) {
7217
7467
  const sections = [];
7218
7468
  const lines = text.split(/\r?\n/);
@@ -7372,7 +7622,7 @@ var init_markdown = __esm({
7372
7622
  text,
7373
7623
  isPending = false
7374
7624
  }) => {
7375
- const sections = useMemo2(() => parseMarkdownSections(text), [text]);
7625
+ const sections = useMemo(() => parseMarkdownSections(text), [text]);
7376
7626
  const elements = sections.map((section, index) => {
7377
7627
  if (section.type === "table" && section.tableRows) {
7378
7628
  return /* @__PURE__ */ React10.createElement(Table, { key: `table-${index}`, rows: section.tableRows });
@@ -7622,7 +7872,7 @@ var init_ErrorMessage = __esm({
7622
7872
  // src/ui/components/messages/LoadingMessage.tsx
7623
7873
  import { Box as Box11, Text as Text11 } from "ink";
7624
7874
  import Spinner2 from "ink-spinner";
7625
- import React13, { useEffect as useEffect5, useState as useState6 } from "react";
7875
+ import React13, { useEffect as useEffect6, useState as useState7 } from "react";
7626
7876
  var LOADING_MESSAGES, SHIMMER_INTERVAL_MS, TEXT_ROTATION_INTERVAL_MS, LoadingMessage;
7627
7877
  var init_LoadingMessage = __esm({
7628
7878
  "src/ui/components/messages/LoadingMessage.tsx"() {
@@ -7639,10 +7889,10 @@ var init_LoadingMessage = __esm({
7639
7889
  SHIMMER_INTERVAL_MS = 80;
7640
7890
  TEXT_ROTATION_INTERVAL_MS = 2e3;
7641
7891
  LoadingMessage = () => {
7642
- const [messageIndex, setMessageIndex] = useState6(0);
7643
- const [shimmerPosition, setShimmerPosition] = useState6(0);
7892
+ const [messageIndex, setMessageIndex] = useState7(0);
7893
+ const [shimmerPosition, setShimmerPosition] = useState7(0);
7644
7894
  const message = LOADING_MESSAGES[messageIndex];
7645
- useEffect5(() => {
7895
+ useEffect6(() => {
7646
7896
  const rotationInterval = setInterval(() => {
7647
7897
  setMessageIndex((prev) => (prev + 1) % LOADING_MESSAGES.length);
7648
7898
  setShimmerPosition(0);
@@ -7651,7 +7901,7 @@ var init_LoadingMessage = __esm({
7651
7901
  clearInterval(rotationInterval);
7652
7902
  };
7653
7903
  }, []);
7654
- useEffect5(() => {
7904
+ useEffect6(() => {
7655
7905
  const shimmerInterval = setInterval(() => {
7656
7906
  setShimmerPosition((prev) => (prev + 1) % (message.length + 1));
7657
7907
  }, SHIMMER_INTERVAL_MS);
@@ -7695,16 +7945,38 @@ var init_TodoMessage = __esm({
7695
7945
  "use strict";
7696
7946
  init_theme();
7697
7947
  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");
7948
+ const completedCount = todos.filter((t) => t.status === "completed").length;
7701
7949
  const total = todos.length;
7702
- const completedCount = completed.length;
7703
7950
  const progress = total > 0 ? Math.round(completedCount / total * 100) : 0;
7704
7951
  const barLength = 20;
7705
7952
  const filledLength = Math.round(barLength * completedCount / total);
7706
7953
  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)))));
7954
+ const getStatusIcon = (status) => {
7955
+ switch (status) {
7956
+ case "completed":
7957
+ return { icon: "\u2713", color: theme.status.completed };
7958
+ case "in_progress":
7959
+ return { icon: "\u2192", color: theme.status.inProgress };
7960
+ case "pending":
7961
+ return { icon: "\u25CB", color: theme.status.pending };
7962
+ }
7963
+ };
7964
+ const getTextColor = (status) => {
7965
+ switch (status) {
7966
+ case "completed":
7967
+ return theme.text.dim;
7968
+ case "in_progress":
7969
+ return theme.text.primary;
7970
+ case "pending":
7971
+ return theme.text.secondary;
7972
+ }
7973
+ };
7974
+ 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) => {
7975
+ const { icon, color } = getStatusIcon(todo.status);
7976
+ const textColor = getTextColor(todo.status);
7977
+ const displayText = todo.status === "in_progress" ? todo.activeForm || todo.content : todo.content;
7978
+ return /* @__PURE__ */ React15.createElement(Box13, { flexDirection: "row", key: `todo-${idx}` }, /* @__PURE__ */ React15.createElement(Text13, { color }, icon, " "), /* @__PURE__ */ React15.createElement(Text13, { color: textColor }, displayText));
7979
+ })));
7708
7980
  };
7709
7981
  }
7710
7982
  });
@@ -7902,7 +8174,7 @@ var init_UserMessage = __esm({
7902
8174
 
7903
8175
  // src/ui/components/MessageList.tsx
7904
8176
  import { Box as Box16, Static } from "ink";
7905
- import React18, { useMemo as useMemo4 } from "react";
8177
+ import React18, { useMemo as useMemo3 } from "react";
7906
8178
  var MessageList;
7907
8179
  var init_MessageList = __esm({
7908
8180
  "src/ui/components/MessageList.tsx"() {
@@ -7972,7 +8244,7 @@ var init_MessageList = __esm({
7972
8244
  return null;
7973
8245
  }
7974
8246
  };
7975
- const { completedMessages, pendingMessages } = useMemo4(() => {
8247
+ const { completedMessages, pendingMessages } = useMemo3(() => {
7976
8248
  const completed = [];
7977
8249
  const pending = [];
7978
8250
  for (const msg of messages) {
@@ -7984,7 +8256,7 @@ var init_MessageList = __esm({
7984
8256
  }
7985
8257
  return { completedMessages: completed, pendingMessages: pending };
7986
8258
  }, [messages]);
7987
- const staticItems = useMemo4(() => [
8259
+ const staticItems = useMemo3(() => [
7988
8260
  { id: "header", type: "header" },
7989
8261
  ...completedMessages.map((msg) => ({ ...msg, _isMessage: true }))
7990
8262
  ], [completedMessages]);
@@ -8036,7 +8308,7 @@ var init_QueuedMessageDisplay = __esm({
8036
8308
 
8037
8309
  // src/ui/components/SessionSelector.tsx
8038
8310
  import { Box as Box18, Text as Text17, useInput as useInput4 } from "ink";
8039
- import React20, { useEffect as useEffect6, useState as useState7 } from "react";
8311
+ import React20, { useEffect as useEffect7, useState as useState8 } from "react";
8040
8312
  function getSessionPrefix(authMethod) {
8041
8313
  return authMethod === "api-key" ? "[Team]" : "[Me]";
8042
8314
  }
@@ -8051,13 +8323,13 @@ var init_SessionSelector = __esm({
8051
8323
  onSelect,
8052
8324
  onCancel
8053
8325
  }) => {
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(() => {
8326
+ const [allSessions, setAllSessions] = useState8([]);
8327
+ const [selectedIndex, setSelectedIndex] = useState8(0);
8328
+ const [isLoading, setIsLoading] = useState8(false);
8329
+ const [hasMore, setHasMore] = useState8(true);
8330
+ const [totalSessions, setTotalSessions] = useState8(0);
8331
+ const [error, setError] = useState8(null);
8332
+ useEffect7(() => {
8061
8333
  loadMoreSessions();
8062
8334
  }, []);
8063
8335
  const loadMoreSessions = async () => {
@@ -8164,11 +8436,11 @@ var init_SessionSelector = __esm({
8164
8436
  });
8165
8437
 
8166
8438
  // src/ui/hooks/useModeToggle.ts
8167
- import { useEffect as useEffect7 } from "react";
8439
+ import { useEffect as useEffect8 } from "react";
8168
8440
  function useModeToggle() {
8169
8441
  const { subscribe, unsubscribe } = useKeypressContext();
8170
8442
  const { agentMode, setAgentMode, isAgentRunning } = useSession();
8171
- useEffect7(() => {
8443
+ useEffect8(() => {
8172
8444
  const handleKeypress = (key) => {
8173
8445
  if (key.name === "tab" && key.shift && !isAgentRunning) {
8174
8446
  const newMode = agentMode === "plan" ? "build" : "plan";
@@ -8189,7 +8461,7 @@ var init_useModeToggle = __esm({
8189
8461
  });
8190
8462
 
8191
8463
  // src/ui/hooks/useOverlayEscapeGuard.ts
8192
- import { useCallback as useCallback3, useMemo as useMemo5, useRef as useRef2 } from "react";
8464
+ import { useCallback as useCallback3, useMemo as useMemo4, useRef as useRef2 } from "react";
8193
8465
  var useOverlayEscapeGuard;
8194
8466
  var init_useOverlayEscapeGuard = __esm({
8195
8467
  "src/ui/hooks/useOverlayEscapeGuard.ts"() {
@@ -8200,7 +8472,7 @@ var init_useOverlayEscapeGuard = __esm({
8200
8472
  suppressUntilRef.current = Date.now() + suppressionMs;
8201
8473
  }, [suppressionMs]);
8202
8474
  const isCancelSuppressed = useCallback3(() => Date.now() < suppressUntilRef.current, []);
8203
- const isOverlayOpen = useMemo5(() => overlays.some(Boolean), [overlays]);
8475
+ const isOverlayOpen = useMemo4(() => overlays.some(Boolean), [overlays]);
8204
8476
  return { isOverlayOpen, isCancelSuppressed, markOverlayClosed };
8205
8477
  };
8206
8478
  }
@@ -8208,10 +8480,10 @@ var init_useOverlayEscapeGuard = __esm({
8208
8480
 
8209
8481
  // src/ui/App.tsx
8210
8482
  import { execSync as execSync3 } from "child_process";
8211
- import { homedir as homedir3 } from "os";
8483
+ import { homedir as homedir2 } from "os";
8212
8484
  import { Box as Box19, Text as Text18, useApp } from "ink";
8213
8485
  import Spinner3 from "ink-spinner";
8214
- import React21, { useEffect as useEffect8, useRef as useRef3, useState as useState8 } from "react";
8486
+ import React21, { useEffect as useEffect9, useRef as useRef3, useState as useState9 } from "react";
8215
8487
  var getGitBranch, getCurrentFolder, AppContent, App;
8216
8488
  var init_App = __esm({
8217
8489
  "src/ui/App.tsx"() {
@@ -8219,6 +8491,7 @@ var init_App = __esm({
8219
8491
  init_shared_es();
8220
8492
  init_login();
8221
8493
  init_setup();
8494
+ init_command_discovery();
8222
8495
  init_stdio();
8223
8496
  init_token_storage();
8224
8497
  init_version();
@@ -8244,43 +8517,43 @@ var init_App = __esm({
8244
8517
  return "";
8245
8518
  }
8246
8519
  };
8247
- getCurrentFolder = () => {
8248
- const cwd = process.cwd();
8249
- const home = homedir3();
8520
+ getCurrentFolder = (configCwd) => {
8521
+ const cwd = configCwd || process.cwd();
8522
+ const home = homedir2();
8250
8523
  if (cwd.startsWith(home)) {
8251
8524
  return `~${cwd.slice(home.length)}`;
8252
8525
  }
8253
8526
  return cwd;
8254
8527
  };
8255
- AppContent = ({ config: config2, sessionId, webUrl, queuedTasks = [], onExit, onSubmitTask, apiClient, onResumeSession }) => {
8528
+ AppContent = ({ config: config2, sessionId, webUrl, queuedTasks = [], onExit, onSubmitTask, apiClient, onResumeSession, onClearSession }) => {
8256
8529
  const { exit } = useApp();
8257
8530
  const { addMessage, clearMessages, isAgentRunning, messages, setSessionId, setWebUrl, setShouldInterruptAgent, setIsAgentRunning, toggleAllToolOutputs, allToolsExpanded, selectedModel, setSelectedModel } = useSession();
8258
8531
  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);
8532
+ const [terminalWidth, setTerminalWidth] = useState9(process.stdout.columns || 80);
8533
+ const [showHelp, setShowHelp] = useState9(false);
8534
+ const [showInput, setShowInput] = useState9(true);
8535
+ const [gitBranch] = useState9(() => getGitBranch());
8536
+ const [currentFolder] = useState9(() => getCurrentFolder(config2.cwd));
8537
+ const [hasInputContent, setHasInputContent] = useState9(false);
8538
+ const [exitWarning, setExitWarning] = useState9(null);
8266
8539
  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(
8540
+ const [showSessionSelector, setShowSessionSelector] = useState9(false);
8541
+ const [showModelSelector, setShowModelSelector] = useState9(false);
8542
+ const [showFeedbackDialog, setShowFeedbackDialog] = useState9(false);
8543
+ const [isLoadingSession, setIsLoadingSession] = useState9(false);
8544
+ const [authState, setAuthState] = useState9(
8272
8545
  () => config2.supatestApiKey ? "authenticated" /* Authenticated */ : "unauthenticated" /* Unauthenticated */
8273
8546
  );
8274
- const [showAuthDialog, setShowAuthDialog] = useState8(false);
8547
+ const [showAuthDialog, setShowAuthDialog] = useState9(false);
8275
8548
  const { isOverlayOpen, isCancelSuppressed, markOverlayClosed } = useOverlayEscapeGuard({
8276
8549
  overlays: [showHelp, showSessionSelector, showAuthDialog, showModelSelector, showFeedbackDialog]
8277
8550
  });
8278
- useEffect8(() => {
8551
+ useEffect9(() => {
8279
8552
  if (!config2.supatestApiKey) {
8280
8553
  setShowAuthDialog(true);
8281
8554
  }
8282
8555
  }, [config2.supatestApiKey]);
8283
- useEffect8(() => {
8556
+ useEffect9(() => {
8284
8557
  if (sessionId) {
8285
8558
  setSessionId(sessionId);
8286
8559
  }
@@ -8322,6 +8595,7 @@ var init_App = __esm({
8322
8595
  if (command === "/clear") {
8323
8596
  clearTerminalViewportAndScrollback();
8324
8597
  clearMessages();
8598
+ onClearSession?.();
8325
8599
  return;
8326
8600
  }
8327
8601
  if (command === "/exit") {
@@ -8420,6 +8694,25 @@ var init_App = __esm({
8420
8694
  }
8421
8695
  return;
8422
8696
  }
8697
+ const projectDir = config2.cwd || process.cwd();
8698
+ const spaceIndex = trimmedTask.indexOf(" ");
8699
+ const commandName = spaceIndex > 0 ? trimmedTask.slice(1, spaceIndex) : trimmedTask.slice(1);
8700
+ const commandArgs = spaceIndex > 0 ? trimmedTask.slice(spaceIndex + 1) : void 0;
8701
+ const expandedContent = expandCommand(projectDir, commandName, commandArgs);
8702
+ if (expandedContent) {
8703
+ addMessage({
8704
+ type: "user",
8705
+ content: trimmedTask
8706
+ });
8707
+ onSubmitTask?.(expandedContent);
8708
+ return;
8709
+ }
8710
+ addMessage({
8711
+ type: "error",
8712
+ content: `Unknown command: ${trimmedTask}. Type /help for available commands.`,
8713
+ errorType: "warning"
8714
+ });
8715
+ return;
8423
8716
  }
8424
8717
  if (authState !== "authenticated" /* Authenticated */) {
8425
8718
  addMessage({
@@ -8509,7 +8802,7 @@ var init_App = __esm({
8509
8802
  markOverlayClosed();
8510
8803
  setShowHelp(false);
8511
8804
  };
8512
- useEffect8(() => {
8805
+ useEffect9(() => {
8513
8806
  const handleResize = () => {
8514
8807
  setTerminalWidth(process.stdout.columns || 80);
8515
8808
  };
@@ -8569,7 +8862,7 @@ var init_App = __esm({
8569
8862
  },
8570
8863
  { isActive: !isOverlayOpen }
8571
8864
  );
8572
- useEffect8(() => {
8865
+ useEffect9(() => {
8573
8866
  if (config2.task) {
8574
8867
  addMessage({
8575
8868
  type: "user",
@@ -8625,6 +8918,7 @@ var init_App = __esm({
8625
8918
  InputPrompt,
8626
8919
  {
8627
8920
  currentFolder,
8921
+ cwd: config2.cwd,
8628
8922
  gitBranch,
8629
8923
  onHelpToggle: () => setShowHelp((prev) => !prev),
8630
8924
  onInputChange: (val) => setHasInputContent(val.trim().length > 0),
@@ -8642,7 +8936,7 @@ var init_App = __esm({
8642
8936
  });
8643
8937
 
8644
8938
  // src/ui/hooks/useBracketedPaste.ts
8645
- import { useEffect as useEffect9 } from "react";
8939
+ import { useEffect as useEffect10 } from "react";
8646
8940
  var ENABLE_BRACKETED_PASTE, DISABLE_BRACKETED_PASTE, useBracketedPaste;
8647
8941
  var init_useBracketedPaste = __esm({
8648
8942
  "src/ui/hooks/useBracketedPaste.ts"() {
@@ -8651,7 +8945,7 @@ var init_useBracketedPaste = __esm({
8651
8945
  ENABLE_BRACKETED_PASTE = "\x1B[?2004h";
8652
8946
  DISABLE_BRACKETED_PASTE = "\x1B[?2004l";
8653
8947
  useBracketedPaste = () => {
8654
- useEffect9(() => {
8948
+ useEffect10(() => {
8655
8949
  writeToStdout(ENABLE_BRACKETED_PASTE);
8656
8950
  const cleanup = () => {
8657
8951
  writeToStdout(DISABLE_BRACKETED_PASTE);
@@ -8672,7 +8966,7 @@ __export(interactive_exports, {
8672
8966
  runInteractive: () => runInteractive
8673
8967
  });
8674
8968
  import { render } from "ink";
8675
- import React22, { useEffect as useEffect10, useRef as useRef4 } from "react";
8969
+ import React22, { useEffect as useEffect11, useRef as useRef4 } from "react";
8676
8970
  function getToolDescription2(toolName, input) {
8677
8971
  switch (toolName) {
8678
8972
  case "Read":
@@ -8874,17 +9168,18 @@ var init_interactive = __esm({
8874
9168
  shouldInterruptAgent,
8875
9169
  setShouldInterruptAgent,
8876
9170
  agentMode,
9171
+ setAgentMode,
8877
9172
  planFilePath,
8878
9173
  selectedModel
8879
9174
  } = useSession();
8880
9175
  const agentRef = useRef4(null);
8881
- useEffect10(() => {
9176
+ useEffect11(() => {
8882
9177
  if (shouldInterruptAgent && agentRef.current) {
8883
9178
  agentRef.current.abort();
8884
9179
  setShouldInterruptAgent(false);
8885
9180
  }
8886
9181
  }, [shouldInterruptAgent, setShouldInterruptAgent]);
8887
- useEffect10(() => {
9182
+ useEffect11(() => {
8888
9183
  let isMounted = true;
8889
9184
  const runAgent2 = async () => {
8890
9185
  setIsAgentRunning(true);
@@ -8917,6 +9212,12 @@ var init_interactive = __esm({
8917
9212
  // Note: onComplete is now called after agent.run() returns
8918
9213
  // to capture the providerSessionId from the result
8919
9214
  onComplete: () => {
9215
+ },
9216
+ onExitPlanMode: () => {
9217
+ if (isMounted) setAgentMode("build");
9218
+ },
9219
+ onEnterPlanMode: () => {
9220
+ if (isMounted) setAgentMode("plan");
8920
9221
  }
8921
9222
  },
8922
9223
  apiClient,
@@ -9033,11 +9334,18 @@ var init_interactive = __esm({
9033
9334
  setShouldRunAgent(true);
9034
9335
  }
9035
9336
  }, [shouldRunAgent, taskQueue, addMessage]);
9337
+ const handleClearSession = React22.useCallback(() => {
9338
+ setSessionId(void 0);
9339
+ setContextSessionId(void 0);
9340
+ setProviderSessionId(void 0);
9341
+ setTaskQueue([]);
9342
+ }, [setContextSessionId]);
9036
9343
  return /* @__PURE__ */ React22.createElement(React22.Fragment, null, /* @__PURE__ */ React22.createElement(
9037
9344
  App,
9038
9345
  {
9039
9346
  apiClient,
9040
9347
  config: { ...config2, task: currentTask },
9348
+ onClearSession: handleClearSession,
9041
9349
  onExit,
9042
9350
  onResumeSession: async (session) => {
9043
9351
  try {