zizou-ai 0.1.1 → 0.1.2

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/cli.js CHANGED
@@ -642,6 +642,7 @@ import { streamText, stepCountIs } from "ai";
642
642
 
643
643
  // src/tools/read-file.ts
644
644
  import { readFileSync } from "node:fs";
645
+ import { resolve } from "node:path";
645
646
  import { z } from "zod";
646
647
  import { tool } from "ai";
647
648
  var readFile = tool({
@@ -656,7 +657,8 @@ var readFile = tool({
656
657
  */
657
658
  execute: async ({ path }) => {
658
659
  try {
659
- const contents = readFileSync(path, "utf-8");
660
+ const absPath = resolve(process.cwd(), path);
661
+ const contents = readFileSync(absPath, "utf-8");
660
662
  return { success: true, contents };
661
663
  } catch (err) {
662
664
  const message = err instanceof Error ? err.message : "Unknown error reading file";
@@ -667,7 +669,7 @@ var readFile = tool({
667
669
 
668
670
  // src/tools/write-file.ts
669
671
  import { writeFileSync, mkdirSync } from "node:fs";
670
- import { dirname, resolve } from "node:path";
672
+ import { dirname, resolve as resolve2 } from "node:path";
671
673
  import { z as z2 } from "zod";
672
674
  import { tool as tool2 } from "ai";
673
675
  var writeFile = tool2({
@@ -678,7 +680,7 @@ var writeFile = tool2({
678
680
  }),
679
681
  execute: async ({ path: inputPath, contents }) => {
680
682
  try {
681
- const absPath = resolve(process.cwd(), inputPath);
683
+ const absPath = resolve2(process.cwd(), inputPath);
682
684
  const dir = dirname(absPath);
683
685
  mkdirSync(dir, { recursive: true });
684
686
  writeFileSync(absPath, contents, "utf-8");
@@ -695,6 +697,7 @@ var writeFile = tool2({
695
697
 
696
698
  // src/tools/edit-file.ts
697
699
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
700
+ import { resolve as resolve3 } from "node:path";
698
701
  import { z as z3 } from "zod";
699
702
  import { tool as tool3 } from "ai";
700
703
  var editFile = tool3({
@@ -706,7 +709,10 @@ var editFile = tool3({
706
709
  }),
707
710
  execute: async ({ path, old_string, new_string }) => {
708
711
  try {
709
- const contents = readFileSync2(path, "utf-8");
712
+ const absPath = resolve3(process.cwd(), path);
713
+ let contents = readFileSync2(absPath, "utf-8");
714
+ contents = contents.replace(/\r\n/g, "\n");
715
+ old_string = old_string.replace(/\r\n/g, "\n");
710
716
  let count = 0;
711
717
  let pos = contents.indexOf(old_string);
712
718
  while (pos !== -1) {
@@ -726,10 +732,10 @@ var editFile = tool3({
726
732
  };
727
733
  }
728
734
  const updatedContents = contents.replace(old_string, new_string);
729
- writeFileSync2(path, updatedContents, "utf-8");
735
+ writeFileSync2(absPath, updatedContents, "utf-8");
730
736
  return {
731
737
  success: true,
732
- message: `Successfully replaced 1 occurrence in ${path}`
738
+ message: `Successfully replaced 1 occurrence in ${absPath}`
733
739
  };
734
740
  } catch (err) {
735
741
  const message = err instanceof Error ? err.message : "Unknown error editing file";
@@ -743,6 +749,45 @@ import { readdirSync, statSync } from "fs";
743
749
  import { join, relative } from "path";
744
750
  import { z as z4 } from "zod";
745
751
  import { tool as tool4 } from "ai";
752
+ function globToRegex(pattern) {
753
+ let p = pattern.replace(/\\/g, "/");
754
+ const hasSlash = p.includes("/");
755
+ let regexStr = "";
756
+ let i = 0;
757
+ while (i < p.length) {
758
+ const char = p[i];
759
+ if (char === "*" && p[i + 1] === "*") {
760
+ if (p[i + 2] === "/") {
761
+ regexStr += "(.*/)?";
762
+ i += 3;
763
+ } else {
764
+ regexStr += ".*";
765
+ i += 2;
766
+ }
767
+ } else if (char === "*") {
768
+ regexStr += "[^/]*";
769
+ i++;
770
+ } else if (char === "?") {
771
+ regexStr += "[^/]";
772
+ i++;
773
+ } else if (char === ".") {
774
+ regexStr += "\\.";
775
+ i++;
776
+ } else if (char === "/" || char === "\\") {
777
+ regexStr += "/";
778
+ i++;
779
+ } else if (".+^${}()|[]\\".includes(char)) {
780
+ regexStr += "\\" + char;
781
+ i++;
782
+ } else {
783
+ regexStr += char;
784
+ i++;
785
+ }
786
+ }
787
+ const finalPattern = hasSlash ? `^${regexStr}$` : `^(.*/)?${regexStr}$`;
788
+ const flags = process.platform === "win32" ? "i" : "";
789
+ return new RegExp(finalPattern, flags);
790
+ }
746
791
  function simpleGlob(dir, pattern, results = [], root = dir) {
747
792
  const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next"]);
748
793
  let entries;
@@ -762,28 +807,78 @@ function simpleGlob(dir, pattern, results = [], root = dir) {
762
807
  }
763
808
  if (stat.isDirectory()) {
764
809
  simpleGlob(fullPath, pattern, results, root);
765
- } else if (pattern.test(entry)) {
766
- results.push(relative(root, fullPath));
810
+ } else {
811
+ const relPath = relative(root, fullPath).replace(/\\/g, "/");
812
+ if (pattern.test(relPath) || pattern.test(entry)) {
813
+ results.push(relPath);
814
+ }
767
815
  }
768
816
  }
769
817
  return results;
770
818
  }
771
819
  var glob = tool4({
772
- description: "Find files in the project by filename pattern (e.g. '*.ts', 'test*.js')",
820
+ description: "Find files in the project by filename pattern (e.g. '*.ts', '**/app.css')",
773
821
  inputSchema: z4.object({
774
- pattern: z4.string().describe("A simple glob-style pattern, e.g. '*.ts'")
822
+ pattern: z4.string().describe("A simple glob-style pattern, e.g. '*.ts' or '**/app.css'")
775
823
  }),
776
824
  execute: async ({ pattern }) => {
777
- const regexPattern = "^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$";
778
- const matches = simpleGlob(".", new RegExp(regexPattern));
779
- return { success: true, files: matches.slice(0, 50) };
825
+ try {
826
+ const regex = globToRegex(pattern);
827
+ const matches = simpleGlob(".", regex);
828
+ return { success: true, files: matches.slice(0, 50) };
829
+ } catch (err) {
830
+ return { success: false, error: String(err) };
831
+ }
780
832
  }
781
833
  });
782
834
 
783
835
  // src/tools/grep.ts
784
- import { execSync } from "child_process";
836
+ import { readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
837
+ import { join as join2, relative as relative2 } from "path";
785
838
  import { z as z5 } from "zod";
786
839
  import { tool as tool5 } from "ai";
840
+ function simpleGrep(dir, regex, filePattern, results = [], root = dir) {
841
+ const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "coverage"]);
842
+ let entries;
843
+ try {
844
+ entries = readdirSync2(dir);
845
+ } catch {
846
+ return results;
847
+ }
848
+ for (const entry of entries) {
849
+ if (SKIP.has(entry)) continue;
850
+ const fullPath = join2(dir, entry);
851
+ let stat;
852
+ try {
853
+ stat = statSync2(fullPath);
854
+ } catch {
855
+ continue;
856
+ }
857
+ if (stat.isDirectory()) {
858
+ simpleGrep(fullPath, regex, filePattern, results, root);
859
+ } else {
860
+ if (stat.size > 1024 * 1024) continue;
861
+ const relPath = relative2(root, fullPath).replace(/\\/g, "/");
862
+ if (filePattern && !filePattern.test(relPath) && !filePattern.test(entry)) {
863
+ continue;
864
+ }
865
+ try {
866
+ const content = readFileSync3(fullPath, "utf-8");
867
+ if (content.indexOf("\0") !== -1) continue;
868
+ const lines = content.split("\n");
869
+ for (let i = 0; i < lines.length; i++) {
870
+ if (regex.test(lines[i])) {
871
+ results.push(`${relPath}:${i + 1}:${lines[i].trim()}`);
872
+ if (results.length >= 50) return results;
873
+ }
874
+ }
875
+ } catch {
876
+ }
877
+ }
878
+ if (results.length >= 50) break;
879
+ }
880
+ return results;
881
+ }
787
882
  var grep = tool5({
788
883
  description: "Search file contents for a text pattern across the project (like grep -r)",
789
884
  inputSchema: z5.object({
@@ -792,12 +887,14 @@ var grep = tool5({
792
887
  }),
793
888
  execute: async ({ pattern, fileGlob }) => {
794
889
  try {
795
- const includeFlag = fileGlob ? `--include="${fileGlob}"` : "";
796
- const safePattern = pattern.replace(/"/g, '\\"');
797
- const cmd = `grep -rn ${includeFlag} --exclude-dir=node_modules --exclude-dir=.git "${safePattern}" . || true`;
798
- const output = execSync(cmd, { encoding: "utf-8", maxBuffer: 1024 * 1024 });
799
- const lines = output.split("\n").filter(Boolean).slice(0, 30);
800
- return { success: true, matches: lines };
890
+ const searchRegex = new RegExp(pattern);
891
+ let fileRegex = null;
892
+ if (fileGlob) {
893
+ const fp = "^" + fileGlob.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$";
894
+ fileRegex = new RegExp(fp);
895
+ }
896
+ const matches = simpleGrep(".", searchRegex, fileRegex);
897
+ return { success: true, matches };
801
898
  } catch (err) {
802
899
  return { success: false, error: String(err) };
803
900
  }
@@ -805,8 +902,8 @@ var grep = tool5({
805
902
  });
806
903
 
807
904
  // src/tools/list-dir.ts
808
- import { readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
809
- import { join as join2, resolve as resolve2 } from "node:path";
905
+ import { readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
906
+ import { join as join3, resolve as resolve4 } from "node:path";
810
907
  import { z as z6 } from "zod";
811
908
  import { tool as tool6 } from "ai";
812
909
  var listDir = tool6({
@@ -819,13 +916,13 @@ var listDir = tool6({
819
916
  execute: async ({ path: inputPath }) => {
820
917
  const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".cache"]);
821
918
  try {
822
- const dir = resolve2(process.cwd(), inputPath ?? ".");
823
- const entries = readdirSync2(dir);
919
+ const dir = resolve4(process.cwd(), inputPath ?? ".");
920
+ const entries = readdirSync3(dir);
824
921
  const items = [];
825
922
  for (const entry of entries) {
826
923
  if (SKIP.has(entry)) continue;
827
924
  try {
828
- const stat = statSync2(join2(dir, entry));
925
+ const stat = statSync3(join3(dir, entry));
829
926
  items.push({ name: entry, type: stat.isDirectory() ? "dir" : "file" });
830
927
  } catch {
831
928
  }
@@ -844,7 +941,7 @@ var listDir = tool6({
844
941
 
845
942
  // src/tools/open-file.ts
846
943
  import { spawn } from "node:child_process";
847
- import { resolve as resolve3, extname } from "node:path";
944
+ import { resolve as resolve5, extname } from "node:path";
848
945
  import { existsSync } from "node:fs";
849
946
  import { z as z7 } from "zod";
850
947
  import { tool as tool7 } from "ai";
@@ -862,7 +959,7 @@ var openFile = tool7({
862
959
  }),
863
960
  execute: async ({ path: inputPath }) => {
864
961
  try {
865
- const absPath = resolve3(process.cwd(), inputPath);
962
+ const absPath = resolve5(process.cwd(), inputPath);
866
963
  if (!existsSync(absPath)) {
867
964
  return {
868
965
  success: false,
@@ -895,10 +992,10 @@ import { tool as tool8 } from "ai";
895
992
  import { z as z8 } from "zod";
896
993
 
897
994
  // src/context/repo-map.ts
898
- import { readFileSync as readFileSync3, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
899
- import { join as join3, relative as relative2 } from "path";
995
+ import { readFileSync as readFileSync4, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
996
+ import { join as join4, relative as relative3 } from "path";
900
997
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "coverage"]);
901
- var CODE_FILE_PATTERN = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
998
+ var CODE_FILE_PATTERN = /\.(ts|tsx|js|jsx|mjs|cjs|html|css)$/;
902
999
  var SYMBOL_PATTERNS = [
903
1000
  /^\s*(export\s+)?(async\s+)?function\s+(\w+)\s*\(/,
904
1001
  /^\s*(export\s+)?const\s+(\w+)\s*=\s*(async\s*)?\(/,
@@ -935,25 +1032,34 @@ function scanRepo(rootDir) {
935
1032
  function walk(dir) {
936
1033
  let entries;
937
1034
  try {
938
- entries = readdirSync3(dir);
1035
+ entries = readdirSync4(dir);
939
1036
  } catch {
940
1037
  return;
941
1038
  }
942
1039
  for (const entry of entries) {
943
1040
  if (SKIP_DIRS.has(entry)) continue;
944
- const fullPath = join3(dir, entry);
1041
+ const fullPath = join4(dir, entry);
945
1042
  let stat;
946
1043
  try {
947
- stat = statSync3(fullPath);
1044
+ stat = statSync4(fullPath);
948
1045
  } catch {
949
1046
  continue;
950
1047
  }
951
1048
  if (stat.isDirectory()) {
952
1049
  walk(fullPath);
953
1050
  } else if (CODE_FILE_PATTERN.test(entry)) {
954
- const content = readFileSync3(fullPath, "utf-8");
955
- const relPath = relative2(rootDir, fullPath);
956
- allSymbols.push(...extractSymbols(relPath, content));
1051
+ const content = readFileSync4(fullPath, "utf-8");
1052
+ const relPath = relative3(rootDir, fullPath).replace(/\\/g, "/");
1053
+ const fileSymbols = extractSymbols(relPath, content);
1054
+ if (fileSymbols.length === 0) {
1055
+ allSymbols.push({
1056
+ file: relPath,
1057
+ line: 1,
1058
+ signature: "(no exported symbols extracted)"
1059
+ });
1060
+ } else {
1061
+ allSymbols.push(...fileSymbols);
1062
+ }
957
1063
  }
958
1064
  }
959
1065
  }
@@ -983,11 +1089,11 @@ function buildRepoMap(rootDir) {
983
1089
  }
984
1090
 
985
1091
  // src/context/build-system-prompt.ts
986
- import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
987
- import { resolve as resolve4 } from "path";
1092
+ import { existsSync as existsSync2, readFileSync as readFileSync5 } from "fs";
1093
+ import { resolve as resolve6 } from "path";
988
1094
  var pinnedContextFiles = /* @__PURE__ */ new Set();
989
1095
  function addPinnedFile(projectRoot, filePath) {
990
- const absolutePath = resolve4(projectRoot, filePath);
1096
+ const absolutePath = resolve6(projectRoot, filePath);
991
1097
  if (!existsSync2(absolutePath)) {
992
1098
  throw new Error(`File not found: ${absolutePath}`);
993
1099
  }
@@ -997,6 +1103,12 @@ function addPinnedFile(projectRoot, filePath) {
997
1103
  function clearPinnedFiles() {
998
1104
  pinnedContextFiles.clear();
999
1105
  }
1106
+ function getOSInfo() {
1107
+ const p = process.platform;
1108
+ if (p === "win32") return { os: "Windows", shell: "PowerShell" };
1109
+ if (p === "darwin") return { os: "macOS", shell: "bash/zsh" };
1110
+ return { os: "Linux", shell: "bash" };
1111
+ }
1000
1112
  var BASE_INSTRUCTIONS = `You are Zizou, an AI coding agent operating inside a user's terminal,
1001
1113
  with direct access to their project's filesystem through tools.
1002
1114
 
@@ -1013,6 +1125,12 @@ You have two complementary ways to understand the codebase:
1013
1125
 
1014
1126
  When creating a new file or completely replacing a file's contents, use the writeFile tool. When modifying existing files in a targeted way, use editFile with an old_string that exactly matches existing content and is unique in the file \u2014 never rewrite whole files from scratch if you are only making minor edits. Before running any shell command, know that the user will be asked to approve it; explain briefly what a command will do if it's not obvious.
1015
1127
 
1128
+ IMPORTANT \u2014 "Read Before Edit" Rule:
1129
+ Before calling editFile, you MUST first readFile the file to see its current contents.
1130
+ The old_string parameter must EXACTLY match text currently in the file \u2014 including all
1131
+ whitespace, indentation, and line breaks. If you guess at the content without reading
1132
+ the file first, the edit will almost certainly fail.
1133
+
1016
1134
  For general knowledge questions, conversational chat, or queries that do not require workspace files or terminal command execution, answer directly from your internal knowledge. Do NOT use tools (such as grep, glob, readFile, or runBash) unless the task specifically requires accessing the project codebase or executing commands.
1017
1135
 
1018
1136
  CRITICAL INSTRUCTION FOR TOOL CALLING: You are interacting with a system that supports native tool calling (function calling). You MUST use the native tool calling protocol.
@@ -1025,9 +1143,12 @@ async function buildSystemPrompt(projectRoot) {
1025
1143
  if (mode !== "light") {
1026
1144
  repoMap = buildRepoMap(projectRoot);
1027
1145
  }
1146
+ const { os, shell } = getOSInfo();
1028
1147
  const SESSION_CONTEXT = `
1029
1148
  --- SESSION CONTEXT ---
1030
1149
  Workspace root (cwd): ${projectRoot}
1150
+ Operating system: ${os}
1151
+ Shell: ${shell}
1031
1152
  All relative paths you provide to tools are resolved from this root.
1032
1153
  When creating or writing a file, you can use either:
1033
1154
  - An absolute path (e.g. ${projectRoot}/index.html)
@@ -1038,24 +1159,65 @@ TOOL GUIDE:
1038
1159
  listDir(path?) \u2192 list immediate children of a directory. Use this FIRST
1039
1160
  whenever you need to understand the folder structure or
1040
1161
  decide where a new file should go.
1041
- readFile(path) \u2192 read the full contents of an existing file.
1162
+ readFile(path) \u2192 read the full contents of an existing file. ALWAYS call
1163
+ this before editFile so you have the exact current text.
1042
1164
  writeFile(path, contents) \u2192 create a NEW file or FULLY REPLACE an existing one.
1043
1165
  Use this for brand-new files or when you want to overwrite everything.
1044
1166
  editFile(path, old_string, new_string) \u2192 make a TARGETED replacement inside an existing
1045
1167
  file. old_string must match exactly and be unique in the file.
1046
- glob(pattern) \u2192 find files recursively by name pattern (e.g. "*.ts").
1047
- grep(query) \u2192 search file contents by text pattern.
1168
+ IMPORTANT: Always readFile first to get the exact text.
1169
+ glob(pattern) \u2192 find files recursively by name pattern (e.g. "*.ts",
1170
+ "**/app.css"). Supports ** for any depth matching.
1171
+ grep(pattern) \u2192 search file contents by text or regex pattern across the project.
1048
1172
  runBash(command) \u2192 run a shell command (user must approve first).
1173
+ On ${os} this runs in ${shell}.
1174
+ Has a 120-second timeout \u2014 suitable for short/medium installation & builds.
1175
+ runBackground(command) \u2192 Run a shell command in the background (non-blocking).
1176
+ Perfect for long-running servers (e.g. 'npm run dev' or 'bun run dev').
1177
+ Returns a 'taskId' and 'pid' immediately.
1178
+ manageTasks(action, taskId?) \u2192 Manage background tasks.
1179
+ - action="list": Return details of all tasks spawned in this session.
1180
+ - action="logs": Return stdout/stderr buffer from a task (helps check server state/logs).
1181
+ - action="kill": Terminate a background task (and its children).
1182
+ managePorts(action, port) \u2192 Find and terminate processes on ports.
1183
+ - action="find": Find PID and name of process listening on 'port'.
1184
+ - action="kill": Kill the process listening on 'port'.
1185
+ Helps solve 'Address already in use' errors.
1186
+ fileOperations(action, source, destination?) \u2192 Native file management.
1187
+ - action="delete": Recursively delete a file/folder.
1188
+ - action="createDirectory": Recursively create folders.
1189
+ - action="copy": Recursively copy a file/folder to destination.
1190
+ - action="move": Move/rename a file/folder to destination.
1049
1191
  openFile(path) \u2192 open a file in the OS default app (HTML \u2192 browser,
1050
1192
  images \u2192 viewer, etc.). Use after creating a file so the
1051
1193
  user can immediately preview it.
1194
+
1195
+ WORKFLOW FOR FINDING AND EDITING FILES:
1196
+ When the user asks you to modify a file you haven't seen yet:
1197
+ 1. Use glob("**/filename") or listDir() to FIND the file path
1198
+ 2. Use readFile(path) to READ its current contents
1199
+ 3. Use editFile(path, old_string, new_string) to EDIT it
1200
+ Never skip step 2 \u2014 editFile needs an exact string match.
1201
+
1202
+ PROJECT SCAFFOLDING (React, Next.js, Vite, etc.):
1203
+ When the user asks you to create a new project with a framework:
1204
+ 1. Use runBash to scaffold: e.g.
1205
+ - React/Vite: npx -y create-vite@latest my-app -- --template react
1206
+ - Next.js: npx -y create-next-app@latest my-app --yes --use-npm
1207
+ - Plain React: npx -y create-react-app my-app
1208
+ 2. Use runBash("cd my-app && npm install") if dependencies weren't auto-installed
1209
+ 3. Spin up development server in the background:
1210
+ - runBackground("cd my-app && npm run dev")
1211
+ 4. Verify server running using:
1212
+ - manageTasks("list")
1213
+ - Wait a few seconds, then query logs using manageTasks("logs", taskId) to see server startup details.
1052
1214
  --- END SESSION CONTEXT ---`;
1053
1215
  let pinnedText = "";
1054
1216
  if (pinnedContextFiles.size > 0) {
1055
1217
  pinnedText = "\n\n--- PINNED FILES ---\nThe user has pinned the following files to your permanent context:\n";
1056
1218
  for (const file of pinnedContextFiles) {
1057
1219
  try {
1058
- const contents = readFileSync4(file, "utf8");
1220
+ const contents = readFileSync5(file, "utf8");
1059
1221
  pinnedText += `
1060
1222
  File: ${file}
1061
1223
  \`\`\`
@@ -1107,12 +1269,21 @@ var addFileToContext = tool8({
1107
1269
  // src/tools/run-bash.ts
1108
1270
  import { exec } from "node:child_process";
1109
1271
  import { promisify } from "node:util";
1272
+ import { join as join5 } from "node:path";
1110
1273
  import { z as z9 } from "zod";
1111
1274
  import { tool as tool9 } from "ai";
1112
1275
  var execAsync = promisify(exec);
1276
+ function getShellConfig() {
1277
+ if (process.platform === "win32") {
1278
+ const systemRoot = process.env.SystemRoot || "C:\\Windows";
1279
+ const powershellPath = join5(systemRoot, "System32\\WindowsPowerShell\\v1.0\\powershell.exe");
1280
+ return { shell: powershellPath };
1281
+ }
1282
+ return {};
1283
+ }
1113
1284
  var createRunBashTool = (confirm) => {
1114
1285
  return tool9({
1115
- description: "Execute a shell command. This requires user confirmation, so only use it when necessary. Commands run with a 15-second timeout. Output is truncated if too long.",
1286
+ description: "Execute a shell command. This requires user confirmation, so only use it when necessary. Commands run with a 120-second timeout. Output is truncated if too long. On Windows this runs in PowerShell; on macOS/Linux it uses the default shell.",
1116
1287
  inputSchema: z9.object({
1117
1288
  command: z9.string().describe("The shell command to execute")
1118
1289
  }),
@@ -1130,10 +1301,13 @@ Allow?`
1130
1301
  }
1131
1302
  try {
1132
1303
  const { stdout, stderr } = await execAsync(command, {
1133
- timeout: 15e3,
1134
- // 15 seconds
1135
- maxBuffer: 1024 * 1024
1304
+ timeout: 12e4,
1305
+ // 120 seconds — enough for npm install, npx create-*, etc.
1306
+ maxBuffer: 1024 * 1024,
1136
1307
  // 1 MB
1308
+ cwd: process.cwd(),
1309
+ // explicit: run from the project root
1310
+ ...getShellConfig()
1137
1311
  });
1138
1312
  const rawOutput = `STDOUT:
1139
1313
  ${stdout}
@@ -1163,6 +1337,487 @@ ${stderr}`;
1163
1337
  });
1164
1338
  };
1165
1339
 
1340
+ // src/tools/run-background.ts
1341
+ import { spawn as spawn2 } from "node:child_process";
1342
+ import { join as join6 } from "node:path";
1343
+ import { z as z10 } from "zod";
1344
+ import { tool as tool10 } from "ai";
1345
+
1346
+ // src/tools/task-registry.ts
1347
+ var tasks = /* @__PURE__ */ new Map();
1348
+ var taskIdCounter = 1;
1349
+ var MAX_BUFFER_LENGTH = 1e4;
1350
+ function registerTask(command, process2) {
1351
+ const id = `task_${taskIdCounter++}`;
1352
+ const task = {
1353
+ id,
1354
+ command,
1355
+ pid: process2.pid,
1356
+ status: "running",
1357
+ exitCode: null,
1358
+ outputBuffer: "",
1359
+ startTime: /* @__PURE__ */ new Date(),
1360
+ process: process2
1361
+ };
1362
+ const appendToBuffer = (data) => {
1363
+ task.outputBuffer += data;
1364
+ if (task.outputBuffer.length > MAX_BUFFER_LENGTH) {
1365
+ task.outputBuffer = task.outputBuffer.slice(task.outputBuffer.length - MAX_BUFFER_LENGTH);
1366
+ }
1367
+ };
1368
+ process2.stdout?.setEncoding("utf-8");
1369
+ process2.stdout?.on("data", (chunk) => {
1370
+ appendToBuffer(chunk);
1371
+ });
1372
+ process2.stderr?.setEncoding("utf-8");
1373
+ process2.stderr?.on("data", (chunk) => {
1374
+ appendToBuffer(chunk);
1375
+ });
1376
+ process2.on("close", (code) => {
1377
+ task.exitCode = code;
1378
+ task.status = code === 0 ? "exited" : "failed";
1379
+ });
1380
+ process2.on("error", (err) => {
1381
+ task.status = "failed";
1382
+ appendToBuffer(`
1383
+ [Task Registry Error]: ${err.message}
1384
+ `);
1385
+ });
1386
+ tasks.set(id, task);
1387
+ return task;
1388
+ }
1389
+ function getTask(id) {
1390
+ return tasks.get(id);
1391
+ }
1392
+ function listTasks() {
1393
+ return Array.from(tasks.values()).map(({ id, command, pid, status, exitCode, outputBuffer, startTime }) => ({
1394
+ id,
1395
+ command,
1396
+ pid,
1397
+ status,
1398
+ exitCode,
1399
+ outputBuffer,
1400
+ startTime
1401
+ }));
1402
+ }
1403
+ function killTask(id) {
1404
+ const task = tasks.get(id);
1405
+ if (!task || task.status !== "running") return false;
1406
+ try {
1407
+ if (process.platform === "win32") {
1408
+ const { execSync } = __require("node:child_process");
1409
+ execSync(`taskkill /pid ${task.process.pid} /T /F`, { stdio: "ignore" });
1410
+ } else {
1411
+ task.process.kill("SIGTERM");
1412
+ setTimeout(() => {
1413
+ if (task.status === "running") {
1414
+ task.process.kill("SIGKILL");
1415
+ }
1416
+ }, 2e3);
1417
+ }
1418
+ task.status = "exited";
1419
+ return true;
1420
+ } catch (err) {
1421
+ try {
1422
+ task.process.kill();
1423
+ task.status = "exited";
1424
+ return true;
1425
+ } catch {
1426
+ return false;
1427
+ }
1428
+ }
1429
+ }
1430
+ function cleanup() {
1431
+ for (const [id, task] of tasks.entries()) {
1432
+ if (task.status === "running") {
1433
+ try {
1434
+ if (process.platform === "win32") {
1435
+ const { execSync } = __require("node:child_process");
1436
+ execSync(`taskkill /pid ${task.process.pid} /T /F`, { stdio: "ignore" });
1437
+ } else {
1438
+ task.process.kill("SIGKILL");
1439
+ }
1440
+ } catch {
1441
+ }
1442
+ }
1443
+ }
1444
+ }
1445
+ process.on("exit", cleanup);
1446
+ process.on("SIGINT", () => {
1447
+ cleanup();
1448
+ process.exit(130);
1449
+ });
1450
+ process.on("SIGTERM", () => {
1451
+ cleanup();
1452
+ process.exit(143);
1453
+ });
1454
+
1455
+ // src/tools/run-background.ts
1456
+ function getShellConfig2() {
1457
+ if (process.platform === "win32") {
1458
+ const systemRoot = process.env.SystemRoot || "C:\\Windows";
1459
+ const powershellPath = join6(systemRoot, "System32\\WindowsPowerShell\\v1.0\\powershell.exe");
1460
+ return { shell: powershellPath };
1461
+ }
1462
+ return { shell: true };
1463
+ }
1464
+ var createRunBackgroundTool = (confirm) => {
1465
+ return tool10({
1466
+ description: "Execute a shell command in the background (non-blocking). This is perfect for starting development servers (e.g. npm run dev), or running long tasks like compilation. Returns a taskId immediately. Requires user confirmation. On Windows, runs in PowerShell; on macOS/Linux it uses the default shell.",
1467
+ inputSchema: z10.object({
1468
+ command: z10.string().describe("The shell command to execute in the background")
1469
+ }),
1470
+ execute: async ({ command }) => {
1471
+ const isApproved = await confirm(
1472
+ `The agent wants to run this command in the background:
1473
+ ${command}
1474
+ Allow?`
1475
+ );
1476
+ if (!isApproved) {
1477
+ return {
1478
+ success: false,
1479
+ error: "User denied permission to run this command."
1480
+ };
1481
+ }
1482
+ try {
1483
+ const shellOpts = getShellConfig2();
1484
+ const child = spawn2(command, {
1485
+ ...shellOpts,
1486
+ cwd: process.cwd(),
1487
+ detached: process.platform !== "win32",
1488
+ // detached group on Unix so child isn't killed immediately if parent detaches
1489
+ stdio: ["ignore", "pipe", "pipe"]
1490
+ // pipe stdout and stderr
1491
+ });
1492
+ if (process.platform !== "win32") {
1493
+ child.unref();
1494
+ }
1495
+ const task = registerTask(command, child);
1496
+ return {
1497
+ success: true,
1498
+ message: `Successfully started background task.`,
1499
+ taskId: task.id,
1500
+ pid: task.pid
1501
+ };
1502
+ } catch (err) {
1503
+ const message = err instanceof Error ? err.message : "Unknown error starting background command";
1504
+ return { success: false, error: message };
1505
+ }
1506
+ }
1507
+ });
1508
+ };
1509
+
1510
+ // src/tools/manage-tasks.ts
1511
+ import { z as z11 } from "zod";
1512
+ import { tool as tool11 } from "ai";
1513
+ var manageTasks = tool11({
1514
+ description: "Manage active background tasks spawned during this session. Can list running tasks, view their real-time output logs, or kill/terminate them.",
1515
+ inputSchema: z11.object({
1516
+ action: z11.enum(["list", "kill", "logs"]).describe("Action to perform: 'list' (all tasks), 'kill' (stop a task), 'logs' (get stdout/stderr output)"),
1517
+ taskId: z11.string().optional().describe("The taskId returned by runBackground. Required for 'kill' and 'logs'.")
1518
+ }),
1519
+ execute: async ({ action, taskId }) => {
1520
+ try {
1521
+ if (action === "list") {
1522
+ const activeTasks = listTasks();
1523
+ return {
1524
+ success: true,
1525
+ tasks: activeTasks.map((t) => ({
1526
+ id: t.id,
1527
+ command: t.command,
1528
+ pid: t.pid,
1529
+ status: t.status,
1530
+ exitCode: t.exitCode,
1531
+ startTime: t.startTime.toISOString(),
1532
+ logSize: t.outputBuffer.length
1533
+ }))
1534
+ };
1535
+ }
1536
+ if (!taskId) {
1537
+ return {
1538
+ success: false,
1539
+ error: "A 'taskId' is required for actions 'kill' and 'logs'."
1540
+ };
1541
+ }
1542
+ if (action === "logs") {
1543
+ const task = getTask(taskId);
1544
+ if (!task) {
1545
+ return {
1546
+ success: false,
1547
+ error: `Task ${taskId} not found.`
1548
+ };
1549
+ }
1550
+ return {
1551
+ success: true,
1552
+ taskId: task.id,
1553
+ command: task.command,
1554
+ status: task.status,
1555
+ logs: task.outputBuffer || "(No output logs yet)"
1556
+ };
1557
+ }
1558
+ if (action === "kill") {
1559
+ const task = getTask(taskId);
1560
+ if (!task) {
1561
+ return {
1562
+ success: false,
1563
+ error: `Task ${taskId} not found.`
1564
+ };
1565
+ }
1566
+ if (task.status !== "running") {
1567
+ return {
1568
+ success: false,
1569
+ error: `Task ${taskId} is not running (status: ${task.status}).`
1570
+ };
1571
+ }
1572
+ const killed = killTask(taskId);
1573
+ if (killed) {
1574
+ return {
1575
+ success: true,
1576
+ message: `Successfully terminated background task ${taskId}.`
1577
+ };
1578
+ } else {
1579
+ return {
1580
+ success: false,
1581
+ error: `Failed to terminate task ${taskId}.`
1582
+ };
1583
+ }
1584
+ }
1585
+ return {
1586
+ success: false,
1587
+ error: `Unsupported action: ${action}`
1588
+ };
1589
+ } catch (err) {
1590
+ const message = err instanceof Error ? err.message : "Unknown error managing tasks";
1591
+ return { success: false, error: message };
1592
+ }
1593
+ }
1594
+ });
1595
+
1596
+ // src/tools/manage-ports.ts
1597
+ import { exec as exec2 } from "node:child_process";
1598
+ import { promisify as promisify2 } from "node:util";
1599
+ import { z as z12 } from "zod";
1600
+ import { tool as tool12 } from "ai";
1601
+ var execAsync2 = promisify2(exec2);
1602
+ async function findProcessOnPort(port) {
1603
+ const results = [];
1604
+ if (process.platform === "win32") {
1605
+ try {
1606
+ const { stdout } = await execAsync2(`netstat -ano | findstr :${port}`);
1607
+ const lines = stdout.split("\n");
1608
+ const pids = /* @__PURE__ */ new Set();
1609
+ for (const line of lines) {
1610
+ const parts = line.trim().split(/\s+/);
1611
+ if (parts.length >= 5) {
1612
+ const localAddr = parts[1];
1613
+ if (localAddr.endsWith(`:${port}`)) {
1614
+ const pid = parseInt(parts[parts.length - 1], 10);
1615
+ if (!isNaN(pid) && pid > 0) {
1616
+ pids.add(pid);
1617
+ }
1618
+ }
1619
+ }
1620
+ }
1621
+ for (const pid of pids) {
1622
+ let name = "Unknown";
1623
+ let details = "";
1624
+ try {
1625
+ const { stdout: taskStdout } = await execAsync2(`tasklist /FI "PID eq ${pid}" /NH`);
1626
+ const taskParts = taskStdout.trim().split(/\s+/);
1627
+ if (taskParts.length > 0 && taskParts[0] !== "INFO:") {
1628
+ name = taskParts[0];
1629
+ }
1630
+ details = `PID: ${pid}, Name: ${name}`;
1631
+ } catch {
1632
+ details = `PID: ${pid}`;
1633
+ }
1634
+ results.push({ pid, name, details });
1635
+ }
1636
+ } catch {
1637
+ }
1638
+ } else {
1639
+ try {
1640
+ const { stdout } = await execAsync2(`lsof -i :${port} -F p c`);
1641
+ const lines = stdout.trim().split("\n");
1642
+ let currentPid = null;
1643
+ for (const line of lines) {
1644
+ if (line.startsWith("p")) {
1645
+ currentPid = parseInt(line.substring(1), 10);
1646
+ } else if (line.startsWith("c") && currentPid !== null) {
1647
+ const name = line.substring(1);
1648
+ results.push({
1649
+ pid: currentPid,
1650
+ name,
1651
+ details: `PID: ${currentPid}, Command: ${name}`
1652
+ });
1653
+ currentPid = null;
1654
+ }
1655
+ }
1656
+ if (results.length === 0 && lines.length > 0) {
1657
+ const pids = lines.filter((l) => l.startsWith("p")).map((l) => parseInt(l.substring(1), 10)).filter((pid) => !isNaN(pid));
1658
+ for (const pid of pids) {
1659
+ results.push({ pid, details: `PID: ${pid}` });
1660
+ }
1661
+ }
1662
+ } catch {
1663
+ }
1664
+ }
1665
+ return results;
1666
+ }
1667
+ async function killProcess(pid) {
1668
+ try {
1669
+ if (process.platform === "win32") {
1670
+ await execAsync2(`taskkill /F /PID ${pid}`);
1671
+ } else {
1672
+ await execAsync2(`kill -9 ${pid}`);
1673
+ }
1674
+ return true;
1675
+ } catch {
1676
+ return false;
1677
+ }
1678
+ }
1679
+ var managePorts = tool12({
1680
+ description: "Find and terminate processes listening on a specific port. Useful for resolving 'Port already in use' errors during web server startup.",
1681
+ inputSchema: z12.object({
1682
+ action: z12.enum(["find", "kill"]).describe("Action to perform: 'find' (view what process is on the port), 'kill' (terminate the process on the port)"),
1683
+ port: z12.number().describe("The local port number (e.g. 3000, 8080)")
1684
+ }),
1685
+ execute: async ({ action, port }) => {
1686
+ try {
1687
+ if (action === "find") {
1688
+ const found = await findProcessOnPort(port);
1689
+ if (found.length === 0) {
1690
+ return {
1691
+ success: true,
1692
+ message: `No active processes found listening on port ${port}.`,
1693
+ processes: []
1694
+ };
1695
+ }
1696
+ return {
1697
+ success: true,
1698
+ message: `Found ${found.length} process(es) listening on port ${port}.`,
1699
+ processes: found
1700
+ };
1701
+ }
1702
+ if (action === "kill") {
1703
+ const found = await findProcessOnPort(port);
1704
+ if (found.length === 0) {
1705
+ return {
1706
+ success: false,
1707
+ error: `No active processes found listening on port ${port} to terminate.`
1708
+ };
1709
+ }
1710
+ const killedPids = [];
1711
+ const failedPids = [];
1712
+ for (const proc of found) {
1713
+ const ok = await killProcess(proc.pid);
1714
+ if (ok) {
1715
+ killedPids.push(proc.pid);
1716
+ } else {
1717
+ failedPids.push(proc.pid);
1718
+ }
1719
+ }
1720
+ if (failedPids.length > 0) {
1721
+ return {
1722
+ success: false,
1723
+ error: `Successfully killed PID(s) ${killedPids.join(", ")}, but failed to kill PID(s) ${failedPids.join(", ")}.`
1724
+ };
1725
+ }
1726
+ return {
1727
+ success: true,
1728
+ message: `Successfully terminated all process(es) on port ${port} (PID(s): ${killedPids.join(", ")}).`
1729
+ };
1730
+ }
1731
+ return {
1732
+ success: false,
1733
+ error: `Unsupported action: ${action}`
1734
+ };
1735
+ } catch (err) {
1736
+ const message = err instanceof Error ? err.message : "Unknown error managing ports";
1737
+ return { success: false, error: message };
1738
+ }
1739
+ }
1740
+ });
1741
+
1742
+ // src/tools/file-operations.ts
1743
+ import { rmSync, mkdirSync as mkdirSync2, cpSync, renameSync, existsSync as existsSync3 } from "node:fs";
1744
+ import { resolve as resolve7 } from "node:path";
1745
+ import { z as z13 } from "zod";
1746
+ import { tool as tool13 } from "ai";
1747
+ var fileOperations = tool13({
1748
+ description: "Perform safe, native filesystem operations (delete, copy, move, create directories) without running shell commands. Always resolves relative paths from the project root.",
1749
+ inputSchema: z13.object({
1750
+ action: z13.enum(["delete", "createDirectory", "copy", "move"]).describe("The filesystem action to perform"),
1751
+ source: z13.string().describe("The target path to delete/copy/move/create. Relative or absolute."),
1752
+ destination: z13.string().optional().describe("The destination path. Required only for 'copy' and 'move' actions.")
1753
+ }),
1754
+ execute: async ({ action, source, destination }) => {
1755
+ try {
1756
+ const absSource = resolve7(process.cwd(), source);
1757
+ if (action === "createDirectory") {
1758
+ mkdirSync2(absSource, { recursive: true });
1759
+ return {
1760
+ success: true,
1761
+ message: `Successfully created directory hierarchy at ${absSource}`
1762
+ };
1763
+ }
1764
+ if (action === "delete") {
1765
+ if (!existsSync3(absSource)) {
1766
+ return {
1767
+ success: false,
1768
+ error: `Path does not exist: ${absSource}`
1769
+ };
1770
+ }
1771
+ rmSync(absSource, { recursive: true, force: true });
1772
+ return {
1773
+ success: true,
1774
+ message: `Successfully deleted ${absSource}`
1775
+ };
1776
+ }
1777
+ if (!destination) {
1778
+ return {
1779
+ success: false,
1780
+ error: `A 'destination' path is required for '${action}' operations.`
1781
+ };
1782
+ }
1783
+ const absDest = resolve7(process.cwd(), destination);
1784
+ if (action === "copy") {
1785
+ if (!existsSync3(absSource)) {
1786
+ return {
1787
+ success: false,
1788
+ error: `Source path does not exist: ${absSource}`
1789
+ };
1790
+ }
1791
+ cpSync(absSource, absDest, { recursive: true });
1792
+ return {
1793
+ success: true,
1794
+ message: `Successfully copied ${absSource} to ${absDest}`
1795
+ };
1796
+ }
1797
+ if (action === "move") {
1798
+ if (!existsSync3(absSource)) {
1799
+ return {
1800
+ success: false,
1801
+ error: `Source path does not exist: ${absSource}`
1802
+ };
1803
+ }
1804
+ renameSync(absSource, absDest);
1805
+ return {
1806
+ success: true,
1807
+ message: `Successfully moved/renamed ${absSource} to ${absDest}`
1808
+ };
1809
+ }
1810
+ return {
1811
+ success: false,
1812
+ error: `Unsupported action: ${action}`
1813
+ };
1814
+ } catch (err) {
1815
+ const message = err instanceof Error ? err.message : "Unknown error in file operations";
1816
+ return { success: false, error: message };
1817
+ }
1818
+ }
1819
+ });
1820
+
1166
1821
  // src/agent/run-turn.ts
1167
1822
  function sanitizeJsonString(jsonStr) {
1168
1823
  let isInsideString = false;
@@ -1213,8 +1868,8 @@ async function* runTurn(options) {
1213
1868
  };
1214
1869
  try {
1215
1870
  const { writeFileSync: writeFileSync3 } = await import("node:fs");
1216
- const { resolve: resolve5 } = await import("node:path");
1217
- debugPath = resolve5(process.cwd(), "zizou-debug.log");
1871
+ const { resolve: resolve8 } = await import("node:path");
1872
+ debugPath = resolve8(process.cwd(), "zizou-debug.log");
1218
1873
  const toolSchemas = [
1219
1874
  {
1220
1875
  name: "readFile",
@@ -1271,6 +1926,34 @@ async function* runTurn(options) {
1271
1926
  parameters: { command: "string \u2014 the shell command" },
1272
1927
  returnShape: "{ success: true, output: string } | { success: false, error }",
1273
1928
  howPassed: "ConfirmFn suspends the agent loop (via Promise) until user presses y/n in the TUI. If approved, uses child_process.exec with 15s timeout + 1MB buffer."
1929
+ },
1930
+ {
1931
+ name: "runBackground",
1932
+ description: "Execute a shell command in the background (non-blocking).",
1933
+ parameters: { command: "string \u2014 the shell command" },
1934
+ returnShape: "{ success: true, taskId: string, pid?: number } | { success: false, error: string }",
1935
+ howPassed: "Uses child_process.spawn to run command in the background, registers it in the task registry."
1936
+ },
1937
+ {
1938
+ name: "manageTasks",
1939
+ description: "Manage background tasks spawned in the session.",
1940
+ parameters: { action: "'list' | 'kill' | 'logs'", taskId: "string" },
1941
+ returnShape: "{ success: true, ... } | { success: false, error: string }",
1942
+ howPassed: "Queries active background task list, logs buffer, or terminates a task."
1943
+ },
1944
+ {
1945
+ name: "managePorts",
1946
+ description: "Find or terminate processes listening on a port.",
1947
+ parameters: { action: "'find' | 'kill'", port: "number" },
1948
+ returnShape: "{ success: true, ... } | { success: false, error: string }",
1949
+ howPassed: "Invokes platform commands (netstat, taskkill, lsof, kill) to find or stop processes on local ports."
1950
+ },
1951
+ {
1952
+ name: "fileOperations",
1953
+ description: "Perform filesystem operations natively (delete, copy, move, create directories).",
1954
+ parameters: { action: "'delete' | 'createDirectory' | 'copy' | 'move'", source: "string", destination: "string" },
1955
+ returnShape: "{ success: true, message: string } | { success: false, error: string }",
1956
+ howPassed: "Uses Node fs methods directly to modify directories and files."
1274
1957
  }
1275
1958
  ];
1276
1959
  const hr = (label) => `
@@ -1390,7 +2073,11 @@ ${"\u2500".repeat(60)}`;
1390
2073
  listDir,
1391
2074
  openFile,
1392
2075
  addFileToContext,
1393
- runBash: createRunBashTool(onConfirm)
2076
+ runBash: createRunBashTool(onConfirm),
2077
+ runBackground: createRunBackgroundTool(onConfirm),
2078
+ manageTasks,
2079
+ managePorts,
2080
+ fileOperations
1394
2081
  };
1395
2082
  const result = streamText({
1396
2083
  model,
@@ -1589,8 +2276,8 @@ ${"\u2500".repeat(60)}`;
1589
2276
  }
1590
2277
 
1591
2278
  // src/commands/index.ts
1592
- import { existsSync as existsSync3, readdirSync as readdirSync4 } from "fs";
1593
- import { join as join4 } from "path";
2279
+ import { existsSync as existsSync4, readdirSync as readdirSync5 } from "fs";
2280
+ import { join as join7 } from "path";
1594
2281
  var SHORT_LABELS2 = {
1595
2282
  groq: "Groq",
1596
2283
  google: "Google Gemini",
@@ -1602,19 +2289,19 @@ var SHORT_LABELS2 = {
1602
2289
  function getSkills() {
1603
2290
  const skills = [];
1604
2291
  const globalPath = "C:\\Users\\Arnv\\.gemini\\config\\skills";
1605
- const localPath = join4(process.cwd(), ".agents", "skills");
1606
- if (existsSync3(globalPath)) {
2292
+ const localPath = join7(process.cwd(), ".agents", "skills");
2293
+ if (existsSync4(globalPath)) {
1607
2294
  try {
1608
- const entries = readdirSync4(globalPath, { withFileTypes: true });
2295
+ const entries = readdirSync5(globalPath, { withFileTypes: true });
1609
2296
  for (const entry of entries) {
1610
2297
  if (entry.isDirectory()) skills.push(`global:${entry.name}`);
1611
2298
  }
1612
2299
  } catch {
1613
2300
  }
1614
2301
  }
1615
- if (existsSync3(localPath)) {
2302
+ if (existsSync4(localPath)) {
1616
2303
  try {
1617
- const entries = readdirSync4(localPath, { withFileTypes: true });
2304
+ const entries = readdirSync5(localPath, { withFileTypes: true });
1618
2305
  for (const entry of entries) {
1619
2306
  if (entry.isDirectory()) skills.push(`workspace:${entry.name}`);
1620
2307
  }
@@ -1994,8 +2681,8 @@ function Chat({ onChangeKeys }) {
1994
2681
  });
1995
2682
  }, []);
1996
2683
  const confirmFn = (description) => {
1997
- return new Promise((resolve5) => {
1998
- setPendingConfirm({ description, resolve: resolve5 });
2684
+ return new Promise((resolve8) => {
2685
+ setPendingConfirm({ description, resolve: resolve8 });
1999
2686
  });
2000
2687
  };
2001
2688
  useInput3((inputChar) => {