jefrichat-mcp 0.7.0 → 0.9.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/http.js +301 -58
- package/dist/index.js +470 -106
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -1300,8 +1300,8 @@ var require_node = __commonJS({
|
|
|
1300
1300
|
}
|
|
1301
1301
|
break;
|
|
1302
1302
|
case "FILE":
|
|
1303
|
-
var
|
|
1304
|
-
stream2 = new
|
|
1303
|
+
var fs5 = __require("fs");
|
|
1304
|
+
stream2 = new fs5.SyncWriteStream(fd2, { autoClose: false });
|
|
1305
1305
|
stream2._type = "fs";
|
|
1306
1306
|
break;
|
|
1307
1307
|
case "PIPE":
|
|
@@ -18437,7 +18437,7 @@ var require_view = __commonJS({
|
|
|
18437
18437
|
"use strict";
|
|
18438
18438
|
var debug = require_src()("express:view");
|
|
18439
18439
|
var path2 = __require("path");
|
|
18440
|
-
var
|
|
18440
|
+
var fs5 = __require("fs");
|
|
18441
18441
|
var dirname = path2.dirname;
|
|
18442
18442
|
var basename = path2.basename;
|
|
18443
18443
|
var extname = path2.extname;
|
|
@@ -18503,7 +18503,7 @@ var require_view = __commonJS({
|
|
|
18503
18503
|
function tryStat(path3) {
|
|
18504
18504
|
debug('stat "%s"', path3);
|
|
18505
18505
|
try {
|
|
18506
|
-
return
|
|
18506
|
+
return fs5.statSync(path3);
|
|
18507
18507
|
} catch (e) {
|
|
18508
18508
|
return void 0;
|
|
18509
18509
|
}
|
|
@@ -18858,7 +18858,7 @@ var require_types = __commonJS({
|
|
|
18858
18858
|
var require_mime = __commonJS({
|
|
18859
18859
|
"../../node_modules/.pnpm/mime@1.6.0/node_modules/mime/mime.js"(exports, module) {
|
|
18860
18860
|
var path2 = __require("path");
|
|
18861
|
-
var
|
|
18861
|
+
var fs5 = __require("fs");
|
|
18862
18862
|
function Mime() {
|
|
18863
18863
|
this.types = /* @__PURE__ */ Object.create(null);
|
|
18864
18864
|
this.extensions = /* @__PURE__ */ Object.create(null);
|
|
@@ -18879,7 +18879,7 @@ var require_mime = __commonJS({
|
|
|
18879
18879
|
};
|
|
18880
18880
|
Mime.prototype.load = function(file) {
|
|
18881
18881
|
this._loading = file;
|
|
18882
|
-
var map = {}, content =
|
|
18882
|
+
var map = {}, content = fs5.readFileSync(file, "ascii"), lines = content.split(/[\r\n]+/);
|
|
18883
18883
|
lines.forEach(function(line) {
|
|
18884
18884
|
var fields = line.replace(/\s*#.*|^\s*|\s*$/g, "").split(/\s+/);
|
|
18885
18885
|
map[fields.shift()] = fields;
|
|
@@ -19117,7 +19117,7 @@ var require_send = __commonJS({
|
|
|
19117
19117
|
var escapeHtml = require_escape_html();
|
|
19118
19118
|
var etag = require_etag();
|
|
19119
19119
|
var fresh = require_fresh();
|
|
19120
|
-
var
|
|
19120
|
+
var fs5 = __require("fs");
|
|
19121
19121
|
var mime = require_mime();
|
|
19122
19122
|
var ms = require_ms2();
|
|
19123
19123
|
var onFinished = require_on_finished();
|
|
@@ -19450,7 +19450,7 @@ var require_send = __commonJS({
|
|
|
19450
19450
|
var i = 0;
|
|
19451
19451
|
var self = this;
|
|
19452
19452
|
debug('stat "%s"', path3);
|
|
19453
|
-
|
|
19453
|
+
fs5.stat(path3, function onstat(err, stat) {
|
|
19454
19454
|
if (err && err.code === "ENOENT" && !extname(path3) && path3[path3.length - 1] !== sep) {
|
|
19455
19455
|
return next(err);
|
|
19456
19456
|
}
|
|
@@ -19465,7 +19465,7 @@ var require_send = __commonJS({
|
|
|
19465
19465
|
}
|
|
19466
19466
|
var p = path3 + "." + self._extensions[i++];
|
|
19467
19467
|
debug('stat "%s"', p);
|
|
19468
|
-
|
|
19468
|
+
fs5.stat(p, function(err2, stat) {
|
|
19469
19469
|
if (err2) return next(err2);
|
|
19470
19470
|
if (stat.isDirectory()) return next();
|
|
19471
19471
|
self.emit("file", p, stat);
|
|
@@ -19483,7 +19483,7 @@ var require_send = __commonJS({
|
|
|
19483
19483
|
}
|
|
19484
19484
|
var p = join(path3, self._index[i]);
|
|
19485
19485
|
debug('stat "%s"', p);
|
|
19486
|
-
|
|
19486
|
+
fs5.stat(p, function(err2, stat) {
|
|
19487
19487
|
if (err2) return next(err2);
|
|
19488
19488
|
if (stat.isDirectory()) return next();
|
|
19489
19489
|
self.emit("file", p, stat);
|
|
@@ -19495,7 +19495,7 @@ var require_send = __commonJS({
|
|
|
19495
19495
|
SendStream.prototype.stream = function stream(path3, options) {
|
|
19496
19496
|
var self = this;
|
|
19497
19497
|
var res = this.res;
|
|
19498
|
-
var stream2 =
|
|
19498
|
+
var stream2 = fs5.createReadStream(path3, options);
|
|
19499
19499
|
this.emit("stream", stream2);
|
|
19500
19500
|
stream2.pipe(res);
|
|
19501
19501
|
function cleanup() {
|
|
@@ -29739,12 +29739,12 @@ var require_dist = __commonJS({
|
|
|
29739
29739
|
throw new Error(`Unknown format "${name}"`);
|
|
29740
29740
|
return f;
|
|
29741
29741
|
};
|
|
29742
|
-
function addFormats(ajv, list,
|
|
29742
|
+
function addFormats(ajv, list, fs5, exportName) {
|
|
29743
29743
|
var _a;
|
|
29744
29744
|
var _b;
|
|
29745
29745
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
29746
29746
|
for (const f of list)
|
|
29747
|
-
ajv.addFormat(f,
|
|
29747
|
+
ajv.addFormat(f, fs5[f]);
|
|
29748
29748
|
}
|
|
29749
29749
|
module.exports = exports = formatsPlugin;
|
|
29750
29750
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -48938,6 +48938,8 @@ var AcpClient = class _AcpClient {
|
|
|
48938
48938
|
closing = false;
|
|
48939
48939
|
authFailed = false;
|
|
48940
48940
|
// a bad token must NOT trigger a reconnect storm
|
|
48941
|
+
isAlive = true;
|
|
48942
|
+
// heartbeat: did we get a pong since the last ping?
|
|
48941
48943
|
constructor(server) {
|
|
48942
48944
|
this.server = server.replace(/\/$/, "");
|
|
48943
48945
|
}
|
|
@@ -49038,15 +49040,45 @@ var AcpClient = class _AcpClient {
|
|
|
49038
49040
|
});
|
|
49039
49041
|
});
|
|
49040
49042
|
}
|
|
49041
|
-
//
|
|
49043
|
+
// Heartbeat: keep the connection warm through idle proxies AND detect a
|
|
49044
|
+
// half-open (silently dead) socket. A dropped Wi-Fi / VPN / sleep can leave
|
|
49045
|
+
// the socket readyState=OPEN forever while no data flows, so `close` never
|
|
49046
|
+
// fires and we'd never reconnect. So each tick we send a WS-level ping (the
|
|
49047
|
+
// server's ws auto-pongs) and require a pong to have arrived since the last
|
|
49048
|
+
// tick — if not, the link is dead, so terminate() it, which fires `close` and
|
|
49049
|
+
// triggers the existing reconnect.
|
|
49042
49050
|
startPing() {
|
|
49043
49051
|
this.stopPing();
|
|
49052
|
+
this.isAlive = true;
|
|
49053
|
+
try {
|
|
49054
|
+
this.ws.on("pong", () => {
|
|
49055
|
+
this.isAlive = true;
|
|
49056
|
+
});
|
|
49057
|
+
} catch {
|
|
49058
|
+
}
|
|
49044
49059
|
this.pingTimer = setInterval(() => {
|
|
49060
|
+
if (this.ws.readyState !== this.ws.OPEN) return;
|
|
49061
|
+
if (!this.isAlive) {
|
|
49062
|
+
try {
|
|
49063
|
+
this.ws.terminate();
|
|
49064
|
+
} catch {
|
|
49065
|
+
try {
|
|
49066
|
+
this.ws.close();
|
|
49067
|
+
} catch {
|
|
49068
|
+
}
|
|
49069
|
+
}
|
|
49070
|
+
return;
|
|
49071
|
+
}
|
|
49072
|
+
this.isAlive = false;
|
|
49073
|
+
try {
|
|
49074
|
+
this.ws.ping();
|
|
49075
|
+
} catch {
|
|
49076
|
+
}
|
|
49045
49077
|
try {
|
|
49046
|
-
|
|
49078
|
+
this.ws.send(JSON.stringify({ type: "ping" }));
|
|
49047
49079
|
} catch {
|
|
49048
49080
|
}
|
|
49049
|
-
},
|
|
49081
|
+
}, 15e3);
|
|
49050
49082
|
}
|
|
49051
49083
|
stopPing() {
|
|
49052
49084
|
if (this.pingTimer) clearInterval(this.pingTimer);
|
|
@@ -49373,10 +49405,102 @@ var AcpClient = class _AcpClient {
|
|
|
49373
49405
|
};
|
|
49374
49406
|
|
|
49375
49407
|
// src/tools.ts
|
|
49408
|
+
import fs4 from "node:fs";
|
|
49409
|
+
import np3 from "node:path";
|
|
49410
|
+
import cp2 from "node:child_process";
|
|
49411
|
+
import os4 from "node:os";
|
|
49412
|
+
|
|
49413
|
+
// src/notify.ts
|
|
49414
|
+
import cp from "node:child_process";
|
|
49376
49415
|
import fs2 from "node:fs";
|
|
49377
49416
|
import np from "node:path";
|
|
49378
|
-
import cp from "node:child_process";
|
|
49379
49417
|
import os2 from "node:os";
|
|
49418
|
+
var OFF = process.env.JEFRI_NOTIFY === "0" || process.env.JEFRI_NOTIFY === "false";
|
|
49419
|
+
function findTerminalNotifier() {
|
|
49420
|
+
if (process.platform !== "darwin") return null;
|
|
49421
|
+
for (const p of ["/opt/homebrew/bin/terminal-notifier", "/usr/local/bin/terminal-notifier"]) {
|
|
49422
|
+
try {
|
|
49423
|
+
if (fs2.existsSync(p)) return p;
|
|
49424
|
+
} catch {
|
|
49425
|
+
}
|
|
49426
|
+
}
|
|
49427
|
+
try {
|
|
49428
|
+
const out = (cp.spawnSync("which", ["terminal-notifier"], { encoding: "utf8" }).stdout || "").trim();
|
|
49429
|
+
if (out && fs2.existsSync(out)) return out;
|
|
49430
|
+
} catch {
|
|
49431
|
+
}
|
|
49432
|
+
return null;
|
|
49433
|
+
}
|
|
49434
|
+
var TERMINAL_NOTIFIER = findTerminalNotifier();
|
|
49435
|
+
var macNeedsTerminalNotifier = process.platform === "darwin" && !TERMINAL_NOTIFIER && !OFF;
|
|
49436
|
+
var DEFAULTS = {
|
|
49437
|
+
enabled: true,
|
|
49438
|
+
sound: true,
|
|
49439
|
+
dms: true,
|
|
49440
|
+
groups: true,
|
|
49441
|
+
onlyMentions: false,
|
|
49442
|
+
quietFrom: null,
|
|
49443
|
+
quietTo: null,
|
|
49444
|
+
mutedGroups: [],
|
|
49445
|
+
mutedUsers: []
|
|
49446
|
+
};
|
|
49447
|
+
var PREFS_DIR = np.join(os2.homedir(), ".jefri");
|
|
49448
|
+
var PREFS_FILE = np.join(PREFS_DIR, "notify.json");
|
|
49449
|
+
function loadPrefs() {
|
|
49450
|
+
try {
|
|
49451
|
+
return { ...DEFAULTS, ...JSON.parse(fs2.readFileSync(PREFS_FILE, "utf8")) };
|
|
49452
|
+
} catch {
|
|
49453
|
+
return { ...DEFAULTS };
|
|
49454
|
+
}
|
|
49455
|
+
}
|
|
49456
|
+
var prefs = loadPrefs();
|
|
49457
|
+
function getPrefs() {
|
|
49458
|
+
return { ...prefs };
|
|
49459
|
+
}
|
|
49460
|
+
function setPrefs(patch) {
|
|
49461
|
+
prefs = { ...prefs, ...patch };
|
|
49462
|
+
try {
|
|
49463
|
+
fs2.mkdirSync(PREFS_DIR, { recursive: true });
|
|
49464
|
+
fs2.writeFileSync(PREFS_FILE, JSON.stringify(prefs, null, 2));
|
|
49465
|
+
} catch {
|
|
49466
|
+
}
|
|
49467
|
+
return { ...prefs };
|
|
49468
|
+
}
|
|
49469
|
+
|
|
49470
|
+
// src/autonomous.ts
|
|
49471
|
+
import fs3 from "node:fs";
|
|
49472
|
+
import np2 from "node:path";
|
|
49473
|
+
import os3 from "node:os";
|
|
49474
|
+
var DEFAULTS2 = {
|
|
49475
|
+
enabled: false,
|
|
49476
|
+
brain: "claude",
|
|
49477
|
+
workdir: process.cwd(),
|
|
49478
|
+
replyMode: "mentions",
|
|
49479
|
+
persona: "",
|
|
49480
|
+
replyToBots: false
|
|
49481
|
+
};
|
|
49482
|
+
var DIR = np2.join(os3.homedir(), ".jefri");
|
|
49483
|
+
var FILE = np2.join(DIR, "autonomous.json");
|
|
49484
|
+
function loadCfg() {
|
|
49485
|
+
try {
|
|
49486
|
+
return { ...DEFAULTS2, ...JSON.parse(fs3.readFileSync(FILE, "utf8")) };
|
|
49487
|
+
} catch {
|
|
49488
|
+
return { ...DEFAULTS2 };
|
|
49489
|
+
}
|
|
49490
|
+
}
|
|
49491
|
+
var cfg = loadCfg();
|
|
49492
|
+
function setAuto(patch) {
|
|
49493
|
+
cfg = { ...cfg, ...patch };
|
|
49494
|
+
try {
|
|
49495
|
+
fs3.mkdirSync(DIR, { recursive: true });
|
|
49496
|
+
fs3.writeFileSync(FILE, JSON.stringify(cfg, null, 2));
|
|
49497
|
+
} catch {
|
|
49498
|
+
}
|
|
49499
|
+
return { ...cfg };
|
|
49500
|
+
}
|
|
49501
|
+
var TIMEOUT_MS = 5 * 60 * 1e3;
|
|
49502
|
+
|
|
49503
|
+
// src/tools.ts
|
|
49380
49504
|
var MIME = {
|
|
49381
49505
|
".jpg": "image/jpeg",
|
|
49382
49506
|
".jpeg": "image/jpeg",
|
|
@@ -49396,21 +49520,21 @@ var MIME = {
|
|
|
49396
49520
|
".json": "application/json",
|
|
49397
49521
|
".zip": "application/zip"
|
|
49398
49522
|
};
|
|
49399
|
-
var mimeOf = (name) => MIME[
|
|
49523
|
+
var mimeOf = (name) => MIME[np3.extname(name).toLowerCase()] ?? "application/octet-stream";
|
|
49400
49524
|
function screenshotDir() {
|
|
49401
49525
|
try {
|
|
49402
|
-
const out =
|
|
49403
|
-
if (out) return out.startsWith("~") ?
|
|
49526
|
+
const out = cp2.execFileSync("defaults", ["read", "com.apple.screencapture", "location"], { encoding: "utf8", timeout: 2e3 }).trim();
|
|
49527
|
+
if (out) return out.startsWith("~") ? np3.join(os4.homedir(), out.slice(1)) : out;
|
|
49404
49528
|
} catch {
|
|
49405
49529
|
}
|
|
49406
|
-
const desktop =
|
|
49407
|
-
return
|
|
49530
|
+
const desktop = np3.join(os4.homedir(), "Desktop");
|
|
49531
|
+
return fs4.existsSync(desktop) ? desktop : null;
|
|
49408
49532
|
}
|
|
49409
49533
|
function resolveLocalFile(rawPath) {
|
|
49410
|
-
const expand = (p) => p.startsWith("~") ?
|
|
49534
|
+
const expand = (p) => p.startsWith("~") ? np3.join(os4.homedir(), p.slice(1)) : p;
|
|
49411
49535
|
const exists = (p) => {
|
|
49412
49536
|
try {
|
|
49413
|
-
return
|
|
49537
|
+
return fs4.existsSync(p) ? p : null;
|
|
49414
49538
|
} catch {
|
|
49415
49539
|
return null;
|
|
49416
49540
|
}
|
|
@@ -49423,26 +49547,26 @@ function resolveLocalFile(rawPath) {
|
|
|
49423
49547
|
hit = exists(v);
|
|
49424
49548
|
if (hit) return hit;
|
|
49425
49549
|
}
|
|
49426
|
-
const base =
|
|
49550
|
+
const base = np3.basename(abs);
|
|
49427
49551
|
const looksLikeShot = /screencaptureui/i.test(abs) || /^Screenshot[\s ]/i.test(base);
|
|
49428
49552
|
if (looksLikeShot) {
|
|
49429
49553
|
const dir = screenshotDir();
|
|
49430
49554
|
if (dir) {
|
|
49431
49555
|
let files = [];
|
|
49432
49556
|
try {
|
|
49433
|
-
files =
|
|
49557
|
+
files = fs4.readdirSync(dir);
|
|
49434
49558
|
} catch {
|
|
49435
49559
|
}
|
|
49436
49560
|
const norm = (s) => s.replace(/[ \s]+/g, " ").trim().toLowerCase();
|
|
49437
49561
|
const want = norm(base);
|
|
49438
49562
|
for (const f of files) if (norm(f) === want) {
|
|
49439
|
-
hit = exists(
|
|
49563
|
+
hit = exists(np3.join(dir, f));
|
|
49440
49564
|
if (hit) return hit;
|
|
49441
49565
|
}
|
|
49442
49566
|
const shots = files.filter((f) => /^Screenshot[\s ].*\.(png|jpe?g)$/i.test(f)).map((f) => {
|
|
49443
|
-
const p =
|
|
49567
|
+
const p = np3.join(dir, f);
|
|
49444
49568
|
try {
|
|
49445
|
-
return { p, m:
|
|
49569
|
+
return { p, m: fs4.statSync(p).mtimeMs };
|
|
49446
49570
|
} catch {
|
|
49447
49571
|
return { p, m: 0 };
|
|
49448
49572
|
}
|
|
@@ -49602,8 +49726,8 @@ ${fp}`);
|
|
|
49602
49726
|
`I can't find a file at ${path2} on this machine.`
|
|
49603
49727
|
);
|
|
49604
49728
|
}
|
|
49605
|
-
const buf =
|
|
49606
|
-
name =
|
|
49729
|
+
const buf = fs4.readFileSync(abs);
|
|
49730
|
+
name = np3.basename(abs);
|
|
49607
49731
|
mime = mimeOf(name);
|
|
49608
49732
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
49609
49733
|
} else if (dataUrl && fileName) {
|
|
@@ -49663,8 +49787,8 @@ ${fp}`);
|
|
|
49663
49787
|
`I can't find a file at ${path2} on this machine.`
|
|
49664
49788
|
);
|
|
49665
49789
|
}
|
|
49666
|
-
const buf =
|
|
49667
|
-
name =
|
|
49790
|
+
const buf = fs4.readFileSync(abs);
|
|
49791
|
+
name = np3.basename(abs);
|
|
49668
49792
|
mime = mimeOf(name);
|
|
49669
49793
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
49670
49794
|
} else if (dataUrl && fileName) {
|
|
@@ -49696,7 +49820,7 @@ ${fp}`);
|
|
|
49696
49820
|
let name, mime, url;
|
|
49697
49821
|
if (path2) {
|
|
49698
49822
|
if (!ctx.local) {
|
|
49699
|
-
const fname =
|
|
49823
|
+
const fname = np3.basename(path2);
|
|
49700
49824
|
const fmime = mimeOf(fname);
|
|
49701
49825
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
49702
49826
|
const web = hub.replace("acp-hub.", "acp-web.").replace(":4000", ":4321");
|
|
@@ -49721,8 +49845,8 @@ ${psCmd}
|
|
|
49721
49845
|
if (!abs) {
|
|
49722
49846
|
return fail(`I can't find a file at ${path2} on this machine.`);
|
|
49723
49847
|
}
|
|
49724
|
-
const buf =
|
|
49725
|
-
name =
|
|
49848
|
+
const buf = fs4.readFileSync(abs);
|
|
49849
|
+
name = np3.basename(abs);
|
|
49726
49850
|
mime = mimeOf(name);
|
|
49727
49851
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
49728
49852
|
} else if (dataUrl && fileName) {
|
|
@@ -49803,57 +49927,57 @@ ${psCmd}
|
|
|
49803
49927
|
`Sending a folder reads files off disk, which this connector can't do \u2014 it runs in the cloud. Run jefri_send_folder from a LOCAL agent (Claude Code with the stdio connector) inside the project.`
|
|
49804
49928
|
);
|
|
49805
49929
|
const rawDir = path2 && path2.trim() ? path2 : process.cwd();
|
|
49806
|
-
const dir = rawDir.startsWith("~") ?
|
|
49807
|
-
if (!
|
|
49930
|
+
const dir = rawDir.startsWith("~") ? np3.join(os4.homedir(), rawDir.slice(1)) : rawDir;
|
|
49931
|
+
if (!fs4.existsSync(dir))
|
|
49808
49932
|
return fail(
|
|
49809
49933
|
`I can't see "${rawDir}" from here. Sending a folder reads it off disk, which only works on the LOCAL (stdio) connector \u2014 run this from Claude Code (or another local agent) inside the project.`
|
|
49810
49934
|
);
|
|
49811
49935
|
let stat;
|
|
49812
49936
|
try {
|
|
49813
|
-
stat =
|
|
49937
|
+
stat = fs4.statSync(dir);
|
|
49814
49938
|
} catch {
|
|
49815
49939
|
return fail(`Cannot read "${rawDir}".`);
|
|
49816
49940
|
}
|
|
49817
49941
|
if (!stat.isDirectory())
|
|
49818
49942
|
return fail(`"${rawDir}" is a file, not a folder \u2014 use jefri_send_file for a single file.`);
|
|
49819
49943
|
try {
|
|
49820
|
-
|
|
49944
|
+
cp2.execFileSync("zip", ["-v"], { stdio: "ignore" });
|
|
49821
49945
|
} catch {
|
|
49822
49946
|
return fail(`The 'zip' command isn't available on this machine, and I need it to package the folder.`);
|
|
49823
49947
|
}
|
|
49824
|
-
const base =
|
|
49825
|
-
const tmpZip =
|
|
49948
|
+
const base = np3.basename(np3.resolve(dir)) || "project";
|
|
49949
|
+
const tmpZip = np3.join(os4.tmpdir(), `acp-${base}-${Date.now()}.zip`);
|
|
49826
49950
|
let isRepo = false;
|
|
49827
49951
|
try {
|
|
49828
|
-
|
|
49952
|
+
cp2.execFileSync("git", ["-C", dir, "rev-parse", "--is-inside-work-tree"], { stdio: "ignore" });
|
|
49829
49953
|
isRepo = true;
|
|
49830
49954
|
} catch {
|
|
49831
49955
|
isRepo = false;
|
|
49832
49956
|
}
|
|
49833
49957
|
try {
|
|
49834
49958
|
if (isRepo) {
|
|
49835
|
-
|
|
49959
|
+
cp2.execSync(
|
|
49836
49960
|
`cd ${shq(dir)} && git ls-files --cached --others --exclude-standard -z | xargs -0 zip -q ${shq(tmpZip)} --`,
|
|
49837
49961
|
{ stdio: "ignore" }
|
|
49838
49962
|
);
|
|
49839
49963
|
} else {
|
|
49840
|
-
|
|
49964
|
+
cp2.execSync(
|
|
49841
49965
|
`cd ${shq(dir)} && zip -qr ${shq(tmpZip)} . -x '*/node_modules/*' '*/.git/*' '*/dist/*' '*/.next/*' '*/build/*' '*/__pycache__/*' '*/.venv/*' '*/venv/*' '*.log' '*/.DS_Store'`,
|
|
49842
49966
|
{ stdio: "ignore" }
|
|
49843
49967
|
);
|
|
49844
49968
|
}
|
|
49845
49969
|
} catch (e) {
|
|
49846
49970
|
try {
|
|
49847
|
-
|
|
49971
|
+
fs4.rmSync(tmpZip, { force: true });
|
|
49848
49972
|
} catch {
|
|
49849
49973
|
}
|
|
49850
49974
|
return fail(`Could not zip "${rawDir}": ${e?.message ?? e}`);
|
|
49851
49975
|
}
|
|
49852
|
-
if (!
|
|
49976
|
+
if (!fs4.existsSync(tmpZip))
|
|
49853
49977
|
return fail(`Nothing to send \u2014 "${rawDir}" produced an empty archive (all files ignored or folder empty).`);
|
|
49854
|
-
const size =
|
|
49978
|
+
const size = fs4.statSync(tmpZip).size;
|
|
49855
49979
|
if (size > MAX_UPLOAD_BYTES) {
|
|
49856
|
-
|
|
49980
|
+
fs4.rmSync(tmpZip, { force: true });
|
|
49857
49981
|
return fail(
|
|
49858
49982
|
`That archive is ${MB(size)} MB, over the ${Math.round(MAX_UPLOAD_BYTES / 1024 / 1024)} MB limit. Send a subfolder (e.g. path=\u2026/src) or remove large files, then try again.`
|
|
49859
49983
|
);
|
|
@@ -49865,7 +49989,7 @@ ${psCmd}
|
|
|
49865
49989
|
const res = await fetch(`${hub}/api/files?${q}`, {
|
|
49866
49990
|
method: "POST",
|
|
49867
49991
|
headers: { Authorization: `Bearer ${c.token}`, "Content-Type": "application/zip" },
|
|
49868
|
-
body:
|
|
49992
|
+
body: fs4.readFileSync(tmpZip)
|
|
49869
49993
|
});
|
|
49870
49994
|
if (!res.ok) {
|
|
49871
49995
|
const t = await res.text().catch(() => "");
|
|
@@ -49875,7 +49999,7 @@ ${psCmd}
|
|
|
49875
49999
|
return fail(`Upload failed: ${e?.message ?? e}`);
|
|
49876
50000
|
} finally {
|
|
49877
50001
|
try {
|
|
49878
|
-
|
|
50002
|
+
fs4.rmSync(tmpZip, { force: true });
|
|
49879
50003
|
} catch {
|
|
49880
50004
|
}
|
|
49881
50005
|
}
|
|
@@ -49910,12 +50034,12 @@ ${psCmd}
|
|
|
49910
50034
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
49911
50035
|
const out = savePath || match.fileName || "downloaded.file";
|
|
49912
50036
|
if (ctx.local) {
|
|
49913
|
-
const outPath = out.startsWith("~") ?
|
|
50037
|
+
const outPath = out.startsWith("~") ? np3.join(os4.homedir(), out.slice(1)) : out;
|
|
49914
50038
|
try {
|
|
49915
50039
|
const r = await fetch(`${hub}/api/files/${match.id}`, { headers: { authorization: `Bearer ${c.token}` } });
|
|
49916
50040
|
if (!r.ok) throw new Error(`download failed (${r.status})`);
|
|
49917
50041
|
const buf = Buffer.from(await r.arrayBuffer());
|
|
49918
|
-
|
|
50042
|
+
fs4.writeFileSync(outPath, buf);
|
|
49919
50043
|
return ok(`\u2705 Saved \u{1F4CE} ${match.fileName} from ${where} to ${outPath} (${MB(buf.length)} MB).`);
|
|
49920
50044
|
} catch (e) {
|
|
49921
50045
|
return fail(`Couldn't save the file: ${e?.message ?? e}`);
|
|
@@ -50016,7 +50140,7 @@ ${cmd}`);
|
|
|
50016
50140
|
}
|
|
50017
50141
|
},
|
|
50018
50142
|
async ({ departmentId, path: path2, kind }) => withClient(async (c) => {
|
|
50019
|
-
const fname =
|
|
50143
|
+
const fname = np3.basename(path2.startsWith("~") ? path2.slice(1) : path2);
|
|
50020
50144
|
const mime = mimeOf(fname);
|
|
50021
50145
|
const q = `name=${encodeURIComponent(fname)}&kind=${kind ?? "file"}`;
|
|
50022
50146
|
const cmd = `curl -s -X POST ${shq(`${hubBase()}/api/departments/${departmentId}/files?${q}`)} -H "Authorization: Bearer ${c.token}" -H "Content-Type: ${mime}" --data-binary @${shq(path2)}`;
|
|
@@ -50100,7 +50224,7 @@ ${cmd}`);
|
|
|
50100
50224
|
inputSchema: { path: external_exports.string().describe("local file path to upload") }
|
|
50101
50225
|
},
|
|
50102
50226
|
async ({ path: path2 }) => withClient(async (c) => {
|
|
50103
|
-
const fname =
|
|
50227
|
+
const fname = np3.basename(path2.startsWith("~") ? path2.slice(1) : path2);
|
|
50104
50228
|
const mime = mimeOf(fname);
|
|
50105
50229
|
const cmd = `curl -s -X POST ${shq(`${hubBase()}/api/agents/${encodeURIComponent(c.identity.username)}/docs?name=${encodeURIComponent(fname)}`)} -H "Authorization: Bearer ${c.token}" -H "Content-Type: ${mime}" --data-binary @${shq(path2)}`;
|
|
50106
50230
|
return ok(`To add ${fname} to your document memory, run in the terminal:
|
|
@@ -50252,7 +50376,7 @@ ${cmd}`);
|
|
|
50252
50376
|
if (path2) {
|
|
50253
50377
|
if (!ctx.local) {
|
|
50254
50378
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
50255
|
-
const fname =
|
|
50379
|
+
const fname = np3.basename(path2);
|
|
50256
50380
|
const fmime = mimeOf(fname);
|
|
50257
50381
|
const q = `groupId=${encodeURIComponent(groupId)}&fileName=${encodeURIComponent(fname)}` + (caption ? `&caption=${encodeURIComponent(caption)}` : "");
|
|
50258
50382
|
const cmd = `curl -s -X POST ${shq(`${hub}/api/files?${q}`)} -H "Authorization: Bearer ${c.token}" -H "Content-Type: ${fmime}" --data-binary @${shq(path2)}`;
|
|
@@ -50264,8 +50388,8 @@ ${cmd}`
|
|
|
50264
50388
|
}
|
|
50265
50389
|
const abs = resolveLocalFile(path2);
|
|
50266
50390
|
if (!abs) return fail(`I can't find a file at ${path2} on this machine.`);
|
|
50267
|
-
const buf =
|
|
50268
|
-
name =
|
|
50391
|
+
const buf = fs4.readFileSync(abs);
|
|
50392
|
+
name = np3.basename(abs);
|
|
50269
50393
|
mime = mimeOf(name);
|
|
50270
50394
|
url = `data:${mime};base64,${buf.toString("base64")}`;
|
|
50271
50395
|
} else if (dataUrl && fileName) {
|
|
@@ -50393,6 +50517,125 @@ ${cmd}`
|
|
|
50393
50517
|
return ok(`Status set to ${status}.`);
|
|
50394
50518
|
})
|
|
50395
50519
|
);
|
|
50520
|
+
if (ctx.local) {
|
|
50521
|
+
server.registerTool(
|
|
50522
|
+
"jefri_notifications",
|
|
50523
|
+
{
|
|
50524
|
+
title: "Notification settings",
|
|
50525
|
+
description: "Show or change desktop notification preferences: master on/off, sound on/off, DMs vs groups, only-@mentions for groups, quiet hours, and muting a specific group or person. Call with NO arguments to just see the current settings. Persists across sessions.",
|
|
50526
|
+
inputSchema: {
|
|
50527
|
+
enabled: external_exports.boolean().optional().describe("master switch for all desktop notifications"),
|
|
50528
|
+
sound: external_exports.boolean().optional().describe("play a sound with notifications"),
|
|
50529
|
+
dms: external_exports.boolean().optional().describe("notify for direct messages"),
|
|
50530
|
+
groups: external_exports.boolean().optional().describe("notify for group messages"),
|
|
50531
|
+
onlyMentions: external_exports.boolean().optional().describe("for groups: only notify when someone @-mentions you"),
|
|
50532
|
+
quietFrom: external_exports.string().optional().describe("start of quiet hours, HH:MM 24h (e.g. 22:00), or 'off' to clear"),
|
|
50533
|
+
quietTo: external_exports.string().optional().describe("end of quiet hours, HH:MM 24h (e.g. 08:00)"),
|
|
50534
|
+
muteGroup: external_exports.string().optional().describe("a group's name or id to silence"),
|
|
50535
|
+
unmuteGroup: external_exports.string().optional().describe("a group's name or id to unmute"),
|
|
50536
|
+
muteUser: external_exports.string().optional().describe("a username to silence"),
|
|
50537
|
+
unmuteUser: external_exports.string().optional().describe("a username to unmute")
|
|
50538
|
+
}
|
|
50539
|
+
},
|
|
50540
|
+
async (args) => withClient(async (c) => {
|
|
50541
|
+
const patch = {};
|
|
50542
|
+
for (const k of ["enabled", "sound", "dms", "groups", "onlyMentions"])
|
|
50543
|
+
if (typeof args[k] === "boolean") patch[k] = args[k];
|
|
50544
|
+
if (args.quietFrom === "off" || args.quietTo === "off") {
|
|
50545
|
+
patch.quietFrom = null;
|
|
50546
|
+
patch.quietTo = null;
|
|
50547
|
+
} else {
|
|
50548
|
+
if (args.quietFrom) patch.quietFrom = args.quietFrom;
|
|
50549
|
+
if (args.quietTo) patch.quietTo = args.quietTo;
|
|
50550
|
+
}
|
|
50551
|
+
const cur = getPrefs();
|
|
50552
|
+
const mutedGroups = new Set(cur.mutedGroups);
|
|
50553
|
+
const mutedUsers = new Set(cur.mutedUsers);
|
|
50554
|
+
let groupList = [];
|
|
50555
|
+
const needGroups = args.muteGroup || args.unmuteGroup;
|
|
50556
|
+
if (needGroups) {
|
|
50557
|
+
try {
|
|
50558
|
+
groupList = await c.groups();
|
|
50559
|
+
} catch {
|
|
50560
|
+
}
|
|
50561
|
+
}
|
|
50562
|
+
const resolveGroup = (nameOrId) => {
|
|
50563
|
+
const byId = groupList.find((g) => g.id === nameOrId);
|
|
50564
|
+
if (byId) return byId.id;
|
|
50565
|
+
const byName = groupList.find((g) => g.name.toLowerCase() === nameOrId.toLowerCase());
|
|
50566
|
+
return byName ? byName.id : null;
|
|
50567
|
+
};
|
|
50568
|
+
if (args.muteGroup) {
|
|
50569
|
+
const id = resolveGroup(args.muteGroup);
|
|
50570
|
+
if (!id) return fail(`No group named "${args.muteGroup}".`);
|
|
50571
|
+
mutedGroups.add(id);
|
|
50572
|
+
}
|
|
50573
|
+
if (args.unmuteGroup) {
|
|
50574
|
+
const id = resolveGroup(args.unmuteGroup);
|
|
50575
|
+
if (id) mutedGroups.delete(id);
|
|
50576
|
+
}
|
|
50577
|
+
if (args.muteUser) mutedUsers.add(args.muteUser.replace(/^@/, ""));
|
|
50578
|
+
if (args.unmuteUser) mutedUsers.delete(args.unmuteUser.replace(/^@/, ""));
|
|
50579
|
+
patch.mutedGroups = [...mutedGroups];
|
|
50580
|
+
patch.mutedUsers = [...mutedUsers];
|
|
50581
|
+
const p = setPrefs(patch);
|
|
50582
|
+
const names = {};
|
|
50583
|
+
if (p.mutedGroups.length) {
|
|
50584
|
+
try {
|
|
50585
|
+
for (const g of await c.groups()) names[g.id] = g.name;
|
|
50586
|
+
} catch {
|
|
50587
|
+
}
|
|
50588
|
+
}
|
|
50589
|
+
const summary = [
|
|
50590
|
+
`\u{1F514} Notifications: ${p.enabled ? "ON" : "OFF"}`,
|
|
50591
|
+
` Sound: ${p.sound ? "on" : "off"}`,
|
|
50592
|
+
` DMs: ${p.dms ? "on" : "off"} \xB7 Groups: ${p.groups ? "on" : "off"}${p.onlyMentions ? " (only @mentions)" : ""}`,
|
|
50593
|
+
` Quiet hours: ${p.quietFrom && p.quietTo ? `${p.quietFrom}\u2013${p.quietTo}` : "off"}`,
|
|
50594
|
+
` Muted groups: ${p.mutedGroups.length ? p.mutedGroups.map((id) => names[id] ?? id).join(", ") : "none"}`,
|
|
50595
|
+
` Muted people: ${p.mutedUsers.length ? p.mutedUsers.map((u) => "@" + u).join(", ") : "none"}`
|
|
50596
|
+
].join("\n");
|
|
50597
|
+
return ok(summary);
|
|
50598
|
+
})
|
|
50599
|
+
);
|
|
50600
|
+
server.registerTool(
|
|
50601
|
+
"jefri_autonomous",
|
|
50602
|
+
{
|
|
50603
|
+
title: "Autonomous mode",
|
|
50604
|
+
description: "Turn autonomous replies on/off for THIS agent. When ON, each incoming message is handled by a 'brain' (claude / codex / a custom command) that does the work in a folder and replies on its own \u2014 so you can, e.g., leave it on and it builds what people ask and answers them. Only works for your own agent (this connector's token). Call with NO arguments to see current settings. \u26A0\uFE0F When on, remote messages drive a tool-enabled agent on your machine \u2014 only keep it on with people you trust, and scope the work folder.",
|
|
50605
|
+
inputSchema: {
|
|
50606
|
+
enabled: external_exports.boolean().optional().describe("turn autonomous replies on/off"),
|
|
50607
|
+
brain: external_exports.string().optional().describe("'claude', 'codex', or a full custom command (e.g. for OpenClaw)"),
|
|
50608
|
+
workdir: external_exports.string().optional().describe("folder the brain works in (scope it!)"),
|
|
50609
|
+
replyMode: external_exports.enum(["mentions", "dms", "all"]).optional().describe("groups: mentions=only when @-mentioned, all=every message, dms=DMs only"),
|
|
50610
|
+
persona: external_exports.string().optional().describe("role/instructions for the agent"),
|
|
50611
|
+
replyToBots: external_exports.boolean().optional().describe("also auto-reply to other agents (default off \u2014 prevents bot loops)")
|
|
50612
|
+
}
|
|
50613
|
+
},
|
|
50614
|
+
async (args) => withClient(async () => {
|
|
50615
|
+
const patch = {};
|
|
50616
|
+
if (typeof args.enabled === "boolean") patch.enabled = args.enabled;
|
|
50617
|
+
if (typeof args.replyToBots === "boolean") patch.replyToBots = args.replyToBots;
|
|
50618
|
+
if (typeof args.brain === "string" && args.brain.trim()) patch.brain = args.brain.trim();
|
|
50619
|
+
if (typeof args.workdir === "string" && args.workdir.trim()) patch.workdir = args.workdir.trim();
|
|
50620
|
+
if (typeof args.persona === "string") patch.persona = args.persona;
|
|
50621
|
+
if (args.replyMode) patch.replyMode = args.replyMode;
|
|
50622
|
+
const c = setAuto(patch);
|
|
50623
|
+
const modeLabel = c.replyMode === "all" ? "every message" : c.replyMode === "dms" ? "DMs only" : "DMs + group @mentions";
|
|
50624
|
+
const lines = [
|
|
50625
|
+
`\u{1F916} Autonomous mode: ${c.enabled ? "ON" : "OFF"}`,
|
|
50626
|
+
` Brain: ${c.brain}`,
|
|
50627
|
+
` Work folder: ${c.workdir}`,
|
|
50628
|
+
` Replies to: ${modeLabel}`,
|
|
50629
|
+
` Persona: ${c.persona ? c.persona.slice(0, 80) : "(none)"}`,
|
|
50630
|
+
` Reply to other bots: ${c.replyToBots ? "yes" : "no"}`
|
|
50631
|
+
];
|
|
50632
|
+
if (c.enabled)
|
|
50633
|
+
lines.push(`
|
|
50634
|
+
\u26A0\uFE0F Incoming messages now drive "${c.brain}" (with tool access) in ${c.workdir}. Keep this on only with people you trust.`);
|
|
50635
|
+
return ok(lines.join("\n"));
|
|
50636
|
+
})
|
|
50637
|
+
);
|
|
50638
|
+
}
|
|
50396
50639
|
}
|
|
50397
50640
|
function attachInbox(c, inbox, self, onIncoming) {
|
|
50398
50641
|
const capture = (e) => {
|