@supyagent/sdk 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -810,7 +810,7 @@ function createBashTool(options) {
810
810
  };
811
811
  }
812
812
  const start = Date.now();
813
- return new Promise((resolve) => {
813
+ return new Promise((resolve6) => {
814
814
  const child = exec(
815
815
  command,
816
816
  {
@@ -824,7 +824,7 @@ function createBashTool(options) {
824
824
  const durationMs = Date.now() - start;
825
825
  const timedOut = error?.killed === true;
826
826
  const exitCode = timedOut ? 124 : typeof error?.code === "number" ? error.code : error ? 1 : 0;
827
- resolve({
827
+ resolve6({
828
828
  stdout: truncate(stdout, maxOutput),
829
829
  stderr: truncate(stderr, maxOutput),
830
830
  exitCode,
@@ -834,7 +834,7 @@ function createBashTool(options) {
834
834
  }
835
835
  );
836
836
  child.on("error", () => {
837
- resolve({
837
+ resolve6({
838
838
  stdout: "",
839
839
  stderr: "Failed to start process",
840
840
  exitCode: 127,
@@ -877,11 +877,503 @@ function createViewImageTool(options) {
877
877
  }
878
878
  });
879
879
  }
880
+
881
+ // src/tools/edit-file.ts
882
+ import { tool as tool5, jsonSchema as jsonSchema5 } from "ai";
883
+ import { readFile, writeFile } from "fs/promises";
884
+ import { resolve } from "path";
885
+ function createEditFileTool(options) {
886
+ const cwd = options?.cwd ?? process.cwd();
887
+ const maxFileSize = options?.maxFileSize ?? 1048576;
888
+ const schema = {
889
+ type: "object",
890
+ properties: {
891
+ path: {
892
+ type: "string",
893
+ description: "File path to edit (absolute or relative to working directory)"
894
+ },
895
+ edits: {
896
+ type: "array",
897
+ description: "Array of search-and-replace operations applied sequentially. Each edit replaces the first occurrence of oldText with newText.",
898
+ items: {
899
+ type: "object",
900
+ properties: {
901
+ oldText: {
902
+ type: "string",
903
+ description: "The exact text to find in the file"
904
+ },
905
+ newText: {
906
+ type: "string",
907
+ description: "The replacement text"
908
+ }
909
+ },
910
+ required: ["oldText", "newText"]
911
+ }
912
+ }
913
+ },
914
+ required: ["path", "edits"]
915
+ };
916
+ return tool5({
917
+ description: "Edit a file by applying one or more search-and-replace operations. Each edit replaces the first occurrence of oldText with newText. More efficient than rewriting entire files \u2014 only send the changed parts. Returns an error with the current file content if any oldText is not found.",
918
+ inputSchema: jsonSchema5(schema),
919
+ execute: async (args) => {
920
+ const { path: filePath, edits } = args;
921
+ const fullPath = resolve(cwd, filePath);
922
+ let content;
923
+ try {
924
+ const buf = await readFile(fullPath);
925
+ if (buf.length > maxFileSize) {
926
+ return { error: `File too large (${buf.length} bytes). Max: ${maxFileSize} bytes.` };
927
+ }
928
+ content = buf.toString("utf-8");
929
+ } catch (err) {
930
+ const code = err.code;
931
+ if (code === "ENOENT") {
932
+ return { error: `File not found: ${filePath}` };
933
+ }
934
+ return { error: `Failed to read file: ${err.message}` };
935
+ }
936
+ let replacements = 0;
937
+ for (const edit of edits) {
938
+ const idx = content.indexOf(edit.oldText);
939
+ if (idx === -1) {
940
+ return {
941
+ error: `oldText not found in file \u2014 it may have been modified. Re-read the file and retry.
942
+
943
+ Searched for:
944
+ ${edit.oldText}`,
945
+ currentContent: content
946
+ };
947
+ }
948
+ content = content.slice(0, idx) + edit.newText + content.slice(idx + edit.oldText.length);
949
+ replacements++;
950
+ }
951
+ await writeFile(fullPath, content, "utf-8");
952
+ return { path: filePath, replacements };
953
+ }
954
+ });
955
+ }
956
+
957
+ // src/tools/grep.ts
958
+ import { tool as tool6, jsonSchema as jsonSchema6 } from "ai";
959
+ import { exec as exec2 } from "child_process";
960
+ import { resolve as resolve2 } from "path";
961
+ var MAX_OUTPUT2 = 3e4;
962
+ var DEFAULT_TIMEOUT2 = 15e3;
963
+ function createGrepTool(options) {
964
+ const cwd = options?.cwd ?? process.cwd();
965
+ const timeout = options?.timeout ?? DEFAULT_TIMEOUT2;
966
+ const maxOutput = options?.maxOutput ?? MAX_OUTPUT2;
967
+ const schema = {
968
+ type: "object",
969
+ properties: {
970
+ pattern: {
971
+ type: "string",
972
+ description: "Search pattern (regular expression)"
973
+ },
974
+ path: {
975
+ type: "string",
976
+ description: "File or directory to search in (relative to working directory). Defaults to current directory."
977
+ },
978
+ include: {
979
+ type: "string",
980
+ description: 'Glob pattern to filter files, e.g. "*.ts", "*.{js,jsx}". Only matching files are searched.'
981
+ },
982
+ ignoreCase: {
983
+ type: "boolean",
984
+ description: "Case-insensitive search. Defaults to false."
985
+ },
986
+ maxResults: {
987
+ type: "number",
988
+ description: "Maximum number of matching lines to return. Defaults to 100."
989
+ }
990
+ },
991
+ required: ["pattern"]
992
+ };
993
+ return tool6({
994
+ description: "Search file contents for a pattern (regular expression). Returns matching lines with file paths and line numbers. Useful for finding function definitions, usages, imports, config values, and more.",
995
+ inputSchema: jsonSchema6(schema),
996
+ execute: async (args) => {
997
+ const {
998
+ pattern,
999
+ path: searchPath,
1000
+ include,
1001
+ ignoreCase,
1002
+ maxResults
1003
+ } = args;
1004
+ const target = resolve2(cwd, searchPath ?? ".");
1005
+ const limit = maxResults ?? 100;
1006
+ const rgArgs = [
1007
+ "--no-heading",
1008
+ "--line-number",
1009
+ "--color=never",
1010
+ `-m ${limit}`
1011
+ ];
1012
+ if (ignoreCase) rgArgs.push("-i");
1013
+ if (include) rgArgs.push(`--glob '${include}'`);
1014
+ const grepArgs = [
1015
+ "-rn",
1016
+ "--color=never",
1017
+ `-m ${limit}`
1018
+ ];
1019
+ if (ignoreCase) grepArgs.push("-i");
1020
+ if (include) grepArgs.push(`--include='${include}'`);
1021
+ const escaped = pattern.replace(/'/g, "'\\''");
1022
+ const command = `rg ${rgArgs.join(" ")} '${escaped}' '${target}' 2>/dev/null || grep ${grepArgs.join(" ")} '${escaped}' '${target}' 2>/dev/null`;
1023
+ return new Promise((resolvePromise) => {
1024
+ exec2(
1025
+ command,
1026
+ {
1027
+ cwd,
1028
+ timeout,
1029
+ maxBuffer: 10 * 1024 * 1024,
1030
+ env: { ...process.env, TERM: "dumb" }
1031
+ },
1032
+ (error, stdout) => {
1033
+ if (error && error.code !== 1 && !error.killed) {
1034
+ resolvePromise({ error: `Search failed: ${error.message}` });
1035
+ return;
1036
+ }
1037
+ if (error?.killed) {
1038
+ resolvePromise({ error: "Search timed out" });
1039
+ return;
1040
+ }
1041
+ const output = stdout.trim();
1042
+ if (!output) {
1043
+ resolvePromise({ matches: "", matchCount: 0, truncated: false });
1044
+ return;
1045
+ }
1046
+ const truncated = output.length > maxOutput;
1047
+ const matches = truncated ? output.slice(0, maxOutput) + "\n... (truncated)" : output;
1048
+ const matchCount = matches.split("\n").filter(Boolean).length;
1049
+ resolvePromise({ matches, matchCount, truncated });
1050
+ }
1051
+ );
1052
+ });
1053
+ }
1054
+ });
1055
+ }
1056
+
1057
+ // src/tools/find.ts
1058
+ import { tool as tool7, jsonSchema as jsonSchema7 } from "ai";
1059
+ import { exec as exec3 } from "child_process";
1060
+ import { resolve as resolve3 } from "path";
1061
+ var DEFAULT_TIMEOUT3 = 15e3;
1062
+ var MAX_OUTPUT3 = 3e4;
1063
+ function createFindTool(options) {
1064
+ const cwd = options?.cwd ?? process.cwd();
1065
+ const timeout = options?.timeout ?? DEFAULT_TIMEOUT3;
1066
+ const maxOutput = options?.maxOutput ?? MAX_OUTPUT3;
1067
+ const schema = {
1068
+ type: "object",
1069
+ properties: {
1070
+ pattern: {
1071
+ type: "string",
1072
+ description: 'File name or glob pattern to search for, e.g. "*.ts", "config.json", "README*"'
1073
+ },
1074
+ path: {
1075
+ type: "string",
1076
+ description: "Directory to search in (relative to working directory). Defaults to current directory."
1077
+ },
1078
+ type: {
1079
+ type: "string",
1080
+ description: 'Filter by type: "f" for files only, "d" for directories only. Defaults to both.',
1081
+ enum: ["f", "d"]
1082
+ },
1083
+ maxDepth: {
1084
+ type: "number",
1085
+ description: "Maximum directory depth to search. Defaults to no limit."
1086
+ },
1087
+ maxResults: {
1088
+ type: "number",
1089
+ description: "Maximum number of results to return. Defaults to 200."
1090
+ }
1091
+ },
1092
+ required: ["pattern"]
1093
+ };
1094
+ const IGNORE_DIRS = [
1095
+ "node_modules",
1096
+ ".git",
1097
+ "dist",
1098
+ "build",
1099
+ ".next",
1100
+ "__pycache__",
1101
+ ".venv",
1102
+ "venv",
1103
+ ".cache",
1104
+ "coverage",
1105
+ ".turbo"
1106
+ ];
1107
+ return tool7({
1108
+ description: "Find files and directories by name or glob pattern. Returns matching paths relative to the working directory. Automatically ignores common directories (node_modules, .git, dist, etc.).",
1109
+ inputSchema: jsonSchema7(schema),
1110
+ execute: async (args) => {
1111
+ const {
1112
+ pattern,
1113
+ path: searchPath,
1114
+ type: fileType,
1115
+ maxDepth,
1116
+ maxResults
1117
+ } = args;
1118
+ const target = resolve3(cwd, searchPath ?? ".");
1119
+ const limit = maxResults ?? 200;
1120
+ const pruneExpr = IGNORE_DIRS.map((d) => `-name '${d}' -prune`).join(" -o ");
1121
+ const parts = [
1122
+ `find '${target}'`,
1123
+ `\\( ${pruneExpr} \\)`,
1124
+ `-o -name '${pattern.replace(/'/g, "'\\''")}'`
1125
+ ];
1126
+ if (fileType) parts.push(`-type ${fileType}`);
1127
+ parts.push("-print");
1128
+ parts.push(`| head -n ${limit}`);
1129
+ if (maxDepth !== void 0) {
1130
+ parts.splice(1, 0, `-maxdepth ${maxDepth}`);
1131
+ }
1132
+ const command = parts.join(" ");
1133
+ return new Promise((resolvePromise) => {
1134
+ exec3(
1135
+ command,
1136
+ {
1137
+ cwd,
1138
+ timeout,
1139
+ maxBuffer: 10 * 1024 * 1024,
1140
+ env: { ...process.env, TERM: "dumb" }
1141
+ },
1142
+ (error, stdout) => {
1143
+ if (error?.killed) {
1144
+ resolvePromise({ error: "Search timed out" });
1145
+ return;
1146
+ }
1147
+ const output = stdout.trim();
1148
+ if (!output) {
1149
+ resolvePromise({ files: [], count: 0, truncated: false });
1150
+ return;
1151
+ }
1152
+ const truncated = output.length > maxOutput;
1153
+ const raw = truncated ? output.slice(0, maxOutput) : output;
1154
+ const files = raw.split("\n").filter(Boolean);
1155
+ resolvePromise({
1156
+ files,
1157
+ count: files.length,
1158
+ truncated: truncated || files.length >= limit
1159
+ });
1160
+ }
1161
+ );
1162
+ });
1163
+ }
1164
+ });
1165
+ }
1166
+
1167
+ // src/tools/read-file-range.ts
1168
+ import { tool as tool8, jsonSchema as jsonSchema8 } from "ai";
1169
+ import { open } from "fs/promises";
1170
+ import { resolve as resolve4 } from "path";
1171
+ var DEFAULT_MAX_LINES = 200;
1172
+ function createReadFileRangeTool(options) {
1173
+ const cwd = options?.cwd ?? process.cwd();
1174
+ const maxLines = options?.maxLines ?? DEFAULT_MAX_LINES;
1175
+ const schema = {
1176
+ type: "object",
1177
+ properties: {
1178
+ path: {
1179
+ type: "string",
1180
+ description: "File path to read (absolute or relative to working directory)"
1181
+ },
1182
+ startLine: {
1183
+ type: "number",
1184
+ description: "First line to read (1-based, inclusive). Defaults to 1."
1185
+ },
1186
+ endLine: {
1187
+ type: "number",
1188
+ description: "Last line to read (1-based, inclusive). Defaults to startLine + 200."
1189
+ }
1190
+ },
1191
+ required: ["path"]
1192
+ };
1193
+ return tool8({
1194
+ description: "Read a specific range of lines from a file, with line numbers. More efficient than reading entire files \u2014 use this after grep to read context around matches, or to inspect specific sections of large files.",
1195
+ inputSchema: jsonSchema8(schema),
1196
+ execute: async (args) => {
1197
+ const { path: filePath, startLine, endLine } = args;
1198
+ const fullPath = resolve4(cwd, filePath);
1199
+ const start = Math.max(1, startLine ?? 1);
1200
+ const requestedEnd = endLine ?? start + maxLines - 1;
1201
+ const end = Math.min(requestedEnd, start + maxLines - 1);
1202
+ let fh;
1203
+ try {
1204
+ fh = await open(fullPath, "r");
1205
+ } catch (err) {
1206
+ const code = err.code;
1207
+ if (code === "ENOENT") {
1208
+ return { error: `File not found: ${filePath}` };
1209
+ }
1210
+ return { error: `Failed to open file: ${err.message}` };
1211
+ }
1212
+ try {
1213
+ const lines = [];
1214
+ let lineNum = 0;
1215
+ for await (const line of fh.readLines()) {
1216
+ lineNum++;
1217
+ if (lineNum > end) break;
1218
+ if (lineNum >= start) {
1219
+ lines.push(`${lineNum} ${line}`);
1220
+ }
1221
+ }
1222
+ if (lines.length === 0 && start > 1) {
1223
+ return {
1224
+ error: `File only has ${lineNum} lines. Requested start line: ${start}.`
1225
+ };
1226
+ }
1227
+ return {
1228
+ path: filePath,
1229
+ content: lines.join("\n"),
1230
+ startLine: start,
1231
+ endLine: Math.min(end, lineNum),
1232
+ totalLines: lineNum
1233
+ };
1234
+ } finally {
1235
+ await fh.close();
1236
+ }
1237
+ }
1238
+ });
1239
+ }
1240
+
1241
+ // src/tools/append-file.ts
1242
+ import { tool as tool9, jsonSchema as jsonSchema9 } from "ai";
1243
+ import { appendFile as fsAppendFile } from "fs/promises";
1244
+ import { resolve as resolve5 } from "path";
1245
+ function createAppendFileTool(options) {
1246
+ const cwd = options?.cwd ?? process.cwd();
1247
+ const schema = {
1248
+ type: "object",
1249
+ properties: {
1250
+ path: {
1251
+ type: "string",
1252
+ description: "File path to append to (absolute or relative to working directory). Created if it doesn't exist."
1253
+ },
1254
+ content: {
1255
+ type: "string",
1256
+ description: "Content to append to the end of the file. Include a leading newline if you want separation from existing content."
1257
+ }
1258
+ },
1259
+ required: ["path", "content"]
1260
+ };
1261
+ return tool9({
1262
+ description: "Append content to the end of a file. Creates the file if it doesn't exist. More efficient than reading a file and rewriting it \u2014 use this for building up logs, CSVs, reports, or any file where you only need to add content.",
1263
+ inputSchema: jsonSchema9(schema),
1264
+ execute: async (args) => {
1265
+ const { path: filePath, content } = args;
1266
+ const fullPath = resolve5(cwd, filePath);
1267
+ try {
1268
+ await fsAppendFile(fullPath, content, "utf-8");
1269
+ return {
1270
+ path: filePath,
1271
+ bytesAppended: Buffer.byteLength(content, "utf-8")
1272
+ };
1273
+ } catch (err) {
1274
+ return { error: `Failed to append to file: ${err.message}` };
1275
+ }
1276
+ }
1277
+ });
1278
+ }
1279
+
1280
+ // src/tools/http-request.ts
1281
+ import { tool as tool10, jsonSchema as jsonSchema10 } from "ai";
1282
+ var DEFAULT_TIMEOUT4 = 3e4;
1283
+ var MAX_RESPONSE_SIZE = 1e5;
1284
+ function createHttpRequestTool(options) {
1285
+ const defaultTimeout = options?.timeout ?? DEFAULT_TIMEOUT4;
1286
+ const maxResponseSize = options?.maxResponseSize ?? MAX_RESPONSE_SIZE;
1287
+ const defaultHeaders = options?.defaultHeaders ?? {};
1288
+ const schema = {
1289
+ type: "object",
1290
+ properties: {
1291
+ url: {
1292
+ type: "string",
1293
+ description: "The URL to request (must include protocol, e.g. https://)"
1294
+ },
1295
+ method: {
1296
+ type: "string",
1297
+ description: "HTTP method. Defaults to GET.",
1298
+ enum: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]
1299
+ },
1300
+ headers: {
1301
+ type: "object",
1302
+ description: 'Request headers as key-value pairs, e.g. { "Content-Type": "application/json" }',
1303
+ additionalProperties: { type: "string" }
1304
+ },
1305
+ body: {
1306
+ type: "string",
1307
+ description: "Request body as a string. For JSON, stringify the object first."
1308
+ },
1309
+ timeout: {
1310
+ type: "number",
1311
+ description: "Request timeout in milliseconds. Defaults to 30000."
1312
+ }
1313
+ },
1314
+ required: ["url"]
1315
+ };
1316
+ return tool10({
1317
+ description: "Make an HTTP request to any URL. Supports GET, POST, PUT, PATCH, DELETE with custom headers and body. Returns status code, headers, and response body. Use this for calling APIs, checking endpoints, fetching data, or sending webhooks.",
1318
+ inputSchema: jsonSchema10(schema),
1319
+ execute: async (args) => {
1320
+ const {
1321
+ url,
1322
+ method,
1323
+ headers,
1324
+ body,
1325
+ timeout: reqTimeout
1326
+ } = args;
1327
+ const effectiveTimeout = reqTimeout ?? defaultTimeout;
1328
+ try {
1329
+ const controller = new AbortController();
1330
+ const timer = setTimeout(() => controller.abort(), effectiveTimeout);
1331
+ const start = Date.now();
1332
+ const response = await fetch(url, {
1333
+ method: method ?? "GET",
1334
+ headers: { ...defaultHeaders, ...headers },
1335
+ body: body ?? void 0,
1336
+ signal: controller.signal
1337
+ });
1338
+ const durationMs = Date.now() - start;
1339
+ clearTimeout(timer);
1340
+ const responseHeaders = {};
1341
+ response.headers.forEach((value, key) => {
1342
+ responseHeaders[key] = value;
1343
+ });
1344
+ let responseBody = await response.text();
1345
+ const truncated = responseBody.length > maxResponseSize;
1346
+ if (truncated) {
1347
+ responseBody = responseBody.slice(0, maxResponseSize) + "\n... (truncated)";
1348
+ }
1349
+ return {
1350
+ status: response.status,
1351
+ statusText: response.statusText,
1352
+ headers: responseHeaders,
1353
+ body: responseBody,
1354
+ durationMs,
1355
+ truncated
1356
+ };
1357
+ } catch (err) {
1358
+ if (err instanceof DOMException && err.name === "AbortError") {
1359
+ return { error: `Request timed out after ${effectiveTimeout}ms` };
1360
+ }
1361
+ return { error: `Request failed: ${err.message}` };
1362
+ }
1363
+ }
1364
+ });
1365
+ }
880
1366
  export {
881
1367
  buildSkillsSystemPrompt,
882
1368
  createApiCallTool,
1369
+ createAppendFileTool,
883
1370
  createBashTool,
1371
+ createEditFileTool,
1372
+ createFindTool,
1373
+ createGrepTool,
1374
+ createHttpRequestTool,
884
1375
  createLoadSkillTool,
1376
+ createReadFileRangeTool,
885
1377
  createViewImageTool,
886
1378
  findSkill,
887
1379
  parseSkillsMarkdown,