@schuttdev/gigai 0.2.7 → 0.2.9
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/{dist-MGLETZTT.js → dist-YWMCMOTP.js} +326 -23
- package/dist/index.js +95 -94
- package/package.json +1 -1
|
@@ -239,7 +239,7 @@ var ScriptToolConfigSchema = z.object({
|
|
|
239
239
|
var BuiltinToolConfigSchema = z.object({
|
|
240
240
|
type: z.literal("builtin"),
|
|
241
241
|
name: z.string(),
|
|
242
|
-
builtin: z.enum(["filesystem", "shell"]),
|
|
242
|
+
builtin: z.enum(["filesystem", "shell", "read", "write", "edit", "glob", "grep", "bash"]),
|
|
243
243
|
description: z.string(),
|
|
244
244
|
config: z.record(z.unknown()).optional()
|
|
245
245
|
});
|
|
@@ -280,24 +280,29 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
|
280
280
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
281
281
|
import { readFileSync } from "fs";
|
|
282
282
|
import { resolve } from "path";
|
|
283
|
-
import {
|
|
283
|
+
import {
|
|
284
|
+
readFile as fsReadFile,
|
|
285
|
+
writeFile as fsWriteFile,
|
|
286
|
+
readdir
|
|
287
|
+
} from "fs/promises";
|
|
284
288
|
import { resolve as resolve2, relative, join } from "path";
|
|
285
289
|
import { realpath } from "fs/promises";
|
|
286
290
|
import { spawn as spawn2 } from "child_process";
|
|
291
|
+
import { spawn as spawn3 } from "child_process";
|
|
287
292
|
import { writeFile, readFile, unlink, mkdir } from "fs/promises";
|
|
288
293
|
import { join as join2 } from "path";
|
|
289
294
|
import { tmpdir } from "os";
|
|
290
295
|
import { nanoid as nanoid3 } from "nanoid";
|
|
291
|
-
import { execFile, spawn as
|
|
296
|
+
import { execFile, spawn as spawn4 } from "child_process";
|
|
292
297
|
import { promisify } from "util";
|
|
293
298
|
import { readFile as readFile2 } from "fs/promises";
|
|
294
299
|
import { resolve as resolve3 } from "path";
|
|
295
|
-
import { spawn as spawn4 } from "child_process";
|
|
296
300
|
import { spawn as spawn5 } from "child_process";
|
|
301
|
+
import { spawn as spawn6 } from "child_process";
|
|
297
302
|
import { input, select, checkbox, confirm } from "@inquirer/prompts";
|
|
298
303
|
import { writeFile as writeFile2 } from "fs/promises";
|
|
299
304
|
import { resolve as resolve4 } from "path";
|
|
300
|
-
import { execFile as execFile2, spawn as
|
|
305
|
+
import { execFile as execFile2, spawn as spawn7 } from "child_process";
|
|
301
306
|
import { promisify as promisify2 } from "util";
|
|
302
307
|
import { input as input2 } from "@inquirer/prompts";
|
|
303
308
|
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
@@ -841,6 +846,26 @@ async function toolRoutes(server) {
|
|
|
841
846
|
server.get("/tools", async () => {
|
|
842
847
|
return { tools: server.registry.list() };
|
|
843
848
|
});
|
|
849
|
+
server.get("/tools/search", async (request) => {
|
|
850
|
+
const query = request.query.q?.toLowerCase().trim();
|
|
851
|
+
if (!query) {
|
|
852
|
+
return { tools: server.registry.list() };
|
|
853
|
+
}
|
|
854
|
+
const all = server.registry.list();
|
|
855
|
+
const keywords = query.split(/\s+/);
|
|
856
|
+
const scored = all.map((tool) => {
|
|
857
|
+
const text = `${tool.name} ${tool.description}`.toLowerCase();
|
|
858
|
+
let score = 0;
|
|
859
|
+
for (const kw of keywords) {
|
|
860
|
+
if (tool.name.toLowerCase() === kw) score += 10;
|
|
861
|
+
else if (tool.name.toLowerCase().includes(kw)) score += 5;
|
|
862
|
+
if (tool.description.toLowerCase().includes(kw)) score += 2;
|
|
863
|
+
}
|
|
864
|
+
return { tool, score };
|
|
865
|
+
});
|
|
866
|
+
const matches = scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, 10).map((s) => s.tool);
|
|
867
|
+
return { tools: matches };
|
|
868
|
+
});
|
|
844
869
|
server.get("/tools/:name", async (request) => {
|
|
845
870
|
const { name } = request.params;
|
|
846
871
|
const detail = server.registry.getDetail(name);
|
|
@@ -864,6 +889,8 @@ async function toolRoutes(server) {
|
|
|
864
889
|
return { tools: mcpTools };
|
|
865
890
|
});
|
|
866
891
|
}
|
|
892
|
+
var MAX_OUTPUT_SIZE2 = 10 * 1024 * 1024;
|
|
893
|
+
var MAX_READ_SIZE = 2 * 1024 * 1024;
|
|
867
894
|
async function validatePath(targetPath, allowedPaths) {
|
|
868
895
|
const resolved = resolve2(targetPath);
|
|
869
896
|
let real;
|
|
@@ -921,6 +948,250 @@ async function searchFilesSafe(path, pattern, allowedPaths) {
|
|
|
921
948
|
await walk(safePath);
|
|
922
949
|
return results;
|
|
923
950
|
}
|
|
951
|
+
async function readBuiltin(args, allowedPaths) {
|
|
952
|
+
const filePath = args[0];
|
|
953
|
+
if (!filePath) {
|
|
954
|
+
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "Usage: read <file> [offset] [limit]");
|
|
955
|
+
}
|
|
956
|
+
const safePath = await validatePath(filePath, allowedPaths);
|
|
957
|
+
const content = await fsReadFile(safePath, "utf8");
|
|
958
|
+
if (content.length > MAX_READ_SIZE) {
|
|
959
|
+
throw new GigaiError(
|
|
960
|
+
ErrorCode.VALIDATION_ERROR,
|
|
961
|
+
`File too large (${(content.length / 1024 / 1024).toFixed(1)}MB). Max: ${MAX_READ_SIZE / 1024 / 1024}MB. Use offset/limit.`
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
const offset = args[1] ? parseInt(args[1], 10) : 0;
|
|
965
|
+
const limit = args[2] ? parseInt(args[2], 10) : 0;
|
|
966
|
+
if (offset || limit) {
|
|
967
|
+
const lines = content.split("\n");
|
|
968
|
+
const start = Math.max(0, offset);
|
|
969
|
+
const end = limit ? start + limit : lines.length;
|
|
970
|
+
const sliced = lines.slice(start, end);
|
|
971
|
+
return { stdout: sliced.join("\n"), stderr: "", exitCode: 0 };
|
|
972
|
+
}
|
|
973
|
+
return { stdout: content, stderr: "", exitCode: 0 };
|
|
974
|
+
}
|
|
975
|
+
async function writeBuiltin(args, allowedPaths) {
|
|
976
|
+
const filePath = args[0];
|
|
977
|
+
const content = args[1];
|
|
978
|
+
if (!filePath || content === void 0) {
|
|
979
|
+
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "Usage: write <file> <content>");
|
|
980
|
+
}
|
|
981
|
+
const safePath = await validatePath(filePath, allowedPaths);
|
|
982
|
+
const { mkdir: mkdir2 } = await import("fs/promises");
|
|
983
|
+
const { dirname } = await import("path");
|
|
984
|
+
await mkdir2(dirname(safePath), { recursive: true });
|
|
985
|
+
await fsWriteFile(safePath, content, "utf8");
|
|
986
|
+
return { stdout: `Written: ${safePath}`, stderr: "", exitCode: 0 };
|
|
987
|
+
}
|
|
988
|
+
async function editBuiltin(args, allowedPaths) {
|
|
989
|
+
const filePath = args[0];
|
|
990
|
+
const oldStr = args[1];
|
|
991
|
+
const newStr = args[2];
|
|
992
|
+
const replaceAll = args.includes("--all");
|
|
993
|
+
if (!filePath || oldStr === void 0 || newStr === void 0) {
|
|
994
|
+
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "Usage: edit <file> <old_string> <new_string> [--all]");
|
|
995
|
+
}
|
|
996
|
+
const safePath = await validatePath(filePath, allowedPaths);
|
|
997
|
+
const content = await fsReadFile(safePath, "utf8");
|
|
998
|
+
if (!content.includes(oldStr)) {
|
|
999
|
+
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "old_string not found in file");
|
|
1000
|
+
}
|
|
1001
|
+
if (!replaceAll) {
|
|
1002
|
+
const firstIdx = content.indexOf(oldStr);
|
|
1003
|
+
const secondIdx = content.indexOf(oldStr, firstIdx + 1);
|
|
1004
|
+
if (secondIdx !== -1) {
|
|
1005
|
+
throw new GigaiError(
|
|
1006
|
+
ErrorCode.VALIDATION_ERROR,
|
|
1007
|
+
"old_string matches multiple locations. Use --all to replace all, or provide more context to make it unique."
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
const updated = replaceAll ? content.split(oldStr).join(newStr) : content.replace(oldStr, newStr);
|
|
1012
|
+
await fsWriteFile(safePath, updated, "utf8");
|
|
1013
|
+
const count = replaceAll ? content.split(oldStr).length - 1 : 1;
|
|
1014
|
+
return { stdout: `Replaced ${count} occurrence(s) in ${safePath}`, stderr: "", exitCode: 0 };
|
|
1015
|
+
}
|
|
1016
|
+
async function globBuiltin(args, allowedPaths) {
|
|
1017
|
+
const pattern = args[0];
|
|
1018
|
+
if (!pattern) {
|
|
1019
|
+
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "Usage: glob <pattern> [path]");
|
|
1020
|
+
}
|
|
1021
|
+
const searchPath = args[1] ?? ".";
|
|
1022
|
+
const safePath = await validatePath(searchPath, allowedPaths);
|
|
1023
|
+
const results = [];
|
|
1024
|
+
const globRegex = globToRegex(pattern);
|
|
1025
|
+
async function walk(dir) {
|
|
1026
|
+
let entries;
|
|
1027
|
+
try {
|
|
1028
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
1029
|
+
} catch {
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
for (const entry of entries) {
|
|
1033
|
+
const fullPath = join(dir, entry.name);
|
|
1034
|
+
const relPath = relative(safePath, fullPath);
|
|
1035
|
+
if (globRegex.test(relPath) || globRegex.test(entry.name)) {
|
|
1036
|
+
results.push(relPath);
|
|
1037
|
+
}
|
|
1038
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
1039
|
+
await walk(fullPath);
|
|
1040
|
+
}
|
|
1041
|
+
if (results.length >= 1e3) return;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
await walk(safePath);
|
|
1045
|
+
return { stdout: results.join("\n"), stderr: "", exitCode: 0 };
|
|
1046
|
+
}
|
|
1047
|
+
async function grepBuiltin(args, allowedPaths) {
|
|
1048
|
+
if (args.length === 0) {
|
|
1049
|
+
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "Usage: grep <pattern> [path] [--glob <filter>] [-i] [-n] [-C <num>]");
|
|
1050
|
+
}
|
|
1051
|
+
const positional = [];
|
|
1052
|
+
const flags = [];
|
|
1053
|
+
let i = 0;
|
|
1054
|
+
while (i < args.length) {
|
|
1055
|
+
const arg = args[i];
|
|
1056
|
+
if (arg === "--glob" && args[i + 1]) {
|
|
1057
|
+
flags.push("--glob", args[i + 1]);
|
|
1058
|
+
i += 2;
|
|
1059
|
+
} else if (arg === "--type" && args[i + 1]) {
|
|
1060
|
+
flags.push("--type", args[i + 1]);
|
|
1061
|
+
i += 2;
|
|
1062
|
+
} else if (arg === "-C" && args[i + 1]) {
|
|
1063
|
+
flags.push("-C", args[i + 1]);
|
|
1064
|
+
i += 2;
|
|
1065
|
+
} else if (arg === "-i" || arg === "-n" || arg === "-l") {
|
|
1066
|
+
flags.push(arg);
|
|
1067
|
+
i++;
|
|
1068
|
+
} else {
|
|
1069
|
+
positional.push(arg);
|
|
1070
|
+
i++;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
const pattern = positional[0];
|
|
1074
|
+
if (!pattern) {
|
|
1075
|
+
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "No search pattern provided");
|
|
1076
|
+
}
|
|
1077
|
+
const searchPath = positional[1] ?? ".";
|
|
1078
|
+
const safePath = await validatePath(searchPath, allowedPaths);
|
|
1079
|
+
try {
|
|
1080
|
+
return await spawnGrep("rg", [pattern, safePath, "-n", ...flags]);
|
|
1081
|
+
} catch {
|
|
1082
|
+
try {
|
|
1083
|
+
return await spawnGrep("grep", ["-rn", ...flags, pattern, safePath]);
|
|
1084
|
+
} catch {
|
|
1085
|
+
return jsGrep(pattern, safePath);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
function spawnGrep(cmd, args) {
|
|
1090
|
+
return new Promise((resolve7, reject) => {
|
|
1091
|
+
const child = spawn2(cmd, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
|
|
1092
|
+
const stdoutChunks = [];
|
|
1093
|
+
const stderrChunks = [];
|
|
1094
|
+
let totalSize = 0;
|
|
1095
|
+
child.stdout.on("data", (chunk) => {
|
|
1096
|
+
totalSize += chunk.length;
|
|
1097
|
+
if (totalSize <= MAX_OUTPUT_SIZE2) stdoutChunks.push(chunk);
|
|
1098
|
+
else child.kill("SIGTERM");
|
|
1099
|
+
});
|
|
1100
|
+
child.stderr.on("data", (chunk) => {
|
|
1101
|
+
totalSize += chunk.length;
|
|
1102
|
+
if (totalSize <= MAX_OUTPUT_SIZE2) stderrChunks.push(chunk);
|
|
1103
|
+
});
|
|
1104
|
+
child.on("error", () => reject(new Error(`${cmd} not available`)));
|
|
1105
|
+
child.on("close", (exitCode) => {
|
|
1106
|
+
resolve7({
|
|
1107
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
1108
|
+
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
1109
|
+
exitCode: exitCode ?? 1
|
|
1110
|
+
});
|
|
1111
|
+
});
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
async function jsGrep(pattern, searchPath) {
|
|
1115
|
+
let regex;
|
|
1116
|
+
try {
|
|
1117
|
+
regex = new RegExp(pattern);
|
|
1118
|
+
} catch {
|
|
1119
|
+
throw new GigaiError(ErrorCode.VALIDATION_ERROR, `Invalid pattern: ${pattern}`);
|
|
1120
|
+
}
|
|
1121
|
+
const results = [];
|
|
1122
|
+
async function walk(dir) {
|
|
1123
|
+
let entries;
|
|
1124
|
+
try {
|
|
1125
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
1126
|
+
} catch {
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
for (const entry of entries) {
|
|
1130
|
+
if (results.length >= 500) return;
|
|
1131
|
+
const fullPath = join(dir, entry.name);
|
|
1132
|
+
if (entry.isDirectory()) {
|
|
1133
|
+
if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
1134
|
+
await walk(fullPath);
|
|
1135
|
+
}
|
|
1136
|
+
} else {
|
|
1137
|
+
try {
|
|
1138
|
+
const content = await fsReadFile(fullPath, "utf8");
|
|
1139
|
+
const lines = content.split("\n");
|
|
1140
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1141
|
+
if (regex.test(lines[i])) {
|
|
1142
|
+
results.push(`${relative(searchPath, fullPath)}:${i + 1}:${lines[i]}`);
|
|
1143
|
+
if (results.length >= 500) return;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
} catch {
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
await walk(searchPath);
|
|
1152
|
+
return {
|
|
1153
|
+
stdout: results.join("\n"),
|
|
1154
|
+
stderr: results.length >= 500 ? "Results truncated at 500 matches" : "",
|
|
1155
|
+
exitCode: results.length > 0 ? 0 : 1
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
function globToRegex(pattern) {
|
|
1159
|
+
let regex = "";
|
|
1160
|
+
let i = 0;
|
|
1161
|
+
while (i < pattern.length) {
|
|
1162
|
+
const c = pattern[i];
|
|
1163
|
+
if (c === "*") {
|
|
1164
|
+
if (pattern[i + 1] === "*") {
|
|
1165
|
+
regex += ".*";
|
|
1166
|
+
i += 2;
|
|
1167
|
+
if (pattern[i] === "/") i++;
|
|
1168
|
+
} else {
|
|
1169
|
+
regex += "[^/]*";
|
|
1170
|
+
i++;
|
|
1171
|
+
}
|
|
1172
|
+
} else if (c === "?") {
|
|
1173
|
+
regex += "[^/]";
|
|
1174
|
+
i++;
|
|
1175
|
+
} else if (c === "{") {
|
|
1176
|
+
const end = pattern.indexOf("}", i);
|
|
1177
|
+
if (end !== -1) {
|
|
1178
|
+
const options = pattern.slice(i + 1, end).split(",");
|
|
1179
|
+
regex += `(?:${options.map(escapeRegex).join("|")})`;
|
|
1180
|
+
i = end + 1;
|
|
1181
|
+
} else {
|
|
1182
|
+
regex += escapeRegex(c);
|
|
1183
|
+
i++;
|
|
1184
|
+
}
|
|
1185
|
+
} else {
|
|
1186
|
+
regex += escapeRegex(c);
|
|
1187
|
+
i++;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return new RegExp(regex);
|
|
1191
|
+
}
|
|
1192
|
+
function escapeRegex(s) {
|
|
1193
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1194
|
+
}
|
|
924
1195
|
var SHELL_INTERPRETERS = /* @__PURE__ */ new Set([
|
|
925
1196
|
"sh",
|
|
926
1197
|
"bash",
|
|
@@ -936,7 +1207,7 @@ var SHELL_INTERPRETERS = /* @__PURE__ */ new Set([
|
|
|
936
1207
|
"strace",
|
|
937
1208
|
"ltrace"
|
|
938
1209
|
]);
|
|
939
|
-
var
|
|
1210
|
+
var MAX_OUTPUT_SIZE3 = 10 * 1024 * 1024;
|
|
940
1211
|
async function execCommandSafe(command, args, config) {
|
|
941
1212
|
if (!config.allowlist.includes(command)) {
|
|
942
1213
|
throw new GigaiError(
|
|
@@ -959,7 +1230,7 @@ async function execCommandSafe(command, args, config) {
|
|
|
959
1230
|
}
|
|
960
1231
|
}
|
|
961
1232
|
return new Promise((resolve7, reject) => {
|
|
962
|
-
const child =
|
|
1233
|
+
const child = spawn3(command, args, {
|
|
963
1234
|
shell: false,
|
|
964
1235
|
stdio: ["ignore", "pipe", "pipe"]
|
|
965
1236
|
});
|
|
@@ -968,12 +1239,12 @@ async function execCommandSafe(command, args, config) {
|
|
|
968
1239
|
let totalSize = 0;
|
|
969
1240
|
child.stdout.on("data", (chunk) => {
|
|
970
1241
|
totalSize += chunk.length;
|
|
971
|
-
if (totalSize <=
|
|
1242
|
+
if (totalSize <= MAX_OUTPUT_SIZE3) stdoutChunks.push(chunk);
|
|
972
1243
|
else child.kill("SIGTERM");
|
|
973
1244
|
});
|
|
974
1245
|
child.stderr.on("data", (chunk) => {
|
|
975
1246
|
totalSize += chunk.length;
|
|
976
|
-
if (totalSize <=
|
|
1247
|
+
if (totalSize <= MAX_OUTPUT_SIZE3) stderrChunks.push(chunk);
|
|
977
1248
|
else child.kill("SIGTERM");
|
|
978
1249
|
});
|
|
979
1250
|
child.on("error", (err) => {
|
|
@@ -1047,6 +1318,7 @@ async function execRoutes(server) {
|
|
|
1047
1318
|
async function handleBuiltin(config, args) {
|
|
1048
1319
|
const builtinConfig = config.config ?? {};
|
|
1049
1320
|
switch (config.builtin) {
|
|
1321
|
+
// Legacy combined filesystem tool
|
|
1050
1322
|
case "filesystem": {
|
|
1051
1323
|
const allowedPaths = builtinConfig.allowedPaths ?? ["."];
|
|
1052
1324
|
const subcommand = args[0];
|
|
@@ -1062,6 +1334,7 @@ async function handleBuiltin(config, args) {
|
|
|
1062
1334
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, `Unknown filesystem subcommand: ${subcommand}. Use: read, list, search`);
|
|
1063
1335
|
}
|
|
1064
1336
|
}
|
|
1337
|
+
// Legacy shell tool
|
|
1065
1338
|
case "shell": {
|
|
1066
1339
|
const allowlist = builtinConfig.allowlist ?? [];
|
|
1067
1340
|
const allowSudo = builtinConfig.allowSudo ?? false;
|
|
@@ -1072,6 +1345,37 @@ async function handleBuiltin(config, args) {
|
|
|
1072
1345
|
const result = await execCommandSafe(command, args.slice(1), { allowlist, allowSudo });
|
|
1073
1346
|
return { ...result, durationMs: 0 };
|
|
1074
1347
|
}
|
|
1348
|
+
// --- New builtins ---
|
|
1349
|
+
case "read": {
|
|
1350
|
+
const allowedPaths = builtinConfig.allowedPaths ?? ["."];
|
|
1351
|
+
return { ...await readBuiltin(args, allowedPaths), durationMs: 0 };
|
|
1352
|
+
}
|
|
1353
|
+
case "write": {
|
|
1354
|
+
const allowedPaths = builtinConfig.allowedPaths ?? ["."];
|
|
1355
|
+
return { ...await writeBuiltin(args, allowedPaths), durationMs: 0 };
|
|
1356
|
+
}
|
|
1357
|
+
case "edit": {
|
|
1358
|
+
const allowedPaths = builtinConfig.allowedPaths ?? ["."];
|
|
1359
|
+
return { ...await editBuiltin(args, allowedPaths), durationMs: 0 };
|
|
1360
|
+
}
|
|
1361
|
+
case "glob": {
|
|
1362
|
+
const allowedPaths = builtinConfig.allowedPaths ?? ["."];
|
|
1363
|
+
return { ...await globBuiltin(args, allowedPaths), durationMs: 0 };
|
|
1364
|
+
}
|
|
1365
|
+
case "grep": {
|
|
1366
|
+
const allowedPaths = builtinConfig.allowedPaths ?? ["."];
|
|
1367
|
+
return { ...await grepBuiltin(args, allowedPaths), durationMs: 0 };
|
|
1368
|
+
}
|
|
1369
|
+
case "bash": {
|
|
1370
|
+
const allowlist = builtinConfig.allowlist ?? [];
|
|
1371
|
+
const allowSudo = builtinConfig.allowSudo ?? false;
|
|
1372
|
+
const command = args[0];
|
|
1373
|
+
if (!command) {
|
|
1374
|
+
throw new GigaiError(ErrorCode.VALIDATION_ERROR, "No command specified");
|
|
1375
|
+
}
|
|
1376
|
+
const result = await execCommandSafe(command, args.slice(1), { allowlist, allowSudo });
|
|
1377
|
+
return { ...result, durationMs: 0 };
|
|
1378
|
+
}
|
|
1075
1379
|
default:
|
|
1076
1380
|
throw new GigaiError(ErrorCode.VALIDATION_ERROR, `Unknown builtin: ${config.builtin}`);
|
|
1077
1381
|
}
|
|
@@ -1147,7 +1451,7 @@ async function adminRoutes(server) {
|
|
|
1147
1451
|
}
|
|
1148
1452
|
setTimeout(async () => {
|
|
1149
1453
|
server.log.info("Restarting server after update...");
|
|
1150
|
-
const args = ["
|
|
1454
|
+
const args = ["start"];
|
|
1151
1455
|
const configIdx = process.argv.indexOf("--config");
|
|
1152
1456
|
if (configIdx !== -1 && process.argv[configIdx + 1]) {
|
|
1153
1457
|
args.push("--config", process.argv[configIdx + 1]);
|
|
@@ -1160,7 +1464,7 @@ async function adminRoutes(server) {
|
|
|
1160
1464
|
args.push("--dev");
|
|
1161
1465
|
}
|
|
1162
1466
|
await server.close();
|
|
1163
|
-
const child =
|
|
1467
|
+
const child = spawn4("gigai", args, {
|
|
1164
1468
|
detached: true,
|
|
1165
1469
|
stdio: "ignore",
|
|
1166
1470
|
cwd: process.cwd()
|
|
@@ -1228,7 +1532,7 @@ async function loadConfig(path) {
|
|
|
1228
1532
|
}
|
|
1229
1533
|
function runCommand(command, args) {
|
|
1230
1534
|
return new Promise((resolve7, reject) => {
|
|
1231
|
-
const child =
|
|
1535
|
+
const child = spawn5(command, args, { shell: false, stdio: ["ignore", "pipe", "pipe"] });
|
|
1232
1536
|
const chunks = [];
|
|
1233
1537
|
child.stdout.on("data", (chunk) => chunks.push(chunk));
|
|
1234
1538
|
child.on("error", reject);
|
|
@@ -1269,7 +1573,7 @@ async function disableFunnel(port) {
|
|
|
1269
1573
|
await runCommand("tailscale", ["funnel", "--bg", "off", `${port}`]);
|
|
1270
1574
|
}
|
|
1271
1575
|
function runTunnel(tunnelName, localPort) {
|
|
1272
|
-
const child =
|
|
1576
|
+
const child = spawn6("cloudflared", [
|
|
1273
1577
|
"tunnel",
|
|
1274
1578
|
"--url",
|
|
1275
1579
|
`http://localhost:${localPort}`,
|
|
@@ -1472,7 +1776,7 @@ async function runInit() {
|
|
|
1472
1776
|
serverUrl = await ensureTailscaleFunnel(port);
|
|
1473
1777
|
} catch (e) {
|
|
1474
1778
|
console.error(` ${e.message}`);
|
|
1475
|
-
console.log(" You can enable Funnel later and run 'gigai
|
|
1779
|
+
console.log(" You can enable Funnel later and run 'gigai start' manually.\n");
|
|
1476
1780
|
}
|
|
1477
1781
|
} else if (httpsProvider === "cloudflare" && httpsConfig && "domain" in httpsConfig && httpsConfig.domain) {
|
|
1478
1782
|
serverUrl = `https://${httpsConfig.domain}`;
|
|
@@ -1485,9 +1789,9 @@ async function runInit() {
|
|
|
1485
1789
|
});
|
|
1486
1790
|
}
|
|
1487
1791
|
console.log("\n Starting server...");
|
|
1488
|
-
const serverArgs = ["
|
|
1792
|
+
const serverArgs = ["start", "--config", configPath];
|
|
1489
1793
|
if (!httpsConfig) serverArgs.push("--dev");
|
|
1490
|
-
const child =
|
|
1794
|
+
const child = spawn7("gigai", serverArgs, {
|
|
1491
1795
|
detached: true,
|
|
1492
1796
|
stdio: "ignore",
|
|
1493
1797
|
cwd: resolve4(".")
|
|
@@ -1518,7 +1822,7 @@ async function runInit() {
|
|
|
1518
1822
|
}
|
|
1519
1823
|
if (!code) {
|
|
1520
1824
|
console.log("\n Server is starting but not ready yet.");
|
|
1521
|
-
console.log(" Run 'gigai
|
|
1825
|
+
console.log(" Run 'gigai pair' once it's up to get a pairing code.\n");
|
|
1522
1826
|
return;
|
|
1523
1827
|
}
|
|
1524
1828
|
console.log(`
|
|
@@ -1536,7 +1840,7 @@ async function runInit() {
|
|
|
1536
1840
|
console.log(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
1537
1841
|
console.log(`
|
|
1538
1842
|
Pairing code expires in ${config.auth.pairingTtlSeconds / 60} minutes.`);
|
|
1539
|
-
console.log(` Run 'gigai
|
|
1843
|
+
console.log(` Run 'gigai pair' to generate a new one.
|
|
1540
1844
|
`);
|
|
1541
1845
|
}
|
|
1542
1846
|
async function loadConfigFile(path) {
|
|
@@ -1663,7 +1967,7 @@ Pairing code: ${data.code}`);
|
|
|
1663
1967
|
console.log(`Expires in ${data.expiresIn / 60} minutes.`);
|
|
1664
1968
|
} catch (e) {
|
|
1665
1969
|
if (e.message.includes("fetch failed") || e.message.includes("ECONNREFUSED")) {
|
|
1666
|
-
console.error("Server is not running. Start it with: gigai
|
|
1970
|
+
console.error("Server is not running. Start it with: gigai start");
|
|
1667
1971
|
} else {
|
|
1668
1972
|
console.error(`Error: ${e.message}`);
|
|
1669
1973
|
}
|
|
@@ -1696,7 +2000,6 @@ function getLaunchdPlist(configPath) {
|
|
|
1696
2000
|
<array>
|
|
1697
2001
|
<string>${nodeBin}</string>
|
|
1698
2002
|
<string>${bin}</string>
|
|
1699
|
-
<string>server</string>
|
|
1700
2003
|
<string>start</string>
|
|
1701
2004
|
<string>--config</string>
|
|
1702
2005
|
<string>${configPath}</string>
|
|
@@ -1723,7 +2026,7 @@ After=network.target
|
|
|
1723
2026
|
|
|
1724
2027
|
[Service]
|
|
1725
2028
|
Type=simple
|
|
1726
|
-
ExecStart=${bin}
|
|
2029
|
+
ExecStart=${bin} start --config ${configPath}
|
|
1727
2030
|
Restart=always
|
|
1728
2031
|
RestartSec=5
|
|
1729
2032
|
WorkingDirectory=${homedir()}
|
|
@@ -1766,7 +2069,7 @@ async function installDaemon(configPath) {
|
|
|
1766
2069
|
console.log(` Remove: systemctl --user disable gigai`);
|
|
1767
2070
|
} else {
|
|
1768
2071
|
console.log(" Persistent daemon not supported on this platform.");
|
|
1769
|
-
console.log(" Run 'gigai
|
|
2072
|
+
console.log(" Run 'gigai start' manually.");
|
|
1770
2073
|
}
|
|
1771
2074
|
}
|
|
1772
2075
|
async function uninstallDaemon() {
|
|
@@ -1804,7 +2107,7 @@ async function stopServer() {
|
|
|
1804
2107
|
const { execFileSync } = await import("child_process");
|
|
1805
2108
|
let pids = [];
|
|
1806
2109
|
try {
|
|
1807
|
-
const out = execFileSync("pgrep", ["-f", "gigai
|
|
2110
|
+
const out = execFileSync("pgrep", ["-f", "gigai start"], { encoding: "utf8" });
|
|
1808
2111
|
pids = out.trim().split("\n").map(Number).filter((pid) => pid && pid !== process.pid);
|
|
1809
2112
|
} catch {
|
|
1810
2113
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,109 +4,104 @@
|
|
|
4
4
|
import { defineCommand, runMain } from "citty";
|
|
5
5
|
|
|
6
6
|
// src/version.ts
|
|
7
|
-
var VERSION = "0.2.
|
|
7
|
+
var VERSION = "0.2.9";
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
10
10
|
async function requireServer() {
|
|
11
11
|
try {
|
|
12
|
-
return await import("./dist-
|
|
12
|
+
return await import("./dist-YWMCMOTP.js");
|
|
13
13
|
} catch {
|
|
14
14
|
console.error("Server dependencies not installed.");
|
|
15
15
|
console.error("Run: npm install -g @schuttdev/gigai");
|
|
16
16
|
process.exit(1);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
-
var
|
|
20
|
-
meta: { name: "
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
await installDaemon(args.config);
|
|
62
|
-
}
|
|
63
|
-
}),
|
|
64
|
-
uninstall: defineCommand({
|
|
65
|
-
meta: { name: "uninstall", description: "Remove background service" },
|
|
66
|
-
async run() {
|
|
67
|
-
const { uninstallDaemon } = await requireServer();
|
|
68
|
-
await uninstallDaemon();
|
|
69
|
-
}
|
|
70
|
-
}),
|
|
71
|
-
stop: defineCommand({
|
|
72
|
-
meta: { name: "stop", description: "Stop the running gigai server" },
|
|
73
|
-
async run() {
|
|
74
|
-
const { execFileSync } = await import("child_process");
|
|
75
|
-
let pids = [];
|
|
76
|
-
try {
|
|
77
|
-
const out = execFileSync("pgrep", ["-f", "gigai server start"], { encoding: "utf8" });
|
|
78
|
-
pids = out.trim().split("\n").map(Number).filter((pid) => pid && pid !== process.pid);
|
|
79
|
-
} catch {
|
|
80
|
-
}
|
|
81
|
-
if (pids.length === 0) {
|
|
82
|
-
console.log("No running gigai server found.");
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
for (const pid of pids) {
|
|
86
|
-
try {
|
|
87
|
-
process.kill(pid, "SIGTERM");
|
|
88
|
-
console.log(`Stopped gigai server (PID ${pid})`);
|
|
89
|
-
} catch (e) {
|
|
90
|
-
console.error(`Failed to stop PID ${pid}: ${e.message}`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}),
|
|
95
|
-
status: defineCommand({
|
|
96
|
-
meta: { name: "status", description: "Show server status" },
|
|
97
|
-
async run() {
|
|
98
|
-
console.log("Server status: checking...");
|
|
99
|
-
try {
|
|
100
|
-
const res = await fetch("http://localhost:7443/health");
|
|
101
|
-
const data = await res.json();
|
|
102
|
-
console.log(`Status: ${data.status}`);
|
|
103
|
-
console.log(`Version: ${data.version}`);
|
|
104
|
-
console.log(`Uptime: ${Math.floor(data.uptime / 1e3)}s`);
|
|
105
|
-
} catch {
|
|
106
|
-
console.log("Server is not running.");
|
|
107
|
-
}
|
|
19
|
+
var initCommand = defineCommand({
|
|
20
|
+
meta: { name: "init", description: "Interactive setup wizard" },
|
|
21
|
+
async run() {
|
|
22
|
+
const { runInit } = await requireServer();
|
|
23
|
+
await runInit();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
var startCommand = defineCommand({
|
|
27
|
+
meta: { name: "start", description: "Start the gigai server" },
|
|
28
|
+
args: {
|
|
29
|
+
config: { type: "string", alias: "c", description: "Config file path" },
|
|
30
|
+
dev: { type: "boolean", description: "Development mode (no HTTPS)" }
|
|
31
|
+
},
|
|
32
|
+
async run({ args }) {
|
|
33
|
+
const { startServer } = await requireServer();
|
|
34
|
+
const extraArgs = [];
|
|
35
|
+
if (args.config) extraArgs.push("--config", args.config);
|
|
36
|
+
if (args.dev) extraArgs.push("--dev");
|
|
37
|
+
process.argv.push(...extraArgs);
|
|
38
|
+
await startServer();
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
var stopCommand = defineCommand({
|
|
42
|
+
meta: { name: "stop", description: "Stop the running gigai server" },
|
|
43
|
+
async run() {
|
|
44
|
+
const { execFileSync } = await import("child_process");
|
|
45
|
+
let pids = [];
|
|
46
|
+
try {
|
|
47
|
+
const out = execFileSync("pgrep", ["-f", "gigai start"], { encoding: "utf8" });
|
|
48
|
+
pids = out.trim().split("\n").map(Number).filter((pid) => pid && pid !== process.pid);
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
if (pids.length === 0) {
|
|
52
|
+
console.log("No running gigai server found.");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
for (const pid of pids) {
|
|
56
|
+
try {
|
|
57
|
+
process.kill(pid, "SIGTERM");
|
|
58
|
+
console.log(`Stopped gigai server (PID ${pid})`);
|
|
59
|
+
} catch (e) {
|
|
60
|
+
console.error(`Failed to stop PID ${pid}: ${e.message}`);
|
|
108
61
|
}
|
|
109
|
-
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
var statusCommand = defineCommand({
|
|
66
|
+
meta: { name: "status", description: "Show server status" },
|
|
67
|
+
async run() {
|
|
68
|
+
console.log("Server status: checking...");
|
|
69
|
+
try {
|
|
70
|
+
const res = await fetch("http://localhost:7443/health");
|
|
71
|
+
const data = await res.json();
|
|
72
|
+
console.log(`Status: ${data.status}`);
|
|
73
|
+
console.log(`Version: ${data.version}`);
|
|
74
|
+
console.log(`Uptime: ${Math.floor(data.uptime / 1e3)}s`);
|
|
75
|
+
} catch {
|
|
76
|
+
console.log("Server is not running.");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
var pairCommand = defineCommand({
|
|
81
|
+
meta: { name: "pair", description: "Generate a pairing code" },
|
|
82
|
+
args: {
|
|
83
|
+
config: { type: "string", alias: "c", description: "Config file path" }
|
|
84
|
+
},
|
|
85
|
+
async run({ args }) {
|
|
86
|
+
const { generateServerPairingCode } = await requireServer();
|
|
87
|
+
await generateServerPairingCode(args.config);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
var installCommand = defineCommand({
|
|
91
|
+
meta: { name: "install", description: "Install as persistent background service" },
|
|
92
|
+
args: {
|
|
93
|
+
config: { type: "string", alias: "c", description: "Config file path" }
|
|
94
|
+
},
|
|
95
|
+
async run({ args }) {
|
|
96
|
+
const { installDaemon } = await requireServer();
|
|
97
|
+
await installDaemon(args.config);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
var uninstallCommand = defineCommand({
|
|
101
|
+
meta: { name: "uninstall", description: "Remove background service" },
|
|
102
|
+
async run() {
|
|
103
|
+
const { uninstallDaemon } = await requireServer();
|
|
104
|
+
await uninstallDaemon();
|
|
110
105
|
}
|
|
111
106
|
});
|
|
112
107
|
var wrapCommand = defineCommand({
|
|
@@ -165,10 +160,16 @@ var main = defineCommand({
|
|
|
165
160
|
meta: {
|
|
166
161
|
name: "gigai",
|
|
167
162
|
version: VERSION,
|
|
168
|
-
description: "gigai
|
|
163
|
+
description: "gigai \u2014 bridge CLI tools to Claude"
|
|
169
164
|
},
|
|
170
165
|
subCommands: {
|
|
171
|
-
|
|
166
|
+
init: initCommand,
|
|
167
|
+
start: startCommand,
|
|
168
|
+
stop: stopCommand,
|
|
169
|
+
status: statusCommand,
|
|
170
|
+
pair: pairCommand,
|
|
171
|
+
install: installCommand,
|
|
172
|
+
uninstall: uninstallCommand,
|
|
172
173
|
wrap: wrapCommand,
|
|
173
174
|
unwrap: unwrapCommand,
|
|
174
175
|
version: versionCommand
|