@supyagent/sdk 0.2.0 → 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.cjs CHANGED
@@ -22,8 +22,14 @@ var src_exports = {};
22
22
  __export(src_exports, {
23
23
  buildSkillsSystemPrompt: () => buildSkillsSystemPrompt,
24
24
  createApiCallTool: () => createApiCallTool,
25
+ createAppendFileTool: () => createAppendFileTool,
25
26
  createBashTool: () => createBashTool,
27
+ createEditFileTool: () => createEditFileTool,
28
+ createFindTool: () => createFindTool,
29
+ createGrepTool: () => createGrepTool,
30
+ createHttpRequestTool: () => createHttpRequestTool,
26
31
  createLoadSkillTool: () => createLoadSkillTool,
32
+ createReadFileRangeTool: () => createReadFileRangeTool,
27
33
  createViewImageTool: () => createViewImageTool,
28
34
  findSkill: () => findSkill,
29
35
  parseSkillsMarkdown: () => parseSkillsMarkdown,
@@ -411,6 +417,33 @@ function createDataPlane(fetcher, baseUrl, apiKey, accountId) {
411
417
  }
412
418
  }
413
419
  return response;
420
+ },
421
+ async searchTools(query) {
422
+ const res = await fetcher(`/api/v1/tools/search/${encodeURIComponent(query)}`);
423
+ const json = await res.json();
424
+ const data = json.data ?? json;
425
+ return {
426
+ tools: Array.isArray(data.tools) ? data.tools : [],
427
+ total: data.total ?? 0
428
+ };
429
+ },
430
+ async toolsByProvider(provider) {
431
+ const res = await fetcher(`/api/v1/tools/provider/${encodeURIComponent(provider)}`);
432
+ const json = await res.json();
433
+ const data = json.data ?? json;
434
+ return {
435
+ tools: Array.isArray(data.tools) ? data.tools : [],
436
+ total: data.total ?? 0
437
+ };
438
+ },
439
+ async toolsByService(service) {
440
+ const res = await fetcher(`/api/v1/tools/service/${encodeURIComponent(service)}`);
441
+ const json = await res.json();
442
+ const data = json.data ?? json;
443
+ return {
444
+ tools: Array.isArray(data.tools) ? data.tools : [],
445
+ total: data.total ?? 0
446
+ };
414
447
  }
415
448
  };
416
449
  }
@@ -816,7 +849,7 @@ function createBashTool(options) {
816
849
  };
817
850
  }
818
851
  const start = Date.now();
819
- return new Promise((resolve) => {
852
+ return new Promise((resolve6) => {
820
853
  const child = (0, import_node_child_process.exec)(
821
854
  command,
822
855
  {
@@ -830,7 +863,7 @@ function createBashTool(options) {
830
863
  const durationMs = Date.now() - start;
831
864
  const timedOut = error?.killed === true;
832
865
  const exitCode = timedOut ? 124 : typeof error?.code === "number" ? error.code : error ? 1 : 0;
833
- resolve({
866
+ resolve6({
834
867
  stdout: truncate(stdout, maxOutput),
835
868
  stderr: truncate(stderr, maxOutput),
836
869
  exitCode,
@@ -840,7 +873,7 @@ function createBashTool(options) {
840
873
  }
841
874
  );
842
875
  child.on("error", () => {
843
- resolve({
876
+ resolve6({
844
877
  stdout: "",
845
878
  stderr: "Failed to start process",
846
879
  exitCode: 127,
@@ -883,12 +916,504 @@ function createViewImageTool(options) {
883
916
  }
884
917
  });
885
918
  }
919
+
920
+ // src/tools/edit-file.ts
921
+ var import_ai5 = require("ai");
922
+ var import_promises = require("fs/promises");
923
+ var import_node_path = require("path");
924
+ function createEditFileTool(options) {
925
+ const cwd = options?.cwd ?? process.cwd();
926
+ const maxFileSize = options?.maxFileSize ?? 1048576;
927
+ const schema = {
928
+ type: "object",
929
+ properties: {
930
+ path: {
931
+ type: "string",
932
+ description: "File path to edit (absolute or relative to working directory)"
933
+ },
934
+ edits: {
935
+ type: "array",
936
+ description: "Array of search-and-replace operations applied sequentially. Each edit replaces the first occurrence of oldText with newText.",
937
+ items: {
938
+ type: "object",
939
+ properties: {
940
+ oldText: {
941
+ type: "string",
942
+ description: "The exact text to find in the file"
943
+ },
944
+ newText: {
945
+ type: "string",
946
+ description: "The replacement text"
947
+ }
948
+ },
949
+ required: ["oldText", "newText"]
950
+ }
951
+ }
952
+ },
953
+ required: ["path", "edits"]
954
+ };
955
+ return (0, import_ai5.tool)({
956
+ 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.",
957
+ inputSchema: (0, import_ai5.jsonSchema)(schema),
958
+ execute: async (args) => {
959
+ const { path: filePath, edits } = args;
960
+ const fullPath = (0, import_node_path.resolve)(cwd, filePath);
961
+ let content;
962
+ try {
963
+ const buf = await (0, import_promises.readFile)(fullPath);
964
+ if (buf.length > maxFileSize) {
965
+ return { error: `File too large (${buf.length} bytes). Max: ${maxFileSize} bytes.` };
966
+ }
967
+ content = buf.toString("utf-8");
968
+ } catch (err) {
969
+ const code = err.code;
970
+ if (code === "ENOENT") {
971
+ return { error: `File not found: ${filePath}` };
972
+ }
973
+ return { error: `Failed to read file: ${err.message}` };
974
+ }
975
+ let replacements = 0;
976
+ for (const edit of edits) {
977
+ const idx = content.indexOf(edit.oldText);
978
+ if (idx === -1) {
979
+ return {
980
+ error: `oldText not found in file \u2014 it may have been modified. Re-read the file and retry.
981
+
982
+ Searched for:
983
+ ${edit.oldText}`,
984
+ currentContent: content
985
+ };
986
+ }
987
+ content = content.slice(0, idx) + edit.newText + content.slice(idx + edit.oldText.length);
988
+ replacements++;
989
+ }
990
+ await (0, import_promises.writeFile)(fullPath, content, "utf-8");
991
+ return { path: filePath, replacements };
992
+ }
993
+ });
994
+ }
995
+
996
+ // src/tools/grep.ts
997
+ var import_ai6 = require("ai");
998
+ var import_node_child_process2 = require("child_process");
999
+ var import_node_path2 = require("path");
1000
+ var MAX_OUTPUT2 = 3e4;
1001
+ var DEFAULT_TIMEOUT2 = 15e3;
1002
+ function createGrepTool(options) {
1003
+ const cwd = options?.cwd ?? process.cwd();
1004
+ const timeout = options?.timeout ?? DEFAULT_TIMEOUT2;
1005
+ const maxOutput = options?.maxOutput ?? MAX_OUTPUT2;
1006
+ const schema = {
1007
+ type: "object",
1008
+ properties: {
1009
+ pattern: {
1010
+ type: "string",
1011
+ description: "Search pattern (regular expression)"
1012
+ },
1013
+ path: {
1014
+ type: "string",
1015
+ description: "File or directory to search in (relative to working directory). Defaults to current directory."
1016
+ },
1017
+ include: {
1018
+ type: "string",
1019
+ description: 'Glob pattern to filter files, e.g. "*.ts", "*.{js,jsx}". Only matching files are searched.'
1020
+ },
1021
+ ignoreCase: {
1022
+ type: "boolean",
1023
+ description: "Case-insensitive search. Defaults to false."
1024
+ },
1025
+ maxResults: {
1026
+ type: "number",
1027
+ description: "Maximum number of matching lines to return. Defaults to 100."
1028
+ }
1029
+ },
1030
+ required: ["pattern"]
1031
+ };
1032
+ return (0, import_ai6.tool)({
1033
+ 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.",
1034
+ inputSchema: (0, import_ai6.jsonSchema)(schema),
1035
+ execute: async (args) => {
1036
+ const {
1037
+ pattern,
1038
+ path: searchPath,
1039
+ include,
1040
+ ignoreCase,
1041
+ maxResults
1042
+ } = args;
1043
+ const target = (0, import_node_path2.resolve)(cwd, searchPath ?? ".");
1044
+ const limit = maxResults ?? 100;
1045
+ const rgArgs = [
1046
+ "--no-heading",
1047
+ "--line-number",
1048
+ "--color=never",
1049
+ `-m ${limit}`
1050
+ ];
1051
+ if (ignoreCase) rgArgs.push("-i");
1052
+ if (include) rgArgs.push(`--glob '${include}'`);
1053
+ const grepArgs = [
1054
+ "-rn",
1055
+ "--color=never",
1056
+ `-m ${limit}`
1057
+ ];
1058
+ if (ignoreCase) grepArgs.push("-i");
1059
+ if (include) grepArgs.push(`--include='${include}'`);
1060
+ const escaped = pattern.replace(/'/g, "'\\''");
1061
+ const command = `rg ${rgArgs.join(" ")} '${escaped}' '${target}' 2>/dev/null || grep ${grepArgs.join(" ")} '${escaped}' '${target}' 2>/dev/null`;
1062
+ return new Promise((resolvePromise) => {
1063
+ (0, import_node_child_process2.exec)(
1064
+ command,
1065
+ {
1066
+ cwd,
1067
+ timeout,
1068
+ maxBuffer: 10 * 1024 * 1024,
1069
+ env: { ...process.env, TERM: "dumb" }
1070
+ },
1071
+ (error, stdout) => {
1072
+ if (error && error.code !== 1 && !error.killed) {
1073
+ resolvePromise({ error: `Search failed: ${error.message}` });
1074
+ return;
1075
+ }
1076
+ if (error?.killed) {
1077
+ resolvePromise({ error: "Search timed out" });
1078
+ return;
1079
+ }
1080
+ const output = stdout.trim();
1081
+ if (!output) {
1082
+ resolvePromise({ matches: "", matchCount: 0, truncated: false });
1083
+ return;
1084
+ }
1085
+ const truncated = output.length > maxOutput;
1086
+ const matches = truncated ? output.slice(0, maxOutput) + "\n... (truncated)" : output;
1087
+ const matchCount = matches.split("\n").filter(Boolean).length;
1088
+ resolvePromise({ matches, matchCount, truncated });
1089
+ }
1090
+ );
1091
+ });
1092
+ }
1093
+ });
1094
+ }
1095
+
1096
+ // src/tools/find.ts
1097
+ var import_ai7 = require("ai");
1098
+ var import_node_child_process3 = require("child_process");
1099
+ var import_node_path3 = require("path");
1100
+ var DEFAULT_TIMEOUT3 = 15e3;
1101
+ var MAX_OUTPUT3 = 3e4;
1102
+ function createFindTool(options) {
1103
+ const cwd = options?.cwd ?? process.cwd();
1104
+ const timeout = options?.timeout ?? DEFAULT_TIMEOUT3;
1105
+ const maxOutput = options?.maxOutput ?? MAX_OUTPUT3;
1106
+ const schema = {
1107
+ type: "object",
1108
+ properties: {
1109
+ pattern: {
1110
+ type: "string",
1111
+ description: 'File name or glob pattern to search for, e.g. "*.ts", "config.json", "README*"'
1112
+ },
1113
+ path: {
1114
+ type: "string",
1115
+ description: "Directory to search in (relative to working directory). Defaults to current directory."
1116
+ },
1117
+ type: {
1118
+ type: "string",
1119
+ description: 'Filter by type: "f" for files only, "d" for directories only. Defaults to both.',
1120
+ enum: ["f", "d"]
1121
+ },
1122
+ maxDepth: {
1123
+ type: "number",
1124
+ description: "Maximum directory depth to search. Defaults to no limit."
1125
+ },
1126
+ maxResults: {
1127
+ type: "number",
1128
+ description: "Maximum number of results to return. Defaults to 200."
1129
+ }
1130
+ },
1131
+ required: ["pattern"]
1132
+ };
1133
+ const IGNORE_DIRS = [
1134
+ "node_modules",
1135
+ ".git",
1136
+ "dist",
1137
+ "build",
1138
+ ".next",
1139
+ "__pycache__",
1140
+ ".venv",
1141
+ "venv",
1142
+ ".cache",
1143
+ "coverage",
1144
+ ".turbo"
1145
+ ];
1146
+ return (0, import_ai7.tool)({
1147
+ 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.).",
1148
+ inputSchema: (0, import_ai7.jsonSchema)(schema),
1149
+ execute: async (args) => {
1150
+ const {
1151
+ pattern,
1152
+ path: searchPath,
1153
+ type: fileType,
1154
+ maxDepth,
1155
+ maxResults
1156
+ } = args;
1157
+ const target = (0, import_node_path3.resolve)(cwd, searchPath ?? ".");
1158
+ const limit = maxResults ?? 200;
1159
+ const pruneExpr = IGNORE_DIRS.map((d) => `-name '${d}' -prune`).join(" -o ");
1160
+ const parts = [
1161
+ `find '${target}'`,
1162
+ `\\( ${pruneExpr} \\)`,
1163
+ `-o -name '${pattern.replace(/'/g, "'\\''")}'`
1164
+ ];
1165
+ if (fileType) parts.push(`-type ${fileType}`);
1166
+ parts.push("-print");
1167
+ parts.push(`| head -n ${limit}`);
1168
+ if (maxDepth !== void 0) {
1169
+ parts.splice(1, 0, `-maxdepth ${maxDepth}`);
1170
+ }
1171
+ const command = parts.join(" ");
1172
+ return new Promise((resolvePromise) => {
1173
+ (0, import_node_child_process3.exec)(
1174
+ command,
1175
+ {
1176
+ cwd,
1177
+ timeout,
1178
+ maxBuffer: 10 * 1024 * 1024,
1179
+ env: { ...process.env, TERM: "dumb" }
1180
+ },
1181
+ (error, stdout) => {
1182
+ if (error?.killed) {
1183
+ resolvePromise({ error: "Search timed out" });
1184
+ return;
1185
+ }
1186
+ const output = stdout.trim();
1187
+ if (!output) {
1188
+ resolvePromise({ files: [], count: 0, truncated: false });
1189
+ return;
1190
+ }
1191
+ const truncated = output.length > maxOutput;
1192
+ const raw = truncated ? output.slice(0, maxOutput) : output;
1193
+ const files = raw.split("\n").filter(Boolean);
1194
+ resolvePromise({
1195
+ files,
1196
+ count: files.length,
1197
+ truncated: truncated || files.length >= limit
1198
+ });
1199
+ }
1200
+ );
1201
+ });
1202
+ }
1203
+ });
1204
+ }
1205
+
1206
+ // src/tools/read-file-range.ts
1207
+ var import_ai8 = require("ai");
1208
+ var import_promises2 = require("fs/promises");
1209
+ var import_node_path4 = require("path");
1210
+ var DEFAULT_MAX_LINES = 200;
1211
+ function createReadFileRangeTool(options) {
1212
+ const cwd = options?.cwd ?? process.cwd();
1213
+ const maxLines = options?.maxLines ?? DEFAULT_MAX_LINES;
1214
+ const schema = {
1215
+ type: "object",
1216
+ properties: {
1217
+ path: {
1218
+ type: "string",
1219
+ description: "File path to read (absolute or relative to working directory)"
1220
+ },
1221
+ startLine: {
1222
+ type: "number",
1223
+ description: "First line to read (1-based, inclusive). Defaults to 1."
1224
+ },
1225
+ endLine: {
1226
+ type: "number",
1227
+ description: "Last line to read (1-based, inclusive). Defaults to startLine + 200."
1228
+ }
1229
+ },
1230
+ required: ["path"]
1231
+ };
1232
+ return (0, import_ai8.tool)({
1233
+ 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.",
1234
+ inputSchema: (0, import_ai8.jsonSchema)(schema),
1235
+ execute: async (args) => {
1236
+ const { path: filePath, startLine, endLine } = args;
1237
+ const fullPath = (0, import_node_path4.resolve)(cwd, filePath);
1238
+ const start = Math.max(1, startLine ?? 1);
1239
+ const requestedEnd = endLine ?? start + maxLines - 1;
1240
+ const end = Math.min(requestedEnd, start + maxLines - 1);
1241
+ let fh;
1242
+ try {
1243
+ fh = await (0, import_promises2.open)(fullPath, "r");
1244
+ } catch (err) {
1245
+ const code = err.code;
1246
+ if (code === "ENOENT") {
1247
+ return { error: `File not found: ${filePath}` };
1248
+ }
1249
+ return { error: `Failed to open file: ${err.message}` };
1250
+ }
1251
+ try {
1252
+ const lines = [];
1253
+ let lineNum = 0;
1254
+ for await (const line of fh.readLines()) {
1255
+ lineNum++;
1256
+ if (lineNum > end) break;
1257
+ if (lineNum >= start) {
1258
+ lines.push(`${lineNum} ${line}`);
1259
+ }
1260
+ }
1261
+ if (lines.length === 0 && start > 1) {
1262
+ return {
1263
+ error: `File only has ${lineNum} lines. Requested start line: ${start}.`
1264
+ };
1265
+ }
1266
+ return {
1267
+ path: filePath,
1268
+ content: lines.join("\n"),
1269
+ startLine: start,
1270
+ endLine: Math.min(end, lineNum),
1271
+ totalLines: lineNum
1272
+ };
1273
+ } finally {
1274
+ await fh.close();
1275
+ }
1276
+ }
1277
+ });
1278
+ }
1279
+
1280
+ // src/tools/append-file.ts
1281
+ var import_ai9 = require("ai");
1282
+ var import_promises3 = require("fs/promises");
1283
+ var import_node_path5 = require("path");
1284
+ function createAppendFileTool(options) {
1285
+ const cwd = options?.cwd ?? process.cwd();
1286
+ const schema = {
1287
+ type: "object",
1288
+ properties: {
1289
+ path: {
1290
+ type: "string",
1291
+ description: "File path to append to (absolute or relative to working directory). Created if it doesn't exist."
1292
+ },
1293
+ content: {
1294
+ type: "string",
1295
+ description: "Content to append to the end of the file. Include a leading newline if you want separation from existing content."
1296
+ }
1297
+ },
1298
+ required: ["path", "content"]
1299
+ };
1300
+ return (0, import_ai9.tool)({
1301
+ 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.",
1302
+ inputSchema: (0, import_ai9.jsonSchema)(schema),
1303
+ execute: async (args) => {
1304
+ const { path: filePath, content } = args;
1305
+ const fullPath = (0, import_node_path5.resolve)(cwd, filePath);
1306
+ try {
1307
+ await (0, import_promises3.appendFile)(fullPath, content, "utf-8");
1308
+ return {
1309
+ path: filePath,
1310
+ bytesAppended: Buffer.byteLength(content, "utf-8")
1311
+ };
1312
+ } catch (err) {
1313
+ return { error: `Failed to append to file: ${err.message}` };
1314
+ }
1315
+ }
1316
+ });
1317
+ }
1318
+
1319
+ // src/tools/http-request.ts
1320
+ var import_ai10 = require("ai");
1321
+ var DEFAULT_TIMEOUT4 = 3e4;
1322
+ var MAX_RESPONSE_SIZE = 1e5;
1323
+ function createHttpRequestTool(options) {
1324
+ const defaultTimeout = options?.timeout ?? DEFAULT_TIMEOUT4;
1325
+ const maxResponseSize = options?.maxResponseSize ?? MAX_RESPONSE_SIZE;
1326
+ const defaultHeaders = options?.defaultHeaders ?? {};
1327
+ const schema = {
1328
+ type: "object",
1329
+ properties: {
1330
+ url: {
1331
+ type: "string",
1332
+ description: "The URL to request (must include protocol, e.g. https://)"
1333
+ },
1334
+ method: {
1335
+ type: "string",
1336
+ description: "HTTP method. Defaults to GET.",
1337
+ enum: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]
1338
+ },
1339
+ headers: {
1340
+ type: "object",
1341
+ description: 'Request headers as key-value pairs, e.g. { "Content-Type": "application/json" }',
1342
+ additionalProperties: { type: "string" }
1343
+ },
1344
+ body: {
1345
+ type: "string",
1346
+ description: "Request body as a string. For JSON, stringify the object first."
1347
+ },
1348
+ timeout: {
1349
+ type: "number",
1350
+ description: "Request timeout in milliseconds. Defaults to 30000."
1351
+ }
1352
+ },
1353
+ required: ["url"]
1354
+ };
1355
+ return (0, import_ai10.tool)({
1356
+ 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.",
1357
+ inputSchema: (0, import_ai10.jsonSchema)(schema),
1358
+ execute: async (args) => {
1359
+ const {
1360
+ url,
1361
+ method,
1362
+ headers,
1363
+ body,
1364
+ timeout: reqTimeout
1365
+ } = args;
1366
+ const effectiveTimeout = reqTimeout ?? defaultTimeout;
1367
+ try {
1368
+ const controller = new AbortController();
1369
+ const timer = setTimeout(() => controller.abort(), effectiveTimeout);
1370
+ const start = Date.now();
1371
+ const response = await fetch(url, {
1372
+ method: method ?? "GET",
1373
+ headers: { ...defaultHeaders, ...headers },
1374
+ body: body ?? void 0,
1375
+ signal: controller.signal
1376
+ });
1377
+ const durationMs = Date.now() - start;
1378
+ clearTimeout(timer);
1379
+ const responseHeaders = {};
1380
+ response.headers.forEach((value, key) => {
1381
+ responseHeaders[key] = value;
1382
+ });
1383
+ let responseBody = await response.text();
1384
+ const truncated = responseBody.length > maxResponseSize;
1385
+ if (truncated) {
1386
+ responseBody = responseBody.slice(0, maxResponseSize) + "\n... (truncated)";
1387
+ }
1388
+ return {
1389
+ status: response.status,
1390
+ statusText: response.statusText,
1391
+ headers: responseHeaders,
1392
+ body: responseBody,
1393
+ durationMs,
1394
+ truncated
1395
+ };
1396
+ } catch (err) {
1397
+ if (err instanceof DOMException && err.name === "AbortError") {
1398
+ return { error: `Request timed out after ${effectiveTimeout}ms` };
1399
+ }
1400
+ return { error: `Request failed: ${err.message}` };
1401
+ }
1402
+ }
1403
+ });
1404
+ }
886
1405
  // Annotate the CommonJS export names for ESM import in node:
887
1406
  0 && (module.exports = {
888
1407
  buildSkillsSystemPrompt,
889
1408
  createApiCallTool,
1409
+ createAppendFileTool,
890
1410
  createBashTool,
1411
+ createEditFileTool,
1412
+ createFindTool,
1413
+ createGrepTool,
1414
+ createHttpRequestTool,
891
1415
  createLoadSkillTool,
1416
+ createReadFileRangeTool,
892
1417
  createViewImageTool,
893
1418
  findSkill,
894
1419
  parseSkillsMarkdown,